mirror of
https://github.com/scummvm/scummvm-tools.git
synced 2026-05-21 05:40:44 +00:00
540 lines
14 KiB
C++
540 lines
14 KiB
C++
/* 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 "disassembler.h"
|
|
#include "opcodes.h"
|
|
|
|
#include "common/util.h"
|
|
#include "common/endian.h"
|
|
|
|
namespace Groovie {
|
|
|
|
/**
|
|
* Value representing an array of values.
|
|
*/
|
|
class ValuesArrayValue : public Value {
|
|
protected:
|
|
const ValueList _values;
|
|
|
|
public:
|
|
/**
|
|
* Constructor for ValuesArrayValue.
|
|
*
|
|
* @param values std::deque of values (left-to-right).
|
|
*/
|
|
ValuesArrayValue(ValueList values) : _values(values) { }
|
|
//TODO: implement dup()?
|
|
|
|
std::ostream &print(std::ostream &output) const;
|
|
};
|
|
|
|
std::ostream &ValuesArrayValue::print(std::ostream &output) const {
|
|
output << "[";
|
|
for (ValueList::const_iterator i = _values.begin(); i != _values.end(); ++i) {
|
|
if (i != _values.begin())
|
|
output << ", ";
|
|
output << *i;
|
|
}
|
|
output << "]";
|
|
return output;
|
|
}
|
|
|
|
class GroovieJumpInstruction : public Instruction {
|
|
public:
|
|
bool isJump() const { return true; }
|
|
uint32 getDestAddress() const;
|
|
void processInst(ValueStack &stack, Engine *engine, CodeGenerator *codeGen) {}
|
|
};
|
|
|
|
uint32 GroovieJumpInstruction::getDestAddress() const {
|
|
std::vector<ValuePtr>::const_iterator i = _params.begin();
|
|
while (i != _params.end()) {
|
|
if ((*i)->isAddress())
|
|
return (*i)->getUnsigned();
|
|
++i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
class GroovieCondJumpInstruction : public GroovieJumpInstruction {
|
|
public:
|
|
bool isCondJump() const { return true; }
|
|
};
|
|
|
|
class GroovieUncondJumpInstruction : public GroovieJumpInstruction {
|
|
public:
|
|
bool isUncondJump() const { return true; }
|
|
};
|
|
|
|
|
|
// GroovieDisassembler
|
|
|
|
GroovieDisassembler::GroovieDisassembler(InstVec &insts, const std::vector<GroovieOpcode> &opcodes) :
|
|
Reassembler(insts), _opcodes(opcodes), _firstBit(false) {
|
|
}
|
|
|
|
InstPtr GroovieDisassembler::readInstruction() {
|
|
// Read the next opcode
|
|
byte opcode;
|
|
try {
|
|
opcode = _f.readByte();
|
|
} catch (Common::FileException e) {
|
|
return NULL;
|
|
}
|
|
if (_f.eos())
|
|
return NULL;
|
|
|
|
return createInstruction(opcode);
|
|
}
|
|
|
|
InstPtr GroovieDisassembler::createInstruction(byte opcode) {
|
|
// Extract the first bit
|
|
_firstBit = opcode & 0x80;
|
|
opcode &= 0x7F;
|
|
|
|
// Verify it's a valid opcode
|
|
if (opcode >= _opcodes.size())
|
|
throw UnknownOpcodeException(_address, opcode);
|
|
|
|
// Create the new instruction
|
|
InstPtr i;
|
|
switch (_opcodes[opcode].type) {
|
|
case kUnaryOpPreInst:
|
|
i = new UnaryOpPrefixStackInstruction();
|
|
break;
|
|
case kBinaryOpInst:
|
|
i = new BinaryOpStackInstruction();
|
|
break;
|
|
case kKernelCallInst:
|
|
i = new KernelCallStackInstruction();
|
|
break;
|
|
case kReturnInst:
|
|
i = new ReturnInstruction();
|
|
break;
|
|
case kJumpInst:
|
|
i = new GroovieUncondJumpInstruction();
|
|
break;
|
|
case kCondJumpInst:
|
|
i = new GroovieCondJumpInstruction();
|
|
break;
|
|
case kCallInst:
|
|
// TODO: mark beginning of functions?
|
|
i = new KernelCallStackInstruction();
|
|
break;
|
|
}
|
|
if (!i)
|
|
return NULL;
|
|
|
|
// Fill the instruction information
|
|
i->_opcode = opcode;
|
|
i->_address = _address++;
|
|
i->_stackChange = 0;
|
|
i->_name = _opcodes[opcode].name;
|
|
|
|
// Handle special cases
|
|
switch (opcode) {
|
|
case 0x0B: // Input Loop Start
|
|
// save its address so the Input Loop End can jump here
|
|
_inputLoopStart = i->_address;
|
|
break;
|
|
case 0x13: // Input Loop End
|
|
// Jump to the address of the Input Loop Start
|
|
i->_params.push_back(new AddressValue(_inputLoopStart));
|
|
break;
|
|
}
|
|
|
|
// Read the current instruction's parameters
|
|
readParams(i, _opcodes[opcode].params);
|
|
return i;
|
|
}
|
|
|
|
void GroovieDisassembler::readParams(InstPtr inst, const char *typeString) {
|
|
while (*typeString) {
|
|
inst->_params.push_back(readParameter(*typeString));
|
|
typeString++;
|
|
}
|
|
}
|
|
|
|
ValuePtr GroovieDisassembler::readParameter(char type) {
|
|
ValuePtr retval = NULL;
|
|
switch (type) {
|
|
case '1': // 8 bits
|
|
retval = new IntValue(_f.readByte(), false);
|
|
_address++;
|
|
break;
|
|
case '2': // 16 bits
|
|
retval = new IntValue(_f.readUint16LE(), false);
|
|
_address += 2;
|
|
break;
|
|
case '3': // 8 or 16 bits
|
|
if (_firstBit)
|
|
retval = readParameter('1');
|
|
else
|
|
retval = readParameter('2');
|
|
break;
|
|
case '4': // 32 bits
|
|
retval = new IntValue(_f.readUint32LE(), false);
|
|
_address += 4;
|
|
break;
|
|
case '@': // Address
|
|
retval = new AddressValue(_f.readUint16LE());
|
|
_address += 2;
|
|
if (retval->getUnsigned() > _maxTargetAddress)
|
|
_maxTargetAddress = retval->getUnsigned();
|
|
break;
|
|
case 'A': // Array
|
|
retval = readParameterArray();
|
|
break;
|
|
case 'S': // Script name
|
|
retval = readParameterScriptName();
|
|
break;
|
|
case 'V': // Video name
|
|
retval = readParameterVideoName();
|
|
break;
|
|
case 'C': // Indexed value
|
|
retval = readParameterIndexed(false, true, true);
|
|
break;
|
|
case 'B': // First Bit
|
|
retval = new IntValue(_firstBit, false);
|
|
break;
|
|
default:
|
|
std::cout << " UNKNOWN param type: " << type << std::endl;
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
ValuePtr GroovieDisassembler::readParameterIndexed(bool allow7C, bool limitVal, bool limitVar) {
|
|
ValuePtr result;
|
|
uint8 data = _f.readByte();
|
|
_address++;
|
|
|
|
if (limitVal) {
|
|
_firstBit = data & 0x80;
|
|
data &= 0x7F;
|
|
}
|
|
|
|
if (allow7C && (data == 0x7C)) {
|
|
// Index a bidimensional array:
|
|
// M[part1, part2]
|
|
// M[0x19 + 10 * part1 + part2]
|
|
|
|
ValuePtr part1 = readParameterIndexed(false, false, false);
|
|
ValuePtr part2 = readParameterIndexed(false, true, true);
|
|
|
|
ValueList idxs;
|
|
idxs.push_back(part1);
|
|
idxs.push_back(part2);
|
|
result = new ArrayValue("M", idxs);
|
|
} else if (data == 0x23) {
|
|
// Index an array:
|
|
// M[data - 0x61]
|
|
|
|
data = _f.readByte();
|
|
_address++;
|
|
if (limitVar) {
|
|
_firstBit = data & 0x80;
|
|
data &= 0x7F;
|
|
}
|
|
|
|
ValueList idxs;
|
|
idxs.push_back(new IntValue(data - 0x61, false));
|
|
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);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
ValuePtr GroovieDisassembler::readParameterArray() {
|
|
ValueList values;
|
|
do {
|
|
values.push_back(readParameterIndexed(true, true, true));
|
|
} while (!_firstBit);
|
|
|
|
return new ValuesArrayValue(values);
|
|
}
|
|
|
|
ValuePtr GroovieDisassembler::readParameterScriptName() {
|
|
std::string result = "";
|
|
byte c;
|
|
_address++;
|
|
while ((c = _f.readByte())) {
|
|
result += (char)c;
|
|
_address++;
|
|
}
|
|
|
|
// Return the read string
|
|
return new StringValue(result);
|
|
}
|
|
|
|
ValuePtr GroovieDisassembler::readParameterVideoName() {
|
|
ValueList values;
|
|
|
|
std::string stringLiteral = "";
|
|
byte data;
|
|
_address++;
|
|
while ((data = _f.readByte())) {
|
|
if (data == 0x7C || data == 0x23) {
|
|
// Check if it breaks a previous string literal
|
|
if (!stringLiteral.empty()) {
|
|
values.push_back(new StringValue(stringLiteral));
|
|
stringLiteral = "";
|
|
}
|
|
|
|
ValueList idxs;
|
|
if (data == 0x7C) {
|
|
// 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));
|
|
} else {
|
|
// To lowercase?
|
|
if (data >= 0x41 && data <= 0x5A) {
|
|
data += 0x20;
|
|
}
|
|
// Append the current character at the end of the string
|
|
stringLiteral += (char)data;
|
|
}
|
|
_address++;
|
|
}
|
|
|
|
// Check if there's a spare string literal
|
|
if (!stringLiteral.empty())
|
|
values.push_back(new StringValue(stringLiteral));
|
|
|
|
// Return the array built of string parts
|
|
return new ValuesArrayValue(values);
|
|
}
|
|
|
|
void GroovieDisassembler::doDisassemble() throw (UnknownOpcodeException) {
|
|
// Prepare to read the input script
|
|
_f.seek(0, SEEK_SET);
|
|
|
|
// Reset the status
|
|
_addressBase = 0;
|
|
_address = _addressBase;
|
|
_maxTargetAddress = 0;
|
|
_inputLoopStart = 0;
|
|
|
|
bool cont = true;
|
|
while (cont) {
|
|
InstPtr i = readInstruction();
|
|
|
|
if (i)
|
|
_insts.push_back(i);
|
|
else
|
|
cont = false;
|
|
}
|
|
|
|
// HACK: insert additional NOPs, since some jumps target beyond
|
|
// the end of the script
|
|
while (_maxTargetAddress >= _address)
|
|
_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;
|
|
assert(args.size() >= strlen(typeString));
|
|
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) {
|
|
u32 = std::stoul(arg, 0, 16);
|
|
assert((boost::format("0x%X") % u32).str() == arg);
|
|
jumpToLabel = (boost::format("%08x") % u32).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;
|
|
case 'B': // First Bit
|
|
i = std::stoi(arg);
|
|
_firstBit = bool(i);
|
|
break;
|
|
default:
|
|
std::cout << "writeParameter UNKNOWN param type: " << type << "\n";
|
|
throw std::runtime_error(std::string() + "writeParameter UNKNOWN param type: " + type);
|
|
}
|
|
}
|
|
|
|
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
|