dmolony-DiskBrowser/src/com/bytezone/diskbrowser/infocom/Instruction.java

547 lines
14 KiB
Java
Raw Normal View History

2015-06-01 09:35:51 +00:00
package com.bytezone.diskbrowser.infocom;
import java.util.ArrayList;
import java.util.List;
2016-02-24 21:11:14 +00:00
import com.bytezone.diskbrowser.utilities.HexFormatter;
2015-06-01 09:35:51 +00:00
class Instruction
{
2020-01-13 00:05:32 +00:00
static int version = 3;
2019-12-29 02:52:06 +00:00
final Opcode opcode;
2020-01-07 04:23:55 +00:00
final int startPtr;
2019-12-29 02:52:06 +00:00
private byte[] buffer;
2019-04-22 04:35:50 +00:00
// List<ZString> abbreviations;
2019-12-29 02:52:06 +00:00
private Header header;
2019-04-22 04:35:50 +00:00
enum OperandType
{
2020-01-25 06:34:41 +00:00
VAR_SP, VAR_LOCAL, VAR_GLOBAL, BYTE, WORD, ARG_BRANCH, ARG_STRING, OBJECT
2019-04-22 04:35:50 +00:00
}
static final String[] name2OP =
{ "*bad*", "je", "jl", "jg", "dec_chk", "inc_chk", "jin", "test", "or", "and",
"test_attr", "set_attr", "clear_attr", "store", "insert_obj", "loadw", "loadb",
"get_prop", "get_prop_addr", "get_next_prop", "add", "sub", "mul", "div", "mod",
"call_2s", "call_2n", "set_colour", "throw", "*bad*", "*bad*", "*bad*" };
static final String[] name1OP =
{ "jz", "get_sibling", "get_child", "get_parent", "get_prop_len", "inc", "dec",
"print_addr", "call_ls", "remove_obj", "print_obj", "ret", "jump", "print_paddr",
"load", "not" };
static final String[] name0OP =
{ "rtrue", "rfalse", "print", "print_ret", "nop", "save", "restore", "restart",
"ret_popped", "pop", "quit", "new_line", "show_status", "verify", "", "piracy" };
static final String[] nameVAR =
{ "call", "storew", "storeb", "put_prop", "sread", "print_char", "print_num",
"random", "push", "pull", "split_window", "set_window", "call_vs2",
"erase_window", "erase_line", "set_cursor", "get_cursor", "set_text_style",
"buffer_mode", "output_stream", "input_stream", "sound_effect", "read_char",
"scan_table", "not", "call_vn", "call_vn2", "tokenise", "encode_text",
"copy_table", "print_table", "check_arg" };
2019-12-29 02:52:06 +00:00
Instruction (byte[] buffer, int ptr, Header header)
2019-04-22 04:35:50 +00:00
{
this.buffer = buffer;
this.startPtr = ptr;
this.header = header;
byte b1 = buffer[ptr];
2020-01-13 00:05:32 +00:00
int type = (b1 & 0xC0) >>> 6;
switch (type)
2019-04-22 04:35:50 +00:00
{
2020-01-13 00:05:32 +00:00
case 0x03: // 11 - variable
if ((b1 & 0x20) == 0x20)
opcode = new OpcodeVar (buffer, ptr);
else
opcode = new Opcode2OPVar (buffer, ptr);
break;
case 0x02: // 10 - extended or short
if (b1 == 0xBE && version >= 5)
opcode = null;
else if ((b1 & 0x30) == 0x30)
opcode = new Opcode0OP (buffer, ptr);
else
opcode = new Opcode1OP (buffer, ptr);
break;
default: // 00, 01 - long
opcode = new Opcode2OPLong (buffer, ptr);
2019-04-22 04:35:50 +00:00
}
}
2019-12-29 02:52:06 +00:00
int length ()
2019-04-22 04:35:50 +00:00
{
return opcode.length ();
}
2019-12-29 02:52:06 +00:00
boolean isReturn ()
2019-04-22 04:35:50 +00:00
{
return opcode.isReturn;
}
2019-12-29 02:52:06 +00:00
boolean isPrint ()
2019-04-22 04:35:50 +00:00
{
return opcode.string != null;
}
2019-12-29 02:52:06 +00:00
boolean isCall ()
2019-04-22 04:35:50 +00:00
{
return opcode.isCall;
}
2019-12-29 02:52:06 +00:00
boolean isJump ()
2019-04-22 04:35:50 +00:00
{
// could use jumpTarget != 0
return (opcode instanceof Opcode1OP && opcode.opcodeNumber == 12);
}
2019-12-29 02:52:06 +00:00
boolean isBranch ()
2019-04-22 04:35:50 +00:00
{
return opcode.branch != null;
}
2019-12-29 02:52:06 +00:00
boolean isStore ()
2019-04-22 04:35:50 +00:00
{
return opcode.store != null;
}
2019-12-29 02:52:06 +00:00
int target ()
2019-04-22 04:35:50 +00:00
{
2020-01-07 04:23:55 +00:00
return isBranch () ? opcode.branch.target : isJump () ? opcode.jumpTarget : 0;
2019-04-22 04:35:50 +00:00
}
2020-01-07 04:23:55 +00:00
String dump ()
{
return String.format ("%05X : %s", startPtr,
HexFormatter.getHexString (buffer, startPtr, opcode.length ()));
}
String getHex ()
2019-04-22 04:35:50 +00:00
{
int max = opcode.length ();
String extra = "";
if (max > 9)
{
max = 9;
extra = "..";
}
2019-12-29 02:52:06 +00:00
2019-04-22 04:35:50 +00:00
String hex = HexFormatter.getHexString (buffer, startPtr, max);
2020-01-07 04:23:55 +00:00
return String.format ("%05X : %-26s%2s", startPtr, hex, extra);
}
2019-12-29 02:52:06 +00:00
2020-01-07 04:23:55 +00:00
@Override
public String toString ()
{
return opcode.toString ();
2019-04-22 04:35:50 +00:00
}
abstract class Opcode
{
int opcodeNumber;
int opcodeLength;
2020-01-25 06:34:41 +00:00
List<Operand> operands = new ArrayList<> ();
2019-04-22 04:35:50 +00:00
int totalOperandLength;
ArgumentBranch branch;
ArgumentString string;
OperandVariable store;
boolean isReturn, isCall, isExit;
int jumpTarget;
int callTarget;
@Override
public String toString ()
{
StringBuilder text = new StringBuilder ();
2019-12-29 02:52:06 +00:00
text.append (String.format ("%-12s", opcodeName ()));
2019-04-22 04:35:50 +00:00
if (jumpTarget != 0)
text.append (String.format (" L:%05X", jumpTarget));
else if (isCall)
{
text.append (String.format (" R:%05X (", callTarget));
int count = 0;
for (Operand op : operands)
if (count++ > 0)
text.append (op + ", ");
if (operands.size () > 1)
text.delete (text.length () - 2, text.length ());
2020-01-25 06:34:41 +00:00
text.append (") -> " + store);
2019-04-22 04:35:50 +00:00
}
else
{
for (Operand op : operands)
text.append (" " + op);
if (branch != null)
text.append (branch);
if (store != null)
2020-01-25 06:34:41 +00:00
text.append (" -> " + store);
2019-04-22 04:35:50 +00:00
if (string != null)
text.append (" \"" + string + "\"");
}
return text.toString ();
}
2019-12-29 02:52:06 +00:00
int length ()
2019-04-22 04:35:50 +00:00
{
int length = totalOperandLength + opcodeLength;
if (branch != null)
length += branch.length;
if (store != null)
length += store.length;
if (string != null)
length += string.length;
return length;
}
2019-12-29 02:52:06 +00:00
abstract String opcodeName ();
2019-04-22 04:35:50 +00:00
private void addOperand (Operand operand)
{
operands.add (operand);
totalOperandLength += operand.length;
}
2019-12-29 02:52:06 +00:00
void addOperand (byte[] buffer, int ptr, boolean bit1, boolean bit2)
2019-04-22 04:35:50 +00:00
{
int offset = ptr + totalOperandLength;
if (bit1)
{
if (!bit2)
addOperand (new OperandVariable (buffer[offset])); // %10
}
else if (bit2)
addOperand (new OperandByte (buffer[offset])); // %01
else
addOperand (new OperandWord (header.getWord (offset))); // %00
}
2019-12-29 02:52:06 +00:00
void addOperand (byte[] buffer, int ptr, boolean bit)
2019-04-22 04:35:50 +00:00
{
int address = ptr + totalOperandLength;
if (address >= buffer.length)
{
System.out.println ("Illegal byte address : " + address);
return;
}
if (bit)
addOperand (new OperandVariable (buffer[address]));
else
addOperand (new OperandByte (buffer[address]));
}
2019-12-29 02:52:06 +00:00
void setVariableOperands (int ptr)
2019-04-22 04:35:50 +00:00
{
int value = buffer[ptr + 1] & 0xFF;
for (int i = 0; i < 4; i++)
{
boolean bit1 = ((value & 0x80) == 0x80);
boolean bit2 = ((value & 0x40) == 0x40);
if (bit1 && bit2)
break;
addOperand (buffer, ptr + 2, bit1, bit2);
value <<= 2;
}
}
2019-12-29 02:52:06 +00:00
void setStore (byte[] buffer)
2019-04-22 04:35:50 +00:00
{
store = new OperandVariable (buffer[startPtr + totalOperandLength + opcodeLength]);
}
2019-12-29 02:52:06 +00:00
void setBranch (byte[] buffer)
2019-04-22 04:35:50 +00:00
{
int offset = startPtr + totalOperandLength + (store == null ? 0 : 1) + opcodeLength;
2019-12-29 02:52:06 +00:00
if ((buffer[offset] & 0x40) != 0)
2019-04-22 04:35:50 +00:00
branch = new ArgumentBranch (buffer[offset], offset);
else
branch = new ArgumentBranch (header.getWord (offset), offset);
}
2019-12-29 02:52:06 +00:00
void setZString (byte[] buffer)
2019-04-22 04:35:50 +00:00
{
int offset = startPtr + totalOperandLength + opcodeLength;
string = new ArgumentString (buffer, offset);
}
}
class Opcode0OP extends Opcode
{
2019-12-29 02:52:06 +00:00
Opcode0OP (byte[] buffer, int ptr)
2019-04-22 04:35:50 +00:00
{
opcodeNumber = buffer[ptr] & 0x0F;
opcodeLength = 1;
if (opcodeNumber == 5 || opcodeNumber == 6 || opcodeNumber == 13)
setBranch (buffer);
if (opcodeNumber == 0 || opcodeNumber == 1 || opcodeNumber == 3
|| opcodeNumber == 8)
isReturn = true;
if (opcodeNumber == 2 || opcodeNumber == 3)
setZString (buffer);
if (opcodeNumber == 7 || opcodeNumber == 10)
isExit = true;
}
@Override
public String opcodeName ()
{
return name0OP[opcodeNumber];
}
}
class Opcode1OP extends Opcode
{
2019-12-29 02:52:06 +00:00
Opcode1OP (byte[] buffer, int ptr)
2019-04-22 04:35:50 +00:00
{
opcodeNumber = buffer[ptr] & 0x0F;
opcodeLength = 1;
boolean bit1 = ((buffer[ptr] & 0x20) == 0x20);
boolean bit2 = ((buffer[ptr] & 0x10) == 0x10);
addOperand (buffer, ptr + 1, bit1, bit2);
if ((opcodeNumber >= 1 && opcodeNumber <= 4) || opcodeNumber == 8
|| opcodeNumber == 14 || opcodeNumber == 15)
setStore (buffer);
if (opcodeNumber <= 2)
setBranch (buffer);
if (opcodeNumber == 12)
jumpTarget = (short) operands.get (0).value + startPtr - 2 + length ();
if (opcodeNumber == 11)
isReturn = true;
}
@Override
public String opcodeName ()
{
return name1OP[opcodeNumber];
}
}
abstract class Opcode2OP extends Opcode
{
2019-12-29 02:52:06 +00:00
Opcode2OP ()
2019-04-22 04:35:50 +00:00
{
opcodeLength = 1;
}
2019-12-29 02:52:06 +00:00
void setArguments (byte[] buffer)
2019-04-22 04:35:50 +00:00
{
if ((opcodeNumber >= 1 && opcodeNumber <= 7) || opcodeNumber == 10)
setBranch (buffer);
else if ((opcodeNumber >= 15 && opcodeNumber <= 25) || opcodeNumber == 8
|| opcodeNumber == 9)
setStore (buffer);
}
@Override
public String opcodeName ()
{
return name2OP[opcodeNumber];
}
}
class Opcode2OPLong extends Opcode2OP
{
2019-12-29 02:52:06 +00:00
Opcode2OPLong (byte[] buffer, int ptr)
2019-04-22 04:35:50 +00:00
{
opcodeNumber = buffer[ptr] & 0x1F;
boolean bit1 = ((buffer[ptr] & 0x40) == 0x40);
boolean bit2 = ((buffer[ptr] & 0x20) == 0x20);
2020-01-25 06:34:41 +00:00
if (opcodeNumber == 0x0D) // store (variable) value
{
addOperand (buffer, ptr + 1, true); // always a variable
addOperand (buffer, ptr + 1, bit2);
}
else
{
addOperand (buffer, ptr + 1, bit1);
addOperand (buffer, ptr + 1, bit2);
}
2019-04-22 04:35:50 +00:00
setArguments (buffer);
}
}
class Opcode2OPVar extends Opcode2OP
{
2019-12-29 02:52:06 +00:00
Opcode2OPVar (byte[] buffer, int ptr)
2019-04-22 04:35:50 +00:00
{
opcodeNumber = buffer[ptr] & 0x1F;
opcodeLength = 2;
setVariableOperands (ptr);
setArguments (buffer);
}
}
class OpcodeVar extends Opcode
{
2019-12-29 02:52:06 +00:00
OpcodeVar (byte[] buffer, int ptr)
2019-04-22 04:35:50 +00:00
{
opcodeNumber = buffer[ptr] & 0x1F;
opcodeLength = 2;
setVariableOperands (ptr);
if (opcodeNumber == 0 || opcodeNumber == 7)
setStore (buffer);
2020-01-07 04:23:55 +00:00
2020-01-25 06:34:41 +00:00
if (opcodeNumber == 0) // call routine
2019-04-22 04:35:50 +00:00
{
isCall = true;
callTarget = operands.get (0).value * 2;
}
2020-01-25 06:34:41 +00:00
if (opcodeNumber == 3) // put prop object propertyValue
{
// first parameter is the object id
operands.get (0).operandType = OperandType.OBJECT;
}
2019-04-22 04:35:50 +00:00
}
@Override
public String opcodeName ()
{
return nameVAR[opcodeNumber];
}
}
abstract class Operand
{
int length;
int value;
OperandType operandType;
2020-01-25 06:34:41 +00:00
@Override
public String toString ()
{
switch (operandType)
{
case VAR_SP:
return ("(SP)");
case VAR_LOCAL:
return (String.format ("L%02X", value - 1));
case VAR_GLOBAL:
return String.format ("G%02X", (value - 16));
case BYTE:
return String.format ("#%02X", value);
case WORD:
return String.format ("#%04X", value);
case OBJECT:
return "\"" + header.objectManager.getObject (value - 1).getName () + "\"";
default:
return "*** Illegal ***";
}
}
2019-04-22 04:35:50 +00:00
}
class OperandWord extends Operand
{
2019-12-29 02:52:06 +00:00
OperandWord (int value)
2019-04-22 04:35:50 +00:00
{
this.value = value;
length = 2;
operandType = OperandType.WORD;
}
}
class OperandByte extends Operand
{
2019-12-29 02:52:06 +00:00
OperandByte (byte value)
2019-04-22 04:35:50 +00:00
{
this.value = value & 0xFF;
length = 1;
operandType = OperandType.BYTE;
}
}
class OperandVariable extends Operand
{
2019-12-29 02:52:06 +00:00
OperandVariable (byte value)
2019-04-22 04:35:50 +00:00
{
this.value = value & 0xFF;
length = 1;
2020-01-07 04:23:55 +00:00
if (this.value == 0)
2019-12-29 02:52:06 +00:00
operandType = OperandType.VAR_SP;
2020-01-07 04:23:55 +00:00
else if (this.value <= 15)
2019-04-22 04:35:50 +00:00
operandType = OperandType.VAR_LOCAL;
else
operandType = OperandType.VAR_GLOBAL;
}
}
class ArgumentBranch extends Operand
{
2019-12-29 02:52:06 +00:00
private int target;
private boolean branchOnTrue;
2019-04-22 04:35:50 +00:00
2019-12-29 02:52:06 +00:00
ArgumentBranch (byte value, int offset)
2019-04-22 04:35:50 +00:00
{
2019-12-29 02:52:06 +00:00
branchOnTrue = (value & 0x80) != 0;
int val = value & 0x3F; // 0 - 63
2019-04-22 04:35:50 +00:00
if (val <= 1)
target = val;
else
target = val + offset - 1;
length = 1;
operandType = OperandType.ARG_BRANCH;
}
2019-12-29 02:52:06 +00:00
ArgumentBranch (int value, int offset)
2019-04-22 04:35:50 +00:00
{
2019-12-29 02:52:06 +00:00
branchOnTrue = (value & 0x8000) != 0;
2020-01-07 04:23:55 +00:00
int val = ((value & 0x3FFF) << 18) >> 18; // signed 14-bit number
2019-12-29 02:52:06 +00:00
2019-04-22 04:35:50 +00:00
target = val + offset;
length = 2;
operandType = OperandType.ARG_BRANCH;
}
@Override
public String toString ()
{
StringBuilder text = new StringBuilder ();
text.append (" [" + (branchOnTrue ? "true" : "false") + "] ");
if (target == 0 || target == 1)
text.append (target == 0 ? "RFALSE" : "RTRUE");
else
text.append (String.format ("%05X", target));
return text.toString ();
}
}
class ArgumentString extends Operand
{
2019-12-29 02:52:06 +00:00
private ZString text;
private int startPtr;
private byte[] buffer;
2019-04-22 04:35:50 +00:00
2019-12-29 02:52:06 +00:00
ArgumentString (byte[] buffer, int offset)
2019-04-22 04:35:50 +00:00
{
this.buffer = buffer;
text = new ZString (header, offset);
length = text.length;
operandType = OperandType.ARG_STRING;
}
@Override
public String toString ()
{
return text.value;
}
}
2015-06-01 09:35:51 +00:00
}