/* * script.bfft - Tomb Raider 1 - 3 Engine Script Format Description * 2014, Thomas Buck * * Based on the TR Rosetta Stone (http://www.tnlc.com/eep/tr/TRosettaStone.html), * this document (http://xythobuz.de/tr_docs/TombPC_TR2.pdf), * and my own reverse-engineering efforts. * Developed for Binspector (https://github.com/binspector/binspector) */ // ########## General data structures ########## struct strings_t { unsigned 16 little stringOffsets[stringsInPackage]; unsigned 16 little stringDataSize; unsigned 8 little stringData[stringDataSize]; } struct script_t { unsigned 16 little sciprOffsets[stringsInPackage]; unsigned 16 little scriptDataSize; unsigned 16 little scriptData[scriptDataSize / 2]; } struct hack_t { signal isTR2 = 0; signal isPCVersion = 1; if ((peek() == 0x13) || (peek() == 0x15)) { // TR2 for PC and PSX has 6 "filler bytes" hex 13 00 14 00 15 00 // (for TR3 for PSX, the filler is hex 15 00 16 00 17 00 instead) // at the end of the script block. We need to skip these... if (peek() == 0x13) { signal isTR2 = 1; } else { signal isTR2 = 0; signal isPCVersion = 0; } skip hack[6]; } if (peek() == 0x01) { // TR2 for PSX has 64 bytes with unknown content (not zero!) here, // TR3 for PSX has 40 bytes. We try to identify and skip them (both start with 1)... // This is also currently used to set the platform specific string count if (isTR2 == 1) { signal isPCVersion = 0; skip hackPSX[64]; } else { skip hackPSX[40]; } } if (isTR2 == 1) { if (isPCVersion == 1) { notify "TR2 PC Version detected!"; signal numPCStrings = 41; } else { notify "TR2 PSX Version detected!"; signal numPCStrings = 80; } } else { if (isPCVersion == 1) { notify "TR3 PC Version detected!"; signal numPCStrings = 41; } else { notify "TR3 PSX Version detected!"; signal numPCStrings = 80; } } } // ########## Main file layout ########## struct main { unsigned 32 little version; unsigned 8 little description[256]; unsigned 16 little gameflowSize; if (gameflowSize != 128) die "Invalid gameflow size!"; unsigned 32 little firstOption; signed 32 little titleReplace; unsigned 32 little onDeathDemoMode; unsigned 32 little onDeathInGame; // Scaled * 100 in TR3 unsigned 32 little noInputTime; unsigned 32 little onDemoInterrupt; unsigned 32 little onDemoEnd; skip filler1[36]; unsigned 16 little numLevels; unsigned 16 little numPictures; unsigned 16 little numTitles; unsigned 16 little numFMVs; unsigned 16 little numCutscenes; unsigned 16 little numDemos; unsigned 16 little titleTrack; signed 16 little singleLevel; skip filler2[32]; unsigned 16 little flags; skip filler3[6]; unsigned 8 little cypherCode; unsigned 8 little language; // Zero in TR3, Part of filler or real number? unsigned 16 little secretTrack; skip filler4[4]; slot stringsInPackage = numLevels; strings_t levelDisplayNames; signal stringsInPackage = numPictures; strings_t pictures; signal stringsInPackage = numTitles; strings_t titleFilenames; signal stringsInPackage = numFMVs; strings_t fmvFilenames; signal stringsInPackage = numLevels; strings_t levelFilenames; signal stringsInPackage = numCutscenes; strings_t cutsceneFilenames; signal stringsInPackage = numLevels + 1; script_t script; slot numPCStrings = 41; // PC Version has 41 pcStrings slot isPCVersion = 1; slot isTR2 = 1; hack_t hack; unsigned 16 little numGameStrings; signal stringsInPackage = numGameStrings; strings_t gameStrings; signal stringsInPackage = numPCStrings; strings_t pcStrings; signal stringsInPackage = numLevels; strings_t puzzles[4]; strings_t pickups[2]; strings_t keys[4]; }