dearxan_test_utils/
lib.rs1use std::{ffi::OsStr, path::PathBuf, sync::LazyLock};
2
3use pelite::pe64::{Pe, PeFile, PeObject, PeView};
4
5pub fn pe_file_to_view(pe: pelite::pe64::PeFile) -> Vec<u8> {
7 let (sizeof_headers, sizeof_image) = {
8 let optional_header = pe.optional_header();
9 (optional_header.SizeOfHeaders, optional_header.SizeOfImage)
10 };
11
12 let mut vec = vec![0u8; sizeof_image as usize];
14
15 let image = pe.image();
17 unsafe {
18 let dest_headers = vec.get_unchecked_mut(..sizeof_headers as usize);
20 let src_headers = image.get_unchecked(..sizeof_headers as usize);
21 dest_headers.copy_from_slice(src_headers);
22 }
23
24 for section in pe.section_headers() {
26 let dest = vec.get_mut(
27 section.VirtualAddress as usize
28 ..u32::wrapping_add(section.VirtualAddress, section.VirtualSize) as usize,
29 );
30 let src = image.get(
31 section.PointerToRawData as usize
32 ..u32::wrapping_add(section.PointerToRawData, section.SizeOfRawData) as usize,
33 );
34 if let (Some(dest), Some(src)) = (dest, src) {
36 let write_sz = src.len().min(dest.len());
37 dest[..write_sz].copy_from_slice(&src[..write_sz]);
38 }
39 }
40
41 vec
42}
43
44#[derive(Debug, Clone)]
45pub struct FsExe {
46 pub game: String,
47 pub ver: String,
48 pub path: PathBuf,
49}
50
51impl FsExe {
52 pub fn load_64(&self) -> pelite::Result<MappedFsExe> {
53 let disk_view = std::fs::read(&self.path).unwrap();
54 let pe_file = PeFile::from_bytes(&disk_view)?;
55 let mem_view = pe_file_to_view(pe_file);
56
57 Ok(MappedFsExe {
58 exe: self.clone(),
59 disk_view,
60 mem_view,
61 })
62 }
63}
64
65#[derive(Debug, Clone)]
66pub struct MappedFsExe {
67 exe: FsExe,
68 disk_view: Vec<u8>,
69 mem_view: Vec<u8>,
70}
71
72impl MappedFsExe {
73 pub fn exe_info(&self) -> &FsExe {
74 &self.exe
75 }
76
77 pub fn game(&self) -> &str {
78 &self.exe.game
79 }
80
81 pub fn ver(&self) -> &str {
82 &self.exe.ver
83 }
84
85 pub fn pe_file(&self) -> PeFile<'_> {
86 PeFile::from_bytes(&self.disk_view).unwrap()
87 }
88
89 pub fn pe_view(&self) -> PeView<'_> {
90 PeView::from_bytes(&self.mem_view).unwrap()
91 }
92}
93
94pub struct FsGame {
95 pub name: String,
96 pub versions: Vec<FsExe>,
97}
98
99pub fn fsbins() -> &'static [FsGame] {
100 static FS_BINS: LazyLock<Vec<FsGame>> = LazyLock::new(|| {
101 let fsbins_root = std::env::var("FSBINS_PATH").expect(
102 "This test requires fsbins to be installed and the FSBINS_PATH environment
103 variable to point to its root.",
104 );
105
106 std::fs::read_dir(fsbins_root)
107 .unwrap()
108 .filter_map(|game_folder| {
109 let game_folder = game_folder.ok()?;
110 let game_name = game_folder.file_name().to_string_lossy().to_string();
111 if !game_folder.file_type().ok()?.is_dir() {
112 return None;
113 }
114
115 let mut versions: Vec<_> = std::fs::read_dir(game_folder.path())
116 .ok()?
117 .filter_map(|ver_folder| {
118 let ver_folder = ver_folder.ok()?;
119 let ver_name = ver_folder.file_name().to_string_lossy().to_string();
120 if !ver_folder.file_type().ok()?.is_dir() {
121 return None;
122 }
123
124 let exe = std::fs::read_dir(ver_folder.path())
125 .ok()?
126 .filter_map(|f| f.ok())
127 .find(|f| f.path().extension() == Some(OsStr::new("exe")))?;
128
129 Some(FsExe {
130 game: game_name.clone(),
131 ver: ver_name.clone(),
132 path: exe.path(),
133 })
134 })
135 .collect();
136
137 if versions.is_empty() {
138 return None;
139 }
140
141 versions.sort_by(|a, b| {
144 let mut a_split = a.ver.split('.');
145 let mut b_split = b.ver.split('.');
146 for (a_seg, b_seg) in (&mut a_split).zip(&mut b_split) {
147 let ordering = match (a_seg.parse::<usize>(), b_seg.parse::<usize>()) {
148 (Ok(a_num), Ok(b_num)) => a_num.cmp(&b_num),
149 _ => a_seg.cmp(b_seg),
150 };
151 if ordering != std::cmp::Ordering::Equal {
152 return ordering;
153 }
154 }
155 a_split.cmp(b_split)
156 });
157 Some(FsGame {
158 name: game_name.to_string(),
159 versions,
160 })
161 })
162 .collect()
163 });
164 &FS_BINS
165}
166
167pub fn latest_fsbins() -> Vec<&'static FsExe> {
168 fsbins().iter().map(|g| g.versions.last().unwrap()).collect()
169}
170
171pub fn init_log(level: log::LevelFilter) {
172 simplelog::SimpleLogger::init(level, simplelog::Config::default()).unwrap();
173}