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 file_data = binding.as_ref().unwrap();
let dos_header = file_data.get_dos_header()?; let dos_header = file_data.get_dos_header()?;
let result = ResponseDOSHeaderData { let result = ResponseDOSHeaderData {
image_dos_header: dos_header.clone(), fields: dos_header.clone(),
base_offset: 0, base_offset: 0,
}; };
Ok(result) Ok(result)

View File

@ -45,9 +45,27 @@ pub struct ImageFileHeader {
pub size_of_optional_header: u16, pub size_of_optional_header: u16,
pub characteristics: FileCharacteristics, 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! { bitflags! {
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy, Serialize)] #[derive(Debug, Clone, Copy)]
pub struct FileCharacteristics: u16 { pub struct FileCharacteristics: u16 {
const RELOCS_STRIPPED = 0x0001; const RELOCS_STRIPPED = 0x0001;
const EXECUTABLE_IMAGE = 0x0002; const EXECUTABLE_IMAGE = 0x0002;
@ -66,7 +84,7 @@ bitflags! {
const BYTES_REVERSED_HI = 0x8000; const BYTES_REVERSED_HI = 0x8000;
} }
#[repr(C)] #[repr(C)]
#[derive(Debug, Clone, Copy, Serialize)] #[derive(Debug, Clone, Copy)]
pub struct DLLCharacteristics: u16 { pub struct DLLCharacteristics: u16 {
const RESERVED1 = 0x0001; const RESERVED1 = 0x0001;
const RESERVED2 = 0x0002; const RESERVED2 = 0x0002;

View File

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

View File

@ -1,453 +1,185 @@
import styles from "./DosHeader.module.scss"; import NodeTableComponent, {
import { Flex } from "antd"; DataTemplateInterface,
import React, { useContext, useEffect, useRef, useState } from "react"; } from "../NodeTableComponent/NodeTableComponent";
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";
type FormInstance<T> = GetRef<typeof Form<T>>; export default function DosHeader() {
const dataTemplate: DataTemplateInterface = {
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: { e_magic: {
name: "e_magic", name: "e_magic",
value: 0, value: 0,
size: 2, size: 2,
offset: 0, offset: 0,
field_type: "WORD", data_type: "hex",
}, },
e_cblp: { e_cblp: {
name: "e_cblp", name: "e_cblp",
value: 0, value: 0,
size: 2, size: 2,
offset: 2, offset: 2,
field_type: "WORD", data_type: "hex",
}, },
e_cp: { e_cp: {
name: "e_cp", name: "e_cp",
value: 0, value: 0,
size: 2, size: 2,
offset: 4, offset: 4,
field_type: "WORD", data_type: "hex",
}, },
e_crlc: { e_crlc: {
name: "e_crlc", name: "e_crlc",
value: 0, value: 0,
size: 2, size: 2,
offset: 6, offset: 6,
field_type: "WORD", data_type: "hex",
}, },
e_cparhdr: { e_cparhdr: {
name: "e_cparhdr", name: "e_cparhdr",
value: 0, value: 0,
size: 2, size: 2,
offset: 8, offset: 8,
field_type: "WORD", data_type: "hex",
}, },
e_minalloc: { e_minalloc: {
name: "e_minalloc", name: "e_minalloc",
value: 0, value: 0,
size: 2, size: 2,
offset: 10, offset: 10,
field_type: "WORD", data_type: "hex",
}, },
e_maxalloc: { e_maxalloc: {
name: "e_maxalloc", name: "e_maxalloc",
value: 0, value: 0,
size: 2, size: 2,
offset: 12, offset: 12,
field_type: "WORD", data_type: "hex",
}, },
e_ss: { e_ss: {
name: "e_ss", name: "e_ss",
value: 0, value: 0,
size: 2, size: 2,
offset: 14, offset: 14,
field_type: "WORD", data_type: "hex",
}, },
e_sp: { e_sp: {
name: "e_sp", name: "e_sp",
value: 0, value: 0,
size: 2, size: 2,
offset: 16, offset: 16,
field_type: "WORD", data_type: "hex",
}, },
e_csum: { e_csum: {
name: "e_csum", name: "e_csum",
value: 0, value: 0,
size: 2, size: 2,
offset: 18, offset: 18,
field_type: "WORD", data_type: "hex",
}, },
e_ip: { e_ip: {
name: "e_ip", name: "e_ip",
value: 0, value: 0,
size: 2, size: 2,
offset: 20, offset: 20,
field_type: "WORD", data_type: "hex",
}, },
e_cs: { e_cs: {
name: "e_cs", name: "e_cs",
value: 0, value: 0,
size: 2, size: 2,
offset: 22, offset: 22,
field_type: "WORD", data_type: "hex",
}, },
e_lfarlc: { e_lfarlc: {
name: "e_lfarlc", name: "e_lfarlc",
value: 0, value: 0,
size: 2, size: 2,
offset: 24, offset: 24,
field_type: "WORD", data_type: "hex",
}, },
e_ovno: { e_ovno: {
name: "e_ovno", name: "e_ovno",
value: 0, value: 0,
size: 2, size: 2,
offset: 26, offset: 26,
field_type: "WORD", data_type: "hex",
}, },
e_res: { e_res: {
name: "e_res", name: "e_res",
value: null, value: null,
size: 8, size: 8,
offset: 28, offset: 28,
field_type: "[WORD; 4]", // field_type: "[WORD; 4]",
array_element_size: 2,
data_type: "array",
}, },
e_oemid: { e_oemid: {
name: "e_oemid", name: "e_oemid",
value: 0, value: 0,
size: 2, size: 2,
offset: 36, offset: 36,
field_type: "WORD", data_type: "hex",
}, },
e_oeminfo: { e_oeminfo: {
name: "e_oeminfo", name: "e_oeminfo",
value: 0, value: 0,
size: 2, size: 2,
offset: 38, offset: 38,
field_type: "WORD", data_type: "hex",
}, },
e_res2: { e_res2: {
name: "e_res2", name: "e_res2",
value: 0, value: 0,
size: 20, size: 20,
offset: 40, offset: 40,
field_type: "[WORD; 10]", data_type: "array",
array_element_size: 2,
}, },
e_lfanew: { e_lfanew: {
name: "e_lfanew", name: "e_lfanew",
value: 0, value: 0,
size: 4, size: 4,
offset: 60, 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 () => { const command = "command_get_pe_data_dos_header";
try {
const values = await form.validateFields();
toggleEdit();
handleSave({ ...record, ...values });
} catch (errInfo) {
console.log("Save failed:", errInfo);
}
};
let childNode = children; const columns = [
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 = [
{ {
title: "字段名", title: "字段名",
dataIndex: "name", dataIndex: "name",
width: "30%", key: "name",
}, },
{ {
title: "偏移", title: "偏移",
dataIndex: "offset", dataIndex: "offset",
defaultSortOrder: "ascend", key: "offset",
render: (text) => { hexSwitch: true,
return offsetHex ? `0x${text.toString(16).toUpperCase()}` : text;
},
onHeaderCell: () => ({
onClick: () => {
setOffsetHex(!offsetHex);
}
})
}, },
{ {
title: "字段类型", title: "大小",
dataIndex: "field_type",
},
{
title: "字段大小",
dataIndex: "size", dataIndex: "size",
key: "size",
}, },
{ {
title: "字段值", title: "值",
dataIndex: "value", dataIndex: "value",
key: "value",
hexSwitch: true,
editable: 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 ( return (
<Table<DataType> <NodeTableComponent
components={components} dataTemplate={dataTemplate}
rowClassName={() => "editable-row"} command={command}
bordered columns={columns as any}
rowKey={(record) => record.name} ></NodeTableComponent>
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>
); );
} }

View File

@ -1,247 +1,159 @@
import { Flex, Form, FormInstance, Input, Table } from "antd"; import NodeTableComponent, {
import styles from "./FileHeader.module.scss"; DataTemplateInterface,
import { useContext, useEffect, useRef, useState } from "react"; } from "../NodeTableComponent/NodeTableComponent";
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 = { export default function FileHeader() {
const dataTemplate: DataTemplateInterface = {
machine: { machine: {
name: "Machine", name: "Machine",
offset: 0, offset: 0,
size: 2, size: 2,
value: null, value: null,
field_type: "WORD", data_type: "hex",
}, },
number_of_sections: { number_of_sections: {
name: "Number of Sections", name: "Number of Sections",
offset: 2, offset: 2,
size: 2, size: 2,
value: null, value: null,
field_type: "WORD", data_type: "hex",
}, },
time_date_stamp: { time_date_stamp: {
name: "Time Date Stamp", name: "Time Date Stamp",
offset: 4, offset: 4,
size: 4, size: 4,
value: null, value: null,
field_type: "DWORD", data_type: "hex",
}, },
pointer_to_symbol_table: { pointer_to_symbol_table: {
name: "Pointer to Symbol Table", name: "Pointer to Symbol Table",
offset: 8, offset: 8,
size: 4, size: 4,
value: null, value: null,
field_type: "DWORD", data_type: "hex",
}, },
number_of_symbols: { number_of_symbols: {
name: "Number of Symbols", name: "Number of Symbols",
offset: 12, offset: 12,
size: 4, size: 4,
value: null, value: null,
field_type: "DWORD", data_type: "hex",
}, },
size_of_optional_header: { size_of_optional_header: {
name: "Size of Optional Header", name: "Size of Optional Header",
offset: 16, offset: 16,
size: 2, size: 2,
value: null, value: null,
field_type: "WORD", data_type: "hex",
}, },
characteristics: { characteristics: {
name: "Characteristics", name: "Characteristics",
offset: 18, offset: 18,
size: 2, size: 2,
value: null, 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 command = "command_get_pe_data_file_header";
const save = async () => { const columns = [
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: "字段名", title: "字段名",
dataIndex: "name", dataIndex: "name",
width: "30%", key: "name",
}, },
{ {
title: "偏移", title: "偏移",
dataIndex: "offset", dataIndex: "offset",
defaultSortOrder: "ascend", key: "offset",
render: (text) => { hexSwitch: true,
return offsetHex ? `0x${text.toString(16).toUpperCase()}` : text;
},
onHeaderCell: () => ({
onClick: () => {
setOffsetHex(!offsetHex);
},
}),
}, },
{ {
title: "字段类型", title: "大小",
dataIndex: "field_type",
},
{
title: "字段大小",
dataIndex: "size", dataIndex: "size",
key: "size",
}, },
{ {
title: "字段值", title: "值",
dataIndex: "value", dataIndex: "value",
key: "value",
hexSwitch: true,
editable: true, editable: true,
}, },
]; {
title: "描述信息",
const replaceNodeByName = (data, name, newNode) => { dataIndex: "description",
return data.map((item) => { key: "description",
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 ( return (
<Table <NodeTableComponent
components={components} dataTemplate={dataTemplate}
rowClassName={() => "editable-row"} command={command}
bordered
rowKey={(record) => record.name}
style={{ width: "100%" }}
dataSource={dataSource}
columns={columns as any} columns={columns as any}
pagination={false} ></NodeTableComponent>
size="small"
sticky={{ offsetHeader: -16 }}
/>
);
};
export default function FileHeader() {
return (
<Flex className={styles.root}>
<App />
</Flex>
); );
} }

View File

@ -1,205 +1,53 @@
import { Flex, Form, FormInstance, Input, Table } from "antd"; import NodeTableComponent, {
import styles from "./NTHeader.module.scss"; DataTemplateInterface,
import { useContext, useEffect, useRef, useState } from "react"; } from "../NodeTableComponent/NodeTableComponent";
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 = { export default function NTHeader() {
const dataTemplate: DataTemplateInterface = {
signature: { signature: {
name: "Signature", name: "Signature",
offset: 0, offset: 0,
size: 4, size: 4,
value: null, value: 0,
field_type: "DWORD", 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 command = "command_get_pe_data_nt_header";
const save = async () => { const columns = [
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: "字段名", title: "字段名",
dataIndex: "name", dataIndex: "name",
width: "30%", key: "name",
}, },
{ {
title: "偏移", title: "偏移",
dataIndex: "offset", dataIndex: "offset",
defaultSortOrder: "ascend", key: "offset",
render: (text) => { hexSwitch: true,
return offsetHex ? `0x${text.toString(16).toUpperCase()}` : text;
},
onHeaderCell: () => ({
onClick: () => {
setOffsetHex(!offsetHex);
},
}),
}, },
{ {
title: "字段类型", title: "大小",
dataIndex: "field_type",
},
{
title: "字段大小",
dataIndex: "size", dataIndex: "size",
key: "size",
}, },
{ {
title: "字段值", title: "值",
dataIndex: "value", dataIndex: "value",
key: "value",
hexSwitch: true,
editable: true, editable: true,
}, },
]; {
title: "描述信息",
const replaceNodeByName = (data, name, newNode) => { dataIndex: "description",
return data.map((item) => { key: "description",
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 ( return (
<Table <NodeTableComponent
components={components} dataTemplate={dataTemplate}
rowClassName={() => "editable-row"} command={command}
bordered
rowKey={(record) => record.name}
style={{ width: "100%" }}
dataSource={dataSource}
columns={columns as any} columns={columns as any}
pagination={false} ></NodeTableComponent>
size="small"
sticky={{ offsetHeader: -16 }}
/>
);
};
export default function NTHeader() {
return (
<Flex className={styles.root}>
<App />
</Flex>
); );
} }

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 DosHeader from "../components/DosHeader/DosHeader";
import NtHeader from "../components/NTHeader/NTHeader"; import NtHeader from "../components/NTHeader/NTHeader";
import FileHeader from "../components/FileHeader/FileHeader"; import FileHeader from "../components/FileHeader/FileHeader";
import NodeTableComponent, {
DataTemplateInterface,
} from "../components/NodeTableComponent/NodeTableComponent";
import { open } from "@tauri-apps/plugin-dialog"; import { open } from "@tauri-apps/plugin-dialog";
const SelectNodeMap = { const SelectNodeMap = {
@ -84,7 +87,7 @@ export default function MainPage() {
style={{ style={{
backgroundColor: "#e5e5e5", backgroundColor: "#e5e5e5",
paddingBottom: "16px", paddingBottom: "16px",
height: "100%" height: "100%",
}} }}
> >
<Flex className={styles.sider}> <Flex className={styles.sider}>
@ -100,8 +103,103 @@ export default function MainPage() {
) : ( ) : (
<Empty description={"请选择一个节点"} /> <Empty description={"请选择一个节点"} />
)} )}
{/* <TestNodeTableComponent></TestNodeTableComponent> */}
</Content> </Content>
</Flex> </Flex>
</Layout> </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>
</>
);
};