216 lines
6.3 KiB
C++
216 lines
6.3 KiB
C++
//
|
|
// 8bitDetokeniser.cpp
|
|
// Phantasma
|
|
//
|
|
// Created by Thomas Harte on 15/12/2013.
|
|
// Copyright (c) 2013 Thomas Harte. All rights reserved.
|
|
//
|
|
|
|
/*
|
|
This has been implemented based on John Elliott's 2001
|
|
reverse engineering of Driller; see http://www.seasip.demon.co.uk/ZX/Driller/
|
|
*/
|
|
|
|
#include "8bitDetokeniser.h"
|
|
|
|
static const int k8bitVariableShield = 256;
|
|
static const int k8bitVariableEnergy = 257;
|
|
static const int k8bitVariableScore = 258;
|
|
|
|
Common::String *detokenise8bitCondition(Common::Array<uint8> &tokenisedCondition, FCLInstructionVector *instructions) {
|
|
Common::String detokenisedStream;
|
|
Common::Array<uint8>::size_type bytePointer = 0;
|
|
Common::Array<uint8>::size_type sizeOfTokenisedContent = tokenisedCondition.size();
|
|
|
|
// on the 8bit platforms, all instructions have a conditional flag;
|
|
// we'll want to convert them into runs of "if shot? then" and "if collided? then",
|
|
// and we'll want to start that from the top
|
|
uint8 conditionalIsShot = 0x1;
|
|
|
|
// this lookup table tells us how many argument bytes to read per opcode
|
|
uint8 argumentsRequiredByOpcode[32] =
|
|
{
|
|
0, 3, 1, 1, 1, 1, 2, 2,
|
|
2, 1, 1, 2, 1, 1, 2, 1,
|
|
1, 2, 2, 1, 2, 0, 0, 0,
|
|
0, 1, 0, 1, 1, 1, 1, 1};
|
|
|
|
while (bytePointer < sizeOfTokenisedContent) {
|
|
// get the conditional type of the next operation
|
|
uint8 newConditionalIsShot = tokenisedCondition[bytePointer] & 0x80;
|
|
|
|
// if the conditional type has changed then end the old conditional,
|
|
// if we were in one, and begin a new one
|
|
if (newConditionalIsShot != conditionalIsShot) {
|
|
conditionalIsShot = newConditionalIsShot;
|
|
if (bytePointer)
|
|
detokenisedStream += "ENDIF\n";
|
|
|
|
if (conditionalIsShot)
|
|
detokenisedStream += "IF SHOT? THEN\n";
|
|
else
|
|
detokenisedStream += "IF COLLIDED? THEN\n";
|
|
}
|
|
|
|
// get the actual operation
|
|
uint8 opcode = tokenisedCondition[bytePointer] & 0x1f;
|
|
bytePointer++;
|
|
|
|
// figure out how many argument bytes we're going to need,
|
|
// check we have enough bytes left to read
|
|
uint8 numberOfArguments = argumentsRequiredByOpcode[opcode];
|
|
if (bytePointer + numberOfArguments > sizeOfTokenisedContent)
|
|
break;
|
|
|
|
// generate the string
|
|
switch (opcode) {
|
|
default:
|
|
detokenisedStream += "<UNKNOWN 8 bit: ";
|
|
detokenisedStream += Common::String::format("%x", (int)opcode);
|
|
detokenisedStream += " > ";
|
|
if (opcode != 0x18)
|
|
error("Unknown FCL instruction: 0x%x", (int)opcode);
|
|
break;
|
|
|
|
case 0:
|
|
break; // NOP
|
|
case 1: // add three-byte value to score
|
|
{
|
|
int32 additionValue =
|
|
tokenisedCondition[bytePointer] |
|
|
(tokenisedCondition[bytePointer + 1] << 8) |
|
|
(tokenisedCondition[bytePointer + 2] << 16);
|
|
detokenisedStream += "ADDVAR";
|
|
detokenisedStream += Common::String::format("(%d, v%d)", additionValue, k8bitVariableScore);
|
|
bytePointer += 3;
|
|
numberOfArguments = 0;
|
|
} break;
|
|
case 2: // add one-byte value to energy
|
|
detokenisedStream += "ADDVAR ";
|
|
detokenisedStream += Common::String::format("(%d, v%d)", (int)tokenisedCondition[bytePointer], k8bitVariableEnergy);
|
|
bytePointer++;
|
|
numberOfArguments = 0;
|
|
break;
|
|
case 19: // add one-byte value to shield
|
|
detokenisedStream += "ADDVAR ";
|
|
detokenisedStream += Common::String::format("(%d, v%d)", (int)tokenisedCondition[bytePointer], k8bitVariableShield);
|
|
bytePointer++;
|
|
numberOfArguments = 0;
|
|
break;
|
|
|
|
case 6:
|
|
case 3:
|
|
detokenisedStream += "TOGVIS (";
|
|
break; // these all come in unary and binary versions,
|
|
case 7:
|
|
case 4:
|
|
detokenisedStream += "VIS (";
|
|
break; // hence each getting two case statement entries
|
|
case 8:
|
|
case 5:
|
|
detokenisedStream += "INVIS (";
|
|
break;
|
|
|
|
case 9:
|
|
detokenisedStream += "ADDVAR (1, v";
|
|
break;
|
|
case 10:
|
|
detokenisedStream += "SUBVAR (1, v";
|
|
break;
|
|
|
|
case 11: // end condition if a variable doesn't have a particular value
|
|
detokenisedStream += "IF VAR!=? ";
|
|
detokenisedStream += Common::String::format("(v%d, v%d)", (int)tokenisedCondition[bytePointer], (int)tokenisedCondition[bytePointer + 1]);
|
|
detokenisedStream += "THEN END ENDIF";
|
|
bytePointer += 2;
|
|
numberOfArguments = 0;
|
|
break;
|
|
case 14: // end condition if a bit doesn't have a particular value
|
|
detokenisedStream += "IF BIT!=? ";
|
|
detokenisedStream += Common::String::format("(%d, %d)", (int)tokenisedCondition[bytePointer], (int)tokenisedCondition[bytePointer + 1]);
|
|
detokenisedStream += "THEN END ENDIF";
|
|
bytePointer += 2;
|
|
numberOfArguments = 0;
|
|
break;
|
|
case 30: // end condition if an object is invisible
|
|
detokenisedStream += "IF INVIS? ";
|
|
detokenisedStream += Common::String::format("(%d)", (int)tokenisedCondition[bytePointer]);
|
|
detokenisedStream += "THEN END ENDIF";
|
|
bytePointer++;
|
|
numberOfArguments = 0;
|
|
break;
|
|
case 31: // end condition if an object is visible
|
|
detokenisedStream += "IF VIS? ";
|
|
detokenisedStream += Common::String::format("(%d)", (int)tokenisedCondition[bytePointer]);
|
|
detokenisedStream += "THEN END ENDIF";
|
|
bytePointer++;
|
|
numberOfArguments = 0;
|
|
break;
|
|
|
|
case 12:
|
|
detokenisedStream += "SETBIT (";
|
|
break;
|
|
case 13:
|
|
detokenisedStream += "CLRBIT (";
|
|
break;
|
|
|
|
case 15:
|
|
detokenisedStream += "SOUND (";
|
|
break;
|
|
case 17:
|
|
case 16:
|
|
detokenisedStream += "DESTROY (";
|
|
break;
|
|
case 18:
|
|
detokenisedStream += "GOTO (";
|
|
break;
|
|
|
|
case 21:
|
|
detokenisedStream += "SWAPJET";
|
|
break;
|
|
case 26:
|
|
detokenisedStream += "REDRAW";
|
|
break;
|
|
case 27:
|
|
detokenisedStream += "DELAY (";
|
|
break;
|
|
case 28:
|
|
detokenisedStream += "SYNCSND (";
|
|
break;
|
|
case 29:
|
|
detokenisedStream += "TOGBIT (";
|
|
break;
|
|
|
|
case 25: {
|
|
// this should toggle border colour; it's therefore a no-op
|
|
bytePointer++;
|
|
numberOfArguments = 0;
|
|
} break;
|
|
|
|
case 20:
|
|
detokenisedStream += "SETVAR ";
|
|
detokenisedStream += Common::String::format("(%d, v%d)", (int)tokenisedCondition[bytePointer], (int)tokenisedCondition[bytePointer + 1]);
|
|
bytePointer += 2;
|
|
numberOfArguments = 0;
|
|
break;
|
|
}
|
|
|
|
// if there are any regular arguments to add, do so
|
|
if (numberOfArguments) {
|
|
for (uint8 argumentNumber = 0; argumentNumber < numberOfArguments; argumentNumber++) {
|
|
detokenisedStream += Common::String::format("%d", (int)tokenisedCondition[bytePointer]);
|
|
bytePointer++;
|
|
|
|
if (argumentNumber < numberOfArguments - 1)
|
|
detokenisedStream += ", ";
|
|
}
|
|
|
|
detokenisedStream += ")";
|
|
}
|
|
|
|
// throw in a newline
|
|
detokenisedStream += "\n";
|
|
}
|
|
|
|
return (new Common::String(detokenisedStream));
|
|
}
|