feat: 提供了修改功能

This commit is contained in:
381848900@qq.com 2024-12-12 16:08:19 +08:00
parent bfe54cae1f
commit d8b49c12d1
10 changed files with 189 additions and 39 deletions

1
.gitignore vendored
View File

@ -22,3 +22,4 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
test_files

View File

@ -21,6 +21,10 @@ pub enum AppError {
/// 初始化打开文件失败 /// 初始化打开文件失败
#[error("初始化打开文件失败!: {0}")] #[error("初始化打开文件失败!: {0}")]
InitOpenFileFailed(String), InitOpenFileFailed(String),
/// 无法修改文件
#[error("无法修改文件!: {0}")]
CannotModifyFile(String),
} }
impl serde::Serialize for AppError { impl serde::Serialize for AppError {

View File

@ -17,14 +17,37 @@ impl AppState {
/// 初始化打开文件 /// 初始化打开文件
pub fn init_open_file(&mut self, file_path: &str) -> Result<(), AppError> { pub fn init_open_file(&mut self, file_path: &str) -> Result<(), AppError> {
self.file_path = Some(file_path.to_string()); 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位 // 读取PE格式来判断是否是64位
let mmap: &Mmap = self.mmap.as_ref().unwrap(); let mmap: &Mmap = self.mmap.as_ref().unwrap();
self.is_64_bit = Some(mmap.is_64_bit()?); self.is_64_bit = Some(mmap.is_64_bit()?);
Ok(()) Ok(())
} }
/// 获取文件内存映射
/// 获取文件内存映射的引用
pub fn get_mmap_ref(&self) -> Result<&Mmap, AppError> { pub fn get_mmap_ref(&self) -> Result<&Mmap, AppError> {
self.mmap.as_ref().ok_or(AppError::NoFileOpened) 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(())
}
} }

View File

@ -30,7 +30,7 @@ pub fn set_complete(app: AppHandle) -> Result<(), AppError> {
} }
// 命令,打开文件 // 命令,打开文件
#[tauri::command] #[tauri::command(async)]
pub fn command_open_file( pub fn command_open_file(
file_path: &str, file_path: &str,
app_state: State<'_, Mutex<AppState>>, app_state: State<'_, Mutex<AppState>>,
@ -43,7 +43,7 @@ pub fn command_open_file(
Ok(()) Ok(())
} }
#[tauri::command] #[tauri::command(async)]
pub fn command_get_file_pe_node_tree_data( pub fn command_get_file_pe_node_tree_data(
app_state: State<'_, Mutex<AppState>>, app_state: State<'_, Mutex<AppState>>,
) -> Result<Vec<PeNodeTreeData>, AppError> { ) -> Result<Vec<PeNodeTreeData>, AppError> {
@ -100,7 +100,7 @@ pub fn command_get_file_pe_node_tree_data(
} }
// 命令获取DOS头数据 // 命令获取DOS头数据
#[tauri::command] #[tauri::command(async)]
pub fn command_get_pe_data_dos_header( pub fn command_get_pe_data_dos_header(
app_state: State<'_, Mutex<AppState>>, app_state: State<'_, Mutex<AppState>>,
) -> Result<Value, AppError> { ) -> Result<Value, AppError> {
@ -114,7 +114,7 @@ pub fn command_get_pe_data_dos_header(
} }
// 命令获取NT头数据 // 命令获取NT头数据
#[tauri::command] #[tauri::command(async)]
pub fn command_get_pe_data_nt_header( pub fn command_get_pe_data_nt_header(
app_state: State<'_, Mutex<AppState>>, app_state: State<'_, Mutex<AppState>>,
) -> Result<Value, AppError> { ) -> Result<Value, AppError> {
@ -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<AppState>>) -> Result<Value, AppError> { pub fn command_get_pe_data_file_header(app_state: State<'_, Mutex<AppState>>) -> Result<Value, AppError> {
let app_state = app_state.lock().unwrap(); let app_state = app_state.lock().unwrap();
let mmap = app_state.get_mmap_ref()?; let mmap = app_state.get_mmap_ref()?;
@ -142,7 +142,7 @@ pub fn command_get_pe_data_file_header(app_state: State<'_, Mutex<AppState>>) ->
} }
// 命令,获取可选头数据 // 命令,获取可选头数据
#[tauri::command] #[tauri::command(async)]
pub fn command_get_pe_data_optional_header(app_state: State<'_, Mutex<AppState>>) -> Result<Value, AppError> { pub fn command_get_pe_data_optional_header(app_state: State<'_, Mutex<AppState>>) -> Result<Value, AppError> {
let app_state = app_state.lock().unwrap(); let app_state = app_state.lock().unwrap();
let mmap = app_state.get_mmap_ref()?; let mmap = app_state.get_mmap_ref()?;
@ -157,7 +157,7 @@ pub fn command_get_pe_data_optional_header(app_state: State<'_, Mutex<AppState>>
} }
// 命令,获取节区头数据 // 命令,获取节区头数据
#[tauri::command] #[tauri::command(async)]
pub fn command_get_pe_data_section_headers(app_state: State<'_, Mutex<AppState>>) -> Result<Value, AppError> { pub fn command_get_pe_data_section_headers(app_state: State<'_, Mutex<AppState>>) -> Result<Value, AppError> {
let app_state = app_state.lock().unwrap(); let app_state = app_state.lock().unwrap();
let mmap = app_state.get_mmap_ref()?; let mmap = app_state.get_mmap_ref()?;
@ -169,3 +169,15 @@ pub fn command_get_pe_data_section_headers(app_state: State<'_, Mutex<AppState>>
"base_offset": section_headers_offset, "base_offset": section_headers_offset,
})) }))
} }
// 命令,修改偏移处数据
#[tauri::command(async)]
pub fn command_write_data(
app_state: State<'_, Mutex<AppState>>,
offset: usize,
data: Vec<u8>,
) -> Result<(), AppError> {
let mut app_state = app_state.lock().unwrap();
app_state.write_data(offset, &data)?;
Ok(())
}

View File

@ -15,6 +15,7 @@ pub fn run() {
.plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_shell::init()) .plugin(tauri_plugin_shell::init())
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
commands::set_complete,
commands::command_open_file, commands::command_open_file,
commands::command_get_file_pe_node_tree_data, commands::command_get_file_pe_node_tree_data,
commands::command_get_pe_data_dos_header, 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_file_header,
commands::command_get_pe_data_optional_header, commands::command_get_pe_data_optional_header,
commands::command_get_pe_data_section_headers, commands::command_get_pe_data_section_headers,
commands::set_complete, commands::command_write_data,
]) ])
.setup(|app| { .setup(|app| {
app.manage(Mutex::new(app_state::AppState::default())); app.manage(Mutex::new(app_state::AppState::default()));

View File

@ -1,7 +1,7 @@
use memmap2::*; use memmap2::*;
/// 以只读的方式创建文件映射 /// 以只读的方式创建文件映射
pub fn mmap_mut_file(file_path: &str) -> Result<Mmap, std::io::Error> { pub fn mmap_file(file_path: &str) -> Result<Mmap, std::io::Error> {
let file = std::fs::OpenOptions::new().read(true).open(file_path)?; let file = std::fs::OpenOptions::new().read(true).open(file_path)?;
unsafe { unsafe {
MmapOptions::new() MmapOptions::new()
@ -9,3 +9,13 @@ pub fn mmap_mut_file(file_path: &str) -> Result<Mmap, std::io::Error> {
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string())) .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))
} }
} }
/// 以读写方式创建文件映射
pub fn mmap_file_rw(file_path: &str) -> Result<MmapMut, std::io::Error> {
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()))
}
}

View File

@ -1,16 +1,73 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import NodeTableComponent, { import NodeTableComponent, {
DataTemplateInterface, DataTemplateInterface,
DataType,
} from "../NodeTableComponent/NodeTableComponent"; } 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() { export default function DosHeader() {
const [data, setData] = useState<DosHeaderResponseData>();
const [data, setData] = useState<any[]>([]); const [messageApi, contextHolder] = message.useMessage();
const success = () => {
messageApi.open({
type: "success",
content: "修改成功!",
});
};
const error = () => {
messageApi.open({
type: "error",
content: "修改失败!",
});
};
const dataTemplate: DataTemplateInterface = { const dataTemplate: DataTemplateInterface = {
e_magic: { e_magic: {
name: "e_magic", name: "e_magic",
description: "标识文件的魔数DOS 可执行文件的值通常为 'MZ'。",
value: 0, value: 0,
size: 2, size: 2,
offset: 0, offset: 0,
@ -18,6 +75,7 @@ export default function DosHeader() {
}, },
e_cblp: { e_cblp: {
name: "e_cblp", name: "e_cblp",
description: "文件最后一页的字节数,指示文件不完整页的大小。",
value: 0, value: 0,
size: 2, size: 2,
offset: 2, offset: 2,
@ -25,6 +83,7 @@ export default function DosHeader() {
}, },
e_cp: { e_cp: {
name: "e_cp", name: "e_cp",
description: "文件的总页数(每页 512 字节)。",
value: 0, value: 0,
size: 2, size: 2,
offset: 4, offset: 4,
@ -32,6 +91,7 @@ export default function DosHeader() {
}, },
e_crlc: { e_crlc: {
name: "e_crlc", name: "e_crlc",
description: "重定位表中的条目数。",
value: 0, value: 0,
size: 2, size: 2,
offset: 6, offset: 6,
@ -39,6 +99,7 @@ export default function DosHeader() {
}, },
e_cparhdr: { e_cparhdr: {
name: "e_cparhdr", name: "e_cparhdr",
description: "文件头部的段数,单位为 16 字节段。",
value: 0, value: 0,
size: 2, size: 2,
offset: 8, offset: 8,
@ -46,6 +107,7 @@ export default function DosHeader() {
}, },
e_minalloc: { e_minalloc: {
name: "e_minalloc", name: "e_minalloc",
description: "程序需要的最小额外段数。",
value: 0, value: 0,
size: 2, size: 2,
offset: 10, offset: 10,
@ -53,6 +115,7 @@ export default function DosHeader() {
}, },
e_maxalloc: { e_maxalloc: {
name: "e_maxalloc", name: "e_maxalloc",
description: "程序可以分配的最大额外段数。",
value: 0, value: 0,
size: 2, size: 2,
offset: 12, offset: 12,
@ -60,6 +123,7 @@ export default function DosHeader() {
}, },
e_ss: { e_ss: {
name: "e_ss", name: "e_ss",
description: "初始的 SS 寄存器值,段偏移地址。",
value: 0, value: 0,
size: 2, size: 2,
offset: 14, offset: 14,
@ -67,6 +131,7 @@ export default function DosHeader() {
}, },
e_sp: { e_sp: {
name: "e_sp", name: "e_sp",
description: "初始的 SP 寄存器值,栈顶指针。",
value: 0, value: 0,
size: 2, size: 2,
offset: 16, offset: 16,
@ -74,6 +139,7 @@ export default function DosHeader() {
}, },
e_csum: { e_csum: {
name: "e_csum", name: "e_csum",
description: "校验和(一般为 0。",
value: 0, value: 0,
size: 2, size: 2,
offset: 18, offset: 18,
@ -81,6 +147,7 @@ export default function DosHeader() {
}, },
e_ip: { e_ip: {
name: "e_ip", name: "e_ip",
description: "初始的 IP 寄存器值,代码段的入口偏移地址。",
value: 0, value: 0,
size: 2, size: 2,
offset: 20, offset: 20,
@ -88,6 +155,7 @@ export default function DosHeader() {
}, },
e_cs: { e_cs: {
name: "e_cs", name: "e_cs",
description: "初始的 CS 寄存器值,代码段的段基址。",
value: 0, value: 0,
size: 2, size: 2,
offset: 22, offset: 22,
@ -95,6 +163,7 @@ export default function DosHeader() {
}, },
e_lfarlc: { e_lfarlc: {
name: "e_lfarlc", name: "e_lfarlc",
description: "重定位表的文件偏移。",
value: 0, value: 0,
size: 2, size: 2,
offset: 24, offset: 24,
@ -102,6 +171,7 @@ export default function DosHeader() {
}, },
e_ovno: { e_ovno: {
name: "e_ovno", name: "e_ovno",
description: "覆盖号(通常为 0。",
value: 0, value: 0,
size: 2, size: 2,
offset: 26, offset: 26,
@ -109,15 +179,16 @@ export default function DosHeader() {
}, },
e_res: { e_res: {
name: "e_res", name: "e_res",
description: "保留字段,通常为 0。",
value: null, value: null,
size: 8, size: 8,
offset: 28, offset: 28,
// field_type: "[WORD; 4]",
array_element_size: 2, array_element_size: 2,
data_type: "array", data_type: "array",
}, },
e_oemid: { e_oemid: {
name: "e_oemid", name: "e_oemid",
description: "OEM 标识符。",
value: 0, value: 0,
size: 2, size: 2,
offset: 36, offset: 36,
@ -125,6 +196,7 @@ export default function DosHeader() {
}, },
e_oeminfo: { e_oeminfo: {
name: "e_oeminfo", name: "e_oeminfo",
description: "OEM 信息。",
value: 0, value: 0,
size: 2, size: 2,
offset: 38, offset: 38,
@ -132,6 +204,7 @@ export default function DosHeader() {
}, },
e_res2: { e_res2: {
name: "e_res2", name: "e_res2",
description: "保留字段,通常为 0。",
value: 0, value: 0,
size: 20, size: 20,
offset: 40, offset: 40,
@ -140,6 +213,7 @@ export default function DosHeader() {
}, },
e_lfanew: { e_lfanew: {
name: "e_lfanew", name: "e_lfanew",
description: "PE 文件头的偏移地址。",
value: 0, value: 0,
size: 4, size: 4,
offset: 60, offset: 60,
@ -154,20 +228,20 @@ export default function DosHeader() {
title: "字段名", title: "字段名",
dataIndex: "name", dataIndex: "name",
key: "name", key: "name",
width: 120 width: 120,
}, },
{ {
title: "偏移", title: "偏移",
dataIndex: "offset", dataIndex: "offset",
key: "offset", key: "offset",
hexSwitch: true, hexSwitch: true,
width: 80 width: 80,
}, },
{ {
title: "大小", title: "大小",
dataIndex: "size", dataIndex: "size",
key: "size", key: "size",
width: 80 width: 80,
}, },
{ {
title: "值", title: "值",
@ -175,28 +249,56 @@ export default function DosHeader() {
key: "value", key: "value",
hexSwitch: true, hexSwitch: true,
editable: true, editable: true,
minWidth: 200 minWidth: 200,
}, },
{ {
title: "描述信息", title: "描述信息",
dataIndex: "description", dataIndex: "description",
key: "description", key: "description",
width: 250 width: 250,
}, },
]; ];
useEffect(()=>{ const handleSave = (item: DataType) => {
invoke(command).then(res => { console.log("handleSave", item);
console.log("asdfsadf",res); if (!checkEditValue(item)) {
setData(res as any); 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 ( return (
<NodeTableComponent <>
dataTemplate={dataTemplate} {contextHolder}
defaultData={data} <NodeTableComponent
columns={columns as any} dataTemplate={dataTemplate}
></NodeTableComponent> defaultData={data}
columns={columns as any}
onEdit={handleSave}
></NodeTableComponent>
</>
); );
} }

View File

@ -14,6 +14,7 @@ export default function NTHeader() {
size: 4, size: 4,
value: 0, value: 0,
data_type: "hex", data_type: "hex",
description: "PE文件的标志可执行文件的标志是0x00004550",
}, },
}; };
const command = "command_get_pe_data_nt_header"; const command = "command_get_pe_data_nt_header";

View File

@ -54,7 +54,8 @@ interface NodeTableComponentProps {
dataTemplate: DataTemplateInterface; // 数据模板 dataTemplate: DataTemplateInterface; // 数据模板
// command: string; // 请求数据的命令 // command: string; // 请求数据的命令
columns: NodeTableColumnInterface<DataType>[]; columns: NodeTableColumnInterface<DataType>[];
defaultData?: DataType[]; defaultData?: ResponsData;
onEdit?: (record: DataType) => void;
} }
interface EditableRowProps { interface EditableRowProps {
@ -77,7 +78,7 @@ interface EditableCellProps {
editable: boolean; editable: boolean;
dataIndex: keyof DataType; dataIndex: keyof DataType;
record: DataType; record: DataType;
handleSave: (record: DataType) => void; handleSave?: (record: DataType) => void;
} }
const EditableCell: React.FC<React.PropsWithChildren<EditableCellProps>> = ({ const EditableCell: React.FC<React.PropsWithChildren<EditableCellProps>> = ({
@ -108,7 +109,7 @@ const EditableCell: React.FC<React.PropsWithChildren<EditableCellProps>> = ({
try { try {
const values = await form.validateFields(); const values = await form.validateFields();
toggleEdit(); toggleEdit();
handleSave({ ...record, ...values }); handleSave?.({ ...record, ...values });
} catch (errInfo) { } catch (errInfo) {
console.log("Save failed:", errInfo); console.log("Save failed:", errInfo);
} }
@ -151,10 +152,6 @@ export default function NodeTableComponent(props: NodeTableComponentProps) {
setHexSwitchStatus(newHexSwitchStatus); setHexSwitchStatus(newHexSwitchStatus);
}; };
const handleSave = (_row: DataType) => {
// TODO: 保存逻辑
};
const hexSwitchRender = (text: string, dataIndex: string) => { const hexSwitchRender = (text: string, dataIndex: string) => {
if (text == null || text == undefined || typeof text !== "number") { if (text == null || text == undefined || typeof text !== "number") {
return text; return text;
@ -180,7 +177,7 @@ export default function NodeTableComponent(props: NodeTableComponentProps) {
editable: col.editable, editable: col.editable,
dataIndex: col.dataIndex, dataIndex: col.dataIndex,
title: col.title, title: col.title,
handleSave, handleSave: props.onEdit,
}); });
} }
if (col.hexSwitch) { if (col.hexSwitch) {

View File

@ -133,7 +133,6 @@ export default function SectionHeaders() {
setData(formatDataHandler(res)); setData(formatDataHandler(res));
}); });
}, []); }, []);
console.log("yhyy", data);
return ( return (
<Flex className={styles.content}> <Flex className={styles.content}>
{data.map((item, index) => { {data.map((item, index) => {