SCENEBIN
A manifest of files to load. Produced by tools/scenebin_converter.py from a plain text
source file, consumed by SceneLoader::LoadScene.
A SCENEBIN does not contain assets. It expands into entries on FileLoader's work queue,
each of which is then loaded by the manager for its type. Loading one is how you avoid
hand-writing a long eastl::vector<LoadQueue> in game code.
Every multi-byte field is little-endian. There is no version byte.
Binary layout
| Field | Type | Bytes | Notes |
|---|---|---|---|
| magic | char[8] | 8 | SCENEBIN, no terminator. |
| fileCount | uint8_t | 1 | 0-255. The converter refuses more than 255 entries. |
Then fileCount entries, each:
| Field | Type | Bytes | Notes |
|---|---|---|---|
| type | uint8_t | 1 | A LoadFileType value, see below. |
| nameLen | uint8_t | 1 | Length of name in bytes. |
| name | char[nameLen] | nameLen | Archive-relative path. Not null-terminated on the wire. |
| vramX | uint16_t | 2 | TEXTURE and TEXTURE_EXTRA entries only. 0xFFFF means take it from the TIM. |
| vramY | uint16_t | 2 | TEXTURE and TEXTURE_EXTRA entries only. 0xFFFF means take it from the TIM. |
| clutX | uint16_t | 2 | TEXTURE and TEXTURE_EXTRA entries only. 0xFFFF means take it from the TIM. |
| clutY | uint16_t | 2 | TEXTURE and TEXTURE_EXTRA entries only. 0xFFFF means take it from the TIM. |
| flags | uint32_t | 4 | TEXTURE_EXTRA entries only. Passed to LoadTIM as userFlags, see below. |
Texture placement
The four placement values are present only when type is TEXTURE or TEXTURE_EXTRA. Every other
type ends its entry after the name.
vramX/vramY are where the image goes in VRAM, clutX/clutY where its palette goes.
They are passed straight through to TextureManager::LoadTIM, which does no automatic
packing, so placement has to be planned per texture. Putting it in the manifest is what
keeps a scene's VRAM layout in one place instead of scattered through game code.
All four are used exactly as given, and 0 is a real coordinate. Write auto in place of a
number to mean "use the position stored in the TIM file itself" for that coordinate; the
converter encodes it as 0xFFFF, which VRAM can never hold. See the
textures page.
Texture Extra placement
A TEXTURE_EXTRA entry carries the same four placement values as TEXTURE, followed by
flags. The converter accepts 0 or 1.
Set flags to 1 for a texture with holes (transparent texels you can see through), so advanced
fog is drawn on the texels and not on the holes. Textures used by an object with
RF_QUICK_ADVANCED_FOG need it too, holes or not; see the render page
for why.
Types
LoadFileType is defined in src/helpers/load_queue.hh.
| Value | Name | Manifest keyword | Extension | Loaded by |
|---|---|---|---|---|
| 0 | OBJECT | object | .meshbin | MeshManager::LoadMesh |
| 1 | TEXTURE | texture | .TIM | TextureManager::LoadTIM |
| 2 | MOD_FILE | mod_file | .MOD | ModSoundManager::LoadMODSound |
| 3 | ANIMATION | animation | .animbin | AnimationManager::LoadAnimation |
| 4 | COLBIN | colbin | .colbin | ColbinManager::LoadColbin |
| 5 | VAG | vag | .VAG | SoundManager::LoadVAGFile |
| 6 | TEXTURE_EXTRA | texture_extra | .TIM | TextureManager::LoadTIM |
| 255 | SCENE | not emitted, see below | .scenebin | SceneLoader::LoadScene, recursively |
"Loaded by" names the method that ultimately handles each type; as of v0.0.1 these are instance methods on MadnightEngine's manager members (e.g. g_madnightEngine.m_meshManager.LoadMesh), not static calls — see Updating the Engine → v0.0.1.
Source manifest
Plain text, one entry per line, whitespace separated. Blank lines are ignored and lines
beginning with # are comments. texture lines take exactly four placements: vramX vramY clutX clutY. Each is an integer, or auto to take that coordinate from the TIM file.
texture_extra lines take the same four followed by flags.
# scene manifest
vag SFX/FCNTNA.VAG
texture TEXTURES/LOGO.TIM 320 0 0 240
texture_extra TEXTURES/SMS.TIM 320 0 0 240 1
texture UI/CS_UI.TIM 320 129 0 241
object MODELS/SBSKT.MB
object MODELS/SCART.MB
python3 tools/scenebin_converter.py scene.txt scene.scenebin --asset-root assets/
That example converts to 6 entries and 138 bytes.
Nested scenes
SCENE is 255, which fits a uint8_t fine, and both SceneLoader::LoadScene and
FileLoader::LoadFiles handle it: a SCENE entry parses with the same layout as any
non-texture entry, and FileLoader recursively expands it into the same queue while
iterating, growing the total file count in place. Nested scene loading works today.
The converter does not currently emit SCENE entries. That is a limitation of the tool, not of the format or the engine.
Loading a scene
eastl::vector<LoadQueue> files = {{.name = "LEVEL01.SCENEBIN", .type = LoadFileType::SCENE}};
co_await g_madnightEngine.m_fileLoader.LoadFiles(eastl::move(files));
SceneLoader (src/scenes/scene_loader.hh) is what actually parses the .scenebin manifest into queue entries, but as of v0.0.1 it's a private member of FileLoader, not reachable from game code directly — pass a SCENE-type entry into FileLoader::LoadFiles (or HardLoadingScreen/SoftLoadingScreen, see below) instead, and it'll be expanded internally.
LoadFiles clears every asset pool first unless you pass false as its second argument.
MadnightEngine::HardLoadingScreen and SoftLoadingScreen wrap this pair with a loading
scene; hard clears the pools, soft does not.
Caveats
- Names are not null-terminated on the wire. The reader constructs a string from exactly
nameLenbytes. A tool writing a terminator will produce paths with a trailing NUL in them. nameLenis bounds-checked againstMAX_ARCHIVE_FILE_NAME_LEN(255) by the reader.- There is no version byte. Any future layout change is silently incompatible with existing files. COLBIN and MESHBIN both carry one.