From d8b49c12d100c072f59202dcb451cdd7563e75df Mon Sep 17 00:00:00 2001 From: "381848900@qq.com" Date: Thu, 12 Dec 2024 16:08:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=8F=90=E4=BE=9B=E4=BA=86=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + src-tauri/src/app_error.rs | 4 + src-tauri/src/app_state.rs | 27 +++- src-tauri/src/commands.rs | 26 +++- src-tauri/src/lib.rs | 3 +- src-tauri/src/services/file.rs | 12 +- src/components/DosHeader/DosHeader.tsx | 140 +++++++++++++++--- src/components/NTHeader/NTHeader.tsx | 1 + .../NodeTableComponent/NodeTableComponent.tsx | 13 +- .../SectionHeaders/SectionHeaders.tsx | 1 - 10 files changed, 189 insertions(+), 39 deletions(-) diff --git a/.gitignore b/.gitignore index 86f7a31..517c835 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ dist-ssr *.njsproj *.sln *.sw? +test_files \ No newline at end of file diff --git a/src-tauri/src/app_error.rs b/src-tauri/src/app_error.rs index bad370e..74c2edf 100644 --- a/src-tauri/src/app_error.rs +++ b/src-tauri/src/app_error.rs @@ -21,6 +21,10 @@ pub enum AppError { /// 初始化打开文件失败 #[error("初始化打开文件失败!: {0}")] InitOpenFileFailed(String), + + /// 无法修改文件 + #[error("无法修改文件!: {0}")] + CannotModifyFile(String), } impl serde::Serialize for AppError { diff --git a/src-tauri/src/app_state.rs b/src-tauri/src/app_state.rs index a3f3d90..4c6b897 100644 --- a/src-tauri/src/app_state.rs +++ b/src-tauri/src/app_state.rs @@ -17,14 +17,37 @@ impl AppState { /// 初始化打开文件 pub fn init_open_file(&mut self, file_path: &str) -> Result<(), AppError> { self.file_path = Some(file_path.to_string()); - self.mmap = Some(file::mmap_mut_file(file_path)?); + self.mmap = Some(file::mmap_file(file_path)?); // 读取PE格式来判断是否是64位 let mmap: &Mmap = self.mmap.as_ref().unwrap(); self.is_64_bit = Some(mmap.is_64_bit()?); Ok(()) } - /// 获取文件内存映射 + + /// 获取文件内存映射的引用 pub fn get_mmap_ref(&self) -> Result<&Mmap, AppError> { self.mmap.as_ref().ok_or(AppError::NoFileOpened) } + + /// 设置文件内存映射 + pub fn set_mmap(&mut self, mmap: Mmap) { + self.mmap = Some(mmap); + } + + /// 在指定偏移处写入数据,此举会同步修改文件内容 + pub fn write_data(&mut self, offset: usize, data: &[u8]) -> Result<(), AppError> { + // 1. 重新以可读可写打开文件 + let file_path = self.file_path.as_ref().ok_or(AppError::NoFileOpened)?; + let mut rw_mmap = file::mmap_file_rw(file_path)?; + // 2. 向内存映射中写入数据 + rw_mmap[offset..(offset + data.len())].copy_from_slice(data); + rw_mmap.flush()?; + + // 3. 重新打开文件 + self.mmap = Some(file::mmap_file(file_path)?); + self.is_64_bit = Some(self.get_mmap_ref()?.is_64_bit()?); + + Ok(()) + } + } \ No newline at end of file diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index c60f772..cde84d5 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -30,7 +30,7 @@ pub fn set_complete(app: AppHandle) -> Result<(), AppError> { } // 命令,打开文件 -#[tauri::command] +#[tauri::command(async)] pub fn command_open_file( file_path: &str, app_state: State<'_, Mutex>, @@ -43,7 +43,7 @@ pub fn command_open_file( Ok(()) } -#[tauri::command] +#[tauri::command(async)] pub fn command_get_file_pe_node_tree_data( app_state: State<'_, Mutex>, ) -> Result, AppError> { @@ -100,7 +100,7 @@ pub fn command_get_file_pe_node_tree_data( } // 命令,获取DOS头数据 -#[tauri::command] +#[tauri::command(async)] pub fn command_get_pe_data_dos_header( app_state: State<'_, Mutex>, ) -> Result { @@ -114,7 +114,7 @@ pub fn command_get_pe_data_dos_header( } // 命令,获取NT头数据 -#[tauri::command] +#[tauri::command(async)] pub fn command_get_pe_data_nt_header( app_state: State<'_, Mutex>, ) -> Result { @@ -129,7 +129,7 @@ pub fn command_get_pe_data_nt_header( } // 命令,获取文件头数据 -#[tauri::command] +#[tauri::command(async)] pub fn command_get_pe_data_file_header(app_state: State<'_, Mutex>) -> Result { let app_state = app_state.lock().unwrap(); let mmap = app_state.get_mmap_ref()?; @@ -142,7 +142,7 @@ pub fn command_get_pe_data_file_header(app_state: State<'_, Mutex>) -> } // 命令,获取可选头数据 -#[tauri::command] +#[tauri::command(async)] pub fn command_get_pe_data_optional_header(app_state: State<'_, Mutex>) -> Result { let app_state = app_state.lock().unwrap(); let mmap = app_state.get_mmap_ref()?; @@ -157,7 +157,7 @@ pub fn command_get_pe_data_optional_header(app_state: State<'_, Mutex> } // 命令,获取节区头数据 -#[tauri::command] +#[tauri::command(async)] pub fn command_get_pe_data_section_headers(app_state: State<'_, Mutex>) -> Result { let app_state = app_state.lock().unwrap(); let mmap = app_state.get_mmap_ref()?; @@ -169,3 +169,15 @@ pub fn command_get_pe_data_section_headers(app_state: State<'_, Mutex> "base_offset": section_headers_offset, })) } + +// 命令,修改偏移处数据 +#[tauri::command(async)] +pub fn command_write_data( + app_state: State<'_, Mutex>, + offset: usize, + data: Vec, +) -> Result<(), AppError> { + let mut app_state = app_state.lock().unwrap(); + app_state.write_data(offset, &data)?; + Ok(()) +} \ No newline at end of file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index b4879de..7d66eca 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -15,6 +15,7 @@ pub fn run() { .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_shell::init()) .invoke_handler(tauri::generate_handler![ + commands::set_complete, commands::command_open_file, commands::command_get_file_pe_node_tree_data, commands::command_get_pe_data_dos_header, @@ -22,7 +23,7 @@ pub fn run() { commands::command_get_pe_data_file_header, commands::command_get_pe_data_optional_header, commands::command_get_pe_data_section_headers, - commands::set_complete, + commands::command_write_data, ]) .setup(|app| { app.manage(Mutex::new(app_state::AppState::default())); diff --git a/src-tauri/src/services/file.rs b/src-tauri/src/services/file.rs index d5c2a5b..484f3e1 100644 --- a/src-tauri/src/services/file.rs +++ b/src-tauri/src/services/file.rs @@ -1,7 +1,7 @@ use memmap2::*; /// 以只读的方式创建文件映射 -pub fn mmap_mut_file(file_path: &str) -> Result { +pub fn mmap_file(file_path: &str) -> Result { let file = std::fs::OpenOptions::new().read(true).open(file_path)?; unsafe { MmapOptions::new() @@ -9,3 +9,13 @@ pub fn mmap_mut_file(file_path: &str) -> Result { .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) } } + +/// 以读写方式创建文件映射 +pub fn mmap_file_rw(file_path: &str) -> Result { + let file = std::fs::OpenOptions::new().read(true).write(true).open(file_path)?; + unsafe { + MmapOptions::new() + .map_mut(&file) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) + } +} \ No newline at end of file diff --git a/src/components/DosHeader/DosHeader.tsx b/src/components/DosHeader/DosHeader.tsx index 43ca13d..af44fac 100644 --- a/src/components/DosHeader/DosHeader.tsx +++ b/src/components/DosHeader/DosHeader.tsx @@ -1,16 +1,73 @@ import { useEffect, useState } from "react"; import NodeTableComponent, { DataTemplateInterface, + DataType, } from "../NodeTableComponent/NodeTableComponent"; -import { invoke } from '@tauri-apps/api/core'; +import { invoke } from "@tauri-apps/api/core"; +import { message } from "antd"; +interface DosHeaderResponseData { + base_offset: number; + fields: { + [key: string]: number | number[]; + }; +} + +const checkEditValue = (item: DataType) => { + const { value, data_type, size } = item; + if (data_type === "hex") { + let numberValue = value as number; + if (typeof value === "string") { + numberValue = parseInt(value, 10); + } + // 如果是2位 最大为 0xFFFF 10进制为 65535 + let maxBit = Math.pow(2, size * 8) - 1; + console.log("maxBit", maxBit); + if (numberValue > maxBit) { + return false; + } + return true; + } +}; + +const formatEditValue = (item: DataType) => { + const { value, data_type, size } = item; + let bufs = []; + + if (data_type === "hex") { + let numberValue = value as number; + if (typeof value === "string") { + numberValue = parseInt(value, 10); + } + // 转小尾 + for (let i = 0; i < size; i++) { + bufs.push(numberValue & 0xff); + numberValue = numberValue >> 8; + } + console.log("bufs", bufs); + return bufs; + } +}; export default function DosHeader() { - - const [data, setData] = useState([]); + const [data, setData] = useState(); + const [messageApi, contextHolder] = message.useMessage(); + const success = () => { + messageApi.open({ + type: "success", + content: "修改成功!", + }); + }; + const error = () => { + messageApi.open({ + type: "error", + content: "修改失败!", + }); + }; const dataTemplate: DataTemplateInterface = { e_magic: { name: "e_magic", + description: "标识文件的魔数,DOS 可执行文件的值通常为 'MZ'。", value: 0, size: 2, offset: 0, @@ -18,6 +75,7 @@ export default function DosHeader() { }, e_cblp: { name: "e_cblp", + description: "文件最后一页的字节数,指示文件不完整页的大小。", value: 0, size: 2, offset: 2, @@ -25,6 +83,7 @@ export default function DosHeader() { }, e_cp: { name: "e_cp", + description: "文件的总页数(每页 512 字节)。", value: 0, size: 2, offset: 4, @@ -32,6 +91,7 @@ export default function DosHeader() { }, e_crlc: { name: "e_crlc", + description: "重定位表中的条目数。", value: 0, size: 2, offset: 6, @@ -39,6 +99,7 @@ export default function DosHeader() { }, e_cparhdr: { name: "e_cparhdr", + description: "文件头部的段数,单位为 16 字节段。", value: 0, size: 2, offset: 8, @@ -46,6 +107,7 @@ export default function DosHeader() { }, e_minalloc: { name: "e_minalloc", + description: "程序需要的最小额外段数。", value: 0, size: 2, offset: 10, @@ -53,6 +115,7 @@ export default function DosHeader() { }, e_maxalloc: { name: "e_maxalloc", + description: "程序可以分配的最大额外段数。", value: 0, size: 2, offset: 12, @@ -60,6 +123,7 @@ export default function DosHeader() { }, e_ss: { name: "e_ss", + description: "初始的 SS 寄存器值,段偏移地址。", value: 0, size: 2, offset: 14, @@ -67,6 +131,7 @@ export default function DosHeader() { }, e_sp: { name: "e_sp", + description: "初始的 SP 寄存器值,栈顶指针。", value: 0, size: 2, offset: 16, @@ -74,6 +139,7 @@ export default function DosHeader() { }, e_csum: { name: "e_csum", + description: "校验和(一般为 0)。", value: 0, size: 2, offset: 18, @@ -81,6 +147,7 @@ export default function DosHeader() { }, e_ip: { name: "e_ip", + description: "初始的 IP 寄存器值,代码段的入口偏移地址。", value: 0, size: 2, offset: 20, @@ -88,6 +155,7 @@ export default function DosHeader() { }, e_cs: { name: "e_cs", + description: "初始的 CS 寄存器值,代码段的段基址。", value: 0, size: 2, offset: 22, @@ -95,6 +163,7 @@ export default function DosHeader() { }, e_lfarlc: { name: "e_lfarlc", + description: "重定位表的文件偏移。", value: 0, size: 2, offset: 24, @@ -102,6 +171,7 @@ export default function DosHeader() { }, e_ovno: { name: "e_ovno", + description: "覆盖号(通常为 0)。", value: 0, size: 2, offset: 26, @@ -109,15 +179,16 @@ export default function DosHeader() { }, e_res: { name: "e_res", + description: "保留字段,通常为 0。", value: null, size: 8, offset: 28, - // field_type: "[WORD; 4]", array_element_size: 2, data_type: "array", }, e_oemid: { name: "e_oemid", + description: "OEM 标识符。", value: 0, size: 2, offset: 36, @@ -125,6 +196,7 @@ export default function DosHeader() { }, e_oeminfo: { name: "e_oeminfo", + description: "OEM 信息。", value: 0, size: 2, offset: 38, @@ -132,6 +204,7 @@ export default function DosHeader() { }, e_res2: { name: "e_res2", + description: "保留字段,通常为 0。", value: 0, size: 20, offset: 40, @@ -140,6 +213,7 @@ export default function DosHeader() { }, e_lfanew: { name: "e_lfanew", + description: "PE 文件头的偏移地址。", value: 0, size: 4, offset: 60, @@ -154,20 +228,20 @@ export default function DosHeader() { title: "字段名", dataIndex: "name", key: "name", - width: 120 + width: 120, }, { title: "偏移", dataIndex: "offset", key: "offset", hexSwitch: true, - width: 80 + width: 80, }, { title: "大小", dataIndex: "size", key: "size", - width: 80 + width: 80, }, { title: "值", @@ -175,28 +249,56 @@ export default function DosHeader() { key: "value", hexSwitch: true, editable: true, - minWidth: 200 + minWidth: 200, }, { title: "描述信息", dataIndex: "description", key: "description", - width: 250 + width: 250, }, ]; - useEffect(()=>{ - invoke(command).then(res => { - console.log("asdfsadf",res); - setData(res as any); + const handleSave = (item: DataType) => { + console.log("handleSave", item); + if (!checkEditValue(item)) { + return; + } + const bufs = formatEditValue(item); + console.log("change value", bufs); + invoke("command_write_data", { + offset: item.offset, + data: bufs, }) - }, []) + .then((res) => { + // 提示 + success(); + getData(); + }) + .catch(() => { + error(); + }); + }; + + const getData = () => { + invoke(command).then((res) => { + setData(res as any); + }); + }; + + useEffect(() => { + getData(); + }, []); return ( - + <> + {contextHolder} + + ); } diff --git a/src/components/NTHeader/NTHeader.tsx b/src/components/NTHeader/NTHeader.tsx index 4da03f3..e7b4f32 100644 --- a/src/components/NTHeader/NTHeader.tsx +++ b/src/components/NTHeader/NTHeader.tsx @@ -14,6 +14,7 @@ export default function NTHeader() { size: 4, value: 0, data_type: "hex", + description: "PE文件的标志,可执行文件的标志是0x00004550", }, }; const command = "command_get_pe_data_nt_header"; diff --git a/src/components/NodeTableComponent/NodeTableComponent.tsx b/src/components/NodeTableComponent/NodeTableComponent.tsx index 2bcf6b5..3e95c69 100644 --- a/src/components/NodeTableComponent/NodeTableComponent.tsx +++ b/src/components/NodeTableComponent/NodeTableComponent.tsx @@ -54,7 +54,8 @@ interface NodeTableComponentProps { dataTemplate: DataTemplateInterface; // 数据模板 // command: string; // 请求数据的命令 columns: NodeTableColumnInterface[]; - defaultData?: DataType[]; + defaultData?: ResponsData; + onEdit?: (record: DataType) => void; } interface EditableRowProps { @@ -77,7 +78,7 @@ interface EditableCellProps { editable: boolean; dataIndex: keyof DataType; record: DataType; - handleSave: (record: DataType) => void; + handleSave?: (record: DataType) => void; } const EditableCell: React.FC> = ({ @@ -108,7 +109,7 @@ const EditableCell: React.FC> = ({ try { const values = await form.validateFields(); toggleEdit(); - handleSave({ ...record, ...values }); + handleSave?.({ ...record, ...values }); } catch (errInfo) { console.log("Save failed:", errInfo); } @@ -151,10 +152,6 @@ export default function NodeTableComponent(props: NodeTableComponentProps) { setHexSwitchStatus(newHexSwitchStatus); }; - const handleSave = (_row: DataType) => { - // TODO: 保存逻辑 - }; - const hexSwitchRender = (text: string, dataIndex: string) => { if (text == null || text == undefined || typeof text !== "number") { return text; @@ -180,7 +177,7 @@ export default function NodeTableComponent(props: NodeTableComponentProps) { editable: col.editable, dataIndex: col.dataIndex, title: col.title, - handleSave, + handleSave: props.onEdit, }); } if (col.hexSwitch) { diff --git a/src/components/SectionHeaders/SectionHeaders.tsx b/src/components/SectionHeaders/SectionHeaders.tsx index d189541..67546e0 100644 --- a/src/components/SectionHeaders/SectionHeaders.tsx +++ b/src/components/SectionHeaders/SectionHeaders.tsx @@ -133,7 +133,6 @@ export default function SectionHeaders() { setData(formatDataHandler(res)); }); }, []); - console.log("yhyy", data); return ( {data.map((item, index) => {