[Scummvm-git-logs] scummvm-tools master -> c17dc85e103380eb74633070a6a10529b7b5ca78

Die4Ever noreply at scummvm.org
Sat Oct 22 21:09:28 UTC 2022


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm-tools' repo located at https://github.com/scummvm/scummvm-tools .

Summary:
c17dc85e10 DECOMPILER: compiler and Groovie 2 support


Commit: c17dc85e103380eb74633070a6a10529b7b5ca78
    https://github.com/scummvm/scummvm-tools/commit/c17dc85e103380eb74633070a6a10529b7b5ca78
Author: Die4Ever (30947252+Die4Ever at users.noreply.github.com)
Date: 2022-10-22T17:09:25-04:00

Commit Message:
DECOMPILER: compiler and Groovie 2 support

New Reassembler base class for compiling assembly back into a binary file. Use like:
./decompile -e groovie -v v2 SCRIPT.GRV.gasm -b SUSCRIPT.GRV
Also support for decompiling/compiling Groovie 2 script files.

Changed paths:
  A decompiler/reassembler.cpp
  A decompiler/reassembler.h
    .gitignore
    Makefile.common
    decompiler/decompiler.cpp
    decompiler/groovie/disassembler.cpp
    decompiler/groovie/disassembler.h
    decompiler/groovie/engine.cpp
    decompiler/groovie/engine.h
    decompiler/test/module.mk


diff --git a/.gitignore b/.gitignore
index 69e8dfab..73fb9c4e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -74,6 +74,7 @@ ipch/
 *.vcxproj*
 *.bat
 *.tss
+.vscode
 
 #Ignore Mac DS_Store files
 .DS_Store
diff --git a/Makefile.common b/Makefile.common
index d0dfa532..27112436 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -395,6 +395,7 @@ decompile_OBJS := \
 	decompiler/control_flow.o \
 	decompiler/decompiler.o \
 	decompiler/disassembler.o \
+	decompiler/reassembler.o \
 	decompiler/graph.o \
 	decompiler/instruction.o \
 	decompiler/simple_disassembler.o \
diff --git a/decompiler/decompiler.cpp b/decompiler/decompiler.cpp
index 4feb7abf..e4d17dd0 100644
--- a/decompiler/decompiler.cpp
+++ b/decompiler/decompiler.cpp
@@ -22,6 +22,7 @@
 #include "objectFactory.h"
 
 #include "disassembler.h"
+#include "reassembler.h"
 #include "engine.h"
 #include "instruction.h"
 
@@ -63,7 +64,9 @@ int main(int argc, char** argv) {
 			("only-graph,G", "Stops after control flow graph has been generated. Implies -g.")
 			("show-unreachable,u", "Show the address and contents of unreachable groups in the script.")
 			("variant,v", po::value<std::string>()->default_value(""), "Tell the engine that the script is from a specific variant. To see a list of variants supported by a specific engine, use the -h option and the -e option together.")
-			("no-stack-effect,s", "Leave out the stack effect when printing raw instructions.");
+			("no-stack-effect,s", "Leave out the stack effect when printing raw instructions.")
+			("dump-binary,b", po::value<std::string>(), "Compile the assembly to a binary file.");
+			// TODO: option for only outputting labels for lines that receive jumps
 
 		po::options_description args("");
 		args.add(visible).add_options()
@@ -134,6 +137,17 @@ int main(int argc, char** argv) {
 		Disassembler *disassembler = engine->getDisassembler(insts);
 		disassembler->open(inputFile.c_str());
 
+		if (vm.count("dump-binary")) {
+			std::ofstream of;
+
+			of.open(vm["dump-binary"].as<std::string>().c_str(), of.binary);
+			dynamic_cast<Reassembler*>(disassembler)->dumpBinary(of);
+
+			delete disassembler;
+			delete engine;
+			return 0;
+		}
+
 		disassembler->disassemble();
 		if (vm.count("dump-disassembly")) {
 			std::streambuf *buf;
diff --git a/decompiler/groovie/disassembler.cpp b/decompiler/groovie/disassembler.cpp
index 74846d8c..5d86f978 100644
--- a/decompiler/groovie/disassembler.cpp
+++ b/decompiler/groovie/disassembler.cpp
@@ -23,6 +23,7 @@
 #include "opcodes.h"
 
 #include "common/util.h"
+#include "common/endian.h"
 
 namespace Groovie {
 
@@ -86,8 +87,8 @@ public:
 
 // GroovieDisassembler
 
-GroovieDisassembler::GroovieDisassembler(InstVec &insts, const GroovieOpcode *opcodes) :
-	Disassembler(insts), _opcodes(opcodes) {
+GroovieDisassembler::GroovieDisassembler(InstVec &insts, const std::vector<GroovieOpcode> &opcodes) :
+	Reassembler(insts), _opcodes(opcodes), _firstBit(false) {
 }
 
 InstPtr GroovieDisassembler::readInstruction() {
@@ -110,7 +111,7 @@ InstPtr GroovieDisassembler::createInstruction(byte opcode) {
 	opcode &= 0x7F;
 
 	// Verify it's a valid opcode
-	if (opcode > 0x59) // TODO: make it depend on the real number of opcodes
+	if (opcode >= _opcodes.size())
 		throw UnknownOpcodeException(_address, opcode);
 
 	// Create the new instruction
@@ -167,12 +168,12 @@ InstPtr GroovieDisassembler::createInstruction(byte opcode) {
 
 void GroovieDisassembler::readParams(InstPtr inst, const char *typeString) {
 	while (*typeString) {
-		inst->_params.push_back(readParameter(inst, *typeString));
+		inst->_params.push_back(readParameter(*typeString));
 		typeString++;
 	}
 }
 
-ValuePtr GroovieDisassembler::readParameter(InstPtr inst, char type) {
+ValuePtr GroovieDisassembler::readParameter(char type) {
 	ValuePtr retval = NULL;
 	switch (type) {
 	case '1': // 8 bits
@@ -185,9 +186,9 @@ ValuePtr GroovieDisassembler::readParameter(InstPtr inst, char type) {
 		break;
 	case '3': // 8 or 16 bits
 		if (_firstBit)
-			retval = readParameter(inst, '1');
+			retval = readParameter('1');
 		else
-			retval = readParameter(inst, '2');
+			retval = readParameter('2');
 		break;
 	case '4': // 32 bits
 		retval = new IntValue(_f.readUint32LE(), false);
@@ -252,7 +253,7 @@ ValuePtr GroovieDisassembler::readParameterIndexed(bool allow7C, bool limitVal,
 
 		ValueList idxs;
 		idxs.push_back(new IntValue(data - 0x61, false));
-		result = new ArrayValue("M", idxs);
+		result = new ArrayValue("m", idxs);// TODO: can probably make writeParameterIndex smart enough to not need the lowercase m here
 	} else {
 		// Immediate value
 		result = new IntValue(data - 0x30, true);
@@ -301,14 +302,16 @@ ValuePtr GroovieDisassembler::readParameterVideoName() {
 				// Indexing a bidimensional array
 				idxs.push_back(readParameterIndexed(false, false, false));
 				idxs.push_back(readParameterIndexed(false, false, false));
+				values.push_back(new ArrayValue("M", idxs));
 			} else if (data == 0x23) {
 				// Indexing an unidimensional array
 				data = _f.readByte();
 				_address++;
 				idxs.push_back(new IntValue(data - 0x61, false));
+				values.push_back(new ArrayValue("m", idxs));// TODO: can probably make writeParameterVideoName smart enough to not need the lowercase m here
 			}
 			// TODO BinaryOpValue: M[...] + 0x30
-			values.push_back(new ArrayValue("M", idxs));
+			//values.push_back(new ArrayValue("M", idxs));
 		} else {
 			// To lowercase?
 			if (data >= 0x41 && data <= 0x5A) {
@@ -354,4 +357,179 @@ void GroovieDisassembler::doDisassemble() throw (UnknownOpcodeException) {
 		_insts.push_back(createInstruction(0));
 }
 
+GroovieOpcode GroovieDisassembler::getInstruction(const std::string &name) throw(std::exception) {
+	for(auto &i : _opcodes) {
+		if(name == i.name) {
+			return i;
+		}
+	}
+	throw std::runtime_error("Unable to find instruction: " + name);
+}
+
+void GroovieDisassembler::doAssembly(const std::string &label, std::string &instruction, const std::vector<std::string> &args, const std::string &comment) throw(std::exception) {
+	// find the matching instruction name
+	GroovieOpcode inst = getInstruction(instruction);
+	
+	// build list of labels, parse arguments, and write bytes to _binary
+	std::vector<byte> bytes;
+	size_t jumpAddrStart;// where the address is stored so it can be overwritten when we have the full list of labels
+	std::string jumpToLabel;
+
+	_firstBit = false;
+	jumpAddrStart = writeParams(bytes, inst.params, args, jumpToLabel);
+	// use writeParams to guess _firstBit, then we write the opcode at the end
+	bytes.insert(bytes.begin(), inst.opcode | (_firstBit<<7));
+	jumpAddrStart++; // increment since we pushed a byte to the front
+
+	addInstruction(bytes, inst.type, jumpAddrStart, 2, label, jumpToLabel);
+}
+
+size_t GroovieDisassembler::writeParams(std::vector<byte> &bytes, const char *typeString, const std::vector<std::string> &args, std::string &jumpToLabel) {
+	size_t jumpAddrStart = 0;
+	for(int i=0; typeString[i]; i++) {
+		writeParameter(typeString[i], bytes, args[i], jumpAddrStart, jumpToLabel);
+	}
+	return jumpAddrStart;
+}
+
+void GroovieDisassembler::writeParameter(char type, std::vector<byte> &bytes, const std::string &arg, size_t &jumpAddrStart, std::string &jumpToLabel) {
+	int i;
+	uint16 i16;
+	uint32 u32;
+
+	switch (type) {
+	case '1': // 8 bits
+		i = std::stoi(arg);
+		bytes.push_back(i);
+		break;
+	case '2': // 16 bits
+		i16 = std::stoi(arg);
+		i16 = TO_LE_16(i16);
+		bytes.push_back(i16);
+		bytes.push_back(i16 >> 8);
+		break;
+	case '3': // 8 or 16 bits
+		i = std::stoi(arg);
+		if(i <= 0xFF || _firstBit) {
+			_firstBit = true;
+			bytes.push_back(i);
+		} else {
+			_firstBit = false;
+			i16 = TO_LE_16(i);
+			bytes.push_back(i16);
+			bytes.push_back(i16 >> 8);
+		}
+		break;
+	case '4': // 32 bits
+		u32 = std::stoul(arg);
+		u32 = TO_LE_32(u32);
+		bytes.push_back(u32);
+		bytes.push_back(u32 >> 8);
+		bytes.push_back(u32 >> 16);
+		bytes.push_back(u32 >> 24);
+		break;
+	case '@': // Address
+		jumpAddrStart = bytes.size();
+		// if arg is in 0xF3DE format, convert to 0000f3de
+		if(arg.find("0x") == 0)
+			jumpToLabel = (boost::format("%08x") % std::stoul(arg, 0, 16)).str();
+		else
+			jumpToLabel = arg;
+		bytes.push_back(0);
+		bytes.push_back(0);
+		break;
+	case 'A': // Array
+		// substring to remove the [ and ]
+		writeParameterArray(bytes, arg.substr(1, arg.length()-2));
+		break;
+	case 'S': // Script name
+		// ignore the quotes around it
+		for(size_t j=1; j<arg.length()-1; j++)
+			bytes.push_back(arg[j]);
+		bytes.push_back(0);
+		break;
+	case 'V': // Video name
+		// substring to remove the [ and ]
+		writeParameterVideoName(bytes, arg.substr(1, arg.length()-2));
+		break;
+	case 'C': // Indexed value
+		writeParameterIndexed(false, true, true, bytes, arg);
+		break;
+	default:
+		std::cout << "writeParameter UNKNOWN param type: " << type << "\n";
+		throw std::runtime_error(std::string() + "writeParameter UNKNOWN param type: " + type);
+	}
+}
+
+void GroovieDisassembler::splitArrayString(const std::string &arg, std::string &first, std::string &second) {
+	size_t e = getEndArgument(arg, 2);
+	first = arg.substr(2, e - 2);
+	second = arg.substr(e + 2);
+	second.pop_back();
+}
+
+void GroovieDisassembler::writeParameterVideoName(std::vector<byte> &bytes, const std::string &arg) {
+	size_t s = 0, e = 0;
+	while(e < arg.length()) {
+		e = getEndArgument(arg, s);
+		std::string a = arg.substr(s, e - s);
+		switch(a[0]) {
+		case '"':
+			for(size_t i=1; i < a.length()-1; i++) {
+				bytes.push_back(a[i]);
+			}
+			break;
+		case 'm':
+			a = a.substr(2);
+			a.pop_back();
+			bytes.push_back(0x23);
+			bytes.push_back(std::stoul(a) + 0x61);
+			break;
+		case 'M':
+			bytes.push_back(0x7C);
+			std::string first;
+			std::string second;
+			splitArrayString(a, first, second);
+			writeParameterIndexed(false, false, false, bytes, first);
+			writeParameterIndexed(false, false, false, bytes, second);
+			break;
+		}
+		s = e + 2;
+	}
+
+	bytes.push_back(0);
+}
+
+void GroovieDisassembler::writeParameterIndexed(bool allow7C, bool limitVal, bool limitVar, std::vector<byte> &bytes, const std::string &arg) {
+	if (allow7C && arg[0] == 'M') {
+		bytes.push_back(0x7C);
+		std::string first;
+		std::string second;
+		splitArrayString(arg, first, second);
+		writeParameterIndexed(false, false, false, bytes, first);
+		writeParameterIndexed(false, true, true, bytes, second);
+	} else if (arg[0] == 'm') {
+		bytes.push_back(0x23);
+		std::string a = arg.substr(2);
+		a.pop_back();
+		uint8 data = std::stoul(a);
+		bytes.push_back(data + 0x61);
+	} else {
+		// Immediate value
+		uint8 data = std::stoul(arg);
+		bytes.push_back(data + 0x30);
+	}
+}
+
+void GroovieDisassembler::writeParameterArray(std::vector<byte> &bytes, const std::string &arg) {
+	size_t s = 0, e = 0;
+	while(e < arg.length()) {
+		e = getEndArgument(arg, s);
+		std::string a = arg.substr(s, e - s);
+		writeParameterIndexed(true, true, true, bytes, a);
+		s = e + 2;
+	}
+	// terminate the array by using the first bit
+	bytes[bytes.size() - 1] |= 0x80;
+}
 } // End of namespace Groovie
diff --git a/decompiler/groovie/disassembler.h b/decompiler/groovie/disassembler.h
index d97d2526..2c71ab7c 100644
--- a/decompiler/groovie/disassembler.h
+++ b/decompiler/groovie/disassembler.h
@@ -22,7 +22,7 @@
 #ifndef GROOVIE_DISASSEMBLER_H
 #define GROOVIE_DISASSEMBLER_H
 
-#include "decompiler/disassembler.h"
+#include "decompiler/reassembler.h"
 
 namespace Groovie {
 
@@ -31,25 +31,33 @@ struct GroovieOpcode;
 /**
  * Disassembler for Groovie scripts.
  */
-class GroovieDisassembler : public Disassembler {
+class GroovieDisassembler : public Reassembler {
 public:
-	GroovieDisassembler(InstVec &insts, const GroovieOpcode *opcodes);
+	GroovieDisassembler(InstVec &insts, const std::vector<GroovieOpcode> &opcodes);
 
 protected:
 	void doDisassemble() throw(UnknownOpcodeException);
-
+	GroovieOpcode getInstruction(const std::string &name) throw(std::exception);
+	void doAssembly(const std::string &label, std::string &instruction, const std::vector<std::string> &args, const std::string &comment) throw(std::exception);
 
 	InstPtr readInstruction();
 	InstPtr createInstruction(byte opcode);
 	void readParams(InstPtr inst, const char *typeString);
-	ValuePtr readParameter(InstPtr inst, char type);
+	ValuePtr readParameter(char type);
+
+	size_t writeParams(std::vector<byte> &bytes, const char *typeString, const std::vector<std::string> &args, std::string &jumpToLabel);
+	void writeParameter(char type, std::vector<byte> &bytes, const std::string &arg, size_t &jumpAddrStart, std::string &jumpToLabel);
+	void writeParameterVideoName(std::vector<byte> &bytes, const std::string &arg);
+	void writeParameterIndexed(bool allow7C, bool limitVal, bool limitVar, std::vector<byte> &bytes, const std::string &arg);
+	void writeParameterArray(std::vector<byte> &bytes, const std::string &arg);
+	void splitArrayString(const std::string &arg, std::string &first, std::string &second);
 
 	ValuePtr readParameterIndexed(bool allow7C, bool limitVal, bool limitVar);
 	ValuePtr readParameterArray();
 	ValuePtr readParameterScriptName();
 	ValuePtr readParameterVideoName();
 
-	const GroovieOpcode *_opcodes;
+	const std::vector<GroovieOpcode> _opcodes;
 	uint32 _address;
 	uint32 _maxTargetAddress;
 	bool _firstBit;
diff --git a/decompiler/groovie/engine.cpp b/decompiler/groovie/engine.cpp
index 803c51f9..e6a6caea 100644
--- a/decompiler/groovie/engine.cpp
+++ b/decompiler/groovie/engine.cpp
@@ -21,6 +21,7 @@
 
 #include "engine.h"
 #include "disassembler.h"
+#include <vector>
 
 namespace Groovie {
 
@@ -41,7 +42,7 @@ CodeGenerator *GroovieEngine::getCodeGenerator(std::ostream &output) {
 	return NULL;
 }
 
-const GroovieOpcode *GroovieEngine::getOpcodes() const {
+const std::vector<GroovieOpcode> &GroovieEngine::getOpcodes() const {
 	if (!_variant.compare("t7g"))
 		return opcodesT7G;
 	else if (!_variant.compare("v2"))
@@ -51,27 +52,27 @@ const GroovieOpcode *GroovieEngine::getOpcodes() const {
 		return opcodesT7G;
 }
 
-const GroovieOpcode GroovieEngine::opcodesT7G[] = {
+const std::vector<GroovieOpcode> GroovieEngine::opcodesT7G = {
 	{0x00, kKernelCallInst, "Nop",                   ""},
 	{0x01, kKernelCallInst, "Nop",                   ""},
 	{0x02, kKernelCallInst, "PlaySong",              "2"},
-	{0x03, kKernelCallInst, "BitFlag 9 ON",          ""},
-	{0x04, kKernelCallInst, "Palette Fade Out",      ""},
-	{0x05, kKernelCallInst, "BitFlag 8 ON",          ""},
-	{0x06, kKernelCallInst, "BitFlag 6 ON",          ""},
-	{0x07, kKernelCallInst, "BitFlag 7 ON",          ""},
+	{0x03, kKernelCallInst, "BitFlag-9-ON",          ""},
+	{0x04, kKernelCallInst, "Palette-Fade-Out",      ""},
+	{0x05, kKernelCallInst, "BitFlag-8-ON",          ""},
+	{0x06, kKernelCallInst, "BitFlag-6-ON",          ""},
+	{0x07, kKernelCallInst, "BitFlag-7-ON",          ""},
 	{0x08, kKernelCallInst, "SetBackgroundSong",     "2"},
 	{0x09, kKernelCallInst, "VideoFromRef",          "2"},
-	{0x0A, kKernelCallInst, "BitFlag 5 ON",          ""},
-	{0x0B, kKernelCallInst, "Input Loop Start",      ""},
-	{0x0C, kCondJumpInst,   "Keyboard Action",       "1@"},
-	{0x0D, kCondJumpInst,   "Hotspot Rect",          "2222 at 1"},
-	{0x0E, kCondJumpInst,   "Hotspot Left",          "@"},
-	{0x0F, kCondJumpInst,   "Hotspot Right",         "@"},
-	{0x10, kCondJumpInst,   "Hotspot Center",        "@"},
-	{0x11, kCondJumpInst,   "Hotspot Center",        "@"},
-	{0x12, kCondJumpInst,   "Hotspot Current",       "@"},
-	{0x13, kJumpInst,       "Input Loop End",        ""},
+	{0x0A, kKernelCallInst, "BitFlag-5-ON",          ""},
+	{0x0B, kKernelCallInst, "Input-Loop-Start",      ""},
+	{0x0C, kCondJumpInst,   "Keyboard-Action",       "1@"},
+	{0x0D, kCondJumpInst,   "Hotspot-Rect",          "2222 at 1"},
+	{0x0E, kCondJumpInst,   "Hotspot-Left",          "@"},
+	{0x0F, kCondJumpInst,   "Hotspot-Right",         "@"},
+	{0x10, kCondJumpInst,   "Hotspot-Center",        "@"},
+	{0x11, kCondJumpInst,   "Hotspot-Center",        "@"},
+	{0x12, kCondJumpInst,   "Hotspot-Current",       "@"},
+	{0x13, kJumpInst,       "Input-Loop-End",        ""},
 	{0x14, kKernelCallInst, "Random",                "31"},
 	{0x15, kJumpInst,       "Jmp",                   "@"},
 	{0x16, kKernelCallInst, "LoadString",            "3A"},
@@ -79,63 +80,63 @@ const GroovieOpcode GroovieEngine::opcodesT7G[] = {
 	{0x18, kCallInst,       "Call",                  "@"},
 	{0x19, kKernelCallInst, "Sleep",                 "2"},
 	{0x1A, kCondJumpInst,   "JmpStrCmp-NE",          "3A@"},
-	{0x1B, kKernelCallInst, "XOR Obfuscate",         "3A"},
-	{0x1C, kKernelCallInst, "VDX Transition",        "2"},
+	{0x1B, kKernelCallInst, "XOR-Obfuscate",         "3A"},
+	{0x1C, kKernelCallInst, "VDX-Transition",        "2"},
 	{0x1D, kKernelCallInst, "Swap",                  "22"},
 	{0x1E, kKernelCallInst, "Nop8",                  "1"},
 	{0x1F, kUnaryOpPreInst, "Inc",                   "3"},
 	{0x20, kUnaryOpPreInst, "Dec",                   "3"},
 	{0x21, kCondJumpInst,   "JmpStrCmpVar-NE",       "2A@"},
-	{0x22, kKernelCallInst, "Copy BG to FG",         ""},
+	{0x22, kKernelCallInst, "Copy-BG-to-FG",         ""},
 	{0x23, kCondJumpInst,   "JmpStrCmp-EQ",          "3A@"},
 	{0x24, kKernelCallInst, "Mov",                   "32"},
 	{0x25, kBinaryOpInst,   "Add",                   "32"},
 	{0x26, kKernelCallInst, "VideoFromString1",      "V"},
 	{0x27, kKernelCallInst, "VideoFromString2",      "V"},
 	{0x28, kKernelCallInst, "Nop16",                 "2"},
-	{0x29, kKernelCallInst, "Stop MIDI",             ""},
-	{0x2A, kKernelCallInst, "End Script",            ""},
+	{0x29, kKernelCallInst, "Stop-MIDI",             ""},
+	{0x2A, kKernelCallInst, "End-Script",            ""},
 	{0x2B, kKernelCallInst, "Nop",                   ""},
-	{0x2C, kCondJumpInst,   "Set Hotspot Top",       "@1"},
-	{0x2D, kCondJumpInst,   "Set Hotspot Bottom",    "@1"},
-	{0x2E, kKernelCallInst, "Load Game",             "3"},
-	{0x2F, kKernelCallInst, "Save Game",             "3"},
-	{0x30, kCondJumpInst,   "Hotspot Bottom 4",      "@"},
-	{0x31, kKernelCallInst, "MIDI Volume",           "22"},
+	{0x2C, kCondJumpInst,   "Set-Hotspot-Top",       "@1"},
+	{0x2D, kCondJumpInst,   "Set-Hotspot-Bottom",    "@1"},
+	{0x2E, kKernelCallInst, "Load-Game",             "3"},
+	{0x2F, kKernelCallInst, "Save-Game",             "3"},
+	{0x30, kCondJumpInst,   "Hotspot-Bottom-4",      "@"},
+	{0x31, kKernelCallInst, "MIDI-Volume",           "22"},
 	{0x32, kCondJumpInst,   "JNE",                   "32@"},
-	{0x33, kKernelCallInst, "Load String Var",       "3A"},
+	{0x33, kKernelCallInst, "Load-String-Var",       "3A"},
 	{0x34, kCondJumpInst,   "JmpCharGreat",          "3A@"},
-	{0x35, kKernelCallInst, "BitFlag 7 OFF",         ""},
+	{0x35, kKernelCallInst, "BitFlag-7-OFF",         ""},
 	{0x36, kCondJumpInst,   "JmpCharLess",           "3A@"},
-	{0x37, kKernelCallInst, "Copy Rect to BG",       "2222"},
-	{0x38, kKernelCallInst, "Restore Stack Pointer", ""},
-	{0x39, kKernelCallInst, "Obscure Swap",          "CCCC"},
-	{0x3A, kKernelCallInst, "Print String",          "A"},
-	{0x3B, kCondJumpInst,   "Hotspot Slot",          "12222 at 1"},
-	{0x3C, kKernelCallInst, "Check Valid Saves",     ""},
-	{0x3D, kKernelCallInst, "Reset Variables",       ""},
+	{0x37, kKernelCallInst, "Copy-Rect-to-BG",       "2222"},
+	{0x38, kKernelCallInst, "Restore-Stack-Pointer", ""},
+	{0x39, kKernelCallInst, "Obscure-Swap",          "CCCC"},
+	{0x3A, kKernelCallInst, "Print-String",          "A"},
+	{0x3B, kCondJumpInst,   "Hotspot-Slot",          "12222 at 1"},
+	{0x3C, kKernelCallInst, "Check-Valid-Saves",     ""},
+	{0x3D, kKernelCallInst, "Reset-Variables",       ""},
 	{0x3E, kBinaryOpInst,   "Mod",                   "31"},
-	{0x3F, kKernelCallInst, "Load Script",           "S"},
-	{0x40, kKernelCallInst, "Set Video Origin",      "22"},
+	{0x3F, kKernelCallInst, "Load-Script",           "S"},
+	{0x40, kKernelCallInst, "Set-Video-Origin",      "22"},
 	{0x41, kBinaryOpInst,   "Sub",                   "32"},
-	{0x42, kKernelCallInst, "Cell Move",             "1"},
-	{0x43, kKernelCallInst, "Return Script",         "1"},
-	{0x44, kCondJumpInst,   "Set Hotspot Right",     "@"},
-	{0x45, kCondJumpInst,   "Set Hotspot Left",      "@"},
+	{0x42, kKernelCallInst, "Cell-Move",             "1"},
+	{0x43, kKernelCallInst, "Return-Script",         "1"},
+	{0x44, kCondJumpInst,   "Set-Hotspot-Right",     "@"},
+	{0x45, kCondJumpInst,   "Set-Hotspot-Left",      "@"},
 	{0x46, kKernelCallInst, "Nop",                   ""},
 	{0x47, kKernelCallInst, "Nop",                   ""},
 	{0x48, kKernelCallInst, "Nop8",                  "1"},
 	{0x49, kKernelCallInst, "Nop",                   ""},
 	{0x4A, kKernelCallInst, "Nop16",                 "2"},
 	{0x4B, kKernelCallInst, "Nop8",                  "1"},
-	{0x4C, kKernelCallInst, "Get CD",                ""},
-	{0x4D, kKernelCallInst, "Play CD",               "1"},
-	{0x4E, kKernelCallInst, "Music Delay",           "2"},
+	{0x4C, kKernelCallInst, "Get-CD",                ""},
+	{0x4D, kKernelCallInst, "Play-CD",               "1"},
+	{0x4E, kKernelCallInst, "Music-Delay",           "2"},
 	{0x4F, kKernelCallInst, "Nop16",                 "2"},
 	{0x50, kKernelCallInst, "Nop16",                 "2"},
 	{0x51, kKernelCallInst, "Nop16",                 "2"},
 	{0x52, kKernelCallInst, "UNKNOWN52",             "1"},
-	{0x53, kCondJumpInst,   "Hotspot OutRect",       "2222@"},
+	{0x53, kCondJumpInst,   "Hotspot-OutRect",       "2222@"},
 	{0x54, kKernelCallInst, "Nop",                   ""},
 	{0x55, kKernelCallInst, "Nop16",                 "2"},
 	{0x56, kKernelCallInst, "Stub56",                "411"},
@@ -144,105 +145,98 @@ const GroovieOpcode GroovieEngine::opcodesT7G[] = {
 	{0x59, kKernelCallInst, "UNKNOWN59",             "31"},
 };
 
-// TODO: fill with the known v2 opcodes
-const GroovieOpcode GroovieEngine::opcodesV2[] = {
-	{0x00, kKernelCallInst, "", ""},
-	{0x59, kKernelCallInst, "", ""},
-};
-
-/*
-const GroovieOpcode GroovieEngine::opcodesV2[] = {
-	o_invalid, // 0x00
-	o_nop,
-	o2_playsong,
-	o_nop,
-	o_nop, // 0x04
-	o_nop,
-	o_nop,
-	o_nop,
-	o2_setbackgroundsong, // 0x08
-	o2_videofromref,
-	o_bf5on,
-	o_inputloopstart,
-	o_keyboardaction, // 0x0C
-	o_hotspot_rect,
-	o_hotspot_left,
-	o_hotspot_right,
-	o_hotspot_center, // 0x10
-	o_hotspot_center,
-	o_hotspot_current,
-	o_inputloopend,
-	o_random, // 0x14
-	o_jmp,
-	o_loadstring,
-	o_ret,
-	o_call, // 0x18
-	o_sleep,
-	o_strcmpnejmp,
-	o_xor_obfuscate,
-	o2_vdxtransition, // 0x1C
-	o_swap,
-	o_invalid,
-	o_inc,
-	o_dec, // 0x20
-	o_strcmpnejmp_var,
-	o_copybgtofg,
-	o_strcmpeqjmp,
-	o_mov, // 0x24
-	o_add,
-	o_videofromstring1,
-	o_videofromstring2,
-	o_invalid, // 0x28
-	o_nop,
-	o_endscript,
-	o_invalid,
-	o_sethotspottop, // 0x2C
-	o_sethotspotbottom,
-	o_loadgame,
-	o_savegame,
-	o_hotspotbottom_4, // 0x30
-	o_midivolume,
-	o_jne,
-	o_loadstringvar,
-	o_chargreatjmp, // 0x34
-	o_bf7off,
-	o_charlessjmp,
-	o_copyrecttobg,
-	o_restorestkpnt, // 0x38
-	o_obscureswap,
-	o_printstring,
-	o_hotspot_slot,
-	o_checkvalidsaves, // 0x3C
-	o_resetvars,
-	o_mod,
-	o_loadscript,
-	o_setvideoorigin, // 0x40
-	o_sub,
-	o_cellmove,
-	o_returnscript,
-	o_sethotspotright, // 0x44
-	o_sethotspotleft,
-	o_invalid,
-	o_invalid,
-	o_invalid, // 0x48
-	o_invalid,
-	o_nop16,
-	o_invalid,
-	o_invalid, // 0x4C
-	o_invalid,
-	o_invalid,
-	o2_copyscreentobg,
-	o2_copybgtoscreen, // 0x50
-	o2_setvideoskip,
-	o2_stub52,
-	o_hotspot_outrect,
-	o_invalid, // 0x54
-	o2_setscriptend,
-	o_stub56,
-	o_invalid,
-	o_invalid, // 0x58
-	o_stub59
+const std::vector<GroovieOpcode> GroovieEngine::opcodesV2 = {
+	{0x00, kKernelCallInst, "Invalid",               ""},
+	{0x01, kKernelCallInst, "Nop",                   ""},
+	{0x02, kKernelCallInst, "PlaySong",              "4"},
+	{0x03, kKernelCallInst, "Nop",                   ""},
+	{0x04, kKernelCallInst, "Nop",                   ""},
+	{0x05, kKernelCallInst, "Nop",                   ""},
+	{0x06, kKernelCallInst, "Nop",                   ""},
+	{0x07, kKernelCallInst, "Nop",                   ""},
+	{0x08, kKernelCallInst, "SetBackgroundSong",     "4"},
+	{0x09, kKernelCallInst, "VideoFromRef",          "4"},
+	{0x0A, kKernelCallInst, "BitFlag-0-ON",          ""},
+	{0x0B, kKernelCallInst, "Input-Loop-Start",      ""},
+	{0x0C, kCondJumpInst,   "Keyboard-Action",       "1@"},
+	{0x0D, kCondJumpInst,   "Hotspot-Rect",          "2222 at 1"},
+	{0x0E, kCondJumpInst,   "Hotspot-Left",          "@"},
+	{0x0F, kCondJumpInst,   "Hotspot-Right",         "@"},
+	{0x10, kCondJumpInst,   "Hotspot-Center",        "@"},
+	{0x11, kCondJumpInst,   "Hotspot-Center",        "@"},
+	{0x12, kCondJumpInst,   "Hotspot-Current",       "@"},
+	{0x13, kJumpInst,       "Input-Loop-End",        ""},
+	{0x14, kKernelCallInst, "Random",                "31"},
+	{0x15, kJumpInst,       "Jmp",                   "@"},
+	{0x16, kKernelCallInst, "LoadString",            "3A"},
+	{0x17, kReturnInst,     "Return",                "1"},
+	{0x18, kCallInst,       "Call",                  "@"},
+	{0x19, kKernelCallInst, "Sleep",                 "2"},
+	{0x1A, kCondJumpInst,   "JmpStrCmp-NE",          "3A@"},
+	{0x1B, kKernelCallInst, "XOR-Obfuscate",         "3A"},
+	{0x1C, kKernelCallInst, "VDX-Transition",        "4"},
+	{0x1D, kKernelCallInst, "Swap",                  "32"},
+	{0x1E, kKernelCallInst, "Invalid",               "1"},
+	{0x1F, kUnaryOpPreInst, "Inc",                   "3"},
+	{0x20, kUnaryOpPreInst, "Dec",                   "3"},
+	{0x21, kCondJumpInst,   "JmpStrCmpVar-NE",       "2A@"},
+	{0x22, kKernelCallInst, "Copy-BG-to-FG",         ""},
+	{0x23, kCondJumpInst,   "JmpStrCmp-EQ",          "3A@"},
+	{0x24, kKernelCallInst, "Mov",                   "32"},
+	{0x25, kBinaryOpInst,   "Add",                   "32"},
+	{0x26, kKernelCallInst, "VideoFromString1",      "V"},
+	{0x27, kKernelCallInst, "VideoFromString2",      "V"},
+	{0x28, kKernelCallInst, "Invalid",               "2"},
+	{0x29, kKernelCallInst, "Nop",                   ""},
+	{0x2A, kKernelCallInst, "End-Script",            ""},
+	{0x2B, kKernelCallInst, "Invalid",               ""},
+	{0x2C, kCondJumpInst,   "Set-Hotspot-Top",       "@1"},
+	{0x2D, kCondJumpInst,   "Set-Hotspot-Bottom",    "@1"},
+	{0x2E, kKernelCallInst, "Load-Game",             "3"},
+	{0x2F, kKernelCallInst, "Save-Game",             "3"},
+	{0x30, kCondJumpInst,   "Hotspot-Bottom-4",      "@"},
+	{0x31, kKernelCallInst, "MIDI-Control",          "22"},
+	{0x32, kCondJumpInst,   "JNE",                   "32@"},
+	{0x33, kKernelCallInst, "Load-String-Var",       "3A"},
+	{0x34, kCondJumpInst,   "JmpCharGreat",          "3A@"},
+	{0x35, kKernelCallInst, "BitFlag-7-OFF",         ""},
+	{0x36, kCondJumpInst,   "JmpCharLess",           "3A@"},
+	{0x37, kKernelCallInst, "Copy-Rect-to-BG",       "2222"},
+	{0x38, kKernelCallInst, "Restore-Stack-Pointer", ""},
+	{0x39, kKernelCallInst, "Obscure-Swap",          "CCCC"},
+	{0x3A, kKernelCallInst, "Print-String",          "22111V"},
+	{0x3B, kCondJumpInst,   "Hotspot-Slot",          "12222 at 1"},
+	{0x3C, kKernelCallInst, "Check-Valid-Saves",     ""},
+	{0x3D, kKernelCallInst, "Reset-Variables",       ""},
+	{0x3E, kBinaryOpInst,   "Mod",                   "31"},
+	{0x3F, kKernelCallInst, "Load-Script",           "S"},
+	{0x40, kKernelCallInst, "Set-Video-Origin",      "22"},
+	{0x41, kBinaryOpInst,   "Sub",                   "32"},
+	{0x42, kKernelCallInst, "Cell-Move",             "1"},
+	{0x43, kKernelCallInst, "Return-Script",         "1"},
+	{0x44, kCondJumpInst,   "Set-Hotspot-Right",     "@"},
+	{0x45, kCondJumpInst,   "Set-Hotspot-Left",      "@"},
+	{0x46, kKernelCallInst, "Invalid",               ""},
+	{0x47, kKernelCallInst, "Invalid",               ""},
+	{0x48, kKernelCallInst, "Invalid",               "1"},
+	{0x49, kKernelCallInst, "Invalid",               ""},
+	{0x4A, kKernelCallInst, "Nop16",                 "2"},
+	{0x4B, kKernelCallInst, "Invalid",               "1"},
+	{0x4C, kKernelCallInst, "Invalid",               ""},
+	{0x4D, kKernelCallInst, "Invalid",               "1"},
+	{0x4E, kKernelCallInst, "Invalid",               "2"},
+	{0x4F, kKernelCallInst, "Save-Screen",           "2"},
+	{0x50, kKernelCallInst, "Restore-Screen",        "2"},
+	{0x51, kCondJumpInst,   "Set-Video-Skip",        "@"},
+	{0x52, kKernelCallInst, "Copy-FG-to-BG",         "1"},
+	{0x53, kCondJumpInst,   "Hotspot-OutRect",       "2222@"},
+	{0x54, kKernelCallInst, "Invalid",               ""},
+	{0x55, kKernelCallInst, "Set-Script-End",        "2"},// should this be an @?
+	{0x56, kKernelCallInst, "Play-Sound",            "411"},
+	{0x57, kKernelCallInst, "Invalid",               "4"},
+	{0x58, kKernelCallInst, "Wipe-Mask-From-String", "V"},
+	{0x59, kKernelCallInst, "Check-Sounds-Overlays", "31"},
+	{0x5A, kKernelCallInst, "Preview-Loadgame",      "1"},
 };
-*/
 
 } // End of namespace Groovie
diff --git a/decompiler/groovie/engine.h b/decompiler/groovie/engine.h
index 50f0a3da..d1e3f517 100644
--- a/decompiler/groovie/engine.h
+++ b/decompiler/groovie/engine.h
@@ -24,6 +24,7 @@
 
 #include "decompiler/engine.h"
 #include "opcodes.h"
+#include <vector>
 
 namespace Groovie {
 
@@ -39,10 +40,10 @@ public:
 	bool supportsCodeGen() const { return false; }
 
 private:
-	const GroovieOpcode *getOpcodes() const;
+	const std::vector<GroovieOpcode> &getOpcodes() const;
 
-	static const GroovieOpcode opcodesT7G[];
-	static const GroovieOpcode opcodesV2[];
+	static const std::vector<GroovieOpcode> opcodesT7G;
+	static const std::vector<GroovieOpcode> opcodesV2;
 };
 
 } // End of namespace Groovie
diff --git a/decompiler/reassembler.cpp b/decompiler/reassembler.cpp
new file mode 100644
index 00000000..486dc99c
--- /dev/null
+++ b/decompiler/reassembler.cpp
@@ -0,0 +1,173 @@
+/* ScummVM Tools
+ *
+ * ScummVM Tools is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "reassembler.h"
+#include "common/endian.h"
+
+Reassembler::Reassembler(InstVec &insts) : Disassembler(insts) { }
+
+void Reassembler::assemble() {
+	// Prepare to read the input script
+	_f.seek(0, SEEK_SET);
+	_binary.clear();
+
+	while(!_f.eos()) {
+		try {
+			std::string line = readLine();
+			auto comment = splitString(line, line.find(";"), 1, true);// remove comments
+			if(line.empty())
+				continue;
+			
+			auto label = splitString(line, line.find(": "), 2);
+			auto instruction = splitString(line, line.find(" "), 1);
+			if(instruction.empty()) {
+				// if it didn't find a space, that means there are no arguments
+				instruction = line;
+				line = "";
+			}
+			std::cout << label << ": " << instruction;
+
+			// parse arguments
+			std::vector<std::string> args;
+			size_t s = 0, e = 0;
+			for(s = 0; s < line.length(); s = e + 2) {
+				if(s > 0) std::cout << ", ";
+				else std::cout << " ";
+				e = getEndArgument(line, s);
+				size_t len = e - s;
+				args.push_back(line.substr(s, len));
+				std::cout << args.back();
+			}
+			std::cout << "; " << comment << "\n";
+			// TODO: maybe parse the arguments into a ValueList of the Value subclasses
+
+			doAssembly(label, instruction, args, comment);
+		} catch(Common::FileException &e) {
+			break;
+		}
+	}
+
+	// 2nd pass in order to set jump addresses after reading all labels
+	for(const auto &j : _jumps) {
+		if(j._label.empty())
+			continue;
+		
+		size_t addr = _labels.at(j._label);
+		uint16 u16;
+		uint32 u32;
+
+		switch(j.len) {
+		case 1:
+			_binary[j.start] = addr;
+			break;
+		case 2:
+			u16 = TO_LE_16(addr);
+			_binary[j.start] = u16;
+			_binary[j.start + 1] = u16 >> 8;
+			break;
+		case 4:
+			u32 = TO_LE_32(addr);
+			_binary[j.start] = u32;
+			_binary[j.start + 1] = u32 >> 8;
+			_binary[j.start + 2] = u32 >> 16;
+			_binary[j.start + 3] = u32 >> 24;
+			break;
+		}
+	}
+}
+
+void Reassembler::doDumpBinary(std::ostream &output) {
+	output.write((char*)_binary.data(), _binary.size());
+}
+
+void Reassembler::dumpBinary(std::ostream &output) {
+	assemble();
+	doDumpBinary(output);
+}
+
+std::string Reassembler::readLine() {
+	std::string line;
+	while(!_f.eos()) {
+		try {
+			char c = _f.readByte();
+			if(c == '\n')
+				break;
+			line += c;
+		} catch(Common::FileException &e) {
+			break;
+		}
+	}
+	return line;
+}
+
+std::string Reassembler::splitString(std::string &from, size_t pos, size_t separator_len, bool reverse) {
+	if(pos == std::string::npos)
+		return std::string();
+	
+	if(reverse) {
+		// return the right side, from is set to the left side
+		std::string ret = from.substr(pos + separator_len);
+		from = from.substr(0, pos);
+		return ret;
+	}
+	// else we return the left side, and from is set to the right side
+	std::string ret = from.substr(0, pos);
+	from = from.substr(pos + separator_len);
+	return ret;
+}
+
+void Reassembler::addInstruction(const std::vector<byte> &bytes, int type, size_t jumpAddrStart, size_t jumpAddrLen, const std::string &label, const std::string &jumpToLabel) {
+	if(!label.empty()) {
+		_labels.emplace(label, _binary.size());
+	}
+
+	if(type == kCallInst || type == kCondJumpInst || type == kJumpInst) {
+		Jump j;
+		j._label = jumpToLabel;
+		j.start = jumpAddrStart + _binary.size();
+		j.len = jumpAddrLen;
+		_jumps.push_back(j);
+	}
+
+	_binary.insert(_binary.end(), bytes.begin(), bytes.end());
+}
+
+size_t Reassembler::getEndArgument(const std::string &s, size_t start) {
+	int brackets = 0;
+	for(size_t i = start; i < s.length(); i++) {
+		switch(s[i]) {
+		case '[':
+			brackets++;
+			break;
+		case ']':
+			brackets--;
+			if(brackets < 0)
+				return i;
+			break;
+		
+		case ',':
+			if(brackets == 0)
+				return i;
+			break;
+		}
+	}
+	return s.length();
+}
diff --git a/decompiler/reassembler.h b/decompiler/reassembler.h
new file mode 100644
index 00000000..7161496a
--- /dev/null
+++ b/decompiler/reassembler.h
@@ -0,0 +1,58 @@
+/* ScummVM Tools
+ *
+ * ScummVM Tools is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef DEC_REASSEMBLER_H
+#define DEC_REASSEMBLER_H
+
+#include <iostream>
+#include <vector>
+#include <unordered_map>
+
+#include "disassembler.h"
+#include "instruction.h"
+#include "common/file.h"
+#include "unknown_opcode.h"
+#include "objectFactory.h"
+
+class Reassembler : public Disassembler {
+	struct Jump {
+		std::string _label;
+		size_t start, len;
+	};
+private:
+	std::vector<byte> _binary;
+	std::unordered_map<std::string, size_t> _labels;
+	std::vector<Jump> _jumps;
+
+protected:
+	virtual void doAssembly(const std::string &label, std::string &instruction, const std::vector<std::string> &args, const std::string &comment) throw(std::exception) = 0; // push_back to _binary
+	virtual void doDumpBinary(std::ostream &output);
+	std::string readLine();
+	std::string splitString(std::string &from, size_t pos, size_t separator_len=0, bool reverse=false);
+	void addInstruction(const std::vector<byte> &bytes, int type, size_t jumpAddrStart=0, size_t jumpAddrLen=0, const std::string &label="", const std::string &jumpToLabel="");// automatically pair the label with the instruction address
+public:
+	Reassembler(InstVec &insts);
+	void assemble();
+	void dumpBinary(std::ostream &output);
+	size_t getEndArgument(const std::string &s, size_t start);
+};
+
+#endif
diff --git a/decompiler/test/module.mk b/decompiler/test/module.mk
index c9cb01ba..a81fc823 100644
--- a/decompiler/test/module.mk
+++ b/decompiler/test/module.mk
@@ -11,6 +11,7 @@ TEST_LIBS    := \
 	decompiler/codegen.o \
 	decompiler/control_flow.o \
 	decompiler/disassembler.o \
+	decompiler/reassembler.o \
 	decompiler/instruction.o \
 	decompiler/simple_disassembler.o \
 	decompiler/value.o \




More information about the Scummvm-git-logs mailing list