2015-06-01 09:35:51 +00:00
|
|
|
package com.bytezone.diskbrowser.applefile;
|
|
|
|
|
|
|
|
import java.io.BufferedReader;
|
|
|
|
import java.io.DataInputStream;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStreamReader;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
|
2019-08-15 07:02:40 +00:00
|
|
|
import com.bytezone.common.Utility;
|
|
|
|
import com.bytezone.diskbrowser.gui.AssemblerPreferences;
|
2015-06-01 09:35:51 +00:00
|
|
|
import com.bytezone.diskbrowser.gui.DiskBrowser;
|
2016-02-24 21:11:14 +00:00
|
|
|
import com.bytezone.diskbrowser.utilities.HexFormatter;
|
2015-06-01 09:35:51 +00:00
|
|
|
|
|
|
|
public class AssemblerProgram extends AbstractFile
|
|
|
|
{
|
2016-02-05 00:23:53 +00:00
|
|
|
private static Map<Integer, String> equates;
|
|
|
|
|
2015-06-01 09:35:51 +00:00
|
|
|
private final int loadAddress;
|
|
|
|
private int executeOffset;
|
2016-02-05 00:23:53 +00:00
|
|
|
|
2016-02-05 01:36:16 +00:00
|
|
|
private byte[] extraBuffer = new byte[0];
|
2015-06-01 09:35:51 +00:00
|
|
|
|
2019-08-21 00:31:11 +00:00
|
|
|
private List<Integer> entryPoints;
|
|
|
|
private List<StringLocation> stringLocations;
|
|
|
|
|
2019-08-15 07:02:40 +00:00
|
|
|
static AssemblerPreferences assemblerPreferences;
|
|
|
|
|
2019-08-21 00:31:11 +00:00
|
|
|
public static void setAssemblerPreferences (AssemblerPreferences assemblerPreferences)
|
|
|
|
{
|
|
|
|
AssemblerProgram.assemblerPreferences = assemblerPreferences;
|
|
|
|
}
|
|
|
|
|
2015-06-01 09:35:51 +00:00
|
|
|
public AssemblerProgram (String name, byte[] buffer, int address)
|
|
|
|
{
|
|
|
|
super (name, buffer);
|
|
|
|
this.loadAddress = address;
|
|
|
|
|
|
|
|
if (equates == null)
|
|
|
|
getEquates ();
|
2019-08-21 00:31:11 +00:00
|
|
|
|
|
|
|
// AssemblerBlocks assemblerBlocks = new AssemblerBlocks (buffer, address);
|
2015-06-01 09:35:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public AssemblerProgram (String name, byte[] buffer, int address, int executeOffset)
|
|
|
|
{
|
|
|
|
this (name, buffer, address);
|
|
|
|
this.executeOffset = executeOffset;
|
|
|
|
}
|
|
|
|
|
2016-02-05 00:23:53 +00:00
|
|
|
public void setExtraBuffer (byte[] fullBuffer, int offset, int length)
|
|
|
|
{
|
2016-02-20 09:48:47 +00:00
|
|
|
if (length >= 0)
|
|
|
|
{
|
|
|
|
this.extraBuffer = new byte[length];
|
|
|
|
System.arraycopy (fullBuffer, offset, extraBuffer, 0, length);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
System.out.println ("Invalid length in setExtraBuffer() : " + length);
|
2016-02-05 00:23:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String getHexDump ()
|
|
|
|
{
|
2016-02-18 07:44:22 +00:00
|
|
|
// It might be useful to add opt-O to change the offset. Sometimes it's useful
|
|
|
|
// to see the hex dump offset from zero, other times it's better to use the
|
|
|
|
// load address.
|
2016-02-05 07:18:23 +00:00
|
|
|
String text = HexFormatter.format (buffer, 0, buffer.length, loadAddress);
|
2016-02-05 00:23:53 +00:00
|
|
|
|
2016-02-05 01:36:16 +00:00
|
|
|
if (extraBuffer.length == 0)
|
2016-02-05 00:23:53 +00:00
|
|
|
return text;
|
|
|
|
|
2017-04-17 03:29:29 +00:00
|
|
|
return text + "\n\nData outside actual buffer:\n\n" + HexFormatter
|
|
|
|
.format (extraBuffer, 0, extraBuffer.length, loadAddress + buffer.length);
|
2016-02-05 00:23:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String getAssembler ()
|
|
|
|
{
|
2016-02-05 07:18:23 +00:00
|
|
|
if (buffer == null)
|
|
|
|
return "No buffer";
|
|
|
|
if (assembler == null)
|
|
|
|
this.assembler = new AssemblerProgram (name, buffer, loadAddress);
|
2016-02-05 00:23:53 +00:00
|
|
|
|
2016-02-05 01:36:16 +00:00
|
|
|
if (extraBuffer.length == 0)
|
2016-02-05 07:18:23 +00:00
|
|
|
return assembler.getText ();
|
2016-02-05 00:23:53 +00:00
|
|
|
|
|
|
|
String extraName = String.format ("%s (extra)", name);
|
|
|
|
AssemblerProgram assemblerProgram =
|
2016-02-05 07:18:23 +00:00
|
|
|
new AssemblerProgram (extraName, extraBuffer, loadAddress + buffer.length);
|
2016-02-05 00:23:53 +00:00
|
|
|
|
2016-02-05 07:18:23 +00:00
|
|
|
return assembler.getText () + "\n\n" + assemblerProgram.getText ();
|
2016-02-05 00:23:53 +00:00
|
|
|
}
|
|
|
|
|
2015-06-01 09:35:51 +00:00
|
|
|
@Override
|
|
|
|
public String getText ()
|
|
|
|
{
|
|
|
|
StringBuilder pgm = new StringBuilder ();
|
|
|
|
|
|
|
|
pgm.append (String.format ("Name : %s%n", name));
|
|
|
|
pgm.append (String.format ("Length : $%04X (%,d)%n", buffer.length, buffer.length));
|
|
|
|
pgm.append (String.format ("Load at : $%04X (%,d)%n", loadAddress, loadAddress));
|
|
|
|
|
|
|
|
if (executeOffset > 0)
|
|
|
|
pgm.append (String.format ("Entry : $%04X%n", (loadAddress + executeOffset)));
|
|
|
|
|
2019-08-15 07:02:40 +00:00
|
|
|
pgm.append ("\n");
|
|
|
|
pgm.append (getListing ());
|
|
|
|
|
|
|
|
if (assemblerPreferences.showStrings)
|
|
|
|
pgm.append (getStringsText ());
|
2019-08-09 23:45:49 +00:00
|
|
|
|
2019-08-15 07:02:40 +00:00
|
|
|
return pgm.toString ();
|
2015-06-01 09:35:51 +00:00
|
|
|
}
|
|
|
|
|
2019-08-15 07:02:40 +00:00
|
|
|
private String getListing ()
|
2015-06-01 09:35:51 +00:00
|
|
|
{
|
|
|
|
StringBuilder pgm = new StringBuilder ();
|
2019-08-21 00:31:11 +00:00
|
|
|
|
2015-06-01 09:35:51 +00:00
|
|
|
List<AssemblerStatement> lines = getLines ();
|
|
|
|
|
2019-08-21 00:31:11 +00:00
|
|
|
if (stringLocations == null)
|
|
|
|
getStrings ();
|
|
|
|
|
2018-12-19 20:05:42 +00:00
|
|
|
// if the assembly doesn't start at the beginning, just dump the bytes that
|
2016-02-05 00:23:53 +00:00
|
|
|
// are skipped
|
2015-06-01 09:35:51 +00:00
|
|
|
for (int i = 0; i < executeOffset; i++)
|
|
|
|
pgm.append (String.format (" %04X: %02X%n", (loadAddress + i), buffer[i]));
|
|
|
|
|
|
|
|
for (AssemblerStatement cmd : lines)
|
|
|
|
{
|
|
|
|
StringBuilder line = new StringBuilder ();
|
|
|
|
|
2019-08-15 07:02:40 +00:00
|
|
|
String arrowText = assemblerPreferences.showTargets ? getArrow (cmd) : "";
|
2016-11-29 21:27:44 +00:00
|
|
|
line.append (
|
2019-08-15 07:02:40 +00:00
|
|
|
String.format ("%3.3s %04X: %02X ", arrowText, cmd.address, cmd.value));
|
2015-06-01 09:35:51 +00:00
|
|
|
|
|
|
|
if (cmd.size > 1)
|
|
|
|
line.append (String.format ("%02X ", cmd.operand1));
|
|
|
|
if (cmd.size > 2)
|
|
|
|
line.append (String.format ("%02X ", cmd.operand2));
|
|
|
|
|
|
|
|
while (line.length () < 23)
|
|
|
|
line.append (" ");
|
|
|
|
|
|
|
|
line.append (cmd.mnemonic + " " + cmd.operand);
|
2019-08-21 00:31:11 +00:00
|
|
|
|
2015-06-01 09:35:51 +00:00
|
|
|
if (cmd.offset != 0)
|
|
|
|
{
|
|
|
|
int branch = cmd.address + cmd.offset + 2;
|
|
|
|
line.append (String.format ("$%04X", branch < 0 ? branch += 0xFFFF : branch));
|
|
|
|
}
|
2016-12-17 08:34:47 +00:00
|
|
|
else if (cmd.target > 0
|
2016-02-05 00:23:53 +00:00
|
|
|
&& (cmd.target < loadAddress - 1 || cmd.target > (loadAddress + buffer.length)))
|
2015-06-01 09:35:51 +00:00
|
|
|
{
|
|
|
|
while (line.length () < 40)
|
|
|
|
line.append (" ");
|
|
|
|
|
|
|
|
String text = equates.get (cmd.target);
|
|
|
|
if (text != null)
|
|
|
|
line.append ("; " + text);
|
|
|
|
else
|
|
|
|
for (int i = 0, max = ApplesoftConstants.tokenAddresses.length; i < max; i++)
|
|
|
|
if (cmd.target == ApplesoftConstants.tokenAddresses[i])
|
|
|
|
{
|
|
|
|
line.append ("; Applesoft - " + ApplesoftConstants.tokens[i]);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2019-08-21 00:31:11 +00:00
|
|
|
|
2015-06-01 09:35:51 +00:00
|
|
|
pgm.append (line.toString () + "\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (pgm.length () > 0)
|
|
|
|
pgm.deleteCharAt (pgm.length () - 1);
|
|
|
|
|
2019-08-15 07:02:40 +00:00
|
|
|
return pgm.toString ();
|
2015-06-01 09:35:51 +00:00
|
|
|
}
|
|
|
|
|
2019-08-21 00:31:11 +00:00
|
|
|
// private int showString (AssemblerStatement cmd, StringBuilder line)
|
|
|
|
// {
|
|
|
|
// int key = cmd.address - loadAddress;
|
|
|
|
// if (strings.containsKey (key))
|
|
|
|
// {
|
|
|
|
// while (line.length () < 40)
|
|
|
|
// line.append (" ");
|
|
|
|
// String s = strings.get (key);
|
|
|
|
// line.append ("# " + s);
|
|
|
|
// return s.length () - cmd.size;
|
|
|
|
// }
|
|
|
|
// return 0;
|
|
|
|
// }
|
|
|
|
|
2015-06-01 09:35:51 +00:00
|
|
|
private List<AssemblerStatement> getLines ()
|
|
|
|
{
|
|
|
|
List<AssemblerStatement> lines = new ArrayList<AssemblerStatement> ();
|
2016-02-05 00:23:53 +00:00
|
|
|
Map<Integer, AssemblerStatement> linesMap =
|
|
|
|
new HashMap<Integer, AssemblerStatement> ();
|
2015-06-01 09:35:51 +00:00
|
|
|
List<Integer> targets = new ArrayList<Integer> ();
|
|
|
|
|
|
|
|
int ptr = executeOffset;
|
|
|
|
int address = loadAddress + executeOffset;
|
|
|
|
|
|
|
|
while (ptr < buffer.length)
|
|
|
|
{
|
|
|
|
AssemblerStatement cmd = new AssemblerStatement (buffer[ptr]);
|
|
|
|
lines.add (cmd);
|
|
|
|
linesMap.put (address, cmd);
|
|
|
|
cmd.address = address;
|
|
|
|
|
|
|
|
if (cmd.size == 2 && ptr < buffer.length - 1)
|
|
|
|
cmd.addData (buffer[ptr + 1]);
|
|
|
|
else if (cmd.size == 3 && ptr < buffer.length - 2)
|
|
|
|
cmd.addData (buffer[ptr + 1], buffer[ptr + 2]);
|
|
|
|
else
|
|
|
|
cmd.size = 1;
|
|
|
|
|
2019-08-21 00:31:11 +00:00
|
|
|
// JMP, JMP, JSR
|
2015-06-01 09:35:51 +00:00
|
|
|
if (cmd.target >= loadAddress && cmd.target < (loadAddress + buffer.length)
|
2016-02-05 00:23:53 +00:00
|
|
|
&& (cmd.value == 0x4C || cmd.value == 0x6C || cmd.value == 0x20))
|
2015-06-01 09:35:51 +00:00
|
|
|
targets.add (cmd.target);
|
2019-08-21 00:31:11 +00:00
|
|
|
|
|
|
|
// branch relative
|
2015-06-01 09:35:51 +00:00
|
|
|
if (cmd.offset != 0)
|
|
|
|
targets.add (cmd.address + cmd.offset + 2);
|
|
|
|
|
|
|
|
address += cmd.size;
|
|
|
|
ptr += cmd.size;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (Integer target : targets)
|
|
|
|
{
|
|
|
|
AssemblerStatement cmd = linesMap.get (target);
|
|
|
|
if (cmd != null)
|
|
|
|
cmd.isTarget = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return lines;
|
|
|
|
}
|
|
|
|
|
2019-08-15 07:02:40 +00:00
|
|
|
private String getStringsText ()
|
|
|
|
{
|
2019-08-21 00:31:11 +00:00
|
|
|
if (stringLocations.size () == 0)
|
2019-08-15 07:02:40 +00:00
|
|
|
return "";
|
|
|
|
|
|
|
|
StringBuilder text = new StringBuilder ("\n\nPossible strings:\n\n");
|
2019-08-21 00:31:11 +00:00
|
|
|
for (StringLocation stringLocation : stringLocations)
|
2019-08-15 07:02:40 +00:00
|
|
|
{
|
2019-08-25 04:16:36 +00:00
|
|
|
int address = stringLocation.offset + loadAddress;
|
|
|
|
text.append (String.format ("%s %04X - %04X %s %n",
|
|
|
|
entryPoints.contains (stringLocation.offset + loadAddress) ? "*" : " ", address,
|
|
|
|
address + stringLocation.length, stringLocation));
|
2019-08-15 07:02:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (text.length () > 0)
|
|
|
|
text.deleteCharAt (text.length () - 1);
|
|
|
|
|
|
|
|
return text.toString ();
|
|
|
|
}
|
|
|
|
|
2019-08-21 00:31:11 +00:00
|
|
|
private void getStrings ()
|
2019-08-09 23:45:49 +00:00
|
|
|
{
|
2019-08-21 00:31:11 +00:00
|
|
|
entryPoints = new ArrayList<> ();
|
|
|
|
stringLocations = new ArrayList<> ();
|
2019-08-09 23:45:49 +00:00
|
|
|
|
|
|
|
int start = 0;
|
2019-08-25 04:16:36 +00:00
|
|
|
int max = buffer.length - 2;
|
2019-08-09 23:45:49 +00:00
|
|
|
for (int ptr = 0; ptr < buffer.length; ptr++)
|
|
|
|
{
|
2019-08-21 00:31:11 +00:00
|
|
|
if ((buffer[ptr] == (byte) 0xBD // LDA Absolute,X
|
|
|
|
|| buffer[ptr] == (byte) 0xB9 // LDA Absolute,Y
|
|
|
|
|| buffer[ptr] == (byte) 0xAD) // LDA Absolute
|
2019-08-25 04:16:36 +00:00
|
|
|
&& (ptr < max))
|
2019-08-15 07:02:40 +00:00
|
|
|
{
|
|
|
|
int address = Utility.getWord (buffer, ptr + 1);
|
2019-08-25 04:16:36 +00:00
|
|
|
if (address >= loadAddress && address < loadAddress + buffer.length)
|
2019-08-15 07:02:40 +00:00
|
|
|
entryPoints.add (address);
|
|
|
|
}
|
2019-08-09 23:45:49 +00:00
|
|
|
|
2019-08-21 00:31:11 +00:00
|
|
|
if ((buffer[ptr] & 0x80) != 0) // hi bit set
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (ptr - start > 3)
|
|
|
|
stringLocations.add (new StringLocation (start, ptr - 1));
|
|
|
|
|
|
|
|
start = ptr + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (buffer.length - start > 3)
|
|
|
|
stringLocations.add (new StringLocation (start, buffer.length - 1));
|
2019-08-09 23:45:49 +00:00
|
|
|
}
|
|
|
|
|
2015-06-01 09:35:51 +00:00
|
|
|
private String getArrow (AssemblerStatement cmd)
|
|
|
|
{
|
|
|
|
String arrow = "";
|
2019-08-15 07:02:40 +00:00
|
|
|
|
2015-06-01 09:35:51 +00:00
|
|
|
if (cmd.value == 0x4C || cmd.value == 0x6C || cmd.value == 0x60 || cmd.offset != 0)
|
|
|
|
arrow = "<--";
|
2019-08-15 07:02:40 +00:00
|
|
|
|
2016-02-05 01:36:16 +00:00
|
|
|
if (cmd.value == 0x20 && isLocal (cmd.target)) // JSR
|
2015-06-01 09:35:51 +00:00
|
|
|
arrow = "<--";
|
2019-08-15 07:02:40 +00:00
|
|
|
|
2015-06-01 09:35:51 +00:00
|
|
|
if (cmd.isTarget)
|
|
|
|
if (arrow.isEmpty ())
|
|
|
|
arrow = "-->";
|
|
|
|
else
|
|
|
|
arrow = "<->";
|
2019-08-15 07:02:40 +00:00
|
|
|
|
2015-06-01 09:35:51 +00:00
|
|
|
return arrow;
|
|
|
|
}
|
|
|
|
|
2016-02-05 01:36:16 +00:00
|
|
|
private boolean isLocal (int target)
|
|
|
|
{
|
|
|
|
return target >= loadAddress
|
|
|
|
&& target < loadAddress + buffer.length + extraBuffer.length;
|
|
|
|
}
|
|
|
|
|
2015-06-01 09:35:51 +00:00
|
|
|
private void getEquates ()
|
|
|
|
{
|
|
|
|
equates = new HashMap<Integer, String> ();
|
|
|
|
DataInputStream inputEquates =
|
2016-02-05 00:23:53 +00:00
|
|
|
new DataInputStream (DiskBrowser.class.getClassLoader ()
|
|
|
|
.getResourceAsStream ("com/bytezone/diskbrowser/applefile/equates.txt"));
|
2015-06-01 09:35:51 +00:00
|
|
|
BufferedReader in = new BufferedReader (new InputStreamReader (inputEquates));
|
|
|
|
|
|
|
|
String line;
|
|
|
|
try
|
|
|
|
{
|
|
|
|
while ((line = in.readLine ()) != null)
|
|
|
|
{
|
|
|
|
if (!line.isEmpty () && !line.startsWith ("*"))
|
|
|
|
{
|
|
|
|
int address = Integer.parseInt (line.substring (0, 4), 16);
|
|
|
|
if (equates.containsKey (address))
|
2016-11-29 21:27:44 +00:00
|
|
|
System.out.printf ("Duplicate equate entry : %04X%n" + address);
|
2015-06-01 09:35:51 +00:00
|
|
|
else
|
|
|
|
equates.put (address, line.substring (6));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
in.close ();
|
|
|
|
}
|
|
|
|
catch (IOException e)
|
|
|
|
{
|
|
|
|
e.printStackTrace ();
|
|
|
|
}
|
|
|
|
}
|
2019-08-21 00:31:11 +00:00
|
|
|
|
|
|
|
class StringLocation
|
|
|
|
{
|
|
|
|
int offset;
|
|
|
|
int length;
|
|
|
|
boolean zeroTerminated;
|
2019-08-25 04:16:36 +00:00
|
|
|
boolean lowTerminated;
|
2019-08-21 00:31:11 +00:00
|
|
|
boolean hasLengthByte;
|
|
|
|
int digits;
|
|
|
|
int letters;
|
|
|
|
int punctuation;
|
|
|
|
int controlChars;
|
|
|
|
int spaces;
|
|
|
|
|
|
|
|
public StringLocation (int first, int last)
|
|
|
|
{
|
|
|
|
offset = first;
|
|
|
|
length = last - offset + 1;
|
|
|
|
|
2019-08-25 04:16:36 +00:00
|
|
|
int end = last + 1;
|
|
|
|
|
|
|
|
zeroTerminated = end < buffer.length && buffer[end] == 0;
|
|
|
|
lowTerminated = end < buffer.length && buffer[end] >= 32 && buffer[end] < 127;
|
2019-08-21 00:31:11 +00:00
|
|
|
hasLengthByte = first > 0 && (buffer[first] & 0xFF) == length;
|
|
|
|
|
|
|
|
for (int i = offset; i < offset + length; i++)
|
|
|
|
{
|
|
|
|
int val = buffer[i] & 0x7F;
|
|
|
|
if (val < 32 || val == 127)
|
|
|
|
++controlChars;
|
|
|
|
else if (val == 32)
|
|
|
|
++spaces;
|
|
|
|
else if (val >= 48 && val <= 57)
|
|
|
|
++digits;
|
|
|
|
else if (val >= 65 && val <= 90)
|
|
|
|
++letters;
|
|
|
|
else if (val >= 97 && val <= 122)
|
|
|
|
++letters;
|
|
|
|
else
|
|
|
|
++punctuation;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
boolean likelyString ()
|
|
|
|
{
|
|
|
|
return spaces > 0 || letters > punctuation;
|
|
|
|
}
|
|
|
|
|
|
|
|
public String toStatisticsString ()
|
|
|
|
{
|
|
|
|
return String.format ("%2d, %2d, %2d, %2d, %2d", digits, letters, punctuation,
|
|
|
|
controlChars, spaces);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public String toString ()
|
|
|
|
{
|
|
|
|
StringBuilder text = new StringBuilder ();
|
|
|
|
|
|
|
|
if (hasLengthByte)
|
|
|
|
text.append ("<length>");
|
|
|
|
|
|
|
|
for (int i = offset; i < offset + length; i++)
|
|
|
|
{
|
|
|
|
int val = buffer[i] & 0x7F;
|
|
|
|
if (val == 4)
|
|
|
|
text.append ("<ctrl-D>");
|
|
|
|
else if (val == 10)
|
|
|
|
text.append ("<LF>");
|
|
|
|
else if (val == 13)
|
|
|
|
text.append ("<CR>");
|
|
|
|
else
|
|
|
|
text.append ((char) val);
|
|
|
|
}
|
2019-08-25 04:16:36 +00:00
|
|
|
if (lowTerminated)
|
|
|
|
text.append ((char) buffer[offset + length]);
|
2019-08-21 00:31:11 +00:00
|
|
|
|
|
|
|
return text.toString ();
|
|
|
|
}
|
|
|
|
}
|
2015-06-01 09:35:51 +00:00
|
|
|
}
|