1use std::collections::BTreeMap;
33
34#[derive(Debug, Default, Clone)]
38pub struct Context {
39 pub vfs: Option<VirtualFs>,
41 pub registry: Option<VirtualRegistry>,
43}
44
45impl Context {
46 #[must_use]
49 pub fn new() -> Self {
50 Self::default()
51 }
52
53 #[must_use]
55 pub fn with_vfs(mut self, vfs: VirtualFs) -> Self {
56 self.vfs = Some(vfs);
57 self
58 }
59
60 #[must_use]
62 pub fn with_registry(mut self, reg: VirtualRegistry) -> Self {
63 self.registry = Some(reg);
64 self
65 }
66}
67
68#[derive(Debug, Default, Clone)]
82pub struct VirtualFs {
83 files: BTreeMap<String, Vec<u8>>,
84 open: BTreeMap<u32, FileHandle>,
85 next_handle: u32,
86}
87
88#[derive(Debug, Clone)]
90pub struct FileHandle {
91 pub path: String,
93 pub pos: u64,
95 pub access: FileAccess,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum FileAccess {
105 Read,
106 Write,
107 ReadWrite,
108}
109
110impl FileAccess {
111 #[must_use]
115 pub fn from_win32_desired_access(flags: u32) -> Self {
116 let read = flags & 0x8000_0000 != 0;
117 let write = flags & 0x4000_0000 != 0;
118 match (read, write) {
119 (true, true) => FileAccess::ReadWrite,
120 (false, true) => FileAccess::Write,
121 _ => FileAccess::Read, }
123 }
124
125 fn allows_read(self) -> bool {
126 matches!(self, FileAccess::Read | FileAccess::ReadWrite)
127 }
128 fn allows_write(self) -> bool {
129 matches!(self, FileAccess::Write | FileAccess::ReadWrite)
130 }
131}
132
133pub const HANDLE_BASE: u32 = 0x6800_0000;
137
138impl VirtualFs {
139 #[must_use]
140 pub fn new() -> Self {
141 Self::default()
142 }
143
144 pub fn insert(&mut self, path: &str, bytes: Vec<u8>) {
147 self.files.insert(normalize_path(path), bytes);
148 }
149
150 #[must_use]
153 pub fn read(&self, path: &str) -> Option<&[u8]> {
154 self.files.get(&normalize_path(path)).map(Vec::as_slice)
155 }
156
157 #[must_use]
159 pub fn contains(&self, path: &str) -> bool {
160 self.files.contains_key(&normalize_path(path))
161 }
162
163 pub fn write_path(&mut self, path: &str, bytes: Vec<u8>) {
166 self.files.insert(normalize_path(path), bytes);
167 }
168
169 pub fn remove(&mut self, path: &str) -> bool {
171 self.files.remove(&normalize_path(path)).is_some()
172 }
173
174 pub fn list(&self) -> impl Iterator<Item = (&str, usize)> {
176 self.files.iter().map(|(k, v)| (k.as_str(), v.len()))
177 }
178
179 pub fn open(&mut self, path: &str, access: FileAccess) -> Option<u32> {
184 let key = normalize_path(path);
185 let exists = self.files.contains_key(&key);
186 if !exists {
187 if !access.allows_write() {
188 return None;
189 }
190 self.files.insert(key.clone(), Vec::new());
191 }
192 let handle = HANDLE_BASE.wrapping_add(self.next_handle);
193 self.next_handle = self.next_handle.wrapping_add(1);
194 self.open.insert(
195 handle,
196 FileHandle {
197 path: key,
198 pos: 0,
199 access,
200 },
201 );
202 Some(handle)
203 }
204
205 pub fn close(&mut self, handle: u32) -> bool {
209 self.open.remove(&handle).is_some()
210 }
211
212 pub fn read_handle(&mut self, handle: u32, buf: &mut [u8]) -> Option<usize> {
218 let fh = self.open.get_mut(&handle)?;
219 if !fh.access.allows_read() {
220 return None;
221 }
222 let file = self.files.get(&fh.path)?;
223 let pos = fh.pos as usize;
224 if pos >= file.len() {
225 return Some(0);
226 }
227 let n = buf.len().min(file.len() - pos);
228 buf[..n].copy_from_slice(&file[pos..pos + n]);
229 fh.pos = fh.pos.wrapping_add(n as u64);
230 Some(n)
231 }
232
233 pub fn write_handle(&mut self, handle: u32, data: &[u8]) -> Option<usize> {
238 let fh = self.open.get_mut(&handle)?;
239 if !fh.access.allows_write() {
240 return None;
241 }
242 let file = self.files.get_mut(&fh.path)?;
243 let pos = fh.pos as usize;
244 if pos + data.len() > file.len() {
245 file.resize(pos + data.len(), 0);
246 }
247 file[pos..pos + data.len()].copy_from_slice(data);
248 fh.pos = fh.pos.wrapping_add(data.len() as u64);
249 Some(data.len())
250 }
251
252 pub fn seek(&mut self, handle: u32, pos: u64) -> Option<u64> {
255 let fh = self.open.get_mut(&handle)?;
256 fh.pos = pos;
257 Some(pos)
258 }
259
260 #[must_use]
263 pub fn size(&self, handle: u32) -> Option<u64> {
264 let fh = self.open.get(&handle)?;
265 let file = self.files.get(&fh.path)?;
266 Some(file.len() as u64)
267 }
268
269 #[must_use]
274 pub fn owns(&self, handle: u32) -> bool {
275 self.open.contains_key(&handle)
276 }
277}
278
279fn normalize_path(path: &str) -> String {
283 let mut out = String::with_capacity(path.len());
284 for c in path.chars() {
285 if c == '\\' {
286 out.push('/');
287 } else {
288 out.extend(c.to_lowercase());
289 }
290 }
291 out
292}
293
294#[derive(Debug, Default, Clone)]
310pub struct VirtualRegistry {
311 keys: BTreeMap<String, RegistryKey>,
313 open: BTreeMap<u32, OpenKey>,
315 next_handle: u32,
316}
317
318#[derive(Debug, Clone)]
320pub struct OpenKey {
321 pub path: String,
322}
323
324#[derive(Debug, Default, Clone)]
326pub struct RegistryKey {
327 values: BTreeMap<String, RegistryValue>,
328}
329
330#[derive(Debug, Clone, PartialEq, Eq)]
333pub enum RegistryValue {
334 Sz(String),
336 ExpandSz(String),
339 Dword(u32),
341 Qword(u64),
343 Binary(Vec<u8>),
345 MultiSz(Vec<String>),
348}
349
350pub const HKEY_CLASSES_ROOT: u32 = 0x8000_0000;
355pub const HKEY_CURRENT_USER: u32 = 0x8000_0001;
356pub const HKEY_LOCAL_MACHINE: u32 = 0x8000_0002;
357pub const HKEY_USERS: u32 = 0x8000_0003;
358pub const HKCR: u32 = HKEY_CLASSES_ROOT;
360pub const HKCU: u32 = HKEY_CURRENT_USER;
361pub const HKLM: u32 = HKEY_LOCAL_MACHINE;
362pub const HKU: u32 = HKEY_USERS;
363
364pub const HKEY_USER_BASE: u32 = 0x6900_0000;
366
367impl VirtualRegistry {
368 #[must_use]
369 pub fn new() -> Self {
370 Self::default()
371 }
372
373 pub fn set_value(&mut self, key_path: &str, name: &str, value: RegistryValue) {
375 let key = normalize_path(key_path);
376 let entry = self.keys.entry(key).or_default();
377 entry.values.insert(name.to_ascii_lowercase(), value);
378 }
379
380 #[must_use]
382 pub fn get_value(&self, key_path: &str, name: &str) -> Option<&RegistryValue> {
383 let key = normalize_path(key_path);
384 self.keys
385 .get(&key)
386 .and_then(|k| k.values.get(&name.to_ascii_lowercase()))
387 }
388
389 pub fn all_values(&self) -> impl Iterator<Item = (&str, &str, &RegistryValue)> {
393 self.keys.iter().flat_map(|(key_path, key)| {
394 key.values
395 .iter()
396 .map(move |(name, value)| (key_path.as_str(), name.as_str(), value))
397 })
398 }
399
400 #[must_use]
402 pub fn contains_key(&self, key_path: &str) -> bool {
403 self.keys.contains_key(&normalize_path(key_path))
404 }
405
406 #[must_use]
409 pub fn predefined_path(hkey: u32) -> Option<&'static str> {
410 match hkey {
411 HKEY_CLASSES_ROOT => Some("hkey_classes_root"),
412 HKEY_CURRENT_USER => Some("hkey_current_user"),
413 HKEY_LOCAL_MACHINE => Some("hkey_local_machine"),
414 HKEY_USERS => Some("hkey_users"),
415 _ => None,
416 }
417 }
418
419 pub fn open_key(&mut self, base_hkey: u32, subkey: &str) -> Option<u32> {
423 let base_path = if let Some(p) = Self::predefined_path(base_hkey) {
424 p.to_string()
425 } else {
426 self.open.get(&base_hkey)?.path.clone()
427 };
428 let combined = if subkey.is_empty() {
429 base_path
430 } else {
431 format!("{}/{}", base_path, normalize_path(subkey))
432 };
433 if !self.keys.contains_key(&combined) {
434 return None;
435 }
436 let h = HKEY_USER_BASE.wrapping_add(self.next_handle);
437 self.next_handle = self.next_handle.wrapping_add(1);
438 self.open.insert(h, OpenKey { path: combined });
439 Some(h)
440 }
441
442 pub fn close_key(&mut self, hkey: u32) -> bool {
447 if Self::predefined_path(hkey).is_some() {
448 return true;
449 }
450 self.open.remove(&hkey).is_some()
451 }
452
453 #[must_use]
458 pub fn owns(&self, hkey: u32) -> bool {
459 Self::predefined_path(hkey).is_some() || self.open.contains_key(&hkey)
460 }
461
462 #[must_use]
465 pub fn path_of(&self, hkey: u32) -> Option<&str> {
466 if let Some(p) = Self::predefined_path(hkey) {
467 return Some(p);
468 }
469 self.open.get(&hkey).map(|k| k.path.as_str())
470 }
471}
472
473#[cfg(test)]
474mod tests {
475 use super::*;
476
477 #[test]
478 fn vfs_insert_and_read_case_insensitive() {
479 let mut vfs = VirtualFs::new();
480 vfs.insert("C:\\Windows\\foo.ini", b"hello".to_vec());
481 assert_eq!(vfs.read("C:\\Windows\\foo.ini"), Some(&b"hello"[..]));
482 assert_eq!(vfs.read("c:/windows/FOO.INI"), Some(&b"hello"[..]));
483 assert_eq!(vfs.read("nope.txt"), None);
484 }
485
486 #[test]
487 fn vfs_open_read_close() {
488 let mut vfs = VirtualFs::new();
489 vfs.insert("a.txt", b"hello world".to_vec());
490 let h = vfs.open("a.txt", FileAccess::Read).expect("opens");
491 let mut buf = [0u8; 5];
492 assert_eq!(vfs.read_handle(h, &mut buf), Some(5));
493 assert_eq!(&buf, b"hello");
494 assert_eq!(vfs.read_handle(h, &mut buf), Some(5));
495 assert_eq!(&buf, b" worl");
496 let mut tail = [0u8; 5];
497 assert_eq!(vfs.read_handle(h, &mut tail), Some(1));
498 assert_eq!(&tail[..1], b"d");
499 assert_eq!(vfs.read_handle(h, &mut buf), Some(0));
500 assert!(vfs.close(h));
501 }
502
503 #[test]
504 fn vfs_write_extends_and_round_trips() {
505 let mut vfs = VirtualFs::new();
506 let h = vfs.open("new.txt", FileAccess::Write).expect("opens");
507 assert_eq!(vfs.write_handle(h, b"hello").unwrap(), 5);
508 assert_eq!(vfs.write_handle(h, b" world").unwrap(), 6);
509 vfs.close(h);
510 assert_eq!(vfs.read("new.txt"), Some(&b"hello world"[..]));
511 }
512
513 #[test]
514 fn vfs_read_only_handle_cannot_write() {
515 let mut vfs = VirtualFs::new();
516 vfs.insert("a.txt", b"hi".to_vec());
517 let h = vfs.open("a.txt", FileAccess::Read).unwrap();
518 assert!(vfs.write_handle(h, b"!").is_none());
519 }
520
521 #[test]
522 fn vfs_open_nonexistent_read_returns_none() {
523 let mut vfs = VirtualFs::new();
524 assert!(vfs.open("missing.txt", FileAccess::Read).is_none());
525 }
526
527 #[test]
528 fn registry_set_get_case_insensitive() {
529 let mut reg = VirtualRegistry::new();
530 reg.set_value(
531 "HKLM\\Software\\Foo",
532 "Version",
533 RegistryValue::Sz("1.2.3".into()),
534 );
535 assert_eq!(
536 reg.get_value("hklm/software/foo", "version"),
537 Some(&RegistryValue::Sz("1.2.3".into()))
538 );
539 assert_eq!(
540 reg.get_value("HKLM\\Software\\Foo", "VERSION"),
541 Some(&RegistryValue::Sz("1.2.3".into()))
542 );
543 }
544
545 #[test]
546 fn registry_open_close_round_trip() {
547 let mut reg = VirtualRegistry::new();
548 reg.set_value(
549 "hkey_local_machine/software/foo",
550 "x",
551 RegistryValue::Dword(1),
552 );
553 let h = reg.open_key(HKLM, "Software\\Foo").expect("opens");
554 assert!(reg.owns(h));
555 assert_eq!(reg.path_of(h), Some("hkey_local_machine/software/foo"));
556 assert!(reg.close_key(h));
557 }
558
559 #[test]
560 fn context_builders() {
561 let mut vfs = VirtualFs::new();
562 vfs.insert("a.txt", b"x".to_vec());
563 let mut reg = VirtualRegistry::new();
564 reg.set_value("hklm", "v", RegistryValue::Dword(1));
565 let ctx = Context::new().with_vfs(vfs).with_registry(reg);
566 assert!(ctx.vfs.is_some());
567 assert!(ctx.registry.is_some());
568 }
569}