mirror of
https://github.com/rwengine/openrw.git
synced 2024-09-18 16:32:32 +02:00
Implement Script Disassembly
This commit is contained in:
parent
53d2e318a3
commit
a690c8c08c
45
rwengine/include/script/ScriptDisassembly.hpp
Normal file
45
rwengine/include/script/ScriptDisassembly.hpp
Normal file
@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
#ifndef _SCRIPTDISASSEMBLY_HPP_
|
||||
#define _SCRIPTDISASSEMBLY_HPP_
|
||||
#include <script/ScriptTypes.hpp>
|
||||
|
||||
class SCMFile;
|
||||
|
||||
/**
|
||||
* Extracts instruction level information from a SCM file
|
||||
*/
|
||||
class ScriptDisassembly
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Information about a single call to a single opcode and
|
||||
* it's parameters
|
||||
*/
|
||||
struct InstructionInfo
|
||||
{
|
||||
SCMOpcode opcode;
|
||||
SCMParams parameters;
|
||||
};
|
||||
|
||||
ScriptDisassembly(SCMOpcodes* codes, SCMFile* scm);
|
||||
|
||||
/**
|
||||
* Execute the disassembly routine.
|
||||
*
|
||||
* If there is an error during disassembly, an exeption will be
|
||||
* thrown
|
||||
*/
|
||||
void disassemble(SCMAddress startAddress);
|
||||
|
||||
std::map<SCMAddress, InstructionInfo>& getInstructions() { return instructions; }
|
||||
|
||||
private:
|
||||
|
||||
SCMOpcodes* codes;
|
||||
SCMFile* scm;
|
||||
|
||||
std::map<SCMAddress, InstructionInfo> instructions;
|
||||
};
|
||||
|
||||
#endif
|
@ -111,7 +111,7 @@ static SCMMicrocodeTable knownOps;
|
||||
|
||||
struct SCMThread
|
||||
{
|
||||
typedef unsigned int pc_t;
|
||||
typedef SCMAddress pc_t;
|
||||
|
||||
char name[17];
|
||||
pc_t baseAddress;
|
||||
|
@ -17,6 +17,7 @@ class GameWorld;
|
||||
|
||||
typedef uint16_t SCMOpcode;
|
||||
typedef char SCMByte;
|
||||
typedef unsigned int SCMAddress;
|
||||
|
||||
enum SCMType {
|
||||
EndOfArgList = 0x00,
|
||||
|
92
rwengine/src/script/ScriptDisassembly.cpp
Normal file
92
rwengine/src/script/ScriptDisassembly.cpp
Normal file
@ -0,0 +1,92 @@
|
||||
#include <script/ScriptDisassembly.hpp>
|
||||
#include <script/SCMFile.hpp>
|
||||
#include <script/ScriptMachine.hpp>
|
||||
|
||||
ScriptDisassembly::ScriptDisassembly(SCMOpcodes* _codes, SCMFile* _scm)
|
||||
: codes(_codes), scm(_scm)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ScriptDisassembly::disassemble(SCMAddress startAddress)
|
||||
{
|
||||
for( SCMAddress a = startAddress; a < scm->getMainSize(); )
|
||||
{
|
||||
auto opcode = scm->read<SCMOpcode>(a);
|
||||
auto opcorg = opcode;
|
||||
|
||||
bool isNegatedConditional = ((opcode & SCM_NEGATE_CONDITIONAL_MASK) == SCM_NEGATE_CONDITIONAL_MASK);
|
||||
opcode = opcode & ~SCM_NEGATE_CONDITIONAL_MASK;
|
||||
|
||||
ScriptFunctionMeta* foundcode;
|
||||
if( ! codes->findOpcode(opcode, &foundcode) )
|
||||
{
|
||||
throw IllegalInstruction(opcode, a, "Disassembler");
|
||||
}
|
||||
ScriptFunctionMeta& code = *foundcode;
|
||||
|
||||
SCMParams parameters;
|
||||
auto instructionAddress = a;
|
||||
a += sizeof(SCMOpcode);
|
||||
|
||||
bool hasExtraParameters = code.arguments < 0;
|
||||
auto requiredParams = std::abs(code.arguments);
|
||||
|
||||
for( int p = 0; p < requiredParams || hasExtraParameters; ++p ) {
|
||||
auto type_r = scm->read<SCMByte>(a);
|
||||
auto type = static_cast<SCMType>(type_r);
|
||||
|
||||
if( type_r > 42 ) {
|
||||
// for implicit strings, we need the byte we just read.
|
||||
type = TString;
|
||||
}
|
||||
else {
|
||||
a += sizeof(SCMByte);
|
||||
}
|
||||
|
||||
parameters.push_back(SCMOpcodeParameter { type, { 0 } });
|
||||
switch(type) {
|
||||
case EndOfArgList:
|
||||
hasExtraParameters = false;
|
||||
break;
|
||||
case TInt8:
|
||||
parameters.back().integer = scm->read<std::uint8_t>(a);
|
||||
a += sizeof(SCMByte);
|
||||
break;
|
||||
case TInt16:
|
||||
parameters.back().integer = scm->read<std::int16_t>(a);
|
||||
a += sizeof(SCMByte) * 2;
|
||||
break;
|
||||
case TGlobal: {
|
||||
auto v = scm->read<std::uint16_t>(a);
|
||||
parameters.back().globalPtr = (void*)v; //* SCM_VARIABLE_SIZE;
|
||||
a += sizeof(SCMByte) * 2;
|
||||
}
|
||||
break;
|
||||
case TLocal: {
|
||||
auto v = scm->read<std::uint16_t>(a);
|
||||
parameters.back().globalPtr = (void*)(v * SCM_VARIABLE_SIZE);
|
||||
a += sizeof(SCMByte) * 2;
|
||||
}
|
||||
break;
|
||||
case TInt32:
|
||||
parameters.back().integer = scm->read<std::uint32_t>(a);
|
||||
a += sizeof(SCMByte) * 4;
|
||||
break;
|
||||
case TString:
|
||||
std::copy(scm->data()+a, scm->data()+a+8,
|
||||
parameters.back().string);
|
||||
a += sizeof(SCMByte) * 8;
|
||||
break;
|
||||
case TFloat16:
|
||||
parameters.back().real = scm->read<std::int16_t>(a) / 16.f;
|
||||
a += sizeof(SCMByte) * 2;
|
||||
break;
|
||||
default:
|
||||
throw UnknownType(type, a, "Disassembler");
|
||||
break;
|
||||
};
|
||||
}
|
||||
instructions[instructionAddress] = InstructionInfo { opcode, parameters };
|
||||
}
|
||||
}
|
@ -3,6 +3,13 @@ add_executable(${SCRIPTTOOL} main.cpp)
|
||||
|
||||
include_directories(${CMAKE_SOURCE_DIR}/rwengine/include)
|
||||
|
||||
target_link_libraries(${SCRIPTTOOL} rwengine)
|
||||
target_link_libraries(${SCRIPTTOOL}
|
||||
rwengine
|
||||
sfml-graphics
|
||||
sfml-window
|
||||
sfml-system
|
||||
${OPENGL_LIBRARIES}
|
||||
GLEW
|
||||
${BULLET_LIBRARIES})
|
||||
|
||||
install(TARGETS ${SCRIPTTOOL} RUNTIME DESTINATION bin)
|
||||
|
@ -5,6 +5,10 @@
|
||||
|
||||
#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
|
||||
@ -37,132 +41,70 @@ void dumpCodeSizes(SCMFile* file)
|
||||
}
|
||||
}
|
||||
|
||||
void dumpOpcodes(SCMFile* scm, unsigned int offset, unsigned int size)
|
||||
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;
|
||||
|
||||
for( unsigned int i = offset; i < offset+size; ) {
|
||||
SCMOpcode op = scm->read<SCMOpcode>(i) & ~SCM_NEGATE_CONDITIONAL_MASK;
|
||||
ScriptDisassembly disassembly(codes, scm);
|
||||
|
||||
auto opit = knownOps.find( op );
|
||||
try
|
||||
{
|
||||
disassembly.disassemble(offset);
|
||||
}
|
||||
catch( IllegalInstruction& ex )
|
||||
{
|
||||
std::cerr << "Error during disassembly: \n"
|
||||
<< ex.what() << std::endl;
|
||||
}
|
||||
|
||||
// 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, "");
|
||||
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) << i << ":" <<
|
||||
std::setw(4) << op <<
|
||||
std::setw(FIELD_DESC_WIDTH) << std::setfill(' ') <<
|
||||
std::left << opit->second.name << 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 << "(";
|
||||
|
||||
i += sizeof(SCMOpcode);
|
||||
|
||||
bool hasMoreArgs = opit->second.parameters < 0;
|
||||
for( int p = 0; p < std::abs(opit->second.parameters) || hasMoreArgs; ++p ) {
|
||||
SCMByte datatype = scm->read<SCMByte>(i);
|
||||
|
||||
auto typeit = typeData.find(static_cast<SCMType>(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<unsigned int>(datatype) << ": ";
|
||||
std::cout << std::setfill(' ') << std::setw(FIELD_PARAM_WIDTH);
|
||||
|
||||
switch( datatype ) {
|
||||
case TInt32:
|
||||
std::cout << std::dec << scm->read<int32_t>(i);
|
||||
break;
|
||||
case TInt16:
|
||||
std::cout << std::dec << scm->read<int16_t>(i);
|
||||
break;
|
||||
case TGlobal:
|
||||
case TLocal:
|
||||
std::cout << std::hex << scm->read<int16_t>(i);
|
||||
break;
|
||||
case TInt8:
|
||||
std::cout << std::dec << static_cast<int>(scm->read<int8_t>(i));
|
||||
break;
|
||||
case TFloat16:
|
||||
std::cout << (float)scm->read<uint16_t>(i) / 16.f;
|
||||
break;
|
||||
case EndOfArgList:
|
||||
hasMoreArgs = false;
|
||||
break;
|
||||
case TString: {
|
||||
char strbuff[8];
|
||||
for(size_t c = 0; c < 8; ++c) {
|
||||
strbuff[c] = scm->read<char>(i++);
|
||||
}
|
||||
std::cout << strbuff << " ";
|
||||
}
|
||||
break;
|
||||
default:
|
||||
std::cout << "{unknown}";
|
||||
break;
|
||||
}
|
||||
|
||||
if( typeit != typeData.end() ) {
|
||||
i += typeit->second.size;
|
||||
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 << std::endl;
|
||||
std::cout << " )\n";
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
void disassemble(const std::string& scmname)
|
||||
{
|
||||
std::ifstream scmfile(scmname.c_str());
|
||||
|
||||
@ -194,8 +136,13 @@ void readSCM(const std::string& scmname)
|
||||
dumpModels(&scm);
|
||||
|
||||
dumpCodeSizes(&scm);
|
||||
|
||||
dumpOpcodes(&scm, scm.getCodeSection(), size);
|
||||
|
||||
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;
|
||||
@ -210,9 +157,7 @@ int main(int argc, char** argv)
|
||||
return 1;
|
||||
}
|
||||
|
||||
loadKnownOps("knownops.txt");
|
||||
|
||||
readSCM(std::string(argv[1]));
|
||||
disassemble(std::string(argv[1]));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user