Reloaded3 File Emulation Framework
🎈 Let's screw with binaries 🎈
A framework for creating virtual files at runtime.
About The Framework
The file emulation framework is a framework for intercepting Operating System API calls related to the reading of files from disk; in order to trick games into loading files that don't really exist.
It builds on top of previous experiments with Reloaded-II:
- Reloaded-II FileEmulationFramework: The reimagining and original C# implementation of this.
- Persona 4 Golden (32-bit) PC modloader: The slightly improved derivative.
- ONE Redirector: The other original.
- AFS Redirector: The original.
A User Friendly Example
Replacing files inside big archives without creating new ones.
In this case, the following files would replace the 7th, 8th, 9th and 10th file in the
SH_VOICE_E.afs
archive.
How It Works
By hooking API calls used to open files, get their properties and read from them, we can create files 'on the fly'
This allows us to perform various forms of post processing such as merging archives in a way where we require zero knowledge of the application (game) running under the hood.
File Emulation therefore works with just about anything.
This includes regular tools/programs reading files, and even emulators.
As long as the emulated file you output is understood by the target applicetion.
Projects using this framework are referred to as 'emulators' hence the name File Emulation Framework
.
This is because they simulate files that don't really exist on disk.
When to Use File Emulation
Use File Emulation rather than Merged File Cache if all/most following criteria hold true
- Emulated Files are Large (>32MB)
- Emulated Files don't have many small (<1MB) files from many sources
- Generated file can reuse existing data in existing files. (e.g. chunks of existing file/archive)
For more info, see Read Performance of SOLID Files.
Performance Impact
In most use cases, emulators have negligible performance impact.
All numbers listed here are on a 5900X with CL16 3000MHz RAM. Numbers below are for original C# implementation, Rust will improve some things very slightly.
Performance varies with a lot of factors, including...
File Open Time
Usually first access to an emulated file may be delayed for a small amount of time
In the original C# emulators, this is:
0.5ms
-1ms
per emulated file for most realistic inputs.
In addition, the original .NET implementations will roughly have:
1ms
JIT time, for creating first emulated file of a given type
This will improve a bit in Rust, since JIT time is not a factor and we get a high quality compilation off the bat, rather than tiered JIT compilation.
Read Overhead
Read speed/overhead is negligible in most cases.
A single typical read operation involves the following:
-
A dictionary lookup to find emulated file.
8ns
, constant.- This will be faster in Rust, because Swisstable (Hashbrown) is faster.
-
A binary search to determine correct stream/source for the data to be read.
2.5ns
-5.5ns
for typical files (under 64 streams).- Example: Inject a few textures into a texture archive.
- or
35ns
for a read of a huge file with 16384 streams.- Example: Replace 25% of a 65536 file archive.
-
Remaining code/other overheads.
- Approx
1
-2ns
, constant.
- Approx
Generally sub 15ns
overhead for each read call in existing implementation.
And 8ns
for non-emulated files.
For non-SOLID archives, the original use case for this framework, this is basically it. Completely negligible.
For files that are read entirely in 1 go, with multiple sources, additional caveats apply.
Load performance may improve or degrade depending on storage medium used.
For more information, see Read Performance of SOLID Files.
Credits, Attributions
- Header icon created by Freepik - Flaticon