feat: 增加导入表的解析
This commit is contained in:
parent
a89a5ae376
commit
ce85787be9
@ -7,6 +7,9 @@ use crate::pe_parse::error::{
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum AppError {
|
||||
#[error(transparent)]
|
||||
Utf8Error(#[from] std::str::Utf8Error),
|
||||
|
||||
#[error(transparent)]
|
||||
Io(#[from] std::io::Error),
|
||||
|
||||
|
@ -1,11 +1,15 @@
|
||||
use std::mem::{self};
|
||||
|
||||
use crate::app_error::AppError;
|
||||
use crate::pe_parse::header::{ImageSectionHeader, SectionCharacteristics};
|
||||
use crate::pe_parse::header::{
|
||||
ImageDirectoryEntry, ImageImportDescriptor, ImageSectionHeader, ImportLookupTable32,
|
||||
ImportLookupTable64, SectionCharacteristics,
|
||||
};
|
||||
use crate::pe_parse::pe::MutablePE;
|
||||
use crate::services::file::expand_file_size;
|
||||
use crate::{pe_parse::pe::ReadOnlyPE, services::file};
|
||||
use memmap2::Mmap;
|
||||
use serde::Serialize;
|
||||
use std::ffi::CStr;
|
||||
use std::mem::{self};
|
||||
|
||||
/// 应用的状态结构体
|
||||
#[derive(Default)]
|
||||
@ -100,7 +104,10 @@ impl AppState {
|
||||
// 2. 以可读可写的方式重新映射文件
|
||||
rw_mmap_option = Some(file::mmap_file_rw(file_path)?);
|
||||
// 拓展头部大小 拓展一个节的大小
|
||||
rw_mmap_option.as_mut().unwrap().expand_headers(file_alignment)?;
|
||||
rw_mmap_option
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.expand_headers(file_alignment)?;
|
||||
}
|
||||
// 2. 添加节
|
||||
// 先拓展文件大小
|
||||
@ -120,4 +127,141 @@ impl AppState {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
pub fn get_import_tables(&self) -> Result<Vec<ImportModuleTable>, AppError> {
|
||||
let mmap = self.get_mmap_ref()?;
|
||||
let is_64_bit = self.is_64_bit.unwrap();
|
||||
// 1. 获取导入表
|
||||
let import_table = mmap.get_data_directory(ImageDirectoryEntry::Import)?;
|
||||
// 2. 获取导入表的RVA
|
||||
let import_table_rva = import_table.virtual_address;
|
||||
// 3. RVA转FOA
|
||||
let import_table_foa = mmap.rva_to_foa(import_table_rva)?;
|
||||
// 从文件import_table_foa循环解析ImageImportDescriptor,当解析出的导入表==ImageImportDescriptor::default()时,结束循环
|
||||
let mut origin_import_module_tables: Vec<&ImageImportDescriptor> = Vec::new();
|
||||
let mut base_offset = import_table_foa as usize;
|
||||
loop {
|
||||
let import_module_item = unsafe {
|
||||
let ptr = mmap.as_ptr().add(base_offset) as *const ImageImportDescriptor;
|
||||
ptr.as_ref().unwrap()
|
||||
};
|
||||
if import_module_item == &ImageImportDescriptor::default() {
|
||||
break;
|
||||
}
|
||||
origin_import_module_tables.push(import_module_item);
|
||||
base_offset += mem::size_of::<ImageImportDescriptor>();
|
||||
}
|
||||
// 4. 解析导入表 主要是来格式化一些数据 先创建一个ImportModuleTable 的变长数组
|
||||
let mut import_module_tables: Vec<ImportModuleTable> = Vec::new();
|
||||
// 循环处理origin_import_module_tables
|
||||
for origin_item in origin_import_module_tables.iter() {
|
||||
let mut import_module = ImportModuleTable::default();
|
||||
// 1. 获取模块名称
|
||||
// 先获取模块名称的FOA
|
||||
let module_name_foa = mmap.rva_to_foa(origin_item.name)?;
|
||||
// 获取模块名称 需要unsafe读取
|
||||
let module_name = unsafe {
|
||||
let ptr = mmap[module_name_foa as usize..].as_ptr() as *const i8;
|
||||
CStr::from_ptr(ptr).to_str()?.to_string()
|
||||
};
|
||||
import_module.module_name = module_name;
|
||||
// 其他成员
|
||||
import_module.forwarder_chain = origin_item.forwarder_chain;
|
||||
import_module.iat_rva = origin_item.first_thunk;
|
||||
import_module.int_rva = origin_item.original_first_thunk;
|
||||
import_module.module_name_rva = origin_item.name;
|
||||
import_module.timestamp = origin_item.time_date_stamp;
|
||||
// 2. 获取函数表
|
||||
let mut functions: Vec<ImportFunctionTableItem> = Vec::new();
|
||||
// 获取函数表的FOA
|
||||
let mut base_offset = mmap.rva_to_foa(origin_item.original_first_thunk)? as usize;
|
||||
// 循环解析函数表
|
||||
loop {
|
||||
// TODO: 这里需要判断是64位还是32位
|
||||
// 如果是64位,每次读取8字节 否则读取4字节
|
||||
let import_lookup_table = if is_64_bit {
|
||||
let ptr = mmap[base_offset as usize..].as_ptr() as *const u64;
|
||||
unsafe { ptr.read() as u64 }
|
||||
} else {
|
||||
let ptr = mmap[base_offset as usize..].as_ptr() as *const u32;
|
||||
unsafe { ptr.read() as u64 }
|
||||
};
|
||||
|
||||
// 如果import_lookup_table == 0 则表示结束
|
||||
if import_lookup_table == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
// 1. 判断是序号还是命名函数
|
||||
let is_named = match is_64_bit {
|
||||
true => {
|
||||
import_lookup_table & ImportLookupTable64::IMPORT_BY_ORDINAL.bits() == 0
|
||||
}
|
||||
false => {
|
||||
import_lookup_table as u32 & ImportLookupTable32::IMPORT_BY_ORDINAL.bits()
|
||||
== 0
|
||||
}
|
||||
};
|
||||
let mut import_function_table = ImportFunctionTable::default();
|
||||
let hint_name_table_rva = import_lookup_table as u32 & 0x7FFFFFFF;
|
||||
let hint_name_table_foa = mmap.rva_to_foa(hint_name_table_rva)?;
|
||||
// 1. 读hint 两个字节
|
||||
let funtion_hint: u16 = unsafe {
|
||||
let ptr = mmap[hint_name_table_foa as usize..].as_ptr() as *const u16;
|
||||
ptr.read()
|
||||
};
|
||||
import_function_table.function_hint = funtion_hint;
|
||||
import_function_table.function_address = hint_name_table_rva;
|
||||
match is_named {
|
||||
true => {
|
||||
// 命名函数
|
||||
let function_name = unsafe {
|
||||
let ptr =
|
||||
mmap[hint_name_table_foa as usize + 2..].as_ptr() as *const i8;
|
||||
CStr::from_ptr(ptr).to_str()?.to_string()
|
||||
};
|
||||
import_function_table.function_name = function_name;
|
||||
functions.push(ImportFunctionTableItem::Named(import_function_table));
|
||||
}
|
||||
false => {
|
||||
functions.push(ImportFunctionTableItem::Ordinal(import_function_table));
|
||||
}
|
||||
}
|
||||
// 下一个
|
||||
base_offset += if is_64_bit { 8 } else { 4 };
|
||||
}
|
||||
import_module.functions = functions;
|
||||
import_module_tables.push(import_module);
|
||||
}
|
||||
Ok(import_module_tables)
|
||||
}
|
||||
}
|
||||
|
||||
// 定义一个获取导入表后需要返回的结构
|
||||
#[derive(Debug, Default, Clone, Serialize)]
|
||||
pub struct ImportModuleTable {
|
||||
pub module_name: String, // 模块名称
|
||||
pub functions: Vec<ImportFunctionTableItem>, // 这里要另一个结构体来描述
|
||||
pub timestamp: u32, // 时间戳
|
||||
pub forwarder_chain: u32, // 转发链
|
||||
pub module_name_rva: u32, // dll名称的RVA
|
||||
pub iat_rva: u32, // IAT的RVA
|
||||
pub int_rva: u32, // INT的RVA
|
||||
}
|
||||
|
||||
// 因为导入查找表可能是函数名、也可能是序号 所以用枚举包一下最好
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub enum ImportFunctionTableItem {
|
||||
// 命名的导入函数
|
||||
Named(ImportFunctionTable),
|
||||
// 序号表示的
|
||||
Ordinal(ImportFunctionTable),
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize)]
|
||||
pub struct ImportFunctionTable {
|
||||
pub function_name: String, // 函数名称
|
||||
pub function_address: u32, // 函数地址 IAT
|
||||
pub function_hint: u16, // 函数提示
|
||||
}
|
@ -97,6 +97,11 @@ pub fn command_get_file_pe_node_tree_data(
|
||||
key: "section_header".to_string(),
|
||||
children: vec![],
|
||||
},
|
||||
PeNodeTreeData {
|
||||
title: "导入目录".to_string(),
|
||||
key: "import_directory".to_string(),
|
||||
children: vec![],
|
||||
},
|
||||
],
|
||||
};
|
||||
Ok(vec![result])
|
||||
@ -179,6 +184,19 @@ pub fn command_get_pe_data_section_headers(
|
||||
}))
|
||||
}
|
||||
|
||||
// 命令,获取导入表数据
|
||||
#[tauri::command(async)]
|
||||
pub fn command_get_pe_data_import_directory(
|
||||
app_state: State<'_, Mutex<AppState>>,
|
||||
) -> Result<Value, AppError> {
|
||||
let app_state = app_state.lock().unwrap();
|
||||
let import_directory = app_state.get_import_tables()?;
|
||||
Ok(json!({
|
||||
"fields": import_directory,
|
||||
"base_offset": 0,
|
||||
}))
|
||||
}
|
||||
|
||||
// 命令,修改偏移处数据
|
||||
#[tauri::command(async)]
|
||||
pub fn command_write_data(
|
||||
|
@ -24,6 +24,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::command_get_pe_data_import_directory,
|
||||
commands::command_write_data,
|
||||
commands::command_add_section,
|
||||
commands::command_test,
|
||||
|
@ -12,6 +12,12 @@ pub enum PEParseError {
|
||||
InvalidOptionalSize,
|
||||
#[error("解析超出了文件范围")]
|
||||
OutOfBounds,
|
||||
#[error("错误的RVA: {0}")]
|
||||
RvaToFoaError(u32),
|
||||
#[error("RVA对应的是一个空节区数据!RVA: {0}")]
|
||||
RvaToEmptySection(u32),
|
||||
#[error("错误的数据目录索引")]
|
||||
InvalidDataDirectoryIndex,
|
||||
}
|
||||
/// PE操作的错误
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -45,7 +45,7 @@ pub struct ImageNTHeader64 {
|
||||
#[repr(C)]
|
||||
#[derive(Serialize, Clone, Copy)]
|
||||
#[serde(untagged)]
|
||||
pub enum ImageNTHeader{
|
||||
pub enum ImageNTHeader {
|
||||
NTHeader32(ImageNTHeader32),
|
||||
NTHeader64(ImageNTHeader64),
|
||||
}
|
||||
@ -53,21 +53,24 @@ pub enum ImageNTHeader{
|
||||
#[repr(C)]
|
||||
#[derive(Serialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum ImageNTHeaderMut<'a>{
|
||||
pub enum ImageNTHeaderMut<'a> {
|
||||
NTHeader32(&'a mut ImageNTHeader32),
|
||||
NTHeader64(&'a mut ImageNTHeader64),
|
||||
}
|
||||
|
||||
impl <'a> ImageNTHeaderMut<'a>{
|
||||
pub fn get_optional_header_mut(&mut self) -> ImageOptionalHeaderMut{
|
||||
match self{
|
||||
ImageNTHeaderMut::NTHeader32(header) => ImageOptionalHeaderMut::OptionalHeader32(&mut header.optional_header),
|
||||
ImageNTHeaderMut::NTHeader64(header) => ImageOptionalHeaderMut::OptionalHeader64(&mut header.optional_header),
|
||||
impl<'a> ImageNTHeaderMut<'a> {
|
||||
pub fn get_optional_header_mut(&mut self) -> ImageOptionalHeaderMut {
|
||||
match self {
|
||||
ImageNTHeaderMut::NTHeader32(header) => {
|
||||
ImageOptionalHeaderMut::OptionalHeader32(&mut header.optional_header)
|
||||
}
|
||||
ImageNTHeaderMut::NTHeader64(header) => {
|
||||
ImageOptionalHeaderMut::OptionalHeader64(&mut header.optional_header)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Serialize)]
|
||||
pub struct ImageFileHeader {
|
||||
@ -374,9 +377,27 @@ impl ImageOptionalHeader {
|
||||
ImageOptionalHeader::OptionalHeader64(header) => header.size_of_image,
|
||||
}
|
||||
}
|
||||
pub fn get_size_of_optional_header(&self) -> u32 {
|
||||
match self {
|
||||
ImageOptionalHeader::OptionalHeader32(_) => {
|
||||
size_of::<ImageOptionalHeader32>() as u32
|
||||
}
|
||||
ImageOptionalHeader::OptionalHeader64(_) => {
|
||||
size_of::<ImageOptionalHeader64>() as u32
|
||||
}
|
||||
}
|
||||
}
|
||||
/// 获取数据目录数量
|
||||
pub fn get_data_directory_count(&self) -> u32 {
|
||||
// 如果数据目录数量大于16,则返回16
|
||||
match self {
|
||||
ImageOptionalHeader::OptionalHeader32(header) => header.number_of_rva_and_sizes,
|
||||
ImageOptionalHeader::OptionalHeader64(header) => header.number_of_rva_and_sizes,
|
||||
}
|
||||
.min(16)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Serialize)]
|
||||
pub struct ImageSectionHeader {
|
||||
@ -391,3 +412,42 @@ pub struct ImageSectionHeader {
|
||||
pub number_of_linenumbers: u16,
|
||||
pub characteristics: SectionCharacteristics,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
|
||||
pub struct ImageImportDescriptor {
|
||||
pub original_first_thunk: u32,
|
||||
pub time_date_stamp: u32,
|
||||
pub forwarder_chain: u32,
|
||||
pub name: u32,
|
||||
pub first_thunk: u32,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
/// 导入查找表 32位
|
||||
/// [Import Lookup Table](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#import-lookup-table)
|
||||
#[derive(Debug, Clone, Copy, Serialize)]
|
||||
pub struct ImportLookupTable32: u32 {
|
||||
/// 第31位为1表示导入函数通过序号导入
|
||||
const IMPORT_BY_ORDINAL = 0x80000000;
|
||||
/// 15-0 位表示导入函数的序号
|
||||
const IMPORT_BY_ORDINAL_MASK = 0x0000FFFF;
|
||||
/// 30-0 位表示导入函数的RVA
|
||||
const IMPORT_BY_RVA_MASK = 0x7FFFFFFF;
|
||||
}
|
||||
|
||||
/// 导入查找表 64位
|
||||
/// [Import Lookup Table](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format#import-lookup-table)
|
||||
#[derive(Debug, Clone, Copy, Serialize)]
|
||||
pub struct ImportLookupTable64: u64 {
|
||||
/// 第63位为1表示导入函数通过序号导入
|
||||
const IMPORT_BY_ORDINAL = 0x8000000000000000;
|
||||
/// 15-0 位表示导入函数的序号
|
||||
const IMPORT_BY_ORDINAL_MASK = 0x0000FFFF;
|
||||
/// 30-0 位表示导入函数的RVA
|
||||
const IMPORT_BY_RVA_MASK = 0x7FFFFFFFFFFFFFFF;
|
||||
/// 62-31 must be zero.
|
||||
const RESERVED = 0x7FFFFFFF00000000;
|
||||
}
|
||||
|
||||
}
|
@ -26,11 +26,86 @@ pub trait ReadOnlyPE: Deref<Target = [u8]> + Sized + AsRef<[u8]> {
|
||||
Ok(is_64_bit)
|
||||
}
|
||||
|
||||
// TODO: 需要一个RVA->FOA的转换函数
|
||||
// TODO: 需要一个FOA->RVA的转换函数
|
||||
// TODO: 获取数据目录、可变数据目录
|
||||
// TODO: 通过枚举获取某个数据目录的数据
|
||||
/// 获取数据目录
|
||||
fn get_data_directories(&self) -> Result<&[ImageDataDirectory], PEParseError> {
|
||||
// 1. 获取数据目录偏移
|
||||
|
||||
let data_directories_offset = self.get_data_directories_offset()?;
|
||||
// 2. 解析数据目录
|
||||
let data_directories = unsafe {
|
||||
let ptr = self
|
||||
.as_ptr()
|
||||
.wrapping_offset(data_directories_offset as isize)
|
||||
as *const ImageDataDirectory;
|
||||
let data_directories_count = self.get_optional_header()?.get_data_directory_count();
|
||||
let result = std::slice::from_raw_parts(ptr, data_directories_count as usize);
|
||||
result
|
||||
};
|
||||
Ok(data_directories)
|
||||
}
|
||||
|
||||
/// 通过枚举获取某项数据目录的成员
|
||||
/// data_directory: 数据目录枚举
|
||||
fn get_data_directory(
|
||||
&self,
|
||||
data_directory: ImageDirectoryEntry,
|
||||
) -> Result<&ImageDataDirectory, PEParseError> {
|
||||
let data_directories = self.get_data_directories()?;
|
||||
let data_directory = data_directories
|
||||
.get(data_directory as usize)
|
||||
.ok_or(PEParseError::InvalidDataDirectoryIndex)?;
|
||||
Ok(data_directory)
|
||||
}
|
||||
|
||||
/// 获取数据目录的偏移
|
||||
fn get_data_directories_offset(&self) -> Result<u32, PEParseError> {
|
||||
// 1. 获取可选头和可选头的偏移
|
||||
let optional_header: ImageOptionalHeader = self.get_optional_header()?;
|
||||
let optional_header_offset = self.get_optional_header_offset()? as u32;
|
||||
// 2. 数据目录的偏移=可选头的偏移+可选头的大小
|
||||
let data_directories_offset =
|
||||
optional_header_offset + optional_header.get_size_of_optional_header();
|
||||
Ok(data_directories_offset)
|
||||
}
|
||||
|
||||
/// 将RVA转换为FOA
|
||||
/// rva: 需要转换的RVA
|
||||
fn rva_to_foa(&self, rva: u32) -> Result<u32, PEParseError> {
|
||||
let sections = self.get_section_headers()?;
|
||||
for section in sections.iter() {
|
||||
let section_start = section.virtual_address;
|
||||
let section_end =
|
||||
section_start + self.align_size_with_file_alignment(section.size_of_raw_data)?;
|
||||
if rva >= section_start && rva < section_end {
|
||||
if section.size_of_raw_data == 0 {
|
||||
// 如果节区的SizeOfRawData是0,那么这个节区是没有数据的 映射到这个节区的RVA一定是无效的
|
||||
return Err(PEParseError::RvaToEmptySection(rva));
|
||||
}
|
||||
let foa = rva - section_start + section.pointer_to_raw_data;
|
||||
return Ok(foa);
|
||||
}
|
||||
}
|
||||
Err(PEParseError::RvaToFoaError(rva))
|
||||
}
|
||||
|
||||
/// 将FOA转换为RVA
|
||||
/// foa: 需要转换的FOA
|
||||
fn foa_to_rva(&self, foa: u32) -> Result<u32, PEParseError> {
|
||||
let sections = self.get_section_headers()?;
|
||||
for section in sections.iter() {
|
||||
// 如果section的PointerToRawData是0,那么这个节区是没有数据的,直接跳过
|
||||
if section.pointer_to_raw_data == 0 {
|
||||
continue;
|
||||
}
|
||||
let section_start = section.pointer_to_raw_data;
|
||||
let section_end = section_start + section.size_of_raw_data;
|
||||
if foa >= section_start && foa < section_end {
|
||||
let rva = foa - section_start + section.virtual_address;
|
||||
return Ok(rva);
|
||||
}
|
||||
}
|
||||
Err(PEParseError::RvaToFoaError(foa))
|
||||
}
|
||||
|
||||
/// 将size与节对齐值进行对齐,返回对齐后的值
|
||||
/// size: 需要对齐的值
|
||||
@ -275,6 +350,36 @@ pub trait MutablePE: ReadOnlyPE + DerefMut<Target = [u8]> + AsMut<[u8]> {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// 获取可变的数据目录引用
|
||||
fn get_data_directories_mut(&mut self) -> Result<&mut [ImageDataDirectory], PEParseError> {
|
||||
// 1. 获取数据目录偏移
|
||||
let data_directories_offset = self.get_data_directories_offset()?;
|
||||
// 2. 解析数据目录
|
||||
let data_directories = unsafe {
|
||||
let ptr = self
|
||||
.as_mut_ptr()
|
||||
.wrapping_offset(data_directories_offset as isize)
|
||||
as *mut ImageDataDirectory;
|
||||
let data_directories_count = self.get_optional_header()?.get_data_directory_count();
|
||||
let result = std::slice::from_raw_parts_mut(ptr, data_directories_count as usize);
|
||||
result
|
||||
};
|
||||
Ok(data_directories)
|
||||
}
|
||||
|
||||
/// 通过枚举获取某项数据目录的可变引用
|
||||
/// data_directory: 数据目录枚举
|
||||
fn get_data_directory_mut(
|
||||
&mut self,
|
||||
data_directory: ImageDirectoryEntry,
|
||||
) -> Result<&mut ImageDataDirectory, PEParseError> {
|
||||
let data_directories = self.get_data_directories_mut()?;
|
||||
let data_directory = data_directories
|
||||
.get_mut(data_directory as usize)
|
||||
.ok_or(PEParseError::InvalidDataDirectoryIndex)?;
|
||||
Ok(data_directory)
|
||||
}
|
||||
|
||||
/// 扩大头映射大小,调用者必须要保证当挪动所有节区数据时,不会超出文件范围
|
||||
/// 也就是,调用者必须要在扩大头映射大小之前,先扩大文件大小
|
||||
/// add_size: 需要扩大的大小,不需要进行对齐,会自动对齐
|
||||
|
19
src/components/ImportDirectory/ImportDirectory.tsx
Normal file
19
src/components/ImportDirectory/ImportDirectory.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { useEffect } from "react";
|
||||
export default function ImportDirectory() {
|
||||
const getData = () => {
|
||||
invoke("command_get_pe_data_import_directory")
|
||||
.then((res) => {
|
||||
console.log("res", res);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log("err", err);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getData();
|
||||
}, []);
|
||||
|
||||
return <div>ImportDirectory</div>;
|
||||
}
|
@ -12,6 +12,7 @@ import FileHeader from "../components/FileHeader/FileHeader";
|
||||
import SectionHeaders from "../components/SectionHeaders/SectionHeaders";
|
||||
import OptionalHeader from "../components/OptionalHeader/OptionalHeader";
|
||||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import ImportDirectory from "../components/ImportDirectory/ImportDirectory";
|
||||
|
||||
const SelectNodeMap = {
|
||||
dos_header: <DosHeader />,
|
||||
@ -19,6 +20,7 @@ const SelectNodeMap = {
|
||||
file_header: <FileHeader />,
|
||||
optional_header: <OptionalHeader />,
|
||||
section_header: <SectionHeaders />,
|
||||
import_directory: <ImportDirectory />,
|
||||
};
|
||||
|
||||
export default function MainPage() {
|
||||
|
Loading…
Reference in New Issue
Block a user