init: first
This commit is contained in:
commit
8362155260
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/target
|
277
Cargo.lock
generated
Normal file
277
Cargo.lock
generated
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler2"
|
||||||
|
version = "2.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.0.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "loadpe-rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"object",
|
||||||
|
"thiserror",
|
||||||
|
"windows",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394"
|
||||||
|
dependencies = [
|
||||||
|
"adler2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.36.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
|
||||||
|
dependencies = [
|
||||||
|
"flate2",
|
||||||
|
"memchr",
|
||||||
|
"ruzstd",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ruzstd"
|
||||||
|
version = "0.7.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fad02996bfc73da3e301efe90b1837be9ed8f4a462b6ed410aa35d00381de89f"
|
||||||
|
dependencies = [
|
||||||
|
"twox-hash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "static_assertions"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.91"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "2.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f072643fd0190df67a8bab670c20ef5d8737177d6ac6b2e9a236cb096206b2cc"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "2.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b50fa271071aae2e6ee85f842e2e28ba8cd2c5fb67f11fcb1fd70b276f9e7d4"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "twox-hash"
|
||||||
|
version = "1.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"static_assertions",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows"
|
||||||
|
version = "0.58.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.58.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99"
|
||||||
|
dependencies = [
|
||||||
|
"windows-implement",
|
||||||
|
"windows-interface",
|
||||||
|
"windows-result",
|
||||||
|
"windows-strings",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-implement"
|
||||||
|
version = "0.58.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-interface"
|
||||||
|
version = "0.58.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-result"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-strings"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
|
||||||
|
dependencies = [
|
||||||
|
"windows-result",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_gnullvm",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
18
Cargo.toml
Normal file
18
Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
name = "loadpe-rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
object = "0.36.7"
|
||||||
|
thiserror = "2.0.9"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
target = "i686-pc-windows-msvc"
|
||||||
|
|
||||||
|
[dependencies.windows]
|
||||||
|
version = "0.58.0"
|
||||||
|
features=[
|
||||||
|
"Win32_System_Memory",
|
||||||
|
"Win32_System_LibraryLoader"
|
||||||
|
]
|
21
src/errors.rs
Normal file
21
src/errors.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
use object::Error as ObjectError;
|
||||||
|
use windows::core::Error as WindowsError;
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum LoadPEError {
|
||||||
|
#[error("文件不存在: {0}")]
|
||||||
|
FileNotFound(String),
|
||||||
|
#[error("文件读取失败: {0}")]
|
||||||
|
FileReadError(#[from] std::io::Error),
|
||||||
|
#[error("文件解析失败: {0}")]
|
||||||
|
ParseError(#[from] ObjectError),
|
||||||
|
#[error("内存分配失败")]
|
||||||
|
MemoryAllocFailed,
|
||||||
|
|
||||||
|
#[error("可执行程序没有包含导入表!")]
|
||||||
|
ExecutableWithoutImportTable,
|
||||||
|
#[error("读取一个C风格字符串错误, {0}")]
|
||||||
|
ReadCStringError(#[from] std::str::Utf8Error),
|
||||||
|
#[error("LoadLibraryA错误!, {0}")]
|
||||||
|
LoadLibraryError(#[from] WindowsError),
|
||||||
|
}
|
7
src/help.rs
Normal file
7
src/help.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#[macro_export]
|
||||||
|
/// 将一个数对齐到另一个数的倍数
|
||||||
|
macro_rules! align_to{
|
||||||
|
($a:expr, $b:expr) => {
|
||||||
|
($a + $b - 1) & !($b - 1)
|
||||||
|
};
|
||||||
|
}
|
156
src/lib.rs
Normal file
156
src/lib.rs
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
use core::alloc;
|
||||||
|
use errors::LoadPEError;
|
||||||
|
use object::{
|
||||||
|
bytes_of, bytes_of_slice, endian,
|
||||||
|
pe::IMAGE_DIRECTORY_ENTRY_IMPORT,
|
||||||
|
read::pe::{self, ImageOptionalHeader, PeFile32, PeFile64},
|
||||||
|
LittleEndian, Object, ObjectSection, Pod,
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
alloc::{alloc, Layout},
|
||||||
|
fs,
|
||||||
|
};
|
||||||
|
use windows::{
|
||||||
|
core::PCSTR,
|
||||||
|
Win32::{Foundation::CloseHandle, System::LibraryLoader::LoadLibraryA},
|
||||||
|
};
|
||||||
|
pub mod errors;
|
||||||
|
mod help;
|
||||||
|
|
||||||
|
/// 加载并启动一个exe文件,需要支持命令行参数
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
|
pub fn load_exe(exe_path: &str, args: Option<Vec<&str>>) -> Result<(), LoadPEError> {
|
||||||
|
// 1. 检查文件是否存在
|
||||||
|
if !fs::metadata(exe_path).is_ok() {
|
||||||
|
return Err(LoadPEError::FileNotFound(exe_path.to_string()));
|
||||||
|
}
|
||||||
|
// 2. 如果文件存在,加载文件
|
||||||
|
let data = fs::read(exe_path)?;
|
||||||
|
#[cfg(target_arch = "x86")]
|
||||||
|
let file: PeFile32 = pe::PeFile::parse(&data[..])?;
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
let file: PeFile64 = pe::PeFile::parse(&data[..])?;
|
||||||
|
|
||||||
|
// 获取镜像大小
|
||||||
|
let image_size = file
|
||||||
|
.nt_headers()
|
||||||
|
.optional_header
|
||||||
|
.size_of_image
|
||||||
|
.get(endian::LittleEndian);
|
||||||
|
// 3. 为镜像分配内存
|
||||||
|
// 分配的内存,后续需要修改内存属性
|
||||||
|
//
|
||||||
|
let buf = unsafe { alloc(Layout::for_value(&image_size)) };
|
||||||
|
if buf.is_null() {
|
||||||
|
return Err(LoadPEError::MemoryAllocFailed);
|
||||||
|
}
|
||||||
|
// 进行数据的拷贝。
|
||||||
|
{
|
||||||
|
// 拷贝整个头部
|
||||||
|
let head_size = file
|
||||||
|
.nt_headers()
|
||||||
|
.optional_header
|
||||||
|
.size_of_headers
|
||||||
|
.get(endian::LittleEndian);
|
||||||
|
let head_data = &data[..head_size as usize];
|
||||||
|
unsafe {
|
||||||
|
std::ptr::copy_nonoverlapping(head_data.as_ptr(), buf, head_size as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// 分块拷贝节区数据
|
||||||
|
|
||||||
|
let section_alignment = file.nt_headers().optional_header.section_alignment();
|
||||||
|
let file_alignment = file.nt_headers().optional_header.file_alignment();
|
||||||
|
for section in file.sections() {
|
||||||
|
// 如果节区没有映射到内存中,那么就不需要拷贝
|
||||||
|
if section.pe_section().size_of_raw_data.get(LittleEndian) == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// VA对齐值
|
||||||
|
let va = align_to!(
|
||||||
|
section.pe_section().virtual_address.get(LittleEndian),
|
||||||
|
section_alignment
|
||||||
|
);
|
||||||
|
// pointer_to_raw_data对齐值
|
||||||
|
let pointer_to_raw_data = align_to!(
|
||||||
|
section.pe_section().pointer_to_raw_data.get(LittleEndian),
|
||||||
|
file_alignment
|
||||||
|
);
|
||||||
|
// size_of_raw_data对齐值
|
||||||
|
let size_of_raw_data = align_to!(
|
||||||
|
section.pe_section().size_of_raw_data.get(LittleEndian),
|
||||||
|
file_alignment
|
||||||
|
);
|
||||||
|
// 从data[pointer_to_raw_data..pointer_to_raw_data + size_of_raw_data]中拷贝数据到buf[va..va + size_of_raw_data]
|
||||||
|
let section_data = &data
|
||||||
|
[pointer_to_raw_data as usize..(pointer_to_raw_data + size_of_raw_data) as usize];
|
||||||
|
unsafe {
|
||||||
|
std::ptr::copy_nonoverlapping(
|
||||||
|
section_data.as_ptr(),
|
||||||
|
buf.add(va as usize),
|
||||||
|
size_of_raw_data as usize,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 修复IAT表
|
||||||
|
{
|
||||||
|
let import_table = file.import_table()?;
|
||||||
|
if import_table.is_none() {
|
||||||
|
return Err(LoadPEError::ExecutableWithoutImportTable);
|
||||||
|
}
|
||||||
|
let import_table = import_table.unwrap();
|
||||||
|
|
||||||
|
// 1. 加载模块
|
||||||
|
let descriptors = import_table.descriptors().unwrap();
|
||||||
|
for descriptor_result in descriptors {
|
||||||
|
// 当descriptor 是Ok(null)的时候表示结束
|
||||||
|
let descriptor = descriptor_result?;
|
||||||
|
if descriptor.is_null() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let module_name_rva = descriptor.name.get(LittleEndian);
|
||||||
|
let p_module_name = unsafe {
|
||||||
|
// 从data[module_name_rva..]中读取一个c风格字符串
|
||||||
|
data.as_ptr().add(module_name_rva as usize) as *const u8
|
||||||
|
};
|
||||||
|
|
||||||
|
// 使用LoadLibraryA加载模块
|
||||||
|
let h_module = unsafe {
|
||||||
|
LoadLibraryA(PCSTR(p_module_name))
|
||||||
|
.map_err(|err| LoadPEError::LoadLibraryError(err))?
|
||||||
|
};
|
||||||
|
if h_module.is_invalid() {
|
||||||
|
// 这个逻辑应该走不到
|
||||||
|
unimplemented!("LoadLibraryA failed");
|
||||||
|
}
|
||||||
|
// TODO: 遍历IAT表,修复IAT表
|
||||||
|
// 加载每一个方法。使用GetProcAddress,获取地址。
|
||||||
|
// 然后修复IAT表。如果其中一个环节出现问题,我们应该:释放所有加载的Module,然后返回错误
|
||||||
|
|
||||||
|
// 如果OriginalFirstThunk不存在,则使用FirstThunk 的值
|
||||||
|
|
||||||
|
|
||||||
|
let original_first_thunk = descriptor.original_first_thunk.get(LittleEndian);
|
||||||
|
if original_first_thunk == 0 {
|
||||||
|
|
||||||
|
}else {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
3
tests/test.rs
Normal file
3
tests/test.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user