feat: 封装代码

This commit is contained in:
381848900@qq.com 2024-12-10 23:53:46 +08:00
parent 1d36639b05
commit 7cf5a9c531
9 changed files with 780 additions and 882 deletions

View File

@ -106,7 +106,7 @@ pub fn command_get_pe_data_dos_header() -> Result<ResponseDOSHeaderData, AppErro
let file_data = binding.as_ref().unwrap();
let dos_header = file_data.get_dos_header()?;
let result = ResponseDOSHeaderData {
image_dos_header: dos_header.clone(),
fields: dos_header.clone(),
base_offset: 0,
};
Ok(result)

View File

@ -45,9 +45,27 @@ pub struct ImageFileHeader {
pub size_of_optional_header: u16,
pub characteristics: FileCharacteristics,
}
impl Serialize for FileCharacteristics{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer {
// 直接返回bitflags的整数值
serializer.serialize_u16(self.bits())
}
}
impl Serialize for DLLCharacteristics{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer {
serializer.serialize_u16(self.bits())
}
}
bitflags! {
#[repr(C)]
#[derive(Debug, Clone, Copy, Serialize)]
#[derive(Debug, Clone, Copy)]
pub struct FileCharacteristics: u16 {
const RELOCS_STRIPPED = 0x0001;
const EXECUTABLE_IMAGE = 0x0002;
@ -66,7 +84,7 @@ bitflags! {
const BYTES_REVERSED_HI = 0x8000;
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Serialize)]
#[derive(Debug, Clone, Copy)]
pub struct DLLCharacteristics: u16 {
const RESERVED1 = 0x0001;
const RESERVED2 = 0x0002;

View File

@ -19,13 +19,13 @@ pub fn mmap_mut_file(file_path: &str) -> Result<Mmap, std::io::Error> {
#[derive(Serialize)]
pub struct ResponseDOSHeaderData {
pub image_dos_header: ImageDosHeader,
pub fields: ImageDosHeader,
pub base_offset: usize,
}
#[derive(Serialize)]
pub struct ResponseNTHeaderData {
image_nt_header: ImageNTHeaders32,
fields: ImageNTHeaders32,
base_offset: usize,
}
@ -35,7 +35,7 @@ pub fn get_nt_headers_data() -> Result<ResponseNTHeaderData, AppError> {
let nt_header = file_data.get_nt_headers()?;
let nt_offset = file_data.get_nt_headers_offset()?;
let result = ResponseNTHeaderData {
image_nt_header: nt_header.clone(),
fields: nt_header.clone(),
base_offset: nt_offset,
};
Ok(result)
@ -44,7 +44,7 @@ pub fn get_nt_headers_data() -> Result<ResponseNTHeaderData, AppError> {
// 获取文件头数据
#[derive(Serialize)]
pub struct ResponseFileHeaderData {
pub file_header: ImageFileHeader,
pub fields: ImageFileHeader,
pub base_offset: usize,
}
@ -54,7 +54,7 @@ pub fn get_file_header_data() -> Result<ResponseFileHeaderData, AppError> {
let file_header = file_data.get_file_header()?;
let file_header_offset = file_data.get_file_header_offset()?;
let result = ResponseFileHeaderData {
file_header: file_header.clone(),
fields: file_header.clone(),
base_offset: file_header_offset,
};
Ok(result)

View File

@ -1,453 +1,185 @@
import styles from "./DosHeader.module.scss";
import { Flex } from "antd";
import React, { useContext, useEffect, useRef, useState } from "react";
import type { GetRef, InputRef, TableProps } from "antd";
import { Form, Input, Table } from "antd";
import { invoke } from "@tauri-apps/api/core";
import { cloneDeep } from "lodash-es";
import NodeTableComponent, {
DataTemplateInterface,
} from "../NodeTableComponent/NodeTableComponent";
type FormInstance<T> = GetRef<typeof Form<T>>;
const EditableContext = React.createContext<FormInstance<any> | null>(null);
interface DosHeaderFieldProps {
name: string;
value: number | [number];
size: number;
offset: number;
field_type: string;
}
interface DosHeaderResponse {
image_dos_header: DosHeaderFieldResponse;
base_offset: number;
}
interface DosHeaderFieldResponse {
e_magic: number;
e_cblp: number;
e_cp: number;
e_crlc: number;
e_cparhdr: number;
e_minalloc: number;
e_maxalloc: number;
e_ss: number;
e_sp: number;
e_csum: number;
e_ip: number;
e_cs: number;
e_lfarlc: number;
e_ovno: number;
e_res: number;
e_oemid: number;
e_oeminfo: number;
e_res2: number;
e_lfanew: number;
}
// 定义DOS Header的数据结构
interface IDosHeader {
e_magic: DosHeaderFieldProps;
e_cblp: DosHeaderFieldProps;
e_cp: DosHeaderFieldProps;
e_crlc: DosHeaderFieldProps;
e_cparhdr: DosHeaderFieldProps;
e_minalloc: DosHeaderFieldProps;
e_maxalloc: DosHeaderFieldProps;
e_ss: DosHeaderFieldProps;
e_sp: DosHeaderFieldProps;
e_csum: DosHeaderFieldProps;
e_ip: DosHeaderFieldProps;
e_cs: DosHeaderFieldProps;
e_lfarlc: DosHeaderFieldProps;
e_ovno: DosHeaderFieldProps;
e_res: DosHeaderFieldProps;
e_oemid: DosHeaderFieldProps;
e_oeminfo: DosHeaderFieldProps;
e_res2: DosHeaderFieldProps;
e_lfanew: DosHeaderFieldProps;
}
const templateData: IDosHeader = {
export default function DosHeader() {
const dataTemplate: DataTemplateInterface = {
e_magic: {
name: "e_magic",
value: 0,
size: 2,
offset: 0,
field_type: "WORD",
data_type: "hex",
},
e_cblp: {
name: "e_cblp",
value: 0,
size: 2,
offset: 2,
field_type: "WORD",
data_type: "hex",
},
e_cp: {
name: "e_cp",
value: 0,
size: 2,
offset: 4,
field_type: "WORD",
data_type: "hex",
},
e_crlc: {
name: "e_crlc",
value: 0,
size: 2,
offset: 6,
field_type: "WORD",
data_type: "hex",
},
e_cparhdr: {
name: "e_cparhdr",
value: 0,
size: 2,
offset: 8,
field_type: "WORD",
data_type: "hex",
},
e_minalloc: {
name: "e_minalloc",
value: 0,
size: 2,
offset: 10,
field_type: "WORD",
data_type: "hex",
},
e_maxalloc: {
name: "e_maxalloc",
value: 0,
size: 2,
offset: 12,
field_type: "WORD",
data_type: "hex",
},
e_ss: {
name: "e_ss",
value: 0,
size: 2,
offset: 14,
field_type: "WORD",
data_type: "hex",
},
e_sp: {
name: "e_sp",
value: 0,
size: 2,
offset: 16,
field_type: "WORD",
data_type: "hex",
},
e_csum: {
name: "e_csum",
value: 0,
size: 2,
offset: 18,
field_type: "WORD",
data_type: "hex",
},
e_ip: {
name: "e_ip",
value: 0,
size: 2,
offset: 20,
field_type: "WORD",
data_type: "hex",
},
e_cs: {
name: "e_cs",
value: 0,
size: 2,
offset: 22,
field_type: "WORD",
data_type: "hex",
},
e_lfarlc: {
name: "e_lfarlc",
value: 0,
size: 2,
offset: 24,
field_type: "WORD",
data_type: "hex",
},
e_ovno: {
name: "e_ovno",
value: 0,
size: 2,
offset: 26,
field_type: "WORD",
data_type: "hex",
},
e_res: {
name: "e_res",
value: null,
size: 8,
offset: 28,
field_type: "[WORD; 4]",
// field_type: "[WORD; 4]",
array_element_size: 2,
data_type: "array",
},
e_oemid: {
name: "e_oemid",
value: 0,
size: 2,
offset: 36,
field_type: "WORD",
data_type: "hex",
},
e_oeminfo: {
name: "e_oeminfo",
value: 0,
size: 2,
offset: 38,
field_type: "WORD",
data_type: "hex",
},
e_res2: {
name: "e_res2",
value: 0,
size: 20,
offset: 40,
field_type: "[WORD; 10]",
data_type: "array",
array_element_size: 2,
},
e_lfanew: {
name: "e_lfanew",
value: 0,
size: 4,
offset: 60,
field_type: "DWORD",
data_type: "hex",
},
};
interface Item {
name: string; // 字段名称
offset: string; // 偏移
size: Number; // 字段大小
value: string; // 字段值
children?: Item[];
}
interface EditableRowProps {
index: number;
}
const EditableRow: React.FC<EditableRowProps> = ({ index, ...props }) => {
const [form] = Form.useForm();
return (
<Form form={form} component={false}>
<EditableContext.Provider value={form}>
<tr {...props} />
</EditableContext.Provider>
</Form>
);
};
interface EditableCellProps {
title: React.ReactNode;
editable: boolean;
dataIndex: keyof Item;
record: Item;
handleSave: (record: Item) => void;
}
const EditableCell: React.FC<React.PropsWithChildren<EditableCellProps>> = ({
title,
editable,
children,
dataIndex,
record,
handleSave,
...restProps
}) => {
const [editing, setEditing] = useState(false);
const inputRef = useRef<InputRef>(null);
const form = useContext(EditableContext)!;
useEffect(() => {
if (editing) {
inputRef.current?.focus();
}
}, [editing]);
const toggleEdit = () => {
setEditing(!editing);
form.setFieldsValue({ [dataIndex]: record[dataIndex] });
};
const save = async () => {
try {
const values = await form.validateFields();
toggleEdit();
handleSave({ ...record, ...values });
} catch (errInfo) {
console.log("Save failed:", errInfo);
}
};
const command = "command_get_pe_data_dos_header";
let childNode = children;
if (editable) {
childNode = editing ? (
<Form.Item
style={{ margin: 0 }}
name={dataIndex}
rules={[{ required: true, message: `${title} is required.` }]}
>
<Input size="small" ref={inputRef} onPressEnter={save} onBlur={save} />
</Form.Item>
) : (
<div
className="editable-cell-value-wrap"
style={{ paddingInlineEnd: 24 }}
onClick={toggleEdit}
>
{children}
</div>
);
}
return <td {...restProps}>{childNode}</td>;
};
interface DataType {
name: string; // 字段名称
offset: string; // 偏移
size: string | Number; // 字段大小
value: string; // 字段值
field_type: string; // 字段类型
children?: DataType[];
}
type ColumnTypes = Exclude<TableProps<DataType>["columns"], undefined>;
const App = () => {
const [offsetHex, setOffsetHex] = useState(false);
const [dataSource, setDataSource] = useState<DataType[]>([]);
useEffect(() => {
invoke("command_get_pe_data_dos_header").then(
(resData: DosHeaderResponse) => {
console.log("resData", resData);
// 格式化数据
let data = cloneDeep(templateData); // 先clone一个模板
let base_offset = resData.base_offset;
Object.keys(resData.image_dos_header).forEach((key) => {
let value = resData.image_dos_header[key];
data[key].offset = base_offset + data[key].offset;
if (Array.isArray(value)) {
// 如果是数组
data[key].children = value.map((item, index) => {
let child_size = data[key].size / value.length;
const extractWord = (input) => {
const match = input.match(/\[([\w]+);/);
return match ? match[1] : null; // 如果匹配到,返回结果;否则返回 null
};
return {
name: `${key}[${index}]`,
offset: index * child_size + data[key].offset,
size: child_size,
value: "0x" + item.toString(16).padStart(4, "0").toUpperCase(),
field_type: extractWord(data[key].field_type),
};
});
} else {
data[key].value =
"0x" + value.toString(16).padStart(4, "0").toUpperCase();
}
});
setDataSource(Object.values(data));
}
);
}, []);
const defaultColumns = [
const columns = [
{
title: "字段名",
title: "字段名",
dataIndex: "name",
width: "30%",
key: "name",
},
{
title: "偏移",
title: "偏移",
dataIndex: "offset",
defaultSortOrder: "ascend",
render: (text) => {
return offsetHex ? `0x${text.toString(16).toUpperCase()}` : text;
},
onHeaderCell: () => ({
onClick: () => {
setOffsetHex(!offsetHex);
}
})
key: "offset",
hexSwitch: true,
},
{
title: "字段类型",
dataIndex: "field_type",
},
{
title: "字段大小",
title: "大小",
dataIndex: "size",
key: "size",
},
{
title: "字段值",
title: "值",
dataIndex: "value",
key: "value",
hexSwitch: true,
editable: true,
},
{
title: "描述信息",
dataIndex: "description",
key: "description",
},
];
const replaceNodeByName = (
data: DataType[],
name: string,
newNode: DataType
): DataType[] => {
return data.map((item) => {
if (item.name === name) {
return newNode;
}
if (item.children) {
return {
...item,
children: replaceNodeByName(item.children, name, newNode),
};
}
return item;
});
};
const handleSave = (row: DataType) => {
let newData = [...dataSource];
newData = replaceNodeByName(newData, row.name, row);
setDataSource(newData);
};
const components = {
body: {
row: EditableRow,
cell: EditableCell,
},
};
const columns = defaultColumns.map((col) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record: DataType) => ({
record,
editable: col.editable,
dataIndex: col.dataIndex,
title: col.title,
handleSave,
}),
};
});
return (
<Table<DataType>
components={components}
rowClassName={() => "editable-row"}
bordered
rowKey={(record) => record.name}
style={{ width: "100%" }}
dataSource={dataSource}
columns={columns as ColumnTypes}
pagination={false}
size="small"
sticky={{ offsetHeader: -16 }}
/>
);
};
export default function DosHeader() {
return (
<Flex className={styles.root}>
<App />
</Flex>
<NodeTableComponent
dataTemplate={dataTemplate}
command={command}
columns={columns as any}
></NodeTableComponent>
);
}

View File

@ -1,247 +1,159 @@
import { Flex, Form, FormInstance, Input, Table } from "antd";
import styles from "./FileHeader.module.scss";
import { useContext, useEffect, useRef, useState } from "react";
import { invoke } from "@tauri-apps/api/core";
import React from "react";
import { cloneDeep } from "lodash-es";
const EditableContext = React.createContext<FormInstance<any> | null>(null);
import NodeTableComponent, {
DataTemplateInterface,
} from "../NodeTableComponent/NodeTableComponent";
const templateData = {
export default function FileHeader() {
const dataTemplate: DataTemplateInterface = {
machine: {
name: "Machine",
offset: 0,
size: 2,
value: null,
field_type: "WORD",
data_type: "hex",
},
number_of_sections: {
name: "Number of Sections",
offset: 2,
size: 2,
value: null,
field_type: "WORD",
data_type: "hex",
},
time_date_stamp: {
name: "Time Date Stamp",
offset: 4,
size: 4,
value: null,
field_type: "DWORD",
data_type: "hex",
},
pointer_to_symbol_table: {
name: "Pointer to Symbol Table",
offset: 8,
size: 4,
value: null,
field_type: "DWORD",
data_type: "hex",
},
number_of_symbols: {
name: "Number of Symbols",
offset: 12,
size: 4,
value: null,
field_type: "DWORD",
data_type: "hex",
},
size_of_optional_header: {
name: "Size of Optional Header",
offset: 16,
size: 2,
value: null,
field_type: "WORD",
data_type: "hex",
},
characteristics: {
name: "Characteristics",
offset: 18,
size: 2,
value: null,
field_type: "WORD",
data_type: "enum",
enum: {
0x1: {
enum_name: "RELOCS_STRIPPED",
description: "Relocation info stripped from file.",
},
0x2: {
enum_name: "EXECUTABLE_IMAGE",
description:
"File is executable (i.e. no unresolved external references).",
},
0x4: {
enum_name: "LINE_NUMS_STRIPPED",
description: "Line nunbers stripped from file.",
},
0x8: {
enum_name: "LOCAL_SYMS_STRIPPED",
description: "Local symbols stripped from file.",
},
0x10: {
enum_name: "AGGRESSIVE_WS_TRIM",
description: "Aggressively trim working set",
},
0x20: {
enum_name: "LARGE_ADDRESS_AWARE",
description: "App can handle >2gb addresses",
},
0x80: {
enum_name: "BYTES_REVERSED_LO",
description: "Bytes of machine word are reversed.",
},
0x100: {
enum_name: "MACHINE_32BIT",
description: "32 bit word machine.",
},
0x200: {
enum_name: "DEBUG_STRIPPED",
description: "Debugging info stripped from file in .DBG file",
},
0x400: {
enum_name: "REMOVABLE_RUN_FROM_SWAP",
description:
"If Image is on removable media, copy and run from the swap file.",
},
0x800: {
enum_name: "NET_RUN_FROM_SWAP",
description: "If Image is on Net, copy and run from the swap file.",
},
0x1000: {
enum_name: "SYSTEM",
description: "System File.",
},
0x2000: {
enum_name: "DLL",
description: "File is a DLL.",
},
0x4000: {
enum_name: "UP_SYSTEM_ONLY",
description: "File should only be run on a UP machine",
},
0x8000: {
enum_name: "BYTES_REVERSED_HI",
description: "Bytes of machine word are reversed.",
},
},
},
};
const EditableRow = ({ index, ...props }) => {
const [form] = Form.useForm();
return (
<Form form={form} component={false}>
<EditableContext.Provider value={form}>
<tr {...props} />
</EditableContext.Provider>
</Form>
);
};
const EditableCell = ({
title,
editable,
children,
dataIndex,
record,
handleSave,
...restProps
}) => {
const [editing, setEditing] = useState(false);
const inputRef = useRef(null);
const form = useContext(EditableContext)!;
useEffect(() => {
if (editing) {
inputRef.current?.focus();
}
}, [editing]);
const toggleEdit = () => {
setEditing(!editing);
form.setFieldsValue({ [dataIndex]: record[dataIndex] });
};
const save = async () => {
try {
const values = await form.validateFields();
toggleEdit();
handleSave({ ...record, ...values });
} catch (errInfo) {
console.log("Save failed:", errInfo);
}
};
let childNode = children;
if (editable) {
childNode = editing ? (
<Form.Item
style={{ margin: 0 }}
name={dataIndex}
rules={[{ required: true, message: `${title} is required.` }]}
>
<Input size="small" ref={inputRef} onPressEnter={save} onBlur={save} />
</Form.Item>
) : (
<div
className="editable-cell-value-wrap"
style={{ paddingInlineEnd: 24 }}
onClick={toggleEdit}
>
{children}
</div>
);
}
return <td {...restProps}>{childNode}</td>;
};
const App = () => {
const [offsetHex, setOffsetHex] = useState(false);
const [dataSource, setDataSource] = useState([]);
useEffect(() => {
invoke("command_get_pe_data_file_header").then((resData: any) => {
console.log("resData", resData);
let data = cloneDeep(templateData);
let base_offset = resData.base_offset;
Object.keys(data).forEach((key) => {
data[key].value = resData.file_header[key];
data[key].offset = base_offset + data[key].offset;
});
setDataSource(Object.values(data));
});
}, []);
const defaultColumns = [
const command = "command_get_pe_data_file_header";
const columns = [
{
title: "字段名",
title: "字段名",
dataIndex: "name",
width: "30%",
key: "name",
},
{
title: "偏移",
title: "偏移",
dataIndex: "offset",
defaultSortOrder: "ascend",
render: (text) => {
return offsetHex ? `0x${text.toString(16).toUpperCase()}` : text;
},
onHeaderCell: () => ({
onClick: () => {
setOffsetHex(!offsetHex);
},
}),
key: "offset",
hexSwitch: true,
},
{
title: "字段类型",
dataIndex: "field_type",
},
{
title: "字段大小",
title: "大小",
dataIndex: "size",
key: "size",
},
{
title: "字段值",
title: "值",
dataIndex: "value",
key: "value",
hexSwitch: true,
editable: true,
},
];
const replaceNodeByName = (data, name, newNode) => {
return data.map((item) => {
if (item.name === name) {
return newNode;
}
if (item.children) {
return {
...item,
children: replaceNodeByName(item.children, name, newNode),
};
}
return item;
});
};
const handleSave = (row) => {
let newData = [...dataSource];
newData = replaceNodeByName(newData, row.name, row);
setDataSource(newData);
};
const components = {
body: {
row: EditableRow,
cell: EditableCell,
{
title: "描述信息",
dataIndex: "description",
key: "description",
},
};
const columns = defaultColumns.map((col) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record) => ({
record,
editable: col.editable,
dataIndex: col.dataIndex,
title: col.title,
handleSave,
}),
};
});
];
return (
<Table
components={components}
rowClassName={() => "editable-row"}
bordered
rowKey={(record) => record.name}
style={{ width: "100%" }}
dataSource={dataSource}
<NodeTableComponent
dataTemplate={dataTemplate}
command={command}
columns={columns as any}
pagination={false}
size="small"
sticky={{ offsetHeader: -16 }}
/>
);
};
export default function FileHeader() {
return (
<Flex className={styles.root}>
<App />
</Flex>
></NodeTableComponent>
);
}

View File

@ -1,205 +1,53 @@
import { Flex, Form, FormInstance, Input, Table } from "antd";
import styles from "./NTHeader.module.scss";
import { useContext, useEffect, useRef, useState } from "react";
import { invoke } from "@tauri-apps/api/core";
import React from "react";
import { cloneDeep } from "lodash-es";
const EditableContext = React.createContext<FormInstance<any> | null>(null);
import NodeTableComponent, {
DataTemplateInterface,
} from "../NodeTableComponent/NodeTableComponent";
const templateData = {
export default function NTHeader() {
const dataTemplate: DataTemplateInterface = {
signature: {
name: "Signature",
offset: 0,
size: 4,
value: null,
field_type: "DWORD",
value: 0,
data_type: "hex",
},
};
const EditableRow = ({ index, ...props }) => {
const [form] = Form.useForm();
return (
<Form form={form} component={false}>
<EditableContext.Provider value={form}>
<tr {...props} />
</EditableContext.Provider>
</Form>
);
};
const EditableCell = ({
title,
editable,
children,
dataIndex,
record,
handleSave,
...restProps
}) => {
const [editing, setEditing] = useState(false);
const inputRef = useRef(null);
const form = useContext(EditableContext)!;
useEffect(() => {
if (editing) {
inputRef.current?.focus();
}
}, [editing]);
const toggleEdit = () => {
setEditing(!editing);
form.setFieldsValue({ [dataIndex]: record[dataIndex] });
};
const save = async () => {
try {
const values = await form.validateFields();
toggleEdit();
handleSave({ ...record, ...values });
} catch (errInfo) {
console.log("Save failed:", errInfo);
}
};
let childNode = children;
if (editable) {
childNode = editing ? (
<Form.Item
style={{ margin: 0 }}
name={dataIndex}
rules={[{ required: true, message: `${title} is required.` }]}
>
<Input size="small" ref={inputRef} onPressEnter={save} onBlur={save} />
</Form.Item>
) : (
<div
className="editable-cell-value-wrap"
style={{ paddingInlineEnd: 24 }}
onClick={toggleEdit}
>
{children}
</div>
);
}
return <td {...restProps}>{childNode}</td>;
};
const App = () => {
const [offsetHex, setOffsetHex] = useState(false);
const [dataSource, setDataSource] = useState([]);
useEffect(() => {
invoke("command_get_pe_data_nt_header").then((resData: any) => {
console.log("resData", resData);
// 格式化数据
let data = cloneDeep(templateData);
let base_offset = resData.base_offset;
data.signature.offset = base_offset;
data.signature.value = "0x" + resData.image_nt_header.signature.toString(16).toUpperCase();
console.log("data", data);
setDataSource(Object.values(data));
});
}, []);
const defaultColumns = [
const command = "command_get_pe_data_nt_header";
const columns = [
{
title: "字段名",
title: "字段名",
dataIndex: "name",
width: "30%",
key: "name",
},
{
title: "偏移",
title: "偏移",
dataIndex: "offset",
defaultSortOrder: "ascend",
render: (text) => {
return offsetHex ? `0x${text.toString(16).toUpperCase()}` : text;
},
onHeaderCell: () => ({
onClick: () => {
setOffsetHex(!offsetHex);
},
}),
key: "offset",
hexSwitch: true,
},
{
title: "字段类型",
dataIndex: "field_type",
},
{
title: "字段大小",
title: "大小",
dataIndex: "size",
key: "size",
},
{
title: "字段值",
title: "值",
dataIndex: "value",
key: "value",
hexSwitch: true,
editable: true,
},
];
const replaceNodeByName = (data, name, newNode) => {
return data.map((item) => {
if (item.name === name) {
return newNode;
}
if (item.children) {
return {
...item,
children: replaceNodeByName(item.children, name, newNode),
};
}
return item;
});
};
const handleSave = (row) => {
let newData = [...dataSource];
newData = replaceNodeByName(newData, row.name, row);
setDataSource(newData);
};
const components = {
body: {
row: EditableRow,
cell: EditableCell,
{
title: "描述信息",
dataIndex: "description",
key: "description",
},
};
const columns = defaultColumns.map((col) => {
if (!col.editable) {
return col;
}
return {
...col,
onCell: (record) => ({
record,
editable: col.editable,
dataIndex: col.dataIndex,
title: col.title,
handleSave,
}),
};
});
];
return (
<Table
components={components}
rowClassName={() => "editable-row"}
bordered
rowKey={(record) => record.name}
style={{ width: "100%" }}
dataSource={dataSource}
<NodeTableComponent
dataTemplate={dataTemplate}
command={command}
columns={columns as any}
pagination={false}
size="small"
sticky={{ offsetHeader: -16 }}
/>
);
};
export default function NTHeader() {
return (
<Flex className={styles.root}>
<App />
</Flex>
></NodeTableComponent>
);
}

View File

@ -0,0 +1,14 @@
.NodeTableComponentRootFlex {
background-color: white;
border-radius: 5px;
padding: 8px;
margin-right: 8px;
overflow-y: scroll;
height: 100%;
padding-bottom: 16px;
}
.NodeTableComponentTable{
height: calc(100% - 16px);
width: 100%;
}

View File

@ -0,0 +1,276 @@
import { Table, Flex } from "antd/lib";
import { ColumnType } from "antd/lib/table";
import { useState, useMemo, useEffect, useRef, useContext } from "react";
import styles from "./NodeTableComponent.module.scss";
import { invoke } from "@tauri-apps/api/core";
import { cloneDeep } from "lodash-es";
import { Form, FormInstance, Input, InputRef } from "antd";
import React from "react";
interface ResponsData {
base_offset: number;
fields: {
[key: string]: number | number[];
};
}
export interface DataTemplateInterface {
[key: string]: DataType;
}
export interface DataType {
offset: number;
name: string;
size: number;
value: number | number[] | null;
description?: string;
// 枚举值 key:value => value: description
enum?: {
[key: number]: {
enum_name: string; // 枚举值名称
description?: string; // 枚举值描述
};
};
array_element_size?: number; // 数组元素大小
// hex: 16进制
// enum: 枚举值,对于枚举的处理一律按位处理
// array: 数组
data_type: "hex" | "enum" | "array";
children?: DataType[];
}
export interface NodeTableColumnInterface<T> extends ColumnType<T> {
dataIndex: string | string[];
title:
| React.ReactNode
| (({ sortOrder, sortColumn, filters }) => React.ReactNode);
editable?: boolean; // 是否可编辑
hexSwitch?: boolean; // 是否切换显示十六进制
sortOrder?: "ascend" | "descend";
}
interface NodeTableComponentProps {
dataTemplate: DataTemplateInterface; // 数据模板
command: string; // 请求数据的命令
columns: NodeTableColumnInterface<DataType>[];
}
interface EditableRowProps {
index: number;
}
const EditableContext = React.createContext<FormInstance<any> | null>(null);
const EditableRow: React.FC<EditableRowProps> = ({ index, ...props }) => {
const [form] = Form.useForm();
return (
<Form form={form} component={false}>
<EditableContext.Provider value={form}>
<tr {...props} />
</EditableContext.Provider>
</Form>
);
};
interface EditableCellProps {
title: React.ReactNode;
editable: boolean;
dataIndex: keyof DataType;
record: DataType;
handleSave: (record: DataType) => void;
}
const EditableCell: React.FC<React.PropsWithChildren<EditableCellProps>> = ({
title,
editable,
children,
dataIndex,
record,
handleSave,
...restProps
}) => {
const [editing, setEditing] = useState(false);
const inputRef = useRef<InputRef>(null);
const form = useContext(EditableContext)!;
useEffect(() => {
if (editing) {
inputRef.current?.focus();
}
}, [editing]);
const toggleEdit = () => {
setEditing(!editing);
form.setFieldsValue({ [dataIndex]: record[dataIndex] });
};
const save = async () => {
try {
const values = await form.validateFields();
toggleEdit();
handleSave({ ...record, ...values });
} catch (errInfo) {
console.log("Save failed:", errInfo);
}
};
let childNode = children;
if (editable) {
childNode = editing ? (
<Form.Item
style={{ margin: 0 }}
name={dataIndex as string}
rules={[{ required: true, message: `${title} is required.` }]}
>
<Input size="small" ref={inputRef} onPressEnter={save} onBlur={save} />
</Form.Item>
) : (
<div
className="editable-cell-value-wrap"
style={{ paddingInlineEnd: 24 }}
onClick={toggleEdit}
>
{children}
</div>
);
}
return <td {...restProps}>{childNode}</td>;
};
export default function NodeTableComponent(props: NodeTableComponentProps) {
const [data, setData] = useState<DataType[]>([]);
// 需要搞一个状态用来保存不同列的hex显示状态
const [hexSwitchStatus, setHexSwitchStatus] = useState<{
[key: string]: boolean;
}>({});
const handleSwitchFieldHexMode = (field: string) => {
console.log("hexSwitchStatus", hexSwitchStatus);
const newHexSwitchStatus = { ...hexSwitchStatus };
newHexSwitchStatus[field] = !newHexSwitchStatus[field];
console.log("newHexSwitchStatus", newHexSwitchStatus);
setHexSwitchStatus(newHexSwitchStatus);
};
const handleSave = (row: DataType) => {
// TODO: 保存逻辑
};
const hexSwitchRender = (text: string, dataIndex: string) => {
if (text == null || text == undefined || typeof text !== "number") {
return text;
}
return hexSwitchStatus[dataIndex]
? "0x" + parseInt(text).toString(16).toUpperCase()
: text;
};
const components = {
body: {
row: EditableRow,
cell: EditableCell,
},
};
const formatCol = useMemo(() => {
return props.columns.map((col) => {
let extraCol = {};
if (col.editable) {
extraCol["onCell"] = (record: DataType) => ({
record,
editable: col.editable,
dataIndex: col.dataIndex,
title: col.title,
handleSave,
});
}
if (col.hexSwitch) {
extraCol["render"] = (text: string) =>
hexSwitchRender(text, col.dataIndex as string);
extraCol["onHeaderCell"] = () => ({
onClick: () => {
handleSwitchFieldHexMode(col.dataIndex as string);
},
});
}
return {
...col,
...extraCol,
};
});
}, [props.columns, hexSwitchStatus]);
const handleData = (resData: ResponsData) => {
// 1. clone一份dataTemplate
const cloneData = cloneDeep(props.dataTemplate);
// 2. 遍历resData更新cloneData
for (let key in cloneData) {
cloneData[key].value = resData.fields[key];
cloneData[key].offset += resData.base_offset;
// 如果是array
if (cloneData[key].data_type === "array") {
let array = [];
for (let i = 0; i < (resData.fields[key] as number[]).length; i++) {
let item = {
offset:
cloneData[key].offset + i * cloneData[key].array_element_size,
name: `${cloneData[key].name}[${i}]`,
size: cloneData[key].array_element_size,
value: resData.fields[key][i],
key: `${key}[${i}]`,
};
array.push(item);
}
cloneData[key].value = null;
cloneData[key].children = array;
}
// TODO: 枚举值处理
if(cloneData[key].data_type === "enum") {
// children
let children = [];
let value = resData.fields[key];
for (let enumValue in cloneData[key].enum) {
if((value as number) & parseInt(enumValue)) {
children.push({
offset: cloneData[key].offset,
name: cloneData[key].enum[enumValue].enum_name,
size: 0,
value: null,
key: `${key}_${enumValue}`,
description: cloneData[key].enum[enumValue].description,
});
}
}
// cloneData[key].value = null;
cloneData[key].children = children;
}
}
// 3. 更新data
setData(Object.values(cloneData).sort((a, b) => a.offset - b.offset));
};
useEffect(() => {
// 请求数据
invoke(props.command).then((resData: ResponsData) => {
console.log("resData", resData);
handleData(resData);
});
}, []);
return (
<Flex className={styles.NodeTableComponentRootFlex}>
<Table
components={components}
className={styles.NodeTableComponentTable}
rowClassName={() => "editable-row"}
columns={formatCol}
dataSource={data}
pagination={false}
rowKey={"name"}
bordered
size="small"
sticky={{ offsetHeader: -8 }}
/>
</Flex>
);
}

View File

@ -9,6 +9,9 @@ import SiderTree from "../components/side_tree/SideTree";
import DosHeader from "../components/DosHeader/DosHeader";
import NtHeader from "../components/NTHeader/NTHeader";
import FileHeader from "../components/FileHeader/FileHeader";
import NodeTableComponent, {
DataTemplateInterface,
} from "../components/NodeTableComponent/NodeTableComponent";
import { open } from "@tauri-apps/plugin-dialog";
const SelectNodeMap = {
@ -84,7 +87,7 @@ export default function MainPage() {
style={{
backgroundColor: "#e5e5e5",
paddingBottom: "16px",
height: "100%"
height: "100%",
}}
>
<Flex className={styles.sider}>
@ -100,8 +103,103 @@ export default function MainPage() {
) : (
<Empty description={"请选择一个节点"} />
)}
{/* <TestNodeTableComponent></TestNodeTableComponent> */}
</Content>
</Flex>
</Layout>
);
}
const TestNodeTableComponent = () => {
const [sortStatus, setSortStatus] = useState<"ascend" | "descend" | null>(
"ascend"
);
const dataTemplate: DataTemplateInterface = {
e_magic: {
name: "e_magic",
value: 0,
size: 2,
offset: 0,
description: "Magic number",
data_type: "hex",
},
e_res: {
name: "e_res",
value: null,
size: 8,
offset: 28,
description: "Reserved",
data_type: "array",
array_element_size: 2,
},
test_enum: {
name: "test_enum",
value: 0,
size: 2,
offset: 10,
description: "Test enum",
data_type: "enum",
enum: {
1: {
enum_name: "enum1",
description: "enum1 description",
},
2: {
enum_name: "enum2",
description: "enum2 description",
},
4: {
enum_name: "enum4",
description: "enum4 description",
},
8: {
enum_name: "enum8",
description: "enum8 description",
},
},
},
};
const command = "test_command";
const columns = [
{
title: "Name",
dataIndex: "name",
key: "name",
},
{
title: "偏移",
dataIndex: "offset",
hexSwitch: true,
key: "offset",
sortOrder: sortStatus,
},
{
title: "大小",
dataIndex: "size",
key: "size",
},
{
title: "值",
dataIndex: "value",
hexSwitch: true,
editable: true,
key: "value",
},
{
title: "描述",
dataIndex: "description",
key: "description",
},
];
return (
<>
<NodeTableComponent
dataTemplate={dataTemplate}
command={command}
columns={columns as any}
></NodeTableComponent>
</>
);
};