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 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)
|
||||||
|
@ -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;
|
||||||
|
@ -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)
|
||||||
|
@ -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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user