Emulator API
This documentation is a translation of the existing C# documentation into Rust
Final APIs may slightly differ. The translation was done by an LLM.
Core API
This is the API that's most commonly used inside emulator implementations.
File I/O APIs
FileEmulatorFramework provides a very minimal, simple API for reading existing file content
POSIX
covers Linux, macOS, and other Unix-like systems.
These APIs abstract away the platform-specific details and provide a consistent interface for file I/O operations that map to low level native API calls.
By using these functions, emulator code can be written in a platform-independent manner.
So you can write and test your emulator on Linux, and it'll automatically work on Windows (and vice versa).
read_file
Reads data from a file, advancing the current file pointer by the number of bytes read.
It maps to different APIs depending on the platform:
Platform | API |
---|---|
Windows | ReadFile |
POSIX | read from libc |
let mut buffer = vec![0; 1024];
let bytes_read = read_file(file_handle, &mut buffer).unwrap();
read_struct
Reads a struct from the current position of the file handle.
This function provides a convenient way to read a struct directly from a file handle.
#[repr(C)]
struct MyStruct {
field1: u32,
field2: u16,
}
let my_struct: MyStruct = read_struct(file_handle).unwrap();
seek_file
Changes the file pointer (offset) of the specified file handle.
Platform | API |
---|---|
Windows | SetFilePointerEx |
POSIX | lseek from libc |
// Seek to the beginning of the file
seek_file(file_handle, 0, SeekOrigin::Start).unwrap();
get_file_pointer
Retrieves the current value of the file pointer (position) of the specified file handle.
Platform | API |
---|---|
Windows | SetFilePointerEx with FILE_CURRENT |
POSIX | lseek from libc with SEEK_CUR |
let current_position = get_file_pointer(file_handle).unwrap();
println!("Current file pointer: {}", current_position);
get_file_size
Retrieves the size of a file given its file handle.
Platform | API |
---|---|
Windows | GetFileSizeEx |
POSIX | fstat from libc |
let file_size = get_file_size(file_handle).unwrap();
println!("File size: {} bytes", file_size);
This API allows you to retrieve the size of a file using its file handle. It abstracts away the platform-specific details and provides a consistent interface for getting the file size.
open_file
Opens a file for reading and returns a file handle.
Platform | API |
---|---|
Windows | CreateFileW |
POSIX | open from libc |
let file_handle = open_file("path/to/file.bin").unwrap();
Route
A utility struct that represents a path to a file within the context of the FileEmulationFramework.
The Route
struct is used to match files that should be emulated based on their paths.
It provides methods to check if a given path matches the route.
let route = Route::new("path/to/file.bin");
as_file_name
Returns a slice of the OsStr
representing the file name of the Route
.
This method is useful when you need to extract the file name from the Route
.
let route = Route::new("path/to/file.bin");
let file_name = route.as_file_name(); // file_name is an &OsStr slice representing "file.bin"
from_folder_and_full_path
Creates a Route
given the full path of a file and the base folder where the emulator's files are contained.
This method is used to create a Route
by removing the base folder path from the full file path.
let base_folder = "path/to/emulator/files";
let full_path = "path/to/emulator/files/subfolder/file.bin";
let route = Route::from_folder_and_full_path(base_folder, full_path); // route is "subfolder/file.bin"
matches_no_subfolder
Checks if the given group.Route
matches the end of the current Route
, without considering subfolders.
This method is commonly used when checking if a file should be emulated based on its path.
let route = Route::new("path/to/file.bin");
let group_route = Route::new("file.bin");
let matches = route.matches_no_subfolder(&group_route); // true
matches_with_subfolder
Checks if the given group.Route
matches the current Route
, considering subfolders.
This method is used when dealing with emulated files that have a hierarchy of internal files or nested folders.
let route = Route::new("parent.bin");
let group_route = Route::new("parent.bin/child/child.dds");
let matches = route.matches_with_subfolder(&group_route); // true
For more detailed information on how the Route
struct and its methods work, please refer
to the Routing page.
Streams
MultiStream
MultiStream
combines multiple streams into a single stream with read and seek support.
The MultiStream
is the primary abstraction used in building emulators. Most emulators simply
build a MultiStream
and use that to resolve read calls directly.
This abstraction is very highly optimised, and is the recommended way to build emulators.
// Build a Stream.
let streams = vec![
StreamOffsetPair::new(File::open("file1.bin").unwrap(), OffsetRange::from_start_and_length(0, 1024)),
StreamOffsetPair::new(File::open("file2.bin").unwrap(), OffsetRange::from_start_and_length(1024, 2048)),
];
let multi_stream = MultiStream::new(streams);
// Read data spanning both streams
let mut data = vec![0; 2048];
multi_stream.read(&mut data).unwrap();
PaddingStream
Stream that fills the read buffer with a single, user specified byte.
Emulated files will very often have padding bytes, for example, padding between the end of one file and the start of another file.
PaddingStream
is used to supply that padding, when used in conjunction with MultiStream.
// Create 1024 bytes of 0x00 padding
let padding_stream = PaddingStream::new(0x00, 1024);
FileSliceStream
A Stream
abstraction that wraps a FileSlice.
These structs allow you to read data from a FileSlice as if it were a stream.
Example:
let slice = FileSlice::new(1024, 4096, "file.bin");
let stream = FileSliceStream::new(slice);
Primitives
FileSlice
An abstraction that allows you to read a region of a given file.
When building emulators, you will often provide a mixture of the original data and new data. This struct will allow you to more easily fetch the original data when needed.
// Create a slice for the first 1024 bytes of a file
let slice = FileSlice::new(0, 1024, "path/to/file.bin");
Merging File Slices
Slices of the same file that touch each other can be merged into singular, larger slices.
This is usually automatically handled by MultiStream under the hood.
For example, 0-4095
and 4096-65536
can be merged into a single slice of 0-65536
.
In practice, this is sometimes possible when working with archives containing file data whereby multiple files are laid out side by side.
let first = FileSlice::new(0, 4096, "file.bin");
let second = FileSlice::new(4096, 4096, "file.bin");
if let Some(merged) = FileSlice::try_merge(first, second) {
// Successfully merged into 'merged'
}
If you are using streams backed by FileSlice
, you can merge them
using FileSliceStream::try_merge
for individual streams or
FileSliceStream::merge_streams
when you have multiple streams.
OffsetRange
A utility struct that stores a start and end offset [inclusive].
Can be used for testing for overlaps, testing if an address is in range, etc.
let range = OffsetRange::from_start_and_length(1024, 512);
let is_in_range = OffsetRange::contains_point(&range, 1536); // true
OffsetRangeSelector
Utility for quickly finding the offset range that contains a given offset.
The OffsetRangeSelector
is used to quickly find the index of an OffsetRange
that contains a
given offset. It assumes that the provided OffsetRange
s are sorted in ascending order and
without gaps.
Internally, the selector uses binary search to efficiently locate the correct range index.
Example usage:
let ranges = vec![
OffsetRange::from_start_and_length(0, 1024),
OffsetRange::from_start_and_length(1024, 2048),
OffsetRange::from_start_and_length(2048, 4096),
];
let selector = OffsetRangeSelector::new(ranges);
let index = selector.select(1500); // Returns 1
The data should be internally represented as 0
, 1024
, 2048
, 4096
,
with the ranges joined.
Utility API
Mathematics
Struct
This struct has some common mathematics related operations, such as rounding up numbers to add padding to files.
let rounded_up = Mathematics::round_up(1234, 512); // 1536
DirectorySearcher
Struct
The DirectorySearcher
struct can be used for extremely fast searching of files on the filesystem.
On Windows, this uses a custom implementation which uses the NtQueryDirectoryFile
under the hood.
For Linux, this uses the standard Rust library as that is already efficient.
Expect a considerable speedup over the built-in Rust implementation.
let (files, directories) = DirectorySearcher::get_directory_contents_recursive("C:/MyFolder");