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

287 lines
9.8 KiB
Java

package com.bytezone.diskbrowser.infocom;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.swing.tree.DefaultMutableTreeNode;
import com.bytezone.diskbrowser.applefile.AbstractFile;
import com.bytezone.diskbrowser.disk.DefaultAppleFileSource;
import com.bytezone.diskbrowser.disk.Disk;
import com.bytezone.diskbrowser.disk.DiskAddress;
import com.bytezone.diskbrowser.infocom.Grammar.Sentence;
import com.bytezone.diskbrowser.infocom.Grammar.SentenceGroup;
import com.bytezone.diskbrowser.utilities.HexFormatter;
// -----------------------------------------------------------------------------------//
class CodeManager extends AbstractFile
// -----------------------------------------------------------------------------------//
{
private final Header header;
private int codeSize;
private final Map<Integer, Routine> routines = new TreeMap<> ();
// ---------------------------------------------------------------------------------//
CodeManager (Header header)
// ---------------------------------------------------------------------------------//
{
super ("Code", header.buffer);
this.header = header;
}
// ---------------------------------------------------------------------------------//
void addNodes (DefaultMutableTreeNode root, InfocomDisk disk)
// ---------------------------------------------------------------------------------//
{
root.setAllowsChildren (true);
// should be set by now - do this better!
codeSize = header.stringPointer - header.highMemory;
int count = 0;
for (Routine routine : routines.values ())
{
String name = String.format ("%3d %s (%04X)", ++count, routine.getName (),
routine.startPtr / 2);
DefaultAppleFileSource dafs = new DefaultAppleFileSource (name, routine, disk);
dafs.setSectors (getSectors (routine, disk.getDisk ()));
DefaultMutableTreeNode node = new DefaultMutableTreeNode (dafs);
node.setAllowsChildren (false);
root.add (node);
}
}
// ---------------------------------------------------------------------------------//
private List<DiskAddress> getSectors (Routine routine, Disk disk)
// ---------------------------------------------------------------------------------//
{
int blockNo = routine.startPtr / 256 + 48;
int size = routine.length;
List<DiskAddress> blocks = new ArrayList<> ();
while (size > 0)
{
blocks.add (disk.getDiskAddress (blockNo++));
size -= 256;
}
return blocks;
}
// ---------------------------------------------------------------------------------//
void addRoutines (int programCounter)
// ---------------------------------------------------------------------------------//
{
addRoutine (programCounter - 1, -1);
addActionRoutines (); // obtained from Grammar
addCodeRoutines (); // obtained from Object properties
addGlobalRoutines ();
addMissingRoutines (); // requires stringPtr to be set
// checkThreeByteProperties ();
if (false)
{
int routineNo = 0;
int ptr = header.highMemory;
for (Routine routine : routines.values ())
{
if (ptr < routine.startPtr)
{
int extraBytes = routine.startPtr - ptr;
if (extraBytes > 1)
System.out.println ("Orphan bytes\n------------");
if (extraBytes == 1)
System.out.println (String.format ("%05X : %s%n", ptr,
HexFormatter.getHexString (buffer, ptr, extraBytes)));
else
System.out
.println (HexFormatter.format (buffer, ptr, extraBytes, ptr) + "\n");
}
System.out.printf ("Routine #%3d%n", ++routineNo);
System.out.println ("------------");
System.out.println (routine.dump ());
ptr = routine.startPtr + routine.length;
}
}
if (false)
{
int ptr = header.highMemory;
for (int key : routines.keySet ())
{
ptr = checkAlignment (ptr);
Routine routine = routines.get (key);
if (routine.startPtr > ptr)
System.out.printf ("skipped %d bytes%n", routine.startPtr - ptr);
System.out.println (routine);
ptr = routine.startPtr + routine.length;
}
}
}
// ---------------------------------------------------------------------------------//
private int checkAlignment (int ptr)
// ---------------------------------------------------------------------------------//
{
if (ptr % 2 == 1) // routine must start on a word boundary
++ptr;
return ptr;
}
// ---------------------------------------------------------------------------------//
private void addGlobalRoutines ()
// ---------------------------------------------------------------------------------//
{
}
// ---------------------------------------------------------------------------------//
private void addMissingRoutines ()
// ---------------------------------------------------------------------------------//
{
System.out.printf ("%nWalking the code block%n%n");
int total = routines.size ();
int ptr = header.highMemory;
while (ptr < header.stringPointer)
{
ptr = checkAlignment (ptr);
if (routines.containsKey (ptr))
{
int length = getRoutine (ptr).length;
if (length == 0)
break;
ptr += length;
continue;
}
Routine routine = addRoutine (ptr, 0);
if (routine == null)
{
System.out.printf ("Invalid routine found : %05X%n", ptr);
ptr = findNextRoutine (ptr + 1);
System.out.printf ("skipping to %05X%n", ptr);
if (ptr == 0)
break;
}
else
{
ptr += routine.length;
System.out.printf ("Routine found: %05X%n", routine.startPtr);
}
}
System.out.printf ("%n%d new routines found by walking the code block%n%n",
routines.size () - total);
}
// ---------------------------------------------------------------------------------//
private int findNextRoutine (int address)
// ---------------------------------------------------------------------------------//
{
for (Routine routine : routines.values ())
if (routine.startPtr > address)
return routine.startPtr;
return 0;
}
// ---------------------------------------------------------------------------------//
@Override
public String getText ()
// ---------------------------------------------------------------------------------//
{
StringBuilder text = new StringBuilder ();
int count = 0;
int nextAddress = header.highMemory;
text.append (" # Address Size Lines Strings Called Calls Gap Pack\n");
text.append ("--- ------- ---- ----- ------- ------ ----- --- ----\n");
for (Routine r : routines.values ())
{
int gap = r.startPtr - nextAddress;
text.append (String.format (
"%3d %05X %5d %3d %2d %3d %3d %4d %04X%n", ++count,
r.startPtr, r.length, r.instructions.size (), r.strings, r.calledBy.size (),
r.calls.size (), gap, r.startPtr / 2));
nextAddress = r.startPtr + r.length;
}
text.deleteCharAt (text.length () - 1);
return text.toString ();
}
// ---------------------------------------------------------------------------------//
private void addCodeRoutines ()
// ---------------------------------------------------------------------------------//
{
List<Integer> routines = header.objectManager.getCodeRoutines ();
System.out.println ("Adding " + routines.size () + " code routines");
for (Integer address : routines)
addRoutine (address, 0);
}
// ---------------------------------------------------------------------------------//
private void addActionRoutines ()
// ---------------------------------------------------------------------------------//
{
int total = routines.size ();
for (SentenceGroup sentenceGroup : header.grammar.getSentenceGroups ())
for (Sentence sentence : sentenceGroup)
{
if (sentence.preActionRoutine > 0)
addRoutine (sentence.preActionRoutine, sentence.startPtr);
addRoutine (sentence.actionRoutine, sentence.startPtr);
}
System.out.printf ("Added %d action routines%n", routines.size () - total);
}
// ---------------------------------------------------------------------------------//
Routine addRoutine (int address, int caller)
// ---------------------------------------------------------------------------------//
{
if (address == 0) // stack-based call
return null;
if (address > header.fileLength)
return null;
// check whether we already have this routine
if (routines.containsKey (address))
{
Routine routine = routines.get (address);
routine.addCaller (caller);
return routine;
}
// try to create a new Routine
Routine routine = new Routine (address, header, caller);
if (!routine.isValid ())
return null;
// recursively add all routines called by this one
routines.put (address, routine);
for (int ptr : routine.calls)
addRoutine (ptr, address);
return routine;
}
// ---------------------------------------------------------------------------------//
Routine getRoutine (int address)
// ---------------------------------------------------------------------------------//
{
return routines.get (address);
}
// ---------------------------------------------------------------------------------//
@Override
public String getHexDump ()
// ---------------------------------------------------------------------------------//
{
// this depends on codeSize being set after the strings have been processed
return HexFormatter.format (buffer, header.highMemory, codeSize);
}
}