feat: 封装代码
This commit is contained in:
parent
1d36639b05
commit
7cf5a9c531
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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 = {
|
||||
e_magic: {
|
||||
name: "e_magic",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 0,
|
||||
field_type: "WORD",
|
||||
},
|
||||
e_cblp: {
|
||||
name: "e_cblp",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 2,
|
||||
field_type: "WORD",
|
||||
},
|
||||
e_cp: {
|
||||
name: "e_cp",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 4,
|
||||
field_type: "WORD",
|
||||
},
|
||||
e_crlc: {
|
||||
name: "e_crlc",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 6,
|
||||
field_type: "WORD",
|
||||
},
|
||||
e_cparhdr: {
|
||||
name: "e_cparhdr",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 8,
|
||||
field_type: "WORD",
|
||||
},
|
||||
e_minalloc: {
|
||||
name: "e_minalloc",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 10,
|
||||
field_type: "WORD",
|
||||
},
|
||||
e_maxalloc: {
|
||||
name: "e_maxalloc",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 12,
|
||||
field_type: "WORD",
|
||||
},
|
||||
e_ss: {
|
||||
name: "e_ss",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 14,
|
||||
field_type: "WORD",
|
||||
},
|
||||
e_sp: {
|
||||
name: "e_sp",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 16,
|
||||
field_type: "WORD",
|
||||
},
|
||||
e_csum: {
|
||||
name: "e_csum",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 18,
|
||||
field_type: "WORD",
|
||||
},
|
||||
e_ip: {
|
||||
name: "e_ip",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 20,
|
||||
field_type: "WORD",
|
||||
},
|
||||
e_cs: {
|
||||
name: "e_cs",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 22,
|
||||
field_type: "WORD",
|
||||
},
|
||||
e_lfarlc: {
|
||||
name: "e_lfarlc",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 24,
|
||||
field_type: "WORD",
|
||||
},
|
||||
e_ovno: {
|
||||
name: "e_ovno",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 26,
|
||||
field_type: "WORD",
|
||||
},
|
||||
e_res: {
|
||||
name: "e_res",
|
||||
value: null,
|
||||
size: 8,
|
||||
offset: 28,
|
||||
field_type: "[WORD; 4]",
|
||||
},
|
||||
e_oemid: {
|
||||
name: "e_oemid",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 36,
|
||||
field_type: "WORD",
|
||||
},
|
||||
e_oeminfo: {
|
||||
name: "e_oeminfo",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 38,
|
||||
field_type: "WORD",
|
||||
},
|
||||
e_res2: {
|
||||
name: "e_res2",
|
||||
value: 0,
|
||||
size: 20,
|
||||
offset: 40,
|
||||
field_type: "[WORD; 10]",
|
||||
},
|
||||
e_lfanew: {
|
||||
name: "e_lfanew",
|
||||
value: 0,
|
||||
size: 4,
|
||||
offset: 60,
|
||||
field_type: "DWORD",
|
||||
},
|
||||
};
|
||||
|
||||
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] });
|
||||
export default function DosHeader() {
|
||||
const dataTemplate: DataTemplateInterface = {
|
||||
e_magic: {
|
||||
name: "e_magic",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 0,
|
||||
data_type: "hex",
|
||||
},
|
||||
e_cblp: {
|
||||
name: "e_cblp",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 2,
|
||||
data_type: "hex",
|
||||
},
|
||||
e_cp: {
|
||||
name: "e_cp",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 4,
|
||||
data_type: "hex",
|
||||
},
|
||||
e_crlc: {
|
||||
name: "e_crlc",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 6,
|
||||
data_type: "hex",
|
||||
},
|
||||
e_cparhdr: {
|
||||
name: "e_cparhdr",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 8,
|
||||
data_type: "hex",
|
||||
},
|
||||
e_minalloc: {
|
||||
name: "e_minalloc",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 10,
|
||||
data_type: "hex",
|
||||
},
|
||||
e_maxalloc: {
|
||||
name: "e_maxalloc",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 12,
|
||||
data_type: "hex",
|
||||
},
|
||||
e_ss: {
|
||||
name: "e_ss",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 14,
|
||||
data_type: "hex",
|
||||
},
|
||||
e_sp: {
|
||||
name: "e_sp",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 16,
|
||||
data_type: "hex",
|
||||
},
|
||||
e_csum: {
|
||||
name: "e_csum",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 18,
|
||||
data_type: "hex",
|
||||
},
|
||||
e_ip: {
|
||||
name: "e_ip",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 20,
|
||||
data_type: "hex",
|
||||
},
|
||||
e_cs: {
|
||||
name: "e_cs",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 22,
|
||||
data_type: "hex",
|
||||
},
|
||||
e_lfarlc: {
|
||||
name: "e_lfarlc",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 24,
|
||||
data_type: "hex",
|
||||
},
|
||||
e_ovno: {
|
||||
name: "e_ovno",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 26,
|
||||
data_type: "hex",
|
||||
},
|
||||
e_res: {
|
||||
name: "e_res",
|
||||
value: null,
|
||||
size: 8,
|
||||
offset: 28,
|
||||
// field_type: "[WORD; 4]",
|
||||
array_element_size: 2,
|
||||
data_type: "array",
|
||||
},
|
||||
e_oemid: {
|
||||
name: "e_oemid",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 36,
|
||||
data_type: "hex",
|
||||
},
|
||||
e_oeminfo: {
|
||||
name: "e_oeminfo",
|
||||
value: 0,
|
||||
size: 2,
|
||||
offset: 38,
|
||||
data_type: "hex",
|
||||
},
|
||||
e_res2: {
|
||||
name: "e_res2",
|
||||
value: 0,
|
||||
size: 20,
|
||||
offset: 40,
|
||||
data_type: "array",
|
||||
array_element_size: 2,
|
||||
},
|
||||
e_lfanew: {
|
||||
name: "e_lfanew",
|
||||
value: 0,
|
||||
size: 4,
|
||||
offset: 60,
|
||||
data_type: "hex",
|
||||
},
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
const templateData = {
|
||||
machine: {
|
||||
name: "Machine",
|
||||
offset: 0,
|
||||
size: 2,
|
||||
value: null,
|
||||
field_type: "WORD",
|
||||
},
|
||||
number_of_sections: {
|
||||
name: "Number of Sections",
|
||||
offset: 2,
|
||||
size: 2,
|
||||
value: null,
|
||||
field_type: "WORD",
|
||||
},
|
||||
time_date_stamp: {
|
||||
name: "Time Date Stamp",
|
||||
offset: 4,
|
||||
size: 4,
|
||||
value: null,
|
||||
field_type: "DWORD",
|
||||
},
|
||||
pointer_to_symbol_table: {
|
||||
name: "Pointer to Symbol Table",
|
||||
offset: 8,
|
||||
size: 4,
|
||||
value: null,
|
||||
field_type: "DWORD",
|
||||
},
|
||||
number_of_symbols: {
|
||||
name: "Number of Symbols",
|
||||
offset: 12,
|
||||
size: 4,
|
||||
value: null,
|
||||
field_type: "DWORD",
|
||||
},
|
||||
size_of_optional_header: {
|
||||
name: "Size of Optional Header",
|
||||
offset: 16,
|
||||
size: 2,
|
||||
value: null,
|
||||
field_type: "WORD",
|
||||
},
|
||||
characteristics: {
|
||||
name: "Characteristics",
|
||||
offset: 18,
|
||||
size: 2,
|
||||
value: null,
|
||||
field_type: "WORD",
|
||||
},
|
||||
};
|
||||
|
||||
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 = [
|
||||
{
|
||||
title: "字段名称",
|
||||
dataIndex: "name",
|
||||
width: "30%",
|
||||
},
|
||||
{
|
||||
title: "偏移量",
|
||||
dataIndex: "offset",
|
||||
defaultSortOrder: "ascend",
|
||||
render: (text) => {
|
||||
return offsetHex ? `0x${text.toString(16).toUpperCase()}` : text;
|
||||
},
|
||||
onHeaderCell: () => ({
|
||||
onClick: () => {
|
||||
setOffsetHex(!offsetHex);
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: "字段类型",
|
||||
dataIndex: "field_type",
|
||||
},
|
||||
{
|
||||
title: "字段大小",
|
||||
dataIndex: "size",
|
||||
},
|
||||
{
|
||||
title: "字段值",
|
||||
dataIndex: "value",
|
||||
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,
|
||||
},
|
||||
};
|
||||
|
||||
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}
|
||||
columns={columns as any}
|
||||
pagination={false}
|
||||
size="small"
|
||||
sticky={{ offsetHeader: -16 }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
import NodeTableComponent, {
|
||||
DataTemplateInterface,
|
||||
} from "../NodeTableComponent/NodeTableComponent";
|
||||
|
||||
export default function FileHeader() {
|
||||
const dataTemplate: DataTemplateInterface = {
|
||||
machine: {
|
||||
name: "Machine",
|
||||
offset: 0,
|
||||
size: 2,
|
||||
value: null,
|
||||
data_type: "hex",
|
||||
},
|
||||
number_of_sections: {
|
||||
name: "Number of Sections",
|
||||
offset: 2,
|
||||
size: 2,
|
||||
value: null,
|
||||
data_type: "hex",
|
||||
},
|
||||
time_date_stamp: {
|
||||
name: "Time Date Stamp",
|
||||
offset: 4,
|
||||
size: 4,
|
||||
value: null,
|
||||
data_type: "hex",
|
||||
},
|
||||
pointer_to_symbol_table: {
|
||||
name: "Pointer to Symbol Table",
|
||||
offset: 8,
|
||||
size: 4,
|
||||
value: null,
|
||||
data_type: "hex",
|
||||
},
|
||||
number_of_symbols: {
|
||||
name: "Number of Symbols",
|
||||
offset: 12,
|
||||
size: 4,
|
||||
value: null,
|
||||
data_type: "hex",
|
||||
},
|
||||
size_of_optional_header: {
|
||||
name: "Size of Optional Header",
|
||||
offset: 16,
|
||||
size: 2,
|
||||
value: null,
|
||||
data_type: "hex",
|
||||
},
|
||||
characteristics: {
|
||||
name: "Characteristics",
|
||||
offset: 18,
|
||||
size: 2,
|
||||
value: null,
|
||||
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 command = "command_get_pe_data_file_header";
|
||||
const columns = [
|
||||
{
|
||||
title: "字段名",
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
},
|
||||
{
|
||||
title: "偏移",
|
||||
dataIndex: "offset",
|
||||
key: "offset",
|
||||
hexSwitch: true,
|
||||
},
|
||||
{
|
||||
title: "大小",
|
||||
dataIndex: "size",
|
||||
key: "size",
|
||||
},
|
||||
{
|
||||
title: "值",
|
||||
dataIndex: "value",
|
||||
key: "value",
|
||||
hexSwitch: true,
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
title: "描述信息",
|
||||
dataIndex: "description",
|
||||
key: "description",
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Flex className={styles.root}>
|
||||
<App />
|
||||
</Flex>
|
||||
<NodeTableComponent
|
||||
dataTemplate={dataTemplate}
|
||||
command={command}
|
||||
columns={columns as any}
|
||||
></NodeTableComponent>
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
const templateData = {
|
||||
signature: {
|
||||
name: "Signature",
|
||||
offset: 0,
|
||||
size: 4,
|
||||
value: null,
|
||||
field_type: "DWORD",
|
||||
},
|
||||
};
|
||||
|
||||
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 = [
|
||||
{
|
||||
title: "字段名称",
|
||||
dataIndex: "name",
|
||||
width: "30%",
|
||||
},
|
||||
{
|
||||
title: "偏移量",
|
||||
dataIndex: "offset",
|
||||
defaultSortOrder: "ascend",
|
||||
render: (text) => {
|
||||
return offsetHex ? `0x${text.toString(16).toUpperCase()}` : text;
|
||||
},
|
||||
onHeaderCell: () => ({
|
||||
onClick: () => {
|
||||
setOffsetHex(!offsetHex);
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: "字段类型",
|
||||
dataIndex: "field_type",
|
||||
},
|
||||
{
|
||||
title: "字段大小",
|
||||
dataIndex: "size",
|
||||
},
|
||||
{
|
||||
title: "字段值",
|
||||
dataIndex: "value",
|
||||
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,
|
||||
},
|
||||
};
|
||||
|
||||
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}
|
||||
columns={columns as any}
|
||||
pagination={false}
|
||||
size="small"
|
||||
sticky={{ offsetHeader: -16 }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
import NodeTableComponent, {
|
||||
DataTemplateInterface,
|
||||
} from "../NodeTableComponent/NodeTableComponent";
|
||||
|
||||
export default function NTHeader() {
|
||||
const dataTemplate: DataTemplateInterface = {
|
||||
signature: {
|
||||
name: "Signature",
|
||||
offset: 0,
|
||||
size: 4,
|
||||
value: 0,
|
||||
data_type: "hex",
|
||||
},
|
||||
};
|
||||
const command = "command_get_pe_data_nt_header";
|
||||
const columns = [
|
||||
{
|
||||
title: "字段名",
|
||||
dataIndex: "name",
|
||||
key: "name",
|
||||
},
|
||||
{
|
||||
title: "偏移",
|
||||
dataIndex: "offset",
|
||||
key: "offset",
|
||||
hexSwitch: true,
|
||||
},
|
||||
{
|
||||
title: "大小",
|
||||
dataIndex: "size",
|
||||
key: "size",
|
||||
},
|
||||
{
|
||||
title: "值",
|
||||
dataIndex: "value",
|
||||
key: "value",
|
||||
hexSwitch: true,
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
title: "描述信息",
|
||||
dataIndex: "description",
|
||||
key: "description",
|
||||
},
|
||||
];
|
||||
return (
|
||||
<Flex className={styles.root}>
|
||||
<App />
|
||||
</Flex>
|
||||
<NodeTableComponent
|
||||
dataTemplate={dataTemplate}
|
||||
command={command}
|
||||
columns={columns as any}
|
||||
></NodeTableComponent>
|
||||
);
|
||||
}
|
||||
|
@ -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%;
|
||||
}
|
276
src/components/NodeTableComponent/NodeTableComponent.tsx
Normal file
276
src/components/NodeTableComponent/NodeTableComponent.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user