Guidelines
This framework prioritises performance and compatibility first.
When writing Emulators, please stick to the following rules
Rule | Summary |
---|---|
Generated Files are Immutable | Always serve the same content on subsequent requests/handle openings. |
Never Disable The Emulator | An emulated file may also be created at any time, you cannot predict when. |
Additional rules automatically handled or encouraged by framework provided abstractions
Rule | Description |
---|---|
Always Return All Requested Bytes | Shield emulator against buggy software implementations. |
Don't Assume Any Read Pattern | Data can be accessed in any order; reads may begin from any offset and/or length. |
Use Lazy Loading | Produce/initialize emulated files only when they are first requested by the application. |
Don't implement hacks!! Focus on compatibility.
Do not implement hacks for things such as hotswapping
files at runtime by serving
different data on future loads; or writing to buffers passed by the application.
Emulators should MAKE NO ASSUMPTIONS about the underlying program.
Additional functionality such as 'hot reload' may instead be implemented on a per game or per middleware basis in additional mods. APIs to allow such advanced features (e.g. 'rebuild emulated file') can be provided by an emulator, but must not be enabled by default.
Generated Files are Immutable
The file emulator should always serve the same file on subsequent requests/handle openings.
Generated files should always persist for whole application lifetime.
Explanation:
- An Emulator should not assume how the game/application will use the file.
- The target application may for example, read part of the file, such as the header and cache it in its own memory.
- Because we don't know what the application will do, we cannot safely change any part of the file after it has been read once.
Never Disable The Emulator
Enable the emulator at startup once and never disable it.
Emulators should not be unloadable or suspendable mods.
An emulated file may also be created at any time, you cannot predict when.
And of course, emulators also work recursively, a mod may have a file your emulator might pick up.
Always Return All Requested Bytes
When a read request is made, the emulator should always return all requested bytes.
A common programmer error is to issue a Read()
command on a file stream and assume that
all bytes requested will be given back.
MultiStream abstraction automatically handles this for you.
If you're using this abstraction, you don't need to worry about this.
Calling Read()
will not always give you all your requested bytes!!
But many developers (even myself included) have been guilty of this mistake for a very long time.
If possible, DO NOT return less than the number of bytes requested in order to shield against buggy software implementations.
If you're using custom logic to resolve read requests that does not involve MultiStream,
please ensure that you always return the requested number of bytes without fail. This may in some cases
require multiple calls to your own implementation of Read()
.
Don't Assume Any Read Pattern
Assume data can be accessed in any order, and reads may begin from any offset and/or length.
MultiStream abstraction automatically handles this for you.
Use Lazy Loading
Only produce/initialize emulated files when they are first requested by the application.
In other words, create your final files in TryCreateFile
API.
And up until that's called, just collect the data you need to create the file.
e.g. Source folders.