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

251 lines
7.7 KiB
Java

package com.bytezone.diskbrowser.infocom;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import com.bytezone.diskbrowser.infocom.Instruction.Operand;
import com.bytezone.diskbrowser.infocom.Instruction.OperandType;
import com.bytezone.diskbrowser.utilities.HexFormatter;
// -----------------------------------------------------------------------------------//
class Routine extends InfocomAbstractFile
implements Iterable<Instruction>, Comparable<Routine>
// -----------------------------------------------------------------------------------//
{
int startPtr, length, strings, locals;
List<Parameter> parameters = new ArrayList<> ();
List<Instruction> instructions = new ArrayList<> ();
List<Integer> calls = new ArrayList<> ();
List<Integer> calledBy = new ArrayList<> ();
List<Integer> actions = new ArrayList<> (); // not used yet
List<Integer> targets = new ArrayList<> ();
// ---------------------------------------------------------------------------------//
Routine (int ptr, Header header, int caller)
// ---------------------------------------------------------------------------------//
{
super (String.format ("Routine %05X", ptr), header.buffer);
locals = buffer[ptr] & 0xFF;
if (locals > 15)
{
System.out.println ("Too many locals: " + locals);
return; // startPtr will be zero
}
startPtr = ptr++; // also used to flag a valid routine
if (!calledBy.contains (caller))
calledBy.add (caller);
for (int i = 1; i <= locals; i++)
{
parameters.add (new Parameter (i, header.getWord (ptr))); // default values
ptr += 2;
}
while (true)
{
if (buffer[ptr] == 0 || buffer[ptr] == 0x20 || buffer[ptr] == 0x40)
{
System.out.println ("Bad instruction found : " + ptr);
return;
}
Instruction instruction = new Instruction (buffer, ptr, header);
instructions.add (instruction);
if (instruction.isCall () && instruction.opcode.callTarget > 0) // not stack-based
calls.add (instruction.opcode.callTarget);
if (instruction.isPrint ())
strings++;
if (instruction.isBranch () && !targets.contains (instruction.target ()))
targets.add (instruction.target ());
if (instruction.isJump () && !targets.contains (instruction.target ()))
targets.add (instruction.target ());
for (Operand operand : instruction.opcode.operands)
if (operand.operandType == OperandType.VAR_GLOBAL)
header.globals.addRoutine (this, operand);
ptr += instruction.length (); // point to next instruction
if (isTarget (ptr))
continue;
// is it a backwards jump?
if (instruction.isJump () && instruction.target () < ptr)
break;
// is it an unconditional return?
if (instruction.isReturn ())
break;
}
length = ptr - startPtr;
hexBlocks.add (new HexBlock (startPtr, length, null));
// check for branches outside this routine
if (true)
{
int endPtr = startPtr + length;
for (Instruction instruction : instructions)
{
int target = instruction.target () > 256 ? instruction.target ()
: instruction.opcode.jumpTarget > 256 ? instruction.opcode.jumpTarget : 0;
if (target == 0)
continue;
if (instruction.isBranch () && (target > endPtr || target < startPtr))
System.out.println (instruction);
if (instruction.isJump () && (target > endPtr || target < startPtr))
System.out.println (instruction);
}
}
}
// ---------------------------------------------------------------------------------//
String dump ()
// ---------------------------------------------------------------------------------//
{
StringBuilder text = new StringBuilder ();
text.append (String.format ("%05X : %s", startPtr,
HexFormatter.getHexString (buffer, startPtr, 1 + locals * 2)));
text.append ("\n");
for (Instruction instruction : instructions)
{
text.append (instruction.dump ());
text.append ("\n");
}
return text.toString ();
}
// ---------------------------------------------------------------------------------//
boolean isValid ()
// ---------------------------------------------------------------------------------//
{
return startPtr > 0;
}
// test whether the routine contains any instructions pointing to this address
// ---------------------------------------------------------------------------------//
private boolean isTarget (int ptr)
// ---------------------------------------------------------------------------------//
{
for (Instruction ins : instructions)
{
if (ins.isBranch () && ins.target () == ptr)
return true;
// should this be calling ins.target () ?
if (ins.isJump () && ins.opcode.jumpTarget == ptr)
return true;
}
return false;
}
// ---------------------------------------------------------------------------------//
public void addCaller (int caller)
// ---------------------------------------------------------------------------------//
{
calledBy.add (caller);
}
// ---------------------------------------------------------------------------------//
@Override
public String getText ()
// ---------------------------------------------------------------------------------//
{
StringBuilder text = new StringBuilder ();
text.append (String.format ("Called by : %3d%n", calledBy.size ()));
text.append (String.format ("Calls : %3d%n", calls.size ()));
text.append (String.format ("Length : %3d%n%n", length));
text.append (String.format ("%05X : %d%n", startPtr, locals));
for (Parameter parameter : parameters)
text.append (parameter.toString () + "\n");
text.append ("\n");
for (Instruction instruction : instructions)
{
text.append (instruction.getHex ());
int offset = instruction.startPtr;
if (targets.contains (offset))
text.append (" L000 ");
else
text.append (" ");
text.append (instruction + "\n");
}
if (calledBy.size () > 0)
{
text.append ("\n\nCalled by\n\n");
for (int i : calledBy)
text.append (String.format ("%05X%n", i));
}
if (calls.size () > 0)
{
text.append ("\n\nCalls\n\n");
for (int i : calls)
text.append (String.format ("%05X%n", i));
}
if (targets.size () > 0)
{
text.append ("\n\nTargets\n\n");
for (int i : targets)
text.append (String.format ("%05X%n", i));
}
return text.toString ();
}
// ---------------------------------------------------------------------------------//
@Override
public String toString ()
// ---------------------------------------------------------------------------------//
{
return String.format ("[Start: %05X, Len: %4d, Strings: %2d, Locals: %2d]", startPtr,
length, strings, locals);
}
// ---------------------------------------------------------------------------------//
class Parameter
// ---------------------------------------------------------------------------------//
{
int value;
int sequence;
public Parameter (int sequence, int value)
{
this.value = value;
this.sequence = sequence;
}
@Override
public String toString ()
{
return String.format ("%05X : L%02d : %d", (startPtr + (sequence - 1) * 2 + 1),
sequence, value);
}
}
@Override
public Iterator<Instruction> iterator ()
{
return instructions.iterator ();
}
@Override
public int compareTo (Routine o)
{
return startPtr - o.startPtr;
}
}