Skip to main content

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​

FieldTypeBytesNotes
magicchar[8]8SCENEBIN, no terminator.
fileCountuint8_t10-255. The converter refuses more than 255 entries.

Then fileCount entries, each:

FieldTypeBytesNotes
typeuint8_t1A LoadFileType value, see below.
nameLenuint8_t1Length of name in bytes.
namechar[nameLen]nameLenArchive-relative path. Not null-terminated on the wire.
vramXuint16_t2TEXTURE and TEXTURE_EXTRA entries only. 0xFFFF means take it from the TIM.
vramYuint16_t2TEXTURE and TEXTURE_EXTRA entries only. 0xFFFF means take it from the TIM.
clutXuint16_t2TEXTURE and TEXTURE_EXTRA entries only. 0xFFFF means take it from the TIM.
clutYuint16_t2TEXTURE and TEXTURE_EXTRA entries only. 0xFFFF means take it from the TIM.
flagsuint32_t4TEXTURE_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.

ValueNameManifest keywordExtensionLoaded by
0OBJECTobject.meshbinMeshManager::LoadMesh
1TEXTUREtexture.TIMTextureManager::LoadTIM
2MOD_FILEmod_file.MODModSoundManager::LoadMODSound
3ANIMATIONanimation.animbinAnimationManager::LoadAnimation
4COLBINcolbin.colbinColbinManager::LoadColbin
5VAGvag.VAGSoundManager::LoadVAGFile
6TEXTURE_EXTRAtexture_extra.TIMTextureManager::LoadTIM
255SCENEnot emitted, see below.scenebinSceneLoader::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 nameLen bytes. A tool writing a terminator will produce paths with a trailing NUL in them.
  • nameLen is bounds-checked against MAX_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.