From 7cf5a9c531764f43723f8dedd0acd3af28fdfb87 Mon Sep 17 00:00:00 2001 From: "381848900@qq.com" Date: Tue, 10 Dec 2024 23:53:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=B0=81=E8=A3=85=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/src/commands.rs | 2 +- src-tauri/src/pe_parse/header.rs | 22 +- src-tauri/src/services/file.rs | 10 +- src/components/DosHeader/DosHeader.tsx | 594 +++++------------- src/components/FileHeader/FileHeader.tsx | 396 +++++------- src/components/NTHeader/NTHeader.tsx | 248 ++------ .../NodeTableComponent.module.scss | 14 + .../NodeTableComponent/NodeTableComponent.tsx | 276 ++++++++ src/pages/MainPage.tsx | 100 ++- 9 files changed, 780 insertions(+), 882 deletions(-) create mode 100644 src/components/NodeTableComponent/NodeTableComponent.module.scss create mode 100644 src/components/NodeTableComponent/NodeTableComponent.tsx diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index ac9ba79..4f0adf2 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -106,7 +106,7 @@ pub fn command_get_pe_data_dos_header() -> Result(&self, serializer: S) -> Result + where + S: serde::Serializer { + // 直接返回bitflags的整数值 + serializer.serialize_u16(self.bits()) + } +} + + +impl Serialize for DLLCharacteristics{ + fn serialize(&self, serializer: S) -> Result + 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; diff --git a/src-tauri/src/services/file.rs b/src-tauri/src/services/file.rs index f3e7177..810e1b7 100644 --- a/src-tauri/src/services/file.rs +++ b/src-tauri/src/services/file.rs @@ -19,13 +19,13 @@ pub fn mmap_mut_file(file_path: &str) -> Result { #[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 { 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 { // 获取文件头数据 #[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 { 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) diff --git a/src/components/DosHeader/DosHeader.tsx b/src/components/DosHeader/DosHeader.tsx index 07fd8e6..0edb96a 100644 --- a/src/components/DosHeader/DosHeader.tsx +++ b/src/components/DosHeader/DosHeader.tsx @@ -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 = GetRef>; - -const EditableContext = React.createContext | 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 = ({ index, ...props }) => { - const [form] = Form.useForm(); - return ( -
- - - -
- ); -}; - -interface EditableCellProps { - title: React.ReactNode; - editable: boolean; - dataIndex: keyof Item; - record: Item; - handleSave: (record: Item) => void; -} - -const EditableCell: React.FC> = ({ - 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] }); +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 ? ( - - - - ) : ( -
- {children} -
- ); - } - - return {childNode}; -}; - -interface DataType { - name: string; // 字段名称 - offset: string; // 偏移 - size: string | Number; // 字段大小 - value: string; // 字段值 - field_type: string; // 字段类型 - children?: DataType[]; -} - -type ColumnTypes = Exclude["columns"], undefined>; - -const App = () => { - const [offsetHex, setOffsetHex] = useState(false); - const [dataSource, setDataSource] = useState([]); - - 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 ( - - 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 ( - - - + ); } diff --git a/src/components/FileHeader/FileHeader.tsx b/src/components/FileHeader/FileHeader.tsx index 6a8c055..c33c81d 100644 --- a/src/components/FileHeader/FileHeader.tsx +++ b/src/components/FileHeader/FileHeader.tsx @@ -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 | 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 ( -
- - - -
- ); -}; - -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 ? ( - - - - ) : ( -
- {children} -
- ); - } - - return {childNode}; -}; - -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 ( - "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 ( - - - + ); } diff --git a/src/components/NTHeader/NTHeader.tsx b/src/components/NTHeader/NTHeader.tsx index 545a7e2..f3bab1f 100644 --- a/src/components/NTHeader/NTHeader.tsx +++ b/src/components/NTHeader/NTHeader.tsx @@ -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 | 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 ( -
- -
- - - ); -}; - -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 ? ( - - - - ) : ( -
- {children} -
- ); - } - - return ; -}; - -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 ( -
{childNode}
"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 ( - - - + ); } diff --git a/src/components/NodeTableComponent/NodeTableComponent.module.scss b/src/components/NodeTableComponent/NodeTableComponent.module.scss new file mode 100644 index 0000000..b5a7f11 --- /dev/null +++ b/src/components/NodeTableComponent/NodeTableComponent.module.scss @@ -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%; +} \ No newline at end of file diff --git a/src/components/NodeTableComponent/NodeTableComponent.tsx b/src/components/NodeTableComponent/NodeTableComponent.tsx new file mode 100644 index 0000000..a1b15b1 --- /dev/null +++ b/src/components/NodeTableComponent/NodeTableComponent.tsx @@ -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 extends ColumnType { + 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[]; +} + +interface EditableRowProps { + index: number; +} +const EditableContext = React.createContext | null>(null); +const EditableRow: React.FC = ({ index, ...props }) => { + const [form] = Form.useForm(); + return ( +
+ +
+ + + ); +}; + +interface EditableCellProps { + title: React.ReactNode; + editable: boolean; + dataIndex: keyof DataType; + record: DataType; + handleSave: (record: DataType) => void; +} + +const EditableCell: React.FC> = ({ + 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 ? ( + + + + ) : ( +
+ {children} +
+ ); + } + + return
; +}; + +export default function NodeTableComponent(props: NodeTableComponentProps) { + const [data, setData] = useState([]); + // 需要搞一个状态,用来保存不同列的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 ( + +
{childNode}
"editable-row"} + columns={formatCol} + dataSource={data} + pagination={false} + rowKey={"name"} + bordered + size="small" + sticky={{ offsetHeader: -8 }} + /> + + ); +} diff --git a/src/pages/MainPage.tsx b/src/pages/MainPage.tsx index 07211b8..25d11b2 100644 --- a/src/pages/MainPage.tsx +++ b/src/pages/MainPage.tsx @@ -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%", }} > @@ -100,8 +103,103 @@ export default function MainPage() { ) : ( )} + {/* */} ); } + +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 ( + <> + + + ); +};