From 7c82c7cc0a96539389d5a6e91dc32ec266685cec Mon Sep 17 00:00:00 2001 From: Daniel Evans Date: Sat, 19 Jul 2014 08:57:36 +0100 Subject: [PATCH] Add test tool for grokking SCM --- CMakeLists.txt | 7 + scripttool/CMakeLists.txt | 6 + scripttool/knownops.txt | 212 +++++++++++++++++++++++++ scripttool/main.cpp | 320 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 545 insertions(+) create mode 100644 scripttool/CMakeLists.txt create mode 100644 scripttool/knownops.txt create mode 100644 scripttool/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 09373a0b..40c1e9fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,9 @@ cmake_minimum_required(VERSION 2.8) project(OpenRW) +# Setup options +SET(BUILD_SCRIPT_TOOL TRUE CACHE BOOL "Build script decompiler tool") + # TODO: # - Visual Studio support # - Find libraries correctly, e.g. FindBullet @@ -24,6 +27,10 @@ IF(BUILD_OLD_TOOLS) add_subdirectory(datadump) ENDIF() +IF(BUILD_SCRIPT_TOOL) + add_subdirectory(scripttool) +ENDIF() + add_subdirectory(rwgame) add_subdirectory(rwviewer) add_subdirectory(rwengine) diff --git a/scripttool/CMakeLists.txt b/scripttool/CMakeLists.txt new file mode 100644 index 00000000..9be2ef0f --- /dev/null +++ b/scripttool/CMakeLists.txt @@ -0,0 +1,6 @@ +SET(SCRIPTTOOL scripttool) +add_executable(${SCRIPTTOOL} main.cpp) + +include_directories(${CMAKE_SOURCE_DIR}/rwengine/include) + +install(TARGETS ${SCRIPTTOOL} RUNTIME DESTINATION bin) diff --git a/scripttool/knownops.txt b/scripttool/knownops.txt new file mode 100644 index 00000000..9d225c5c --- /dev/null +++ b/scripttool/knownops.txt @@ -0,0 +1,212 @@ +# L = local, G = Global, V = Value Type +# Opc desc, param num (- indicates optional), flags (1 = has string) +0x0000, nop, 0, 0 +0x0001, wait, 1, 0 +0x0002, Jump, 1, 0 + +0x0004, Set G to Vint, 2, 0 +0x0005, Set G to Vfloat, 2, 0 +0x0006, Set L to Vint, 2, 0 +0x0007, Set L to Vfloat, 2, 0 + +0x0015, Div G by Vfloat, 2, 0 + +0x0018, Is G greater Vint, 2, 0 + +0x0020, Is G greater Vfloat, 2, 0 + +0x0022, Is Vfloat greater G, 2, 0 + +0x001A, Is Vint gequal G, 2, 0 + +0x0028, Is G gequal Vint, 2, 0 + +0x0038, Is G equal Vint, 2, 0 + +0x0042, Is G equal Vfloat, 2, 0 + +0x004D, Jump if false, 1, 0 +0x004E, Terminate this script 0, 0 +0x004F, Start script with args, -1, 0 + +0x0053, Create player, 5, 0 + +0x0055, Set player coordinates, 4, 0 +0x0056, Is player in area 2D, 6, 0 +0x0057, Is player in area 3D, 8, 0 + +0x0086, Set G to Gfloat, 2, 0 + +0x00BA, Print big, 3, 0 +0x00BB, Print, 3, 0 +0x00BC, Print now, 3, 0 +0x00BD, Print soon, 3, 0 + +0x00BF, Get time of day, 2, 0 +0x00C0, Set time of day, 2, 0 + +0x00D7, Start script, 1, 0 +0x00D6, If, 1, 0 + +0x00DE, Is player in model, 2, 0 + +0x00E0, Is player in any car, 1, 0 +0x00E1, Is button pressed, 2, 0 + +0x00E4, Locate player on foot 2D, 6, 0 + +0x00F5, Locate player by any means 2D, 8, 0 +0x00F6, Locate player on foot 3D, 8, 0 + +0x00F9, Locate play. stoped onfoot 3d, 6, 0 + +0x0106, Locate car ped near ped 3D, 6, 0 + +0x010A, Is player money greater, 2, 0 + +0x0111, Set deatharrest state, 1, 0 + +0x0121, Is player in zone, 2, 0 + +0x014B, Create car generator, 13, 0 +0x014C, Switch car generator, 2, 0 +0x014D, Add Pager mesasge, 4, 0 + +0x0152, Set zone car info, 17, 0 + +0x015C, Set zone ped info, 11, 0 + +0x015F, Set fixed camera position, 6, 0 +0x0160, Point camera at point, 4, 0 + +0x0164, Remove blip, 1, 0 + +0x0169, Set fade colour, 3, 0 +0x016A, Fade screen, 2, 0 +0x016B, Get fade status, 0, 0 +0x016C, Add Hospital restart, 4, 0 +0x016D, Add Police restart, 4, 0 + +0x0171, Set player heading, 2, 0 + +0x0176, Get object heading, 2, 0 +0x0177, Set object heading, 2, 0 + +0x0180, Set on mission flag, 1, 0 +0x0181, nop, 2, 0 +0x0182, Unkown function, 2, 0 + +0x018B, Change Blip display, 2, 0 + +0x018E, Remove sound, 1, 0 + +0x019C, Is player in area onfoot 3D, 8, 0 + +0x01B4, Set player control, 2, 0 + +0x01C7, Don't remove object, 1, 0 + +0x01E8, Switch roads off, 6, 0 + +0x018D, Add continious sound, 5, 0 + +0x01F0, Set max wanted level, 1, 0 + +0x01F5, Get player char, 2, 0 + +0x0213, Create pickup, 6, 0 +0x0214, has picked been collected, 1, 0 + +0x0215, Remove pickup, 1, 0 + +0x0219, Create garage, 8, 0 + +0x022B, Switch ped paths off, 6, 0 + +0x0236, Set gang car, 2, 0 +0x0237, Set gang weapons, 3, 0 + +0x024a, Get phone, 3, 0 + +0x024E, Turn phone off, 1, 0 + +0x0256, Is player playing, 1, 0 + +0x0293, Get controller mode, 1, 0 + +0x029B, Create object, 5, 0 + +0x02A7, Add blip for contact point, 5, 0 +0x02A8, Add blip for coord, 5, 0 + +0x02B4, Is player in angle area onfoot 3D, 9, 0 + +0x02CD, Unkown function, 2, 0 + +0x02DE, Is player in taxi, 1, 0 + +0x02EB, Restore camera jumpcut, 0, 0 +0x02EC, Put hidden package, 3, 0 +0x02ED, Set total hidden packages, 1, 0 + +0x02FB, Create crusher crane, 10, 0 + +0x030D, Set max progress, 1, 0 + +0x031A, Remove all script fires, 0, 0 + +0x0324, Set pedgroup info, 3, 0 + +0x0339, Is area occupied, 11, 0 + +0x034D, Rotate object, 4, 0 +0x034E, Slide object, 8, 0 + +0x035D, Make object targetable, 1, 0 + +0x0363, Set vis. of. closest of type, 6, 0 + +0x0368, Create ev? crane, 10, 0 + +0x0395, Clear area, 5, 0 + +0x0399, Switch ped roads off angle, 7, 0 + +0x03A4, Thread name, 1, 0 + +0x03AF, Switch map streaming, 1, 0 + +0x03B6, Swap nearest building model, 6, 0 + +0x03BB, Set garage door rotate, 1, 0 + +0x03C6, Is collision in memory, 1, 0 + +0x03C8, Set camera infront of player, 0, 0 + +0x03CA, Does object exist, 1, 0 + +0x03CF, Load mission audio, 1, 0 +0x03D0, Has mission audio loaded, 0, 0 +0x03D1, Play mission audio, 0, 0 +0x03D2, Has mission audio finished, 0, 0 + +0x03D8, Activate save menu, 0, 0 +0x03D9, Did game save successfully, 0, 0 +0x03DA, Grage camera follow player, 1, 0 + +0x03E5, Print help, 1, 0 +0x03E6, Clear help, 0, 0 + +0x03EE, Can player start mission, 1, 0 +0x03EF, Make player safe for cutscene, 1, 0 + +0x03F1, Set threat for ped type, 2, 0 + +0x0405, Enable phone, 1, 0 + +0x0417, Start mission, 1, 0 + +0x042C, Set total missions, 1, 0 + +0x0426, Unkown function, 6, 0 diff --git a/scripttool/main.cpp b/scripttool/main.cpp new file mode 100644 index 00000000..d5e76841 --- /dev/null +++ b/scripttool/main.cpp @@ -0,0 +1,320 @@ +#include +#include +#include +#include +#include +#include + +#define FIELD_DESC_WIDTH 30 +#define FIELD_PARAM_WIDTH 8 +#define CONDITIONAL_MASK 0xF000 + +void printUsage(); + +typedef uint16_t SCMOpcode; +typedef char SCMByte; + +template T readFromSCM(SCMByte* scm, unsigned int offset) +{ + return *((T*)(scm+offset)); +} + +SCMOpcode readOpcode(SCMByte* scm, unsigned int offset) +{ + return readFromSCM(scm, offset); +} + +struct SCMException +{ + virtual ~SCMException() { } + virtual std::string what() const = 0; +}; + +struct IllegalInstruction : SCMException +{ + SCMOpcode opcode; + unsigned int offset; + + IllegalInstruction(SCMOpcode opcode, unsigned int offset) + : opcode(opcode), offset(offset) { } + + std::string what() const { + std::stringstream ss; + ss << "Illegal Instruction " << + std::setfill('0') << std::setw(4) << std::hex << opcode << + " encountered at offset " << + std::setfill('0') << std::setw(4) << std::hex << offset; + return ss.str(); + } +}; + +struct UnknownType : SCMException +{ + SCMByte type; + unsigned int offset; + + UnknownType(SCMByte type, unsigned int offset) + : type(type), offset(offset) {} + + std::string what() const { + std::stringstream ss; + ss << "Unkown data type " << + std::setfill('0') << std::hex << static_cast(type) << + " encountered at offset " << + std::setfill('0') << std::hex << offset; + return ss.str(); + } +}; + +struct SCMMicrocode { + std::string name; + int parameters; +}; + +typedef std::map SCMMicrocodeTable; + +SCMMicrocodeTable knownOps; + +enum SCMType { + EndOfArgList = 0x00, + TInt32 = 0x01, + TGlobal = 0x02, + TLocal = 0x03, + TInt8 = 0x04, + TInt16 = 0x05, + TFloat16 = 0x06, + TString = 0x09, +}; + +struct SCMTypeInfo { + uint8_t size; +}; + +typedef std::map SCMTypeInfoTable; + +SCMTypeInfoTable typeData = { + {TInt8, {1}}, + {TInt16, {2}}, + {TInt32, {4}}, + {TInt8, {1}}, + {TGlobal, {2}}, + {TLocal, {2}}, + {TFloat16,{2}}, + {EndOfArgList, {0}}, +}; + +void dumpSection(SCMByte* scm, unsigned int offset, unsigned int size) +{ + for( unsigned int i = offset; i < offset+size; ) { + SCMOpcode op = readOpcode(scm, i) & ~CONDITIONAL_MASK; + + auto opit = knownOps.find( op ); + + // If we don't know the size of the operator's parameters we can't jump over it. + if( opit == knownOps.end() ) { + throw IllegalInstruction(op, i); + } + + std::cout << std::hex << std::setfill('0') << std::right << + std::setw(4) << i << ":" << + std::setw(4) << op << + " " << std::setw(FIELD_DESC_WIDTH) << std::setfill(' ') << + std::left << opit->second.name << std::right; + + i += sizeof(SCMOpcode); + + bool hasMoreArgs = opit->second.parameters < 0; + for( int p = 0; p < std::abs(opit->second.parameters) || hasMoreArgs; ++p ) { + SCMByte datatype = readFromSCM(scm, i); + + auto typeit = typeData.find(static_cast(datatype)); + if( typeit == typeData.end()) { + if( datatype < 0x06 ) { + throw UnknownType(datatype, i); + } + else { + datatype = TString; + } + } + else { + i += sizeof(SCMByte); + } + + std::cout << " " << std::setfill('0') << std::setw(2) << + static_cast(datatype) << ": "; + std::cout << std::setfill(' ') << std::setw(FIELD_PARAM_WIDTH); + + switch( datatype ) { + case TInt32: + std::cout << std::dec << readFromSCM(scm, i); + break; + case TInt16: + std::cout << std::dec << readFromSCM(scm, i); + break; + case TGlobal: + case TLocal: + std::cout << std::hex << readFromSCM(scm, i); + break; + case TInt8: + std::cout << std::dec << static_cast(readFromSCM(scm, i)); + break; + case TFloat16: + std::cout << (float)readFromSCM(scm, i) / 16.f; + break; + case EndOfArgList: + hasMoreArgs = false; + break; + case TString: { + char strbuff[8]; + for(size_t c = 0; c < 8; ++c) { + strbuff[c] = readFromSCM(scm, i++); + } + std::cout << strbuff << " "; + } + break; + default: + std::cout << "{unknown}"; + break; + } + + if( typeit != typeData.end() ) { + i += typeit->second.size; + } + } + + std::cout << std::endl; + } +} + +void loadKnownOps(const std::string& dbfile) +{ + std::ifstream dbstream(dbfile.c_str()); + + if( !dbstream.is_open() ) { + std::cerr << "Failed to open " << dbfile << std::endl; + return; + } + + while( ! dbstream.eof() ) { + std::string line; + std::getline(dbstream, line); + auto fnws = line.find_first_not_of(" "); + if( fnws == line.npos || line.at(fnws) == '#' ) continue; + + std::stringstream ss(line); + + std::string sec; + + std::getline(ss, sec, ','); + + SCMMicrocode m; + + uint16_t opcode = std::stoi(sec, 0, 16); + + std::getline(ss, m.name, ','); + + std::getline(ss, sec, ','); + m.parameters = std::stoi(sec); + + std::getline(ss, sec, ','); + uint16_t flags = std::stoi(sec); + + knownOps.insert({opcode, m}); + } + + std::cout << knownOps.size() << " known opcodes " << std::endl; +} + +void readSCM(const std::string& scmname) +{ + std::ifstream scmfile(scmname.c_str()); + + if( !scmfile.is_open() ) { + std::cerr << "Failed to open " << scmname << std::endl; + return; + } + + scmfile.seekg(0, std::ios_base::end); + int size = scmfile.tellg(); + scmfile.seekg(0); + + SCMByte* scm = new SCMByte[size]; + scmfile.read(scm, size); + + int section_globals = 10; + int section_models = 0; + int section_sizes = 0; + int section_main = 0; + + try { + int i = 0; + + SCMOpcode op; + SCMByte param; + + op = readOpcode(scm, i); + i += sizeof(SCMOpcode); + + param = readFromSCM(scm, i); + i += sizeof(SCMByte); + + section_models = readFromSCM(scm, i); + + i = section_models; + + op = readOpcode(scm, i); + i += sizeof(SCMOpcode); + + param = readFromSCM(scm, i); + i += sizeof(SCMByte); + + section_sizes = readFromSCM(scm, i); + + i = section_sizes; + + op = readOpcode(scm, i); + i += sizeof(SCMOpcode); + + param = readFromSCM(scm, i); + i += sizeof(SCMByte); + + section_main = readFromSCM(scm, i); + + i = section_main; + + std::cout << "section_globals = " << section_globals << std::endl; + std::cout << "section_models = " << section_models << std::endl; + std::cout << "section_sizes = " << section_sizes << std::endl; + std::cout << "section_main = " << section_main << std::endl; + + std::cout << "Offs Opcd " << std::setw(FIELD_DESC_WIDTH) << std::left + << "Description" << " Parameters" << std::endl; + + dumpSection(scm, i, size); + } + catch (SCMException& ex) { + std::cerr << ex.what() << std::endl; + } + + delete scm; +} + +int main(int argc, char** argv) +{ + if( argc < 2 ) { + std::cerr << "Missing argument" << std::endl; + printUsage(); + return 1; + } + + loadKnownOps("knownops.txt"); + + readSCM(std::string(argv[1])); + + return 0; +} + +void printUsage() { + std::cout << "Usage:" << std::endl; + std::cout << " scripttool scmfile" << std::endl; +}