From a279a262b7a84e4c39b696605d6527dda4c38d88 Mon Sep 17 00:00:00 2001 From: Asahi <381848900@qq.com> Date: Tue, 10 Dec 2024 11:25:55 +0800 Subject: [PATCH] =?UTF-8?q?styles:=20=E6=9B=B4=E6=94=B9=E4=B8=80=E4=BA=9B?= =?UTF-8?q?=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/Cargo.lock | 22 +- src-tauri/Cargo.toml | 5 +- src-tauri/pe_serialize_proc_macro/.gitignore | 1 - src-tauri/pe_serialize_proc_macro/Cargo.lock | 68 ----- src-tauri/pe_serialize_proc_macro/Cargo.toml | 16 -- src-tauri/pe_serialize_proc_macro/src/lib.rs | 85 ------ src-tauri/src/commands.rs | 45 ++- src-tauri/src/lib.rs | 2 + src-tauri/src/pe_parse/header.rs | 12 +- src-tauri/src/pe_parse/pe.rs | 45 +++ src-tauri/src/pe_parse/types.rs | 9 + src-tauri/src/services/file.rs | 51 +++- src-tauri/src/services/mod.rs | 2 +- .../DosHeader/DosHeader.module.scss | 1 + src/components/DosHeader/DosHeader.tsx | 261 +++++++++++++++++- .../FileHeader/FileHeader.module.scss | 9 + src/components/FileHeader/FileHeader.tsx | 247 +++++++++++++++++ src/components/NTHeader/NTHeader.module.scss | 8 + src/components/NTHeader/NTHeader.tsx | 205 ++++++++++++++ src/components/side_tree/SideTree.module.scss | 1 - src/components/side_tree/SideTree.tsx | 4 +- src/pages/MainPage.module.scss | 10 +- src/pages/MainPage.tsx | 32 +-- 23 files changed, 879 insertions(+), 262 deletions(-) delete mode 100644 src-tauri/pe_serialize_proc_macro/.gitignore delete mode 100644 src-tauri/pe_serialize_proc_macro/Cargo.lock delete mode 100644 src-tauri/pe_serialize_proc_macro/Cargo.toml delete mode 100644 src-tauri/pe_serialize_proc_macro/src/lib.rs create mode 100644 src/components/FileHeader/FileHeader.module.scss create mode 100644 src/components/FileHeader/FileHeader.tsx create mode 100644 src/components/NTHeader/NTHeader.module.scss create mode 100644 src/components/NTHeader/NTHeader.tsx diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 85ebeae..b894041 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1913,13 +1913,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] -name = "memmap" -version = "0.7.0" +name = "memmap2" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", - "winapi", ] [[package]] @@ -2063,7 +2062,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", "syn 2.0.90", @@ -2409,16 +2408,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" -[[package]] -name = "pe_serialize_proc_macro" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn 2.0.90", -] - [[package]] name = "percent-encoding" version = "2.3.1" @@ -3773,8 +3762,7 @@ name = "test-tauri" version = "0.1.0" dependencies = [ "bitflags 2.6.0", - "memmap", - "pe_serialize_proc_macro", + "memmap2", "serde", "serde_json", "tauri", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 76a0c2b..3caf704 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -18,9 +18,8 @@ name = "test_tauri_lib" tauri-build = {version = "2", features = [] } [dependencies] -bitflags = "2.6.0" -memmap = "0.7.0" -pe_serialize_proc_macro = {path = "./pe_serialize_proc_macro"} +bitflags = {version= "2.6.0", features = ["serde"] } +memmap2 = "0.9.5" serde = {version = "1", features = ["derive"] } serde_json = "1" tauri = {version = "2", features = [] } diff --git a/src-tauri/pe_serialize_proc_macro/.gitignore b/src-tauri/pe_serialize_proc_macro/.gitignore deleted file mode 100644 index b83d222..0000000 --- a/src-tauri/pe_serialize_proc_macro/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target/ diff --git a/src-tauri/pe_serialize_proc_macro/Cargo.lock b/src-tauri/pe_serialize_proc_macro/Cargo.lock deleted file mode 100644 index f156b99..0000000 --- a/src-tauri/pe_serialize_proc_macro/Cargo.lock +++ /dev/null @@ -1,68 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "pe_serialize_proc_macro" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "serde", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "serde" -version = "1.0.215" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.215" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "syn" -version = "2.0.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "unicode-ident" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" diff --git a/src-tauri/pe_serialize_proc_macro/Cargo.toml b/src-tauri/pe_serialize_proc_macro/Cargo.toml deleted file mode 100644 index 4195813..0000000 --- a/src-tauri/pe_serialize_proc_macro/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[lib] -proc-macro = true - -[package] -name = "pe_serialize_proc_macro" -version = "0.1.0" -edition = "2021" - -[dependencies] -proc-macro2 = "1.0.92" -quote = "1.0.37" -serde = "1.0.215" -syn = "2.0.90" - -[serde.features] -default = ["derive"] \ No newline at end of file diff --git a/src-tauri/pe_serialize_proc_macro/src/lib.rs b/src-tauri/pe_serialize_proc_macro/src/lib.rs deleted file mode 100644 index b4a134c..0000000 --- a/src-tauri/pe_serialize_proc_macro/src/lib.rs +++ /dev/null @@ -1,85 +0,0 @@ -use proc_macro::TokenStream; -use quote::quote; -use syn::Data; - -fn impl_serialize_ex(ast: &syn::DeriveInput) -> TokenStream { - let name = &ast.ident; - let fields = match ast.data { - Data::Struct(ref data) => &data.fields, - _ => panic!("CustomSerialize 只能用于结构体!"), - }; - let field_len = fields.len(); - // 迭代器,为每一个字段实现serialize_field - let serialize_fields = match fields { - syn::Fields::Named(ref fields) => { - fields.named.iter().map(|f| { - let name = &f.ident; - let ty = &f.ty; - let mut child_token_stream = proc_macro2::TokenStream::new(); - // 如果字段是数组类型 - if let syn::Type::Array(ref array) = ty { - let len = &array.len; - let child_ty = &array.elem; - // 类型所占字节 - - child_token_stream = quote! { - children = Some(Vec::new()); - let field_size = std::mem::size_of::<#child_ty>(); - for i in 0..#len { - let child_value = serde_json::json!({ - "name": format!("{}[{}]", stringify!(#name), i), - "value": format!("0x{:0width$X}",self.#name[i], width=field_size), - "field_type": stringify!(#child_ty), - "size": field_size, - "offset": (&self.#name[i] as *const _ as usize) - (self as *const _ as usize), - "children": null - }); - children.as_mut().unwrap().push(child_value); - } - }; - }else{ - child_token_stream = quote! { - let field_size = std::mem::size_of::<#ty>(); - value = serde_json::json!(format!("0x{:0width$X}",&self.#name, width=field_size)); - }; - } - quote! { - let mut value = serde_json::Value::Null; - let mut children: Option> = None; - #child_token_stream - - let json_value = serde_json::json!({ - "name": stringify!(#name), - "value": value, - "field_type": stringify!(#ty), - "size": std::mem::size_of::<#ty>(), - "offset": (&self.#name as *const _ as usize) - (self as *const _ as usize), - "children": &children - }); - state.serialize_field(stringify!(#name), &json_value)?; - } - }) - } - _ => panic!("CustomSerialize 只能用于结构体!"), - }; - - let gen = quote! { - impl serde::Serialize for #name { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer { - let mut state = serializer.serialize_struct(stringify!(#name), #field_len)?; - #(#serialize_fields)* - state.end() - } - } - }; - gen.into() -} - -// 拓展Serialize -#[proc_macro_derive(SerializeEX)] -pub fn serialize_ex(input: TokenStream) -> TokenStream { - let ast = syn::parse(input).unwrap(); - impl_serialize_ex(&ast) -} diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 722e7f0..a610c83 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -1,21 +1,10 @@ use crate::{ app_error::AppError, pe_parse::{header::ImageDosHeader, pe::PE}, - services::{self, GLOBAL_FILE_DATA}, + services::{self, file::{get_nt_headers_data, ResponseDOSHeaderData, ResponseFileHeaderData, ResponseNTHeaderData}, GLOBAL_FILE_DATA}, }; use serde::Serialize; -#[tauri::command] -pub fn greet(name: &str) -> String { - format!("Hello, {}! You've been greeted from Rust!", name) -} - -macro_rules! offset_of { - ($base:expr, $field:ident) => { - (&$base.$field as *const _ as usize) - ($base as *const _ as usize) - }; -} - #[derive(Serialize)] pub struct PeNodeTreeData { title: String, @@ -89,21 +78,29 @@ pub fn command_open_file(file_path: &str) -> Result<(), AppError> { Ok(()) } -#[derive(Serialize, Default)] -pub struct ResponseDOSHeaderData { - name: String, - offset: usize, - size: usize, - field_type: String, - value: Option, - children: Option>, -} - // 命令,获取DOS头数据 #[tauri::command] -pub fn command_get_pe_data_dos_header() -> Result { +pub fn command_get_pe_data_dos_header() -> Result { let binding = GLOBAL_FILE_DATA.lock().unwrap(); let file_data = binding.as_ref().unwrap(); let dos_header = file_data.get_dos_header()?; - Ok(dos_header.clone()) + let result = ResponseDOSHeaderData { + image_dos_header: dos_header.clone(), + base_offset: 0, + }; + Ok(result) } + +// 命令,获取NT头数据 +#[tauri::command] +pub fn command_get_pe_data_nt_header() -> Result { + let result = get_nt_headers_data()?; + Ok(result) +} + +// 命令,获取文件头数据 +#[tauri::command] +pub fn command_get_pe_data_file_header() -> Result { + let result = services::file::get_file_header_data()?; + Ok(result) +} \ No newline at end of file diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 75497e6..c3b6501 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -13,6 +13,8 @@ pub fn run() { commands::command_open_file, commands::command_get_file_pe_node_tree_data, commands::command_get_pe_data_dos_header, + commands::command_get_pe_data_nt_header, + commands::command_get_pe_data_file_header, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/pe_parse/header.rs b/src-tauri/src/pe_parse/header.rs index 655a083..e3853cc 100644 --- a/src-tauri/src/pe_parse/header.rs +++ b/src-tauri/src/pe_parse/header.rs @@ -1,10 +1,9 @@ use super::types::*; use bitflags::bitflags; -use pe_serialize_proc_macro::SerializeEX; -use serde::ser::SerializeStruct; +use serde::Serialize; #[repr(C)] -#[derive(Debug, Clone, Copy, SerializeEX)] +#[derive(Debug, Clone, Copy, Serialize)] pub struct ImageDosHeader { pub e_magic: u16, // Magic number 固定值 0x5A4D pub e_cblp: u16, @@ -24,10 +23,11 @@ pub struct ImageDosHeader { pub e_oemid: u16, pub e_oeminfo: u16, pub e_res2: [u16; 10], - pub e_lfanew: u32, // File address of new exe header nt头的偏移 + pub e_lfanew: Offset, // File address of new exe header nt头的偏移 } #[repr(C)] +#[derive(Debug, Clone, Copy, Serialize)] pub struct ImageNTHeaders32 { pub signature: u32, pub file_header: ImageFileHeader, @@ -35,6 +35,7 @@ pub struct ImageNTHeaders32 { } #[repr(C)] +#[derive(Debug, Clone, Copy, Serialize)] pub struct ImageFileHeader { pub machine: u16, pub number_of_sections: u16, @@ -46,6 +47,7 @@ pub struct ImageFileHeader { } bitflags! { #[repr(C)] + #[derive(Debug, Clone, Copy, Serialize)] pub struct FileCharacteristics: u16 { const RELOCS_STRIPPED = 0x0001; const EXECUTABLE_IMAGE = 0x0002; @@ -64,6 +66,7 @@ bitflags! { const BYTES_REVERSED_HI = 0x8000; } #[repr(C)] + #[derive(Debug, Clone, Copy, Serialize)] pub struct DLLCharacteristics: u16 { const RESERVED1 = 0x0001; const RESERVED2 = 0x0002; @@ -83,6 +86,7 @@ bitflags! { } } #[repr(C)] +#[derive(Debug, Clone, Copy, Serialize)] pub struct ImageOptionalHeader32 { pub magic: u16, pub major_linker_version: u8, diff --git a/src-tauri/src/pe_parse/pe.rs b/src-tauri/src/pe_parse/pe.rs index 52b8b52..d689710 100644 --- a/src-tauri/src/pe_parse/pe.rs +++ b/src-tauri/src/pe_parse/pe.rs @@ -32,4 +32,49 @@ pub trait PE: Deref + DerefMut + Sized { let result = self.get_mut::(0)?; Ok(result) } + + /// Get the NT headers without verifying its contents. + fn get_nt_headers(&self) -> Result<&ImageNTHeaders32, PEParseError> { + let nt_offset = self.get_nt_headers_offset()?; + let result = self.get_ref::(nt_offset)?; + Ok(result) + } + + // 获取nt头的偏移 + fn get_nt_headers_offset(&self) -> Result { + let dos_header = self.get_dos_header()?; + let nt_offset = dos_header.e_lfanew.0 as usize; + Ok(nt_offset) + } + + /// Get the NT headers without mutating its contents. + fn get_nt_headers_mut(&mut self) -> Result<&mut ImageNTHeaders32, PEParseError> { + let nt_offset = self.get_nt_headers_offset()?; + let result = self.get_mut::(nt_offset)?; + Ok(result) + } + + // 获取文件头的偏移 + fn get_file_header_offset(&self) -> Result { + // 1. 获取nt头偏移 + let nt_offset = self.get_nt_headers_offset()?; + // 2. 计算文件头偏移 + let file_header_offset = nt_offset + std::mem::size_of::(); + Ok(file_header_offset) + } + + // 获取文件头数据 + fn get_file_header(&self) -> Result<&ImageFileHeader, PEParseError> { + let file_header_offset = self.get_file_header_offset()?; + let result = self.get_ref::(file_header_offset)?; + Ok(result) + } + // 获取文件头数据 + fn get_file_header_mut(&mut self) -> Result<&mut ImageFileHeader, PEParseError> { + let file_header_offset = self.get_file_header_offset()?; + let result = self.get_mut::(file_header_offset)?; + Ok(result) + } + + } diff --git a/src-tauri/src/pe_parse/types.rs b/src-tauri/src/pe_parse/types.rs index ff5d4f1..c526003 100644 --- a/src-tauri/src/pe_parse/types.rs +++ b/src-tauri/src/pe_parse/types.rs @@ -1,9 +1,18 @@ +use std::fmt::{Formatter, UpperHex}; + use serde::Serialize; #[repr(C)] #[derive(Debug, Clone, Copy, Serialize)] pub struct Offset(pub u32); + +impl UpperHex for Offset { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + #[repr(C)] #[derive(Debug, Clone, Copy, Serialize)] pub struct RVA(pub u32); diff --git a/src-tauri/src/services/file.rs b/src-tauri/src/services/file.rs index 83e5100..f3e7177 100644 --- a/src-tauri/src/services/file.rs +++ b/src-tauri/src/services/file.rs @@ -1,4 +1,9 @@ -use memmap::*; +use memmap2::*; +use serde::Serialize; + +use crate::{app_error::AppError, pe_parse::{header::{ImageDosHeader, ImageFileHeader, ImageNTHeaders32}, pe::PE}}; + +use super::GLOBAL_FILE_DATA; // TODO: 打开文件,并将文件映射到内存 pub fn mmap_mut_file(file_path: &str) -> Result { @@ -10,3 +15,47 @@ pub fn mmap_mut_file(file_path: &str) -> Result { .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) } } + + +#[derive(Serialize)] +pub struct ResponseDOSHeaderData { + pub image_dos_header: ImageDosHeader, + pub base_offset: usize, +} + +#[derive(Serialize)] +pub struct ResponseNTHeaderData { + image_nt_header: ImageNTHeaders32, + base_offset: usize, +} + +pub fn get_nt_headers_data() -> Result { + let binding = GLOBAL_FILE_DATA.lock().unwrap(); + let file_data = binding.as_ref().unwrap(); + 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(), + base_offset: nt_offset, + }; + Ok(result) +} + +// 获取文件头数据 +#[derive(Serialize)] +pub struct ResponseFileHeaderData { + pub file_header: ImageFileHeader, + pub base_offset: usize, +} + +pub fn get_file_header_data() -> Result { + let binding = GLOBAL_FILE_DATA.lock().unwrap(); + let file_data = binding.as_ref().unwrap(); + 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(), + base_offset: file_header_offset, + }; + Ok(result) +} \ No newline at end of file diff --git a/src-tauri/src/services/mod.rs b/src-tauri/src/services/mod.rs index 1b3a966..2d59074 100644 --- a/src-tauri/src/services/mod.rs +++ b/src-tauri/src/services/mod.rs @@ -1,4 +1,4 @@ -use memmap::{Mmap, MmapMut}; +use memmap2::{Mmap, MmapMut}; use std::{ ops::{Deref, DerefMut}, sync::Mutex, diff --git a/src/components/DosHeader/DosHeader.module.scss b/src/components/DosHeader/DosHeader.module.scss index 54e9c35..b7539c5 100644 --- a/src/components/DosHeader/DosHeader.module.scss +++ b/src/components/DosHeader/DosHeader.module.scss @@ -5,4 +5,5 @@ background-color: white; border-radius: 5px; padding: 16px; + overflow: scroll; } \ No newline at end of file diff --git a/src/components/DosHeader/DosHeader.tsx b/src/components/DosHeader/DosHeader.tsx index 34e9d33..6d2d431 100644 --- a/src/components/DosHeader/DosHeader.tsx +++ b/src/components/DosHeader/DosHeader.tsx @@ -3,11 +3,207 @@ 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"; 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; // 偏移 @@ -109,16 +305,47 @@ interface DataType { type ColumnTypes = Exclude["columns"], undefined>; -const App = ({ defaultData = [] }) => { - const [dataSource, setDataSource] = useState(defaultData); - useEffect(()=>{ - setDataSource(defaultData) - }, [defaultData]) +const App = () => { + const [offsetHex, setOffsetHex] = useState(false); + const [dataSource, setDataSource] = useState([]); - const defaultColumns: (ColumnTypes[number] & { - editable?: boolean; - dataIndex: string; - })[] = [ + 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: "字段名称", dataIndex: "name", @@ -127,6 +354,15 @@ const App = ({ defaultData = [] }) => { { title: "偏移量", dataIndex: "offset", + defaultSortOrder: "ascend", + render: (text) => { + return offsetHex ? `0x${text.toString(16).toUpperCase()}` : text; + }, + onHeaderCell: (column) => ({ + onClick: () => { + setOffsetHex(!offsetHex); + } + }) }, { title: "字段类型", @@ -202,15 +438,16 @@ const App = ({ defaultData = [] }) => { columns={columns as ColumnTypes} pagination={false} size="small" + sticky={{ offsetHeader: -16 }} + /> ); }; -export default function DosHeader({ defaultData }) { - console.log("DosHeader defaultData:", defaultData); +export default function DosHeader() { return ( - + ); } diff --git a/src/components/FileHeader/FileHeader.module.scss b/src/components/FileHeader/FileHeader.module.scss new file mode 100644 index 0000000..b7539c5 --- /dev/null +++ b/src/components/FileHeader/FileHeader.module.scss @@ -0,0 +1,9 @@ +.root { + margin: 8px; + margin-top: 0; + height: 100%; + background-color: white; + border-radius: 5px; + padding: 16px; + overflow: scroll; +} \ No newline at end of file diff --git a/src/components/FileHeader/FileHeader.tsx b/src/components/FileHeader/FileHeader.tsx new file mode 100644 index 0000000..0cad445 --- /dev/null +++ b/src/components/FileHeader/FileHeader.tsx @@ -0,0 +1,247 @@ +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: (column) => ({ + 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 }} + /> + ); +}; + +export default function FileHeader() { + return ( + + + + ); +} diff --git a/src/components/NTHeader/NTHeader.module.scss b/src/components/NTHeader/NTHeader.module.scss new file mode 100644 index 0000000..54e9c35 --- /dev/null +++ b/src/components/NTHeader/NTHeader.module.scss @@ -0,0 +1,8 @@ +.root { + margin: 8px; + margin-top: 0; + height: 100%; + background-color: white; + border-radius: 5px; + padding: 16px; +} \ No newline at end of file diff --git a/src/components/NTHeader/NTHeader.tsx b/src/components/NTHeader/NTHeader.tsx new file mode 100644 index 0000000..2d5ce7e --- /dev/null +++ b/src/components/NTHeader/NTHeader.tsx @@ -0,0 +1,205 @@ +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: (column) => ({ + 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 }} + /> + ); +}; + +export default function NTHeader() { + return ( + + + + ); +} diff --git a/src/components/side_tree/SideTree.module.scss b/src/components/side_tree/SideTree.module.scss index 84c3eb1..21e8508 100644 --- a/src/components/side_tree/SideTree.module.scss +++ b/src/components/side_tree/SideTree.module.scss @@ -1,6 +1,5 @@ .root { background-color: white; - min-height: 500px; width: 100%; margin: 8px; margin-top: 0; diff --git a/src/components/side_tree/SideTree.tsx b/src/components/side_tree/SideTree.tsx index 0adb9d4..53baf0f 100644 --- a/src/components/side_tree/SideTree.tsx +++ b/src/components/side_tree/SideTree.tsx @@ -17,8 +17,8 @@ export default function SiderTree({ const [selectedKey, setSelectedKey] = useState(defaultSelectedKey); const onSelect: TreeProps["onSelect"] = (selectedKeys, info) => { - console.log("selected", selectedKeys, info); let key = info.node.key as string; + console.log("onSelect", key); setSelectedKey(key); onTreeSelect(key); }; @@ -43,8 +43,6 @@ export default function SiderTree({ // 设置默认选中的节点 setSelectedKey(defaultSelectedKey); }, [treeData]); - console.log("selectedKey", selectedKey); - return ( , + nt_header: , + file_header: , }; export default function MainPage() { const [treeData, setTreeData] = useState([]); const [filePath, setFilePath] = useState(""); const [selectedKey, setSelectedKey] = useState(""); - const [nodeData, setNodeData] = useState([]); const openFile = async () => { const file = await open({ multiple: false, directory: false, }); - console.log("file:", file); if (file) { setFilePath(file); } @@ -33,26 +35,12 @@ export default function MainPage() { const changeFile = (filePath: string) => { invoke("command_open_file", { filePath }).then(() => { invoke("command_get_file_pe_node_tree_data").then((res) => { - console.log("tree nodes:", res); setTreeData(res as any); }); }); }; const onSelect = (newSelectedKey) => { - console.log("newSelectedKey", newSelectedKey); - // TODO: 获取节点数据 - newSelectedKey && - invoke(`command_get_pe_data_${newSelectedKey}`).then((resData: any) => { - console.log("resData", resData); - // 格式化 - let ary = []; - for (let key in resData) { - ary.push(resData[key]); - } - console.log("ary", ary); - setNodeData(ary); - }); setSelectedKey(newSelectedKey); }; @@ -61,7 +49,6 @@ export default function MainPage() { "tauri://drag-drop", (event) => { const filePath = event.payload.paths[0]; - console.log("file path:", filePath); setFilePath(filePath); } ); @@ -76,8 +63,6 @@ export default function MainPage() { } }, [filePath]); - console.log("selectedKey", selectedKey); - return ( @@ -99,6 +84,7 @@ export default function MainPage() { style={{ backgroundColor: "#e5e5e5", paddingBottom: "16px", + height: "100%" }} > @@ -108,11 +94,9 @@ export default function MainPage() { onSelect={onSelect} /> - + {selectedKey && SelectNodeMap[selectedKey] ? ( - SelectNodeMap[selectedKey]({ - defaultData: nodeData, - }) + SelectNodeMap[selectedKey] ) : ( )}