449 lines
16 KiB
C++
449 lines
16 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM 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/>.
|
|
*
|
|
*/
|
|
|
|
// Based on Phantasma code by Thomas Harte (2013),
|
|
// available at https://github.com/TomHarte/Phantasma/ (MIT)
|
|
// which was implemented based on John Elliott's reverse engineering of Driller (2001)
|
|
// http://www.seasip.demon.co.uk/ZX/Driller/
|
|
|
|
#include "freescape/freescape.h"
|
|
#include "freescape/language/8bitDetokeniser.h"
|
|
|
|
namespace Freescape {
|
|
|
|
uint8 k8bitMaxVariable = 64;
|
|
uint8 k8bitMaxShield = 64;
|
|
uint8 k8bitMaxEnergy = 64;
|
|
|
|
Common::String detokenise8bitCondition(Common::Array<uint8> &tokenisedCondition, FCLInstructionVector &instructions, bool multipleConditionals) {
|
|
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", "if collided? then" or "if timer? then",
|
|
// and we'll want to start that from the top
|
|
FCLInstructionVector *conditionalInstructions = new FCLInstructionVector();
|
|
FCLInstruction currentInstruction = FCLInstruction(Token::UNKNOWN);
|
|
|
|
// this lookup table tells us how many argument bytes to read per opcode
|
|
uint8 argumentsRequiredByOpcode[49] =
|
|
{0, 3, 1, 1, 1, 1, 2, 2,
|
|
2, 1, 1, 2, 1, 1, 2, 1,
|
|
1, 2, 2, 1, 2, 0, 0, 0,
|
|
1, 1, 0, 1, 1, 1, 1, 1,
|
|
2, 2, 1, 1, 0, 1, 0, 0,
|
|
0, 0, 0, 0, 0, 0, 2, 2,
|
|
1};
|
|
|
|
if (sizeOfTokenisedContent > 0)
|
|
detokenisedStream += Common::String::format("CONDITION FLAG: %x\n", tokenisedCondition[0]);
|
|
uint16 newConditional = 0;
|
|
uint16 oldConditional = 0;
|
|
|
|
while (bytePointer < sizeOfTokenisedContent) {
|
|
// get the conditional type of the next operation
|
|
uint8 conditionalByte = tokenisedCondition[bytePointer] & 0xc0;
|
|
//detokenisedStream += Common::String::format("CONDITION FLAG: %x\n", conditionalByte);
|
|
newConditional = 0;
|
|
|
|
if (conditionalByte == 0x40)
|
|
newConditional = kConditionalTimeout;
|
|
else if (conditionalByte == 0x80)
|
|
newConditional = kConditionalShot;
|
|
else if (conditionalByte == 0xc0)
|
|
newConditional = kConditionalActivated;
|
|
else
|
|
newConditional = kConditionalCollided;
|
|
|
|
// if the conditional type has changed then end the old conditional,
|
|
// if we were in one, and begin a new one
|
|
if (bytePointer == 0 || newConditional != oldConditional) {
|
|
oldConditional = newConditional;
|
|
FCLInstruction branch;
|
|
branch = FCLInstruction(Token::CONDITIONAL);
|
|
|
|
if (bytePointer > 0) {
|
|
detokenisedStream += "ENDIF\n";
|
|
// Allocate the next vector of instructions
|
|
conditionalInstructions = new FCLInstructionVector();
|
|
}
|
|
|
|
branch.setBranches(conditionalInstructions, nullptr);
|
|
branch.setSource(oldConditional); // conditional flag
|
|
instructions.push_back(branch);
|
|
|
|
detokenisedStream += "IF ";
|
|
|
|
if (oldConditional & kConditionalShot)
|
|
detokenisedStream += "SHOT? ";
|
|
else if (oldConditional & kConditionalTimeout)
|
|
detokenisedStream += "TIMER? ";
|
|
else if (oldConditional & kConditionalCollided)
|
|
detokenisedStream += "COLLIDED? ";
|
|
else if (oldConditional & kConditionalActivated)
|
|
detokenisedStream += "ACTIVATED? ";
|
|
else
|
|
error("Invalid conditional: %x", oldConditional);
|
|
|
|
detokenisedStream += "THEN\n";
|
|
}
|
|
|
|
// get the actual operation
|
|
uint16 opcode = tokenisedCondition[bytePointer] & 0x3f;
|
|
bytePointer++;
|
|
|
|
// figure out how many argument bytes we're going to need,
|
|
// check we have enough bytes left to read
|
|
if (opcode > 48) {
|
|
debugC(1, kFreescapeDebugParser, "%s", detokenisedStream.c_str());
|
|
if (opcode != 0x3f)
|
|
error("ERROR: failed to read opcode: %x", opcode);
|
|
break;
|
|
}
|
|
|
|
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 += " > ";
|
|
break;
|
|
|
|
case 0:
|
|
detokenisedStream += "NOP ";
|
|
currentInstruction = FCLInstruction(Token::NOP);
|
|
conditionalInstructions->push_back(currentInstruction);
|
|
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
|
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);
|
|
currentInstruction = FCLInstruction(Token::ADDVAR);
|
|
currentInstruction.setSource(k8bitVariableScore);
|
|
currentInstruction.setDestination(additionValue);
|
|
conditionalInstructions->push_back(currentInstruction);
|
|
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
|
bytePointer += 3;
|
|
numberOfArguments = 0;
|
|
} break;
|
|
case 2: // add one-byte value to energy
|
|
detokenisedStream += "ADDVAR ";
|
|
detokenisedStream += Common::String::format("(%d, v%d)", (int8)tokenisedCondition[bytePointer], k8bitVariableEnergy);
|
|
currentInstruction = FCLInstruction(Token::ADDVAR);
|
|
currentInstruction.setSource(k8bitVariableEnergy);
|
|
currentInstruction.setDestination((int8)tokenisedCondition[bytePointer]);
|
|
conditionalInstructions->push_back(currentInstruction);
|
|
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
|
bytePointer++;
|
|
numberOfArguments = 0;
|
|
break;
|
|
case 19: // add one-byte value to shield
|
|
detokenisedStream += "ADDVAR ";
|
|
detokenisedStream += Common::String::format("(%d, v%d)", (int8)tokenisedCondition[bytePointer], k8bitVariableShield);
|
|
currentInstruction = FCLInstruction(Token::ADDVAR);
|
|
currentInstruction.setSource(k8bitVariableShield);
|
|
currentInstruction.setDestination((int8)tokenisedCondition[bytePointer]);
|
|
conditionalInstructions->push_back(currentInstruction);
|
|
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
|
bytePointer++;
|
|
numberOfArguments = 0;
|
|
break;
|
|
|
|
case 6:
|
|
case 3:
|
|
detokenisedStream += "TOGVIS (";
|
|
currentInstruction = FCLInstruction(Token::TOGVIS);
|
|
currentInstruction.setSource(0);
|
|
currentInstruction.setDestination(0);
|
|
break; // these all come in unary and binary versions,
|
|
case 7:
|
|
case 4:
|
|
detokenisedStream += "VIS (";
|
|
currentInstruction = FCLInstruction(Token::VIS);
|
|
currentInstruction.setSource(0);
|
|
currentInstruction.setDestination(0);
|
|
break; // hence each getting two case statement entries
|
|
case 8:
|
|
case 5:
|
|
detokenisedStream += "INVIS (";
|
|
currentInstruction = FCLInstruction(Token::INVIS);
|
|
currentInstruction.setSource(0);
|
|
currentInstruction.setDestination(0);
|
|
break;
|
|
|
|
case 9:
|
|
detokenisedStream += "ADDVAR (1, v";
|
|
detokenisedStream += Common::String::format("%d)", tokenisedCondition[bytePointer]);
|
|
currentInstruction = FCLInstruction(Token::ADDVAR);
|
|
currentInstruction.setSource(tokenisedCondition[bytePointer]);
|
|
currentInstruction.setDestination(1);
|
|
conditionalInstructions->push_back(currentInstruction);
|
|
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
|
bytePointer++;
|
|
numberOfArguments = 0;
|
|
break;
|
|
case 10:
|
|
detokenisedStream += "SUBVAR (1, v";
|
|
detokenisedStream += Common::String::format("%d)", tokenisedCondition[bytePointer]);
|
|
currentInstruction = FCLInstruction(Token::SUBVAR);
|
|
currentInstruction.setSource(tokenisedCondition[bytePointer]);
|
|
currentInstruction.setDestination(1);
|
|
conditionalInstructions->push_back(currentInstruction);
|
|
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
|
bytePointer++;
|
|
numberOfArguments = 0;
|
|
break;
|
|
|
|
case 11: // end condition if a variable doesn't have a particular value
|
|
detokenisedStream += "IF VAR!=? ";
|
|
detokenisedStream += Common::String::format("(v%d, %d)", (int)tokenisedCondition[bytePointer], (int)tokenisedCondition[bytePointer + 1]);
|
|
detokenisedStream += " THEN END ENDIF";
|
|
currentInstruction = FCLInstruction(Token::VARNOTEQ);
|
|
currentInstruction.setSource(tokenisedCondition[bytePointer]);
|
|
currentInstruction.setDestination(tokenisedCondition[bytePointer + 1]);
|
|
conditionalInstructions->push_back(currentInstruction);
|
|
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
|
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";
|
|
currentInstruction = FCLInstruction(Token::BITNOTEQ);
|
|
currentInstruction.setSource(tokenisedCondition[bytePointer]);
|
|
currentInstruction.setDestination(tokenisedCondition[bytePointer + 1]);
|
|
conditionalInstructions->push_back(currentInstruction);
|
|
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
|
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";
|
|
currentInstruction = FCLInstruction(Token::INVISQ);
|
|
currentInstruction.setSource(tokenisedCondition[bytePointer]);
|
|
currentInstruction.setDestination(true); // invisible
|
|
conditionalInstructions->push_back(currentInstruction);
|
|
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
|
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";
|
|
currentInstruction = FCLInstruction(Token::INVISQ);
|
|
currentInstruction.setSource(tokenisedCondition[bytePointer]);
|
|
currentInstruction.setDestination(false); // visible
|
|
conditionalInstructions->push_back(currentInstruction);
|
|
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
|
bytePointer++;
|
|
numberOfArguments = 0;
|
|
break;
|
|
|
|
case 32: // end condition if an object is visible in another area
|
|
detokenisedStream += "IF RINVIS? ";
|
|
detokenisedStream += Common::String::format("(%d, %d)", (int)tokenisedCondition[bytePointer], (int)tokenisedCondition[bytePointer + 1]);
|
|
detokenisedStream += " THEN END ENDIF";
|
|
currentInstruction = FCLInstruction(Token::INVISQ);
|
|
currentInstruction.setSource(tokenisedCondition[bytePointer]);
|
|
currentInstruction.setAdditional(tokenisedCondition[bytePointer + 1]);
|
|
currentInstruction.setDestination(true); // invisible
|
|
conditionalInstructions->push_back(currentInstruction);
|
|
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
|
bytePointer += 2;
|
|
numberOfArguments = 0;
|
|
break;
|
|
|
|
case 33: // end condition if an object is invisible in another area
|
|
detokenisedStream += "IF RVIS? ";
|
|
detokenisedStream += Common::String::format("(%d, %d)", (int)tokenisedCondition[bytePointer], (int)tokenisedCondition[bytePointer + 1]);
|
|
detokenisedStream += " THEN END ENDIF";
|
|
currentInstruction = FCLInstruction(Token::INVISQ);
|
|
currentInstruction.setSource(tokenisedCondition[bytePointer]);
|
|
currentInstruction.setAdditional(tokenisedCondition[bytePointer + 1]);
|
|
currentInstruction.setDestination(false); // visible
|
|
conditionalInstructions->push_back(currentInstruction);
|
|
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
|
bytePointer += 2;
|
|
numberOfArguments = 0;
|
|
break;
|
|
|
|
case 34: // show a message on screen
|
|
detokenisedStream += "PRINT (";
|
|
currentInstruction = FCLInstruction(Token::PRINT);
|
|
break;
|
|
|
|
case 37:
|
|
detokenisedStream += "STARTANIM (";
|
|
currentInstruction = FCLInstruction(Token::STARTANIM);
|
|
break;
|
|
|
|
case 12:
|
|
detokenisedStream += "SETBIT (";
|
|
currentInstruction = FCLInstruction(Token::SETBIT);
|
|
break;
|
|
case 13:
|
|
detokenisedStream += "CLRBIT (";
|
|
currentInstruction = FCLInstruction(Token::CLEARBIT);
|
|
break;
|
|
|
|
case 15:
|
|
detokenisedStream += "SOUND (";
|
|
currentInstruction = FCLInstruction(Token::SOUND);
|
|
currentInstruction.setAdditional(false);
|
|
break;
|
|
case 17:
|
|
case 16:
|
|
detokenisedStream += "DESTROY (";
|
|
currentInstruction = FCLInstruction(Token::DESTROY);
|
|
break;
|
|
case 18:
|
|
detokenisedStream += "GOTO (";
|
|
currentInstruction = FCLInstruction(Token::GOTO);
|
|
break;
|
|
|
|
case 21:
|
|
detokenisedStream += "SWAPJET";
|
|
currentInstruction = FCLInstruction(Token::SWAPJET);
|
|
conditionalInstructions->push_back(currentInstruction);
|
|
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
|
bytePointer++;
|
|
numberOfArguments = 0;
|
|
break;
|
|
case 26:
|
|
detokenisedStream += "REDRAW";
|
|
currentInstruction = FCLInstruction(Token::REDRAW);
|
|
conditionalInstructions->push_back(currentInstruction);
|
|
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
|
break;
|
|
case 27:
|
|
detokenisedStream += "DELAY (";
|
|
currentInstruction = FCLInstruction(Token::DELAY);
|
|
break;
|
|
case 28:
|
|
detokenisedStream += "SYNCSND (";
|
|
currentInstruction = FCLInstruction(Token::SOUND);
|
|
currentInstruction.setAdditional(true);
|
|
break;
|
|
case 29:
|
|
detokenisedStream += "TOGGLEBIT (";
|
|
currentInstruction = FCLInstruction(Token::TOGGLEBIT);
|
|
break;
|
|
|
|
case 25:
|
|
// this should toggle border colour or the room palette
|
|
detokenisedStream += "SPFX (";
|
|
currentInstruction = FCLInstruction(Token::SPFX);
|
|
currentInstruction.setSource(tokenisedCondition[bytePointer] >> 4);
|
|
currentInstruction.setDestination(tokenisedCondition[bytePointer] & 0xf);
|
|
detokenisedStream += Common::String::format("%d, %d)", currentInstruction._source, currentInstruction._destination);
|
|
conditionalInstructions->push_back(currentInstruction);
|
|
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
|
bytePointer++;
|
|
numberOfArguments = 0;
|
|
break;
|
|
|
|
case 20:
|
|
detokenisedStream += "SETVAR (v";
|
|
currentInstruction = FCLInstruction(Token::SETVAR);
|
|
break;
|
|
|
|
case 35:
|
|
detokenisedStream += "SCREEN (";
|
|
currentInstruction = FCLInstruction(Token::SCREEN);
|
|
break;
|
|
|
|
case 44:
|
|
detokenisedStream += "ELSE ";
|
|
currentInstruction = FCLInstruction(Token::ELSE);
|
|
conditionalInstructions->push_back(currentInstruction);
|
|
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
|
numberOfArguments = 0;
|
|
break;
|
|
|
|
case 45:
|
|
detokenisedStream += "ENDIF ";
|
|
currentInstruction = FCLInstruction(Token::ENDIF);
|
|
conditionalInstructions->push_back(currentInstruction);
|
|
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
|
numberOfArguments = 0;
|
|
break;
|
|
|
|
case 46:
|
|
detokenisedStream += "IFGTE (v";
|
|
currentInstruction = FCLInstruction(Token::IFGTEQ);
|
|
break;
|
|
|
|
case 47:
|
|
detokenisedStream += "IFLTE (v";
|
|
currentInstruction = FCLInstruction(Token::IFGTEQ);
|
|
break;
|
|
|
|
case 48:
|
|
detokenisedStream += "EXECUTE (";
|
|
currentInstruction = FCLInstruction(Token::EXECUTE);
|
|
break;
|
|
}
|
|
|
|
// if there are any regular arguments to add, do so
|
|
if (numberOfArguments) {
|
|
for (uint8 argumentNumber = 0; argumentNumber < numberOfArguments; argumentNumber++) {
|
|
if (argumentNumber == 0)
|
|
currentInstruction.setSource(tokenisedCondition[bytePointer]);
|
|
else if (argumentNumber == 1)
|
|
currentInstruction.setDestination(tokenisedCondition[bytePointer]);
|
|
else
|
|
error("Unexpected number of arguments!");
|
|
|
|
detokenisedStream += Common::String::format("%d", (int)tokenisedCondition[bytePointer]);
|
|
bytePointer++;
|
|
|
|
if (argumentNumber < numberOfArguments - 1)
|
|
detokenisedStream += ", ";
|
|
}
|
|
|
|
detokenisedStream += ")";
|
|
assert(currentInstruction.getType() != Token::UNKNOWN);
|
|
conditionalInstructions->push_back(currentInstruction);
|
|
currentInstruction = FCLInstruction(Token::UNKNOWN);
|
|
}
|
|
|
|
// throw in a newline
|
|
detokenisedStream += "\n";
|
|
}
|
|
|
|
return detokenisedStream;
|
|
}
|
|
|
|
} // End of namespace Freescape
|