1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-11-25 11:52:40 +01:00

Merge pull request #188 from danhedron/feature/cleanup-script

Script cleanup and remove old tool
This commit is contained in:
Daniel Evans 2016-08-12 23:36:26 +01:00 committed by GitHub
commit 2f0b24c6ea
11 changed files with 45 additions and 444 deletions

View File

@ -21,7 +21,7 @@ git:
script: script:
- mkdir build - mkdir build
- cd build - cd build
- cmake .. -DBUILD_TESTS=1 -DTESTS_NODATA=1 -DBUILD_VIEWER=1 -DBUILD_SCRIPT_TOOL=1 && make - cmake .. -DBUILD_TESTS=1 -DTESTS_NODATA=1 -DBUILD_VIEWER=1 && make
- tests/run_tests - tests/run_tests
notifications: notifications:
email: false email: false

View File

@ -16,7 +16,6 @@ set(RW_VERBOSE_DEBUG_MESSAGES 1 CACHE BOOL "Print verbose debugging messages")
# Optional components # Optional components
option(BUILD_TESTS "Build test suite") option(BUILD_TESTS "Build test suite")
option(BUILD_VIEWER "Build GUI data viewer") option(BUILD_VIEWER "Build GUI data viewer")
option(BUILD_SCRIPT_TOOL "Build script decompiler tool")
# Compile-time Options & Features # Compile-time Options & Features
option(ENABLE_SCRIPT_DEBUG "Enable verbose script execution") option(ENABLE_SCRIPT_DEBUG "Enable verbose script execution")
@ -115,9 +114,6 @@ add_subdirectory(rwlib)
add_subdirectory(rwengine) add_subdirectory(rwengine)
add_subdirectory(rwgame) add_subdirectory(rwgame)
IF(${BUILD_SCRIPT_TOOL})
add_subdirectory(scripttool)
ENDIF()
IF(${BUILD_VIEWER}) IF(${BUILD_VIEWER})
add_subdirectory(rwviewer) add_subdirectory(rwviewer)
ENDIF() ENDIF()

View File

@ -11,14 +11,14 @@ void SCMFile::loadFile(char *data, unsigned int size)
_target = static_cast<SCMTarget>(_data[jumpOpSize]); _target = static_cast<SCMTarget>(_data[jumpOpSize]);
globalSectionOffset = jumpOpSize+1u; globalSectionOffset = jumpOpSize+1u;
modelSectionOffset = read<uint32>(jumpParamSize) + jumpOpSize + 1u; modelSectionOffset = read<uint32_t>(jumpParamSize) + jumpOpSize + 1u;
missionSectionOffset = read<uint32>(modelSectionOffset-jumpOpSize-1u+jumpParamSize) + jumpOpSize + 1u; missionSectionOffset = read<uint32_t>(modelSectionOffset-jumpOpSize-1u+jumpParamSize) + jumpOpSize + 1u;
codeSectionOffset = read<uint32>(missionSectionOffset-jumpOpSize-1u+jumpParamSize); codeSectionOffset = read<uint32_t>(missionSectionOffset-jumpOpSize-1u+jumpParamSize);
unsigned int model_count = read<uint32>(modelSectionOffset); unsigned int model_count = read<uint32_t>(modelSectionOffset);
models.reserve(model_count); models.reserve(model_count);
int i = modelSectionOffset + sizeof(uint32); int i = modelSectionOffset + sizeof(uint32_t);
for(unsigned int m = 0; m < model_count; ++m) { for(unsigned int m = 0; m < model_count; ++m) {
char model_name[24]; char model_name[24];
for(size_t c = 0; c < 24; ++c) { for(size_t c = 0; c < 24; ++c) {
@ -28,16 +28,16 @@ void SCMFile::loadFile(char *data, unsigned int size)
} }
i = missionSectionOffset; i = missionSectionOffset;
mainSize = read<uint32>(i); mainSize = read<uint32_t>(i);
i += sizeof(uint32); i += sizeof(uint32_t);
missionLargestSize = read<uint32>(i); missionLargestSize = read<uint32_t>(i);
i += sizeof(uint32); i += sizeof(uint32_t);
unsigned int missionCount = read<uint32>(i); unsigned int missionCount = read<uint32_t>(i);
missionOffsets.reserve(missionCount); missionOffsets.reserve(missionCount);
i += sizeof(uint32); i += sizeof(uint32_t);
for(unsigned int m = 0; m < missionCount; ++m) { for(unsigned int m = 0; m < missionCount; ++m) {
missionOffsets.push_back(read<uint32>(i)); missionOffsets.push_back(read<uint32_t>(i));
i += sizeof(uint32); i += sizeof(uint32_t);
} }
} }

View File

@ -1,6 +1,5 @@
#pragma once #ifndef RWENGINE_SCMFILE_HPP
#ifndef _SCMFILE_HPP_ #define RWENGINE_SCMFILE_HPP
#define _SCMFILE_HPP_
#include <vector> #include <vector>
#include <string> #include <string>
#include <script/ScriptTypes.hpp> #include <script/ScriptTypes.hpp>
@ -11,10 +10,6 @@
class SCMFile { class SCMFile {
public: public:
typedef std::uint32_t uint32;
typedef std::uint16_t u16;
typedef std::uint8_t u8;
enum SCMTarget { enum SCMTarget {
NoTarget = 0, NoTarget = 0,
GTAIII = 0xC6, GTAIII = 0xC6,
@ -41,17 +36,17 @@ public:
return *(T*)(_data+offset); return *(T*)(_data+offset);
} }
uint32 getMainSize() const { return mainSize; } uint32_t getMainSize() const { return mainSize; }
uint32 getLargestMissionSize() const { return missionLargestSize; } uint32_t getLargestMissionSize() const { return missionLargestSize; }
const std::vector<std::string>& getModels() const { return models; } const std::vector<std::string>& getModels() const { return models; }
const std::vector<unsigned int>& getMissionOffsets() const { return missionOffsets; } const std::vector<unsigned int>& getMissionOffsets() const { return missionOffsets; }
std::uint32_t getGlobalSection() const { return globalSectionOffset; } uint32_t getGlobalSection() const { return globalSectionOffset; }
std::uint32_t getModelSection() const { return modelSectionOffset; } uint32_t getModelSection() const { return modelSectionOffset; }
std::uint32_t getMissionSection() const { return missionSectionOffset; } uint32_t getMissionSection() const { return missionSectionOffset; }
std::uint32_t getCodeSection() const { return codeSectionOffset; } uint32_t getCodeSection() const { return codeSectionOffset; }
unsigned int getGlobalsSize() const { return modelSectionOffset - globalSectionOffset; } unsigned int getGlobalsSize() const { return modelSectionOffset - globalSectionOffset; }
@ -65,13 +60,13 @@ private:
std::vector<unsigned int> missionOffsets; std::vector<unsigned int> missionOffsets;
uint32 mainSize; uint32_t mainSize;
uint32 missionLargestSize; uint32_t missionLargestSize;
uint32 globalSectionOffset; uint32_t globalSectionOffset;
uint32 modelSectionOffset; uint32_t modelSectionOffset;
uint32 missionSectionOffset; uint32_t missionSectionOffset;
uint32 codeSectionOffset; uint32_t codeSectionOffset;
}; };
#endif #endif

View File

@ -27,12 +27,6 @@ bool SCMOpcodes::findOpcode(ScriptFunctionID id, ScriptFunctionMeta** out)
return false; return false;
} }
void ScriptMachine::interuptNext()
{
interupt = true;
}
#include <iostream>
void ScriptMachine::executeThread(SCMThread &t, int msPassed) void ScriptMachine::executeThread(SCMThread &t, int msPassed)
{ {
if( t.wakeCounter > 0 ) { if( t.wakeCounter > 0 ) {
@ -40,8 +34,6 @@ void ScriptMachine::executeThread(SCMThread &t, int msPassed)
} }
if( t.wakeCounter > 0 ) return; if( t.wakeCounter > 0 ) return;
bool hasDebugging = !! bpHandler;
while( t.wakeCounter == 0 ) { while( t.wakeCounter == 0 ) {
auto pc = t.programCounter; auto pc = t.programCounter;
auto opcode = _file->read<SCMOpcode>(pc); auto opcode = _file->read<SCMOpcode>(pc);
@ -129,29 +121,22 @@ void ScriptMachine::executeThread(SCMThread &t, int msPassed)
ScriptArguments sca(&parameters, &t, this); ScriptArguments sca(&parameters, &t, this);
if( hasDebugging )
{
auto activeBreakpoint = findBreakpoint(t, pc);
if( activeBreakpoint || interupt )
{
interupt = false;
SCMBreakpoint bp;
bp.pc = t.programCounter;
bp.thread = &t;
bp.vm = this;
bp.function = &code;
bp.args = &sca;
bpHandler(bp);
}
}
#if RW_SCRIPT_DEBUG #if RW_SCRIPT_DEBUG
if (strcmp(t.name, "EIGHT") == 0) static auto sDebugThreadName = getenv("OPENRW_DEBUG_THREAD");
if (!sDebugThreadName || strncmp(t.name, sDebugThreadName, 8) == 0)
{ {
printf("% 8s %04x %04x % 25s", t.name, t.programCounter, opcode, code.signature.c_str()); printf("%8s %01x %06x %04x %s", t.name, t.conditionResult, t.programCounter, opcode, code.signature.c_str());
for (auto& a : sca.getParameters()) for (auto& a : sca.getParameters())
{ {
printf(" %08x", a.integerValue()); if (a.type == SCMType::TString) {
printf(" %1x:'%s'", a.type, a.string);
}
else if (a.type == SCMType::TFloat16) {
printf(" %1x:%f", a.type, a.realValue());
}
else {
printf(" %1x:%d", a.type, a.integerValue());
}
} }
printf("\n"); printf("\n");
} }
@ -205,7 +190,7 @@ void ScriptMachine::executeThread(SCMThread &t, int msPassed)
} }
ScriptMachine::ScriptMachine(GameState* _state, SCMFile *file, SCMOpcodes *ops) ScriptMachine::ScriptMachine(GameState* _state, SCMFile *file, SCMOpcodes *ops)
: _file(file), _ops(ops), state(_state), interupt(false) : _file(file), _ops(ops), state(_state)
{ {
// Copy globals // Copy globals
auto size = _file->getGlobalsSize(); auto size = _file->getGlobalsSize();
@ -259,49 +244,3 @@ void ScriptMachine::execute(float dt)
} }
} }
SCMBreakpointInfo* ScriptMachine::findBreakpoint(SCMThread& t, SCMThread::pc_t pc)
{
for(std::vector<SCMBreakpointInfo>::iterator bp = breakpoints.begin(); bp != breakpoints.end(); ++bp)
{
if( (bp->breakpointFlags & SCMBreakpointInfo::BP_ProgramCounter) == SCMBreakpointInfo::BP_ProgramCounter )
{
if( bp->programCounter != pc )
{
continue;
}
}
if( (bp->breakpointFlags & SCMBreakpointInfo::BP_ThreadName) == SCMBreakpointInfo::BP_ThreadName )
{
if( std::strcmp(bp->threadName, t.name) != 0 )
{
continue;
}
}
return &(*bp);
}
return nullptr;
}
void ScriptMachine::setBreakpointHandler(const ScriptMachine::BreakpointHandler& handler)
{
bpHandler = handler;
}
void ScriptMachine::addBreakpoint(const SCMBreakpointInfo& bpi)
{
breakpoints.push_back(bpi);
}
void ScriptMachine::removeBreakpoint(const SCMBreakpointInfo& bpi)
{
for (size_t i = 0; i < breakpoints.size(); ++i)
{
if (bpi == breakpoints[i])
{
breakpoints.erase(breakpoints.begin() + i);
return;
}
}
}

View File

@ -1,6 +1,5 @@
#pragma once #ifndef RWENGINE_SCRIPTMACHINE_HPP
#ifndef _SCRIPTMACHINE_HPP_ #define RWENGINE_SCRIPTMACHINE_HPP
#define _SCRIPTMACHINE_HPP_
#include <rw/defines.hpp> #include <rw/defines.hpp>
#include <script/ScriptTypes.hpp> #include <script/ScriptTypes.hpp>
#include <sstream> #include <sstream>
@ -72,44 +71,6 @@ struct UnknownType : SCMException
} }
}; };
struct UnimplementedOpcode : SCMException
{
SCMOpcode opcode;
SCMParams parameters;
UnimplementedOpcode(SCMOpcode opcode, SCMParams parameters)
: opcode(opcode), parameters(parameters) {}
std::string what() const {
std::stringstream ss;
ss << "Unimplemented opcode " <<
std::setfill('0') << std::hex << opcode <<
" called with parameters:\n";
int i = 0;
for(const SCMOpcodeParameter& p : parameters) {
ss << (i++) << " " << p.type << " ";
switch (p.type) {
case TInt8:
case TInt16:
case TInt32:
ss << p.integer;
break;
case TFloat16:
ss << p.real;
break;
case TGlobal:
ss << "Global: " << p.globalPtr;
break;
default:
ss << "Unprintable";
break;
}
ss << "\n";
}
return ss.str();
}
};
static SCMMicrocodeTable knownOps; static SCMMicrocodeTable knownOps;
struct SCMThread struct SCMThread
@ -137,59 +98,6 @@ struct SCMThread
std::array<pc_t, SCM_STACK_DEPTH> calls; std::array<pc_t, SCM_STACK_DEPTH> calls;
}; };
#include <cstring>
/**
* Stores information about where breakpoints should be triggered.
*
* breakpointFlags stores the state to be checked against.
*/
struct SCMBreakpointInfo
{
enum /* Breakpoint Flags */ {
BP_ProgramCounter = 1,
BP_ThreadName = 2
};
uint8_t breakpointFlags;
SCMThread::pc_t programCounter;
char threadName[17];
bool operator == (const SCMBreakpointInfo& rhs) const
{
if (breakpointFlags != rhs.breakpointFlags) return false;
if ((breakpointFlags & BP_ProgramCounter) != 0)
{
if (programCounter != rhs.programCounter) return false;
}
if ((breakpointFlags & BP_ThreadName) != 0)
{
if (strncmp(threadName, rhs.threadName, 17) != 0) return false;
}
return true;
}
static SCMBreakpointInfo breakThreadName(char threadName[17])
{
SCMBreakpointInfo i;
i.breakpointFlags = BP_ThreadName;
std::strncpy(i.threadName, threadName, 17);
return i;
}
};
/**
* Information about breakpoints that have been hit
*/
struct SCMBreakpoint
{
SCMThread::pc_t pc;
SCMThread* thread;
ScriptMachine* vm;
ScriptFunctionMeta* function;
ScriptArguments* args;
/** The breakpoint entry that triggered this breakpoint */
SCMBreakpointInfo* info;
};
/** /**
* Implements the actual fetch-execute mechanism for the game script virtual machine. * Implements the actual fetch-execute mechanism for the game script virtual machine.
* *
@ -206,9 +114,6 @@ struct SCMBreakpoint
* Within ScriptMachine, each thread's program counter is used to execute an instruction * Within ScriptMachine, each thread's program counter is used to execute an instruction
* by consuming the correct number of arguments, allowing the next instruction to be found, * by consuming the correct number of arguments, allowing the next instruction to be found,
* and then dispatching a call to the opcode's function. * and then dispatching a call to the opcode's function.
*
* Breakpoints can be set which will call the breakpoint hander, where it is possible
* to halt execution by refusing to return until the handler is ready to continue.
*/ */
class ScriptMachine class ScriptMachine
{ {
@ -228,32 +133,6 @@ public:
std::vector<SCMByte>& getGlobalData() { return globalData; } std::vector<SCMByte>& getGlobalData() { return globalData; }
GameState* getState() const { return state; } GameState* getState() const { return state; }
typedef std::function<void (const SCMBreakpoint&)> BreakpointHandler;
/**
* Set the breakpoint handler callback.
*
* When the VM reaches an instruction marked as a brekapoint
* by addBreakpoint, the handler will be called with information
* about the state of the VM and the active thread.
*/
void setBreakpointHandler(const BreakpointHandler& handler);
/**
* Adds a breakpoint
*/
void addBreakpoint(const SCMBreakpointInfo& bpi);
/**
* Removes a breakpoint.
*/
void removeBreakpoint(const SCMBreakpointInfo& bpi);
/**
* Interupt VM execution at the start of the next instruction
*/
void interuptNext();
/** /**
* @brief executes threads until they are all in waiting state. * @brief executes threads until they are all in waiting state.
@ -264,18 +143,12 @@ private:
SCMFile* _file; SCMFile* _file;
SCMOpcodes* _ops; SCMOpcodes* _ops;
GameState* state; GameState* state;
bool interupt;
std::list<SCMThread> _activeThreads; std::list<SCMThread> _activeThreads;
void executeThread(SCMThread& t, int msPassed); void executeThread(SCMThread& t, int msPassed);
SCMBreakpointInfo* findBreakpoint(SCMThread& t, SCMThread::pc_t pc);
std::vector<SCMByte> globalData; std::vector<SCMByte> globalData;
BreakpointHandler bpHandler;
std::vector<SCMBreakpointInfo> breakpoints;
}; };
#endif #endif

View File

@ -1,6 +1,5 @@
#pragma once #ifndef RWENGINE_SCRIPTTYPES_HPP
#ifndef _SCRIPTTYPES_HPP_ #define RWENGINE_SCRIPTTYPES_HPP
#define _SCRIPTTYPES_HPP_
#include <rw/defines.hpp> #include <rw/defines.hpp>
#include <cstdint> #include <cstdint>

View File

@ -255,26 +255,6 @@ void RWGame::startScript(const std::string& name)
script = new ScriptMachine(state, f, opcodes); script = new ScriptMachine(state, f, opcodes);
// Set up breakpoint handler
script->setBreakpointHandler(
[&](const SCMBreakpoint& bp)
{
log.info("Script", "Breakpoint hit!");
std::stringstream ss;
ss << " " << bp.function->description << ".";
ss << " Args:";
for(size_t a = 0; a < bp.args->getParameters().size(); a++)
{
auto& arg = bp.args->getParameters()[a];
ss << " " << arg.integerValue();
if( a != bp.args->getParameters().size()-1 )
{
ss << ",";
}
}
log.info("Script", ss.str());
});
state->script = script; state->script = script;
} }
else { else {

View File

@ -1,10 +0,0 @@
SET(SCRIPTTOOL scripttool)
add_executable(${SCRIPTTOOL} main.cpp)
target_link_libraries(${SCRIPTTOOL}
rwengine
${OPENGL_LIBRARIES}
${BULLET_LIBRARIES}
${SDL2_LIBRARY})
install(TARGETS ${SCRIPTTOOL} RUNTIME DESTINATION "${BIN_DIR}")

View File

@ -1,3 +0,0 @@
# Scripttool
Decompiles SCM files into their instructions using the same call tables as the game itself.

View File

@ -1,168 +0,0 @@
#include <iostream>
#include <sstream>
#include <fstream>
#include <iomanip>
#include <script/SCMFile.hpp>
#include <script/ScriptMachine.hpp>
#include <script/ScriptDisassembly.hpp>
#include <script/modules/VMModule.hpp>
#include <script/modules/GameModule.hpp>
#include <script/modules/ObjectModule.hpp>
#define FIELD_DESC_WIDTH 30
#define FIELD_PARAM_WIDTH 8
void printUsage();
void dumpModels(SCMFile* file)
{
std::cout << "model count: " << std::dec << file->getModels().size() << std::endl;
int i = 0;
for( auto& m : file->getModels() ) {
std::cout << std::dec << (i++) << ": " << m << std::endl;
}
}
void dumpCodeSizes(SCMFile* file)
{
std::cout << "main size: " << std::hex <<
file->getMainSize() << std::endl;
std::cout << "largest mission size: " << std::hex <<
file->getLargestMissionSize() << std::endl;
std::cout << "mission count: " << std::dec <<
file->getMissionOffsets().size() << std::endl;
int i = 0;
for(auto& m : file->getMissionOffsets()) {
std::cout << std::dec << (i++) << ": " << std::hex << m << std::endl;
}
}
void dumpOpcodes(SCMFile* scm, SCMOpcodes* codes, unsigned int offset, unsigned int size)
{
std::cout << "Offs Opcd " << std::setw(FIELD_DESC_WIDTH) << std::left
<< "Description" << "Parameters" << std::endl;
ScriptDisassembly disassembly(codes, scm);
try
{
disassembly.disassemble(offset);
}
catch( IllegalInstruction& ex )
{
std::cerr << "Error during disassembly: \n"
<< ex.what() << std::endl;
}
for( auto& inst : disassembly.getInstructions() )
{
ScriptFunctionMeta* code;
if(! codes->findOpcode(inst.second.opcode, &code) )
{
std::cerr << "Invalid opcode in disassembly (" << inst.second.opcode << ")" << std::endl;
}
std::cout << std::hex << std::setfill('0') << std::right <<
std::setw(4) << inst.first << ":" <<
std::setw(4) << inst.second.opcode << " " <<
std::setw(FIELD_DESC_WIDTH) << std::setfill(' ') <<
std::left << code->signature << std::right << "(";
for( SCMOpcodeParameter& param : inst.second.parameters )
{
switch( param.type )
{
case TInt8:
std::cout << " i8: " << param.integer;
break;
case TInt16:
std::cout << " i16: " << param.integer;
break;
case TInt32:
std::cout << " i32: " << param.integer;
break;
case TFloat16:
std::cout << " f16: " << param.real;
break;
case TString:
std::cout << " str: " << param.string;
break;
case TGlobal:
std::cout << " g: " << param.globalPtr;
break;
case TLocal:
std::cout << " l: " << param.globalPtr;
break;
}
}
std::cout << " )\n";
}
}
void disassemble(const std::string& scmname)
{
std::ifstream scmfile(scmname.c_str(), std::ios_base::binary);
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* byff = new SCMByte[size];
scmfile.read(byff, size);
SCMFile scm;
scm.loadFile(byff, size);
delete byff;
try {
std::cout << "section globals: " << std::hex <<
scm.getGlobalSection() << std::endl;
std::cout << "section models: " << std::hex <<
scm.getModelSection() << std::endl;
std::cout << "section sizes: " << std::hex <<
scm.getMissionSection() << std::endl;
std::cout << "section main: " << std::hex <<
scm.getCodeSection() << std::endl;
dumpModels(&scm);
dumpCodeSizes(&scm);
SCMOpcodes* opcodes = new SCMOpcodes;
opcodes->modules.push_back(new VMModule);
opcodes->modules.push_back(new GameModule);
opcodes->modules.push_back(new ObjectModule);
dumpOpcodes(&scm, opcodes, scm.getCodeSection(), size);
}
catch (SCMException& ex) {
std::cerr << ex.what() << std::endl;
}
}
int main(int argc, char** argv)
{
if( argc < 2 ) {
std::cerr << "Missing argument" << std::endl;
printUsage();
return 1;
}
disassemble(std::string(argv[1]));
return 0;
}
void printUsage() {
std::cout << "Usage:" << std::endl;
std::cout << " scripttool scmfile" << std::endl;
}