Skip to content

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 OffsetRanges 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");