123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365 |
- /*!
- * \file src/Script.cpp
- * \brief Tomb Raider 2/3 Script Loader
- *
- * \author xythobuz
- */
-
- #include "global.h"
- #include "Script.h"
-
- const bool Script::opcodeHasOperand[OP_UNKNOWN] {
- true, true, true, true, true, true,
- false, true, true, false, true, false,
- true, false, false, false, true, true,
- true, true, true, false, false
- };
-
- Script::Script() : puzzles(4), pickups(2), keys(4) {
- version = 0;
- firstOption = 0;
- titleReplace = 0;
- onDeathDemoMode = 0;
- onDeathInGame = 0;
- noInputTime = 0;
- onDemoInterrupt = 0;
- onDemoEnd = 0;
- numLevels = 0;
- numPictures = 0;
- numTitles = 0;
- numFMVs = 0;
- numCutscenes = 0;
- numDemos = 0;
- titleTrack = 0;
- singleLevel = 0;
- flags = 0;
- cypherCode = 0;
- language = 0;
- secretTrack = 0;
- numPCStrings = 41;
- numGameStrings = 0;
- }
-
- int Script::load(std::string file) {
- BinaryFile f;
-
- if (f.open(file) != 0)
- return 1;
-
- version = f.readU32();
-
- char desc[256];
- for (int i = 0; i < 256; i++)
- desc[i] = f.read8();
- description = desc;
-
- uint16_t gameflowSize = f.readU16();
- orAssertEqual(gameflowSize, 128);
-
- firstOption = f.readU32();
- titleReplace = f.read32();
- onDeathDemoMode = f.readU32();
- onDeathInGame = f.readU32();
- noInputTime = f.readU32(); // Scaled *100 in TR3
- onDemoInterrupt = f.readU32();
- onDemoEnd = f.readU32();
-
- // Filler 1 (36 bytes)
- f.seek(f.tell() + 36);
-
- numLevels = f.readU16();
- numPictures = f.readU16();
- numTitles = f.readU16();
- numFMVs = f.readU16();
- numCutscenes = f.readU16();
- numDemos = f.readU16();
- titleTrack = f.readU16();
- singleLevel = f.read16();
-
- // Filler 2 (32 bytes)
- f.seek(f.tell() + 32);
-
- flags = f.readU16();
-
- // Filler 3 (6 bytes)
- f.seek(f.tell() + 6);
-
- cypherCode = f.readU8();
- language = f.readU8();
- secretTrack = f.readU16(); // Zero in TR3, Part of filler or real number?
-
- // Filler 4 (4 bytes)
- f.seek(f.tell() + 4);
-
- // Strings
- readStringPackage(f, levelNames, numLevels);
- readStringPackage(f, pictureFilenames, numPictures);
- readStringPackage(f, titleFilenames, numTitles);
- readStringPackage(f, fmvFilenames, numFMVs);
- readStringPackage(f, levelFilenames, numLevels);
- readStringPackage(f, cutsceneFilenames, numCutscenes);
-
- // Level Scripts
- readScriptPackage(f, script, numLevels + 1);
-
- numGameStrings = f.readU16();
-
- // More strings...
- readStringPackage(f, gameStrings, numGameStrings);
- readStringPackage(f, pcStrings, numPCStrings);
- readStringPackage(f, puzzles[0], numLevels);
- readStringPackage(f, puzzles[1], numLevels);
- readStringPackage(f, puzzles[2], numLevels);
- readStringPackage(f, puzzles[3], numLevels);
- readStringPackage(f, pickups[0], numLevels);
- readStringPackage(f, pickups[1], numLevels);
- readStringPackage(f, keys[0], numLevels);
- readStringPackage(f, keys[1], numLevels);
- readStringPackage(f, keys[2], numLevels);
- readStringPackage(f, keys[3], numLevels);
-
- return 0;
- }
-
- void Script::readStringPackage(BinaryFile& f, std::vector<std::string>& v, unsigned int n) {
- uint16_t* offset = new uint16_t[n];
- for (unsigned int i = 0; i < n; i++)
- offset[i] = f.readU16();
-
- uint16_t numBytes = f.readU16();
-
- char* list = new char[numBytes];
- for (uint16_t i = 0; i < numBytes; i++) {
- list[i] = f.read8();
- if (flags & S_UseSecurityTag) {
- list[i] ^= cypherCode;
- }
- }
-
- for (unsigned int i = 0; i < n; i++) {
- std::string tmp(list + offset[i]);
- v.push_back(tmp);
- }
-
- delete [] list;
- delete [] offset;
- }
-
- void Script::readScriptPackage(BinaryFile& f, std::vector<std::vector<uint16_t>>& v,
- unsigned int n) {
- uint16_t* offset = new uint16_t[n];
- for (unsigned int i = 0; i < n; i++) {
- offset[i] = f.readU16();
- orAssertEqual(offset[i] % 2, 0);
- }
-
- uint16_t numBytes = f.readU16();
- orAssertEqual(numBytes % 2, 0); // 16 bit opcodes and operands
-
- uint16_t* list = new uint16_t[(numBytes + 6) / 2];
- for (uint16_t i = 0; i < (numBytes / 2); i++) {
- list[i] = f.readU16();
- }
-
- // 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...
- uint16_t hack[3];
- hack[0] = f.readU16();
- hack[1] = f.readU16();
- hack[2] = f.readU16();
- if (((hack[0] == 19) && (hack[1] == 20) && (hack[2] == 21))
- || ((hack[0] == 21) && (hack[1] == 22) && (hack[2] == 23))) {
- list[numBytes / 2] = hack[0];
- list[(numBytes / 2) + 1] = hack[1];
- list[(numBytes / 2) + 2] = hack[2];
- } else {
- f.seek(f.tell() - 6);
- }
-
- // 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...
- // This is also currently used to set the platform specific string count
- hack[0] = f.readU16();
- hack[1] = f.readU16();
- hack[2] = f.readU16();
- if ((hack[0] == 1) && (hack[1] == 0) && (hack[2] == 864)) {
- f.seek(f.tell() + 58);
- numPCStrings = 80; // TR2 has 80 PSX Strings
- } else if ((hack[0] == 1) && (hack[1] == 0) && (hack[2] == 817)) {
- f.seek(f.tell() + 34);
- numPCStrings = 80; // TR3 also has 80 PSX Strings
- } else {
- f.seek(f.tell() - 6);
- numPCStrings = 41;
- }
-
- for (unsigned int i = 0; i < n; i++) {
- unsigned int end = offset[i] / 2;
-
- // We need to detect the OP_END opcode marking the end of a
- // script sequence (like the '\0' for the strings).
- // However, the numerical value of OP_END could also be used
- // as an operand for another opcode, so we have to check for this
- bool readingOperand = false;
- while (readingOperand || (list[end] != OP_END)) {
- if (readingOperand) {
- readingOperand = false;
- end++;
- } else {
- if (opcodeHasOperand[list[end]]) {
- readingOperand = true;
- }
- end++;
- }
- }
- end++;
-
- std::vector<uint16_t> tmp;
- for (unsigned int a = (offset[i] / 2); a < end; a++)
- tmp.push_back(list[a]);
-
- v.push_back(tmp);
- }
-
- delete [] list;
- delete [] offset;
- }
-
- unsigned int Script::levelCount() {
- return numLevels;
- }
-
- std::string Script::getLevelName(unsigned int i) {
- orAssertLessThan(i, numLevels);
- return levelNames.at(i);
- }
-
- std::string Script::getLevelFilename(unsigned int i) {
- orAssertLessThan(i, numLevels);
- return levelFilenames.at(i);
- }
-
- unsigned int Script::pictureCount() {
- return numPictures;
- }
-
- std::string Script::getPictureFilename(unsigned int i) {
- orAssertLessThan(i, numPictures);
- return pictureFilenames.at(i);
- }
-
- unsigned int Script::cutsceneCount() {
- return numCutscenes;
- }
-
- std::string Script::getCutsceneFilename(unsigned int i) {
- orAssertLessThan(i, numCutscenes);
- return cutsceneFilenames.at(i);
- }
-
- unsigned int Script::titleCount() {
- return numTitles;
- }
-
- std::string Script::getTitleFilename(unsigned int i) {
- orAssertLessThan(i, numTitles);
- return titleFilenames.at(i);
- }
-
- unsigned int Script::videoCount() {
- return numFMVs;
- }
-
- std::string Script::getVideoFilename(unsigned int i) {
- orAssertLessThan(i, numFMVs);
- return fmvFilenames.at(i);
- }
-
- unsigned int Script::gameStringCount() {
- return numGameStrings;
- }
-
- std::string Script::getGameString(unsigned int i) {
- orAssertLessThan(i, numGameStrings);
- return gameStrings.at(i);
- }
-
- unsigned int Script::pcStringCount() {
- return numPCStrings;
- }
-
- std::string Script::getPCString(unsigned int i) {
- orAssertLessThan(i, numPCStrings);
- return pcStrings.at(i);
- }
-
- std::string Script::getPuzzleString(unsigned int i, unsigned int j) {
- orAssertLessThan(i, 4);
- orAssertLessThan(j, numLevels);
- return puzzles.at(i).at(j);
- }
-
- std::string Script::getPickupString(unsigned int i, unsigned int j) {
- orAssertLessThan(i, 2);
- orAssertLessThan(j, numLevels);
- return pickups.at(i).at(j);
- }
-
- std::string Script::getKeyString(unsigned int i, unsigned int j) {
- orAssertLessThan(i, 4);
- orAssertLessThan(j, numLevels);
- return keys.at(i).at(j);
- }
-
- void Script::registerScriptHandler(ScriptOpCode op, std::function<int (bool, uint16_t)> func) {
- orAssertLessThan(op, OP_UNKNOWN);
- scriptHandlers[op] = func;
- }
-
- int Script::runScript(unsigned int level) {
- orAssertLessThan(level, (numLevels + 1));
- std::vector<uint16_t> s = script.at(level);
-
- for (unsigned int i = 0; i < s.size(); i++) {
- uint16_t opcode = s.at(i);
- if (opcode >= OP_UNKNOWN) {
- return 1;
- }
-
- uint16_t operand = 0;
- if (opcodeHasOperand[opcode]) {
- if ((i + 1) >= s.size())
- return 2; // Can't read operand!
-
- operand = s.at(++i);
- }
-
- if (scriptHandlers[opcode]) {
- int error = scriptHandlers[opcode](opcodeHasOperand[opcode], operand);
- if (error != 0)
- return error;
- } else {
- return 3;
- }
- }
-
- return 0;
- }
-
- std::string Script::getLanguage() {
- if (language == S_English) {
- return "English (UK)";
- } else if (language == S_French) {
- return "French";
- } else if (language == S_German) {
- return "German";
- } else if (language == S_American) {
- return "English (US)";
- } else if (language == S_Japanese) {
- return "Japanese";
- } else {
- return "Unknown";
- }
- }
|