mirror of
https://github.com/dmolony/DiskBrowser.git
synced 2024-06-03 16:29:29 +00:00
Initial commit
This commit is contained in:
commit
c953204c60
3
resources/buildNumber
Executable file
3
resources/buildNumber
Executable file
|
@ -0,0 +1,3 @@
|
||||||
|
#Build Number for ANT. Do not edit!
|
||||||
|
#Mon Jun 01 19:29:14 AEST 2015
|
||||||
|
build.number=627
|
42
src/com/bytezone/diskbrowser/DateTime.java
Normal file
42
src/com/bytezone/diskbrowser/DateTime.java
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package com.bytezone.diskbrowser;
|
||||||
|
|
||||||
|
class DateTime
|
||||||
|
{
|
||||||
|
private static String[] months = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
|
||||||
|
"Sep", "Oct", "Nov", "Dec" };
|
||||||
|
private static String[] days = { "", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
|
||||||
|
"Friday", "Saturday" };
|
||||||
|
|
||||||
|
private final int second;
|
||||||
|
private final int minute;
|
||||||
|
private final int hour;
|
||||||
|
private final int year;
|
||||||
|
private final int day;
|
||||||
|
private final int month;
|
||||||
|
private final int weekDay;
|
||||||
|
|
||||||
|
public DateTime (byte[] buffer, int ptr)
|
||||||
|
{
|
||||||
|
second = buffer[ptr] & 0xFF;
|
||||||
|
minute = buffer[++ptr] & 0xFF;
|
||||||
|
hour = buffer[++ptr] & 0xFF;
|
||||||
|
year = buffer[++ptr] & 0xFF;
|
||||||
|
day = buffer[++ptr] & 0xFF;
|
||||||
|
month = buffer[++ptr] & 0xFF;
|
||||||
|
++ptr; // empty
|
||||||
|
weekDay = buffer[++ptr] & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String format ()
|
||||||
|
{
|
||||||
|
return String.format ("%02d:%02d:%02d %s %d %s %d", hour, minute, second, days[weekDay],
|
||||||
|
day, months[month], year);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
return "DateTime [second=" + second + ", minute=" + minute + ", hour=" + hour + ", year="
|
||||||
|
+ year + ", day=" + day + ", month=" + month + ", weekDay=" + weekDay + "]";
|
||||||
|
}
|
||||||
|
}
|
17
src/com/bytezone/diskbrowser/FileFormatException.java
Normal file
17
src/com/bytezone/diskbrowser/FileFormatException.java
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package com.bytezone.diskbrowser;
|
||||||
|
|
||||||
|
public class FileFormatException extends RuntimeException
|
||||||
|
{
|
||||||
|
String message;
|
||||||
|
|
||||||
|
public FileFormatException (String string)
|
||||||
|
{
|
||||||
|
this.message = string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
439
src/com/bytezone/diskbrowser/HexFormatter.java
Executable file
439
src/com/bytezone/diskbrowser/HexFormatter.java
Executable file
|
@ -0,0 +1,439 @@
|
||||||
|
package com.bytezone.diskbrowser;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.MathContext;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
|
||||||
|
public class HexFormatter
|
||||||
|
{
|
||||||
|
private static String[] hex = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B",
|
||||||
|
"C", "D", "E", "F" };
|
||||||
|
private static MathContext mathContext = new MathContext (9);
|
||||||
|
|
||||||
|
public static String format (byte[] buffer)
|
||||||
|
{
|
||||||
|
return format (buffer, 0, buffer.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String format (byte[] buffer, int offset, int length)
|
||||||
|
{
|
||||||
|
return format (buffer, offset, length, true, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String format (byte[] buffer, int offset, int length, int startingAddress)
|
||||||
|
{
|
||||||
|
return format (buffer, offset, length, true, startingAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatNoHeader (byte[] buffer, int offset, int length)
|
||||||
|
{
|
||||||
|
return format (buffer, offset, length, false, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String formatNoHeader (byte[] buffer, int offset, int length,
|
||||||
|
int startingAddress)
|
||||||
|
{
|
||||||
|
return format (buffer, offset, length, false, startingAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String format (byte[] buffer, int offset, int length, boolean header,
|
||||||
|
int startingAddress)
|
||||||
|
{
|
||||||
|
StringBuffer line = new StringBuffer ();
|
||||||
|
int[] freq = new int[256];
|
||||||
|
|
||||||
|
if (header)
|
||||||
|
{
|
||||||
|
line.append (" ");
|
||||||
|
for (int i = 0; i < 16; i++)
|
||||||
|
line.append (" " + hex[i]);
|
||||||
|
if (offset == 0)
|
||||||
|
line.append ("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = offset; i < offset + length; i += 16)
|
||||||
|
{
|
||||||
|
if (line.length () > 0 && i > 0)
|
||||||
|
line.append ("\n");
|
||||||
|
|
||||||
|
// print offset
|
||||||
|
for (int temp = i + startingAddress, max = 65536; max > 0; max /= 16)
|
||||||
|
{
|
||||||
|
if (temp >= max)
|
||||||
|
{
|
||||||
|
line.append (hex[temp / max]);
|
||||||
|
temp %= max;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
line.append ("0");
|
||||||
|
}
|
||||||
|
|
||||||
|
// print hex values
|
||||||
|
line.append (" : ");
|
||||||
|
StringBuffer trans = new StringBuffer ();
|
||||||
|
StringBuffer hexLine = new StringBuffer ();
|
||||||
|
|
||||||
|
for (int j = i; (j < i + 16) && (j < offset + length); j++)
|
||||||
|
{
|
||||||
|
int c = buffer[j] & 0xFF;
|
||||||
|
freq[c]++;
|
||||||
|
hexLine.append (String.format ("%02X ", c));
|
||||||
|
|
||||||
|
if (c > 127)
|
||||||
|
{
|
||||||
|
if (c < 160)
|
||||||
|
c -= 64;
|
||||||
|
else
|
||||||
|
c -= 128;
|
||||||
|
}
|
||||||
|
if (c < 32 || c == 127) // non-printable
|
||||||
|
trans.append (".");
|
||||||
|
else
|
||||||
|
// standard ascii
|
||||||
|
trans.append ((char) c);
|
||||||
|
}
|
||||||
|
while (hexLine.length () < 48)
|
||||||
|
hexLine.append (" ");
|
||||||
|
|
||||||
|
line.append (hexLine.toString () + ": " + trans.toString ());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false)
|
||||||
|
{
|
||||||
|
line.append ("\n\n");
|
||||||
|
int totalBits = 0;
|
||||||
|
for (int i = 0; i < freq.length; i++)
|
||||||
|
if (freq[i] > 0)
|
||||||
|
{
|
||||||
|
totalBits += (Integer.bitCount (i) * freq[i]);
|
||||||
|
line.append (String.format ("%02X %3d %d%n", i, freq[i], Integer.bitCount (i)));
|
||||||
|
}
|
||||||
|
line.append (String.format ("%nTotal bits : %d%n", totalBits));
|
||||||
|
}
|
||||||
|
return line.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String sanitiseString (byte[] buffer, int offset, int length)
|
||||||
|
{
|
||||||
|
StringBuilder trans = new StringBuilder ();
|
||||||
|
for (int j = offset; j < offset + length; j++)
|
||||||
|
{
|
||||||
|
int c = buffer[j] & 0xFF;
|
||||||
|
|
||||||
|
if (c > 127)
|
||||||
|
{
|
||||||
|
if (c < 160)
|
||||||
|
c -= 64;
|
||||||
|
else
|
||||||
|
c -= 128;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c < 32 || c == 127) // non-printable
|
||||||
|
trans.append (".");
|
||||||
|
else
|
||||||
|
trans.append ((char) c); // standard ascii
|
||||||
|
}
|
||||||
|
return trans.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getString (byte[] buffer)
|
||||||
|
{
|
||||||
|
return getString (buffer, 0, buffer.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getString (byte[] buffer, int offset, int length)
|
||||||
|
{
|
||||||
|
StringBuffer text = new StringBuffer ();
|
||||||
|
|
||||||
|
for (int i = offset; i < offset + length; i++)
|
||||||
|
{
|
||||||
|
int c = intValue (buffer[i]);
|
||||||
|
if (c > 127)
|
||||||
|
{
|
||||||
|
if (c < 160)
|
||||||
|
c -= 64;
|
||||||
|
else
|
||||||
|
c -= 128;
|
||||||
|
}
|
||||||
|
if (c == 13)
|
||||||
|
text.append ("\n");
|
||||||
|
else if (c < 32) // non-printable
|
||||||
|
text.append (".");
|
||||||
|
else
|
||||||
|
// standard ascii
|
||||||
|
text.append ((char) c);
|
||||||
|
}
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getString2 (byte[] buffer, int offset, int length)
|
||||||
|
{
|
||||||
|
StringBuffer text = new StringBuffer ();
|
||||||
|
|
||||||
|
for (int i = offset; i < offset + length; i++)
|
||||||
|
{
|
||||||
|
int c = intValue (buffer[i]);
|
||||||
|
if (c == 136 && text.length () > 0)
|
||||||
|
{
|
||||||
|
System.out.println (text.toString ());
|
||||||
|
text.deleteCharAt (text.length () - 1);
|
||||||
|
System.out.println ("deleted");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c > 127)
|
||||||
|
{
|
||||||
|
if (c < 160)
|
||||||
|
c -= 64;
|
||||||
|
else
|
||||||
|
c -= 128;
|
||||||
|
}
|
||||||
|
if (c < 32) // non-printable
|
||||||
|
text.append ((char) (c + 64));
|
||||||
|
else
|
||||||
|
// standard ascii
|
||||||
|
text.append ((char) c);
|
||||||
|
}
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getHexString (byte[] buffer, int offset, int length)
|
||||||
|
{
|
||||||
|
return getHexString (buffer, offset, length, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getHexString (byte[] buffer)
|
||||||
|
{
|
||||||
|
return getHexString (buffer, 0, buffer.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getHexString (byte[] buffer, int offset, int length, boolean space)
|
||||||
|
{
|
||||||
|
StringBuilder hex = new StringBuilder ();
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
hex.append (String.format ("%02X", buffer[offset + i]));
|
||||||
|
if (space)
|
||||||
|
hex.append (' ');
|
||||||
|
}
|
||||||
|
if (length > 0 && space)
|
||||||
|
hex.deleteCharAt (hex.length () - 1);
|
||||||
|
return hex.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getHexStringReversed (byte[] buffer, int offset, int length,
|
||||||
|
boolean space)
|
||||||
|
{
|
||||||
|
StringBuilder hex = new StringBuilder ();
|
||||||
|
for (int i = length - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
hex.append (String.format ("%02X", buffer[offset + i]));
|
||||||
|
if (space)
|
||||||
|
hex.append (' ');
|
||||||
|
}
|
||||||
|
if (length > 0 && space)
|
||||||
|
hex.deleteCharAt (hex.length () - 1);
|
||||||
|
return hex.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static char byteValue (byte b)
|
||||||
|
{
|
||||||
|
int c = intValue (b);
|
||||||
|
if (c > 127)
|
||||||
|
c -= 128;
|
||||||
|
if (c > 95)
|
||||||
|
c -= 64;
|
||||||
|
if (c < 32) // non-printable
|
||||||
|
return '.';
|
||||||
|
return (char) c; // standard ascii
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String format4 (int value)
|
||||||
|
{
|
||||||
|
if (value < 0)
|
||||||
|
return "***err**";
|
||||||
|
StringBuffer text = new StringBuffer ();
|
||||||
|
for (int i = 0, weight = 4096; i < 4; i++)
|
||||||
|
{
|
||||||
|
int digit = value / weight;
|
||||||
|
if (digit < 0 || digit > 15)
|
||||||
|
return "***err**";
|
||||||
|
text.append (hex[digit]);
|
||||||
|
value %= weight;
|
||||||
|
weight /= 16;
|
||||||
|
}
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String format3 (int value)
|
||||||
|
{
|
||||||
|
return format4 (value).substring (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String format2 (int value)
|
||||||
|
{
|
||||||
|
if (value < 0)
|
||||||
|
value += 256;
|
||||||
|
String text = hex[value / 16] + hex[value % 16];
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String format1 (int value)
|
||||||
|
{
|
||||||
|
String text = hex[value];
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int intValue (byte b1)
|
||||||
|
{
|
||||||
|
// int i1 = b1;
|
||||||
|
// if (i1 < 0)
|
||||||
|
// i1 += 256;
|
||||||
|
|
||||||
|
// return i1;
|
||||||
|
return b1 & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int intValue (byte b1, byte b2)
|
||||||
|
{
|
||||||
|
return intValue (b1) + intValue (b2) * 256;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int intValue (byte b1, byte b2, byte b3)
|
||||||
|
{
|
||||||
|
return intValue (b1) + intValue (b2) * 256 + intValue (b3) * 65536;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getLong (byte[] buffer, int ptr)
|
||||||
|
{
|
||||||
|
int val = 0;
|
||||||
|
for (int i = 3; i >= 0; i--)
|
||||||
|
{
|
||||||
|
val <<= 8;
|
||||||
|
val += buffer[ptr + i] & 0xFF;
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double getSANEDouble (byte[] buffer, int offset)
|
||||||
|
{
|
||||||
|
long bits = 0;
|
||||||
|
for (int i = 7; i >= 0; i--)
|
||||||
|
{
|
||||||
|
bits <<= 8;
|
||||||
|
bits |= buffer[offset + i] & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Double.longBitsToDouble (bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getWord (byte[] buffer, int ptr)
|
||||||
|
{
|
||||||
|
int val = 0;
|
||||||
|
for (int i = 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
val <<= 8;
|
||||||
|
val += buffer[ptr + i] & 0xFF;
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getSignedWord (byte b1, byte b2)
|
||||||
|
{
|
||||||
|
return (b2 << 8) | (b1 & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
// public static int getSignedWord (byte[] buffer, int ptr)
|
||||||
|
// {
|
||||||
|
// short val = buffer[ptr] << 8;
|
||||||
|
// return buffer[ptr] * 256 + buffer[ptr + 1];
|
||||||
|
// }
|
||||||
|
|
||||||
|
public static double floatValueOld (byte[] buffer, int offset)
|
||||||
|
{
|
||||||
|
double val = 0;
|
||||||
|
|
||||||
|
int exponent = HexFormatter.intValue (buffer[offset]) - 0x80;
|
||||||
|
|
||||||
|
int mantissa =
|
||||||
|
(buffer[offset + 1] & 0x7F) * 0x1000000 + intValue (buffer[offset + 2]) * 0x10000
|
||||||
|
+ intValue (buffer[offset + 3]) * 0x100 + intValue (buffer[offset + 4]);
|
||||||
|
|
||||||
|
int weight1 = 1;
|
||||||
|
long weight2 = 2147483648L;
|
||||||
|
double multiplier = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < 32; i++)
|
||||||
|
{
|
||||||
|
if ((mantissa & weight2) > 0)
|
||||||
|
multiplier += (1.0 / weight1);
|
||||||
|
weight2 /= 2;
|
||||||
|
weight1 *= 2;
|
||||||
|
}
|
||||||
|
val = Math.pow (2, exponent - 1) * (multiplier + 1);
|
||||||
|
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static double floatValue (byte[] buffer, int ptr)
|
||||||
|
{
|
||||||
|
int exponent = buffer[ptr] & 0x7F; // biased 128
|
||||||
|
if (exponent == 0)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
int mantissa =
|
||||||
|
(buffer[ptr + 1] & 0x7F) << 24 | (buffer[ptr + 2] & 0xFF) << 16
|
||||||
|
| (buffer[ptr + 3] & 0xFF) << 8 | (buffer[ptr + 4] & 0xFF);
|
||||||
|
boolean negative = (buffer[ptr + 1] & 0x80) > 0;
|
||||||
|
double value = 0.5;
|
||||||
|
for (int i = 2, weight = 0x40000000; i <= 32; i++, weight >>>= 1)
|
||||||
|
if ((mantissa & weight) > 0)
|
||||||
|
value += Math.pow (0.5, i);
|
||||||
|
value *= Math.pow (2, exponent);
|
||||||
|
BigDecimal bd = new BigDecimal (value);
|
||||||
|
double rounded = bd.round (mathContext).doubleValue ();
|
||||||
|
return negative ? rounded * -1 : rounded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GregorianCalendar getAppleDate (byte[] buffer, int offset)
|
||||||
|
{
|
||||||
|
int date = HexFormatter.intValue (buffer[offset], buffer[offset + 1]);
|
||||||
|
if (date > 0)
|
||||||
|
{
|
||||||
|
int year = (date & 0xFE00) >> 9;
|
||||||
|
int month = (date & 0x01E0) >> 5;
|
||||||
|
int day = date & 0x001F;
|
||||||
|
int hour = HexFormatter.intValue (buffer[offset + 3]) & 0x1F;
|
||||||
|
int minute = HexFormatter.intValue (buffer[offset + 2]) & 0x3F;
|
||||||
|
if (year < 70)
|
||||||
|
year += 2000;
|
||||||
|
else
|
||||||
|
year += 1900;
|
||||||
|
return new GregorianCalendar (year, month - 1, day, hour, minute);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GregorianCalendar getPascalDate (byte[] buffer, int offset)
|
||||||
|
{
|
||||||
|
int year = intValue (buffer[offset + 1]);
|
||||||
|
int day = (buffer[offset] & 0xF0) >> 4;
|
||||||
|
int month = buffer[offset] & 0x0F;
|
||||||
|
if (day == 0 || month == 0)
|
||||||
|
return null;
|
||||||
|
if (year % 2 > 0)
|
||||||
|
day += 16;
|
||||||
|
year /= 2;
|
||||||
|
if (year < 70)
|
||||||
|
year += 2000;
|
||||||
|
else
|
||||||
|
year += 1900;
|
||||||
|
return new GregorianCalendar (year, month - 1, day);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getPascalString (byte[] buffer, int offset)
|
||||||
|
{
|
||||||
|
int length = HexFormatter.intValue (buffer[offset]);
|
||||||
|
return HexFormatter.getString (buffer, offset + 1, length);
|
||||||
|
}
|
||||||
|
}
|
113
src/com/bytezone/diskbrowser/LZW.java
Normal file
113
src/com/bytezone/diskbrowser/LZW.java
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
package com.bytezone.diskbrowser;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
class LZW
|
||||||
|
{
|
||||||
|
static protected final String[] st = new String[0x1000];
|
||||||
|
static protected final int TRACK_LENGTH = 0x1000;
|
||||||
|
|
||||||
|
protected final List<byte[]> chunks = new ArrayList<byte[]> ();
|
||||||
|
protected int volume;
|
||||||
|
protected byte runLengthChar;
|
||||||
|
protected int crc;
|
||||||
|
protected int crcBase;
|
||||||
|
|
||||||
|
private int buffer; // one character buffer
|
||||||
|
private int bitsLeft; // unused bits left in buffer
|
||||||
|
|
||||||
|
private int ptr;
|
||||||
|
private int startPtr;
|
||||||
|
protected byte[] bytes;
|
||||||
|
|
||||||
|
static
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 256; i++)
|
||||||
|
st[i] = "" + (char) i;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBuffer (byte[] buffer, int ptr)
|
||||||
|
{
|
||||||
|
bytes = buffer;
|
||||||
|
startPtr = this.ptr = ptr;
|
||||||
|
bitsLeft = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int bytesRead ()
|
||||||
|
{
|
||||||
|
return ptr - startPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillBuffer ()
|
||||||
|
{
|
||||||
|
buffer = bytes[ptr++] & 0xFF;
|
||||||
|
bitsLeft = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean readBoolean ()
|
||||||
|
{
|
||||||
|
if (bitsLeft == 0)
|
||||||
|
fillBuffer ();
|
||||||
|
|
||||||
|
bitsLeft--;
|
||||||
|
boolean bit = ((buffer << bitsLeft) & 0x80) == 0x80;
|
||||||
|
return bit;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int readInt (int width)
|
||||||
|
{
|
||||||
|
if (width < 8 || width > 12)
|
||||||
|
throw new RuntimeException ("Illegal value of r = " + width);
|
||||||
|
|
||||||
|
int x = 0;
|
||||||
|
for (int i = 0, weight = 1; i < width; i++, weight <<= 1)
|
||||||
|
if (readBoolean ())
|
||||||
|
x |= weight;
|
||||||
|
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected byte[] undoRLE (byte[] inBuffer, int inPtr, int length)
|
||||||
|
{
|
||||||
|
byte[] outBuffer = new byte[TRACK_LENGTH];
|
||||||
|
int outPtr = 0;
|
||||||
|
int max = inPtr + length;
|
||||||
|
|
||||||
|
while (inPtr < max)
|
||||||
|
{
|
||||||
|
byte b = inBuffer[inPtr++];
|
||||||
|
if (b == runLengthChar)
|
||||||
|
{
|
||||||
|
b = inBuffer[inPtr++];
|
||||||
|
int rpt = inBuffer[inPtr++] & 0xFF;
|
||||||
|
while (rpt-- >= 0)
|
||||||
|
outBuffer[outPtr++] = b;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
outBuffer[outPtr++] = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert outPtr == TRACK_LENGTH;
|
||||||
|
return outBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getData ()
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[chunks.size () * TRACK_LENGTH];
|
||||||
|
int trackNumber = 0;
|
||||||
|
|
||||||
|
for (byte[] track : chunks)
|
||||||
|
System.arraycopy (track, 0, buffer, trackNumber++ * TRACK_LENGTH, TRACK_LENGTH);
|
||||||
|
|
||||||
|
if (crc != NuFX.getCRC (buffer, crcBase))
|
||||||
|
System.out.println ("\n*** LZW CRC mismatch ***");
|
||||||
|
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int width (int maximumValue)
|
||||||
|
{
|
||||||
|
return 32 - Integer.numberOfLeadingZeros (maximumValue);
|
||||||
|
}
|
||||||
|
}
|
88
src/com/bytezone/diskbrowser/LZW1.java
Normal file
88
src/com/bytezone/diskbrowser/LZW1.java
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
package com.bytezone.diskbrowser;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import com.bytezone.common.Utility;
|
||||||
|
|
||||||
|
class LZW1 extends LZW
|
||||||
|
{
|
||||||
|
public LZW1 (byte[] buffer)
|
||||||
|
{
|
||||||
|
bytes = Objects.requireNonNull (buffer);
|
||||||
|
|
||||||
|
crc = Utility.getWord (buffer, 0);
|
||||||
|
crcBase = 0;
|
||||||
|
|
||||||
|
volume = buffer[2] & 0xFF;
|
||||||
|
runLengthChar = (byte) (buffer[3] & 0xFF);
|
||||||
|
int ptr = 4;
|
||||||
|
|
||||||
|
while (ptr < buffer.length - 1) // what is in the last byte?
|
||||||
|
{
|
||||||
|
int rleLength = Utility.getWord (buffer, ptr);
|
||||||
|
int lzwPerformed = buffer[ptr + 2] & 0xFF;
|
||||||
|
ptr += 3;
|
||||||
|
|
||||||
|
if (lzwPerformed != 0)
|
||||||
|
{
|
||||||
|
setBuffer (buffer, ptr); // prepare to read n-bit integers
|
||||||
|
byte[] lzwBuffer = undoLZW (rleLength);
|
||||||
|
|
||||||
|
if (rleLength == TRACK_LENGTH) // no run length encoding
|
||||||
|
chunks.add (lzwBuffer);
|
||||||
|
else
|
||||||
|
chunks.add (undoRLE (lzwBuffer, 0, lzwBuffer.length));
|
||||||
|
|
||||||
|
ptr += bytesRead (); // since the setBuffer()
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (rleLength == TRACK_LENGTH) // no run length encoding
|
||||||
|
{
|
||||||
|
byte[] originalBuffer = new byte[TRACK_LENGTH];
|
||||||
|
System.arraycopy (buffer, ptr, originalBuffer, 0, originalBuffer.length);
|
||||||
|
chunks.add (originalBuffer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
chunks.add (undoRLE (buffer, ptr, rleLength));
|
||||||
|
|
||||||
|
ptr += rleLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected byte[] undoLZW (int rleLength)
|
||||||
|
{
|
||||||
|
byte[] lzwBuffer = new byte[rleLength]; // must fill this array from input
|
||||||
|
int ptr = 0;
|
||||||
|
int nextEntry = 0x100; // always start with a fresh table
|
||||||
|
String prev = "";
|
||||||
|
|
||||||
|
while (ptr < rleLength)
|
||||||
|
{
|
||||||
|
int codeWord = readInt (width (nextEntry + 1));
|
||||||
|
String s = (nextEntry == codeWord) ? prev + prev.charAt (0) : st[codeWord];
|
||||||
|
|
||||||
|
if (nextEntry < st.length)
|
||||||
|
st[nextEntry++] = prev + s.charAt (0);
|
||||||
|
|
||||||
|
for (int i = 0; i < s.length (); i++)
|
||||||
|
lzwBuffer[ptr++] = (byte) s.charAt (i);
|
||||||
|
|
||||||
|
prev = s;
|
||||||
|
}
|
||||||
|
return lzwBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
text.append (String.format (" crc ............... %,d (%04X)%n", crc, crc));
|
||||||
|
text.append (String.format (" volume ............ %,d%n", volume));
|
||||||
|
text.append (String.format (" RLE char .......... $%02X", runLengthChar));
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
112
src/com/bytezone/diskbrowser/LZW2.java
Normal file
112
src/com/bytezone/diskbrowser/LZW2.java
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
package com.bytezone.diskbrowser;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import com.bytezone.common.Utility;
|
||||||
|
|
||||||
|
class LZW2 extends LZW
|
||||||
|
{
|
||||||
|
private int nextEntry = 0x100;
|
||||||
|
private String prev = "";
|
||||||
|
private int codeWord;
|
||||||
|
|
||||||
|
public LZW2 (byte[] buffer, int crc)
|
||||||
|
{
|
||||||
|
bytes = Objects.requireNonNull (buffer);
|
||||||
|
|
||||||
|
this.crc = crc;
|
||||||
|
crcBase = 0xFFFF;
|
||||||
|
codeWord = 0;
|
||||||
|
|
||||||
|
volume = buffer[0] & 0xFF;
|
||||||
|
runLengthChar = (byte) (buffer[1] & 0xFF);
|
||||||
|
int ptr = 2;
|
||||||
|
|
||||||
|
while (ptr < buffer.length - 1) // what is in the last byte?
|
||||||
|
{
|
||||||
|
int rleLength = Utility.getWord (buffer, ptr);
|
||||||
|
boolean lzwPerformed = (rleLength & 0x8000) != 0;
|
||||||
|
ptr += 2;
|
||||||
|
|
||||||
|
if (lzwPerformed)
|
||||||
|
{
|
||||||
|
rleLength &= 0x0FFF; // remove the LZW flag
|
||||||
|
if (rleLength == 0)
|
||||||
|
rleLength = TRACK_LENGTH;
|
||||||
|
|
||||||
|
int chunkLength = Utility.getWord (buffer, ptr);
|
||||||
|
ptr += 2;
|
||||||
|
|
||||||
|
setBuffer (buffer, ptr); // prepare to read n-bit integers
|
||||||
|
byte[] lzwBuffer = undoLZW (rleLength);
|
||||||
|
|
||||||
|
assert (chunkLength - 4) == bytesRead ();
|
||||||
|
|
||||||
|
if (rleLength == TRACK_LENGTH) // no run length encoding
|
||||||
|
chunks.add (lzwBuffer);
|
||||||
|
else
|
||||||
|
chunks.add (undoRLE (lzwBuffer, 0, lzwBuffer.length));
|
||||||
|
|
||||||
|
ptr += bytesRead (); // since the setBuffer()
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nextEntry = 0x100;
|
||||||
|
if (rleLength == 0)
|
||||||
|
rleLength = TRACK_LENGTH;
|
||||||
|
|
||||||
|
if (rleLength == TRACK_LENGTH) // no run length encoding
|
||||||
|
{
|
||||||
|
byte[] originalBuffer = new byte[TRACK_LENGTH];
|
||||||
|
System.arraycopy (buffer, ptr, originalBuffer, 0, originalBuffer.length);
|
||||||
|
chunks.add (originalBuffer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
chunks.add (undoRLE (buffer, ptr, rleLength));
|
||||||
|
|
||||||
|
ptr += rleLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected byte[] undoLZW (int rleLength)
|
||||||
|
{
|
||||||
|
byte[] lzwBuffer = new byte[rleLength]; // must fill this array from buffer
|
||||||
|
int ptr = 0;
|
||||||
|
|
||||||
|
while (ptr < rleLength)
|
||||||
|
{
|
||||||
|
codeWord = readInt (width (nextEntry + 1));
|
||||||
|
|
||||||
|
if (codeWord == 0x100) // clear the table
|
||||||
|
{
|
||||||
|
nextEntry = 0x100;
|
||||||
|
codeWord = readInt (9);
|
||||||
|
prev = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String s = (nextEntry == codeWord) ? prev + prev.charAt (0) : st[codeWord];
|
||||||
|
|
||||||
|
if (nextEntry < st.length)
|
||||||
|
st[nextEntry++] = prev + s.charAt (0);
|
||||||
|
|
||||||
|
for (int i = 0; i < s.length (); i++)
|
||||||
|
lzwBuffer[ptr++] = (byte) s.charAt (i);
|
||||||
|
|
||||||
|
prev = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lzwBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
text.append (String.format (" volume ............ %,d%n", volume));
|
||||||
|
text.append (String.format (" RLE char .......... $%02X", runLengthChar));
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
268
src/com/bytezone/diskbrowser/NuFX.java
Normal file
268
src/com/bytezone/diskbrowser/NuFX.java
Normal file
|
@ -0,0 +1,268 @@
|
||||||
|
package com.bytezone.diskbrowser;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.bytezone.common.Utility;
|
||||||
|
|
||||||
|
public class NuFX
|
||||||
|
{
|
||||||
|
private static String[] fileSystems = {//
|
||||||
|
"", "ProDOS/SOS", "DOS 3.3", "DOS 3.2", "Apple II Pascal", "Macintosh HFS",
|
||||||
|
"Macintosh MFS", "Lisa File System", "Apple CP/M", "", "MS-DOS", "High Sierra",
|
||||||
|
"ISO 9660", "AppleShare" };
|
||||||
|
private final Header header;
|
||||||
|
private final byte[] buffer;
|
||||||
|
private final boolean debug = false;
|
||||||
|
|
||||||
|
private final List<Record> records = new ArrayList<Record> ();
|
||||||
|
private final List<Thread> threads = new ArrayList<Thread> ();
|
||||||
|
|
||||||
|
public NuFX (Path path) throws FileFormatException, IOException
|
||||||
|
{
|
||||||
|
buffer = Files.readAllBytes (path);
|
||||||
|
header = new Header (buffer);
|
||||||
|
|
||||||
|
int dataPtr = 48;
|
||||||
|
if (header.bin2)
|
||||||
|
dataPtr += 128;
|
||||||
|
|
||||||
|
if (debug)
|
||||||
|
System.out.printf ("%s%n%n", header);
|
||||||
|
|
||||||
|
for (int rec = 0; rec < header.totalRecords; rec++)
|
||||||
|
{
|
||||||
|
Record record = new Record (dataPtr);
|
||||||
|
records.add (record);
|
||||||
|
|
||||||
|
if (debug)
|
||||||
|
System.out.printf ("Record: %d%n%n%s%n%n", rec, record);
|
||||||
|
|
||||||
|
dataPtr += record.attributes + record.fileNameLength;
|
||||||
|
int threadsPtr = dataPtr;
|
||||||
|
dataPtr += record.totThreads * 16;
|
||||||
|
|
||||||
|
for (int i = 0; i < record.totThreads; i++)
|
||||||
|
{
|
||||||
|
Thread thread = new Thread (buffer, threadsPtr + i * 16, dataPtr);
|
||||||
|
threads.add (thread);
|
||||||
|
dataPtr += thread.getCompressedEOF ();
|
||||||
|
|
||||||
|
if (debug)
|
||||||
|
System.out.printf ("Thread: %d%n%n%s%n%n", i, thread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getBuffer ()
|
||||||
|
{
|
||||||
|
for (Thread thread : threads)
|
||||||
|
if (thread.hasDisk ())
|
||||||
|
return thread.getData ();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
for (Thread thread : threads)
|
||||||
|
if (thread.hasDisk ())
|
||||||
|
return thread.toString ();
|
||||||
|
return "no disk";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static int getCRC (final byte[] buffer, int base)
|
||||||
|
{
|
||||||
|
int crc = base;
|
||||||
|
for (int j = 0; j < buffer.length; j++)
|
||||||
|
{
|
||||||
|
crc = ((crc >>> 8) | (crc << 8)) & 0xFFFF;
|
||||||
|
crc ^= (buffer[j] & 0xFF);
|
||||||
|
crc ^= ((crc & 0xFF) >> 4);
|
||||||
|
crc ^= (crc << 12) & 0xFFFF;
|
||||||
|
crc ^= ((crc & 0xFF) << 5) & 0xFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
crc &= 0xFFFF;
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Header
|
||||||
|
{
|
||||||
|
private final int totalRecords;
|
||||||
|
private final int version;
|
||||||
|
private final int eof;
|
||||||
|
private final int crc;
|
||||||
|
private final DateTime created;
|
||||||
|
private final DateTime modified;
|
||||||
|
boolean bin2;
|
||||||
|
|
||||||
|
public Header (byte[] buffer) throws FileFormatException
|
||||||
|
{
|
||||||
|
int ptr = 0;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (isNuFile (buffer, ptr))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (isBin2 (buffer, ptr))
|
||||||
|
{
|
||||||
|
ptr += 128;
|
||||||
|
bin2 = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new FileFormatException ("NuFile not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
crc = Utility.getWord (buffer, ptr + 6);
|
||||||
|
totalRecords = Utility.getLong (buffer, ptr + 8);
|
||||||
|
created = new DateTime (buffer, ptr + 12);
|
||||||
|
modified = new DateTime (buffer, ptr + 20);
|
||||||
|
version = Utility.getWord (buffer, ptr + 28);
|
||||||
|
eof = Utility.getLong (buffer, ptr + 38);
|
||||||
|
|
||||||
|
byte[] crcBuffer = new byte[40];
|
||||||
|
System.arraycopy (buffer, ptr + 8, crcBuffer, 0, crcBuffer.length);
|
||||||
|
if (crc != getCRC (crcBuffer, 0))
|
||||||
|
{
|
||||||
|
System.out.println ("***** Master CRC mismatch *****");
|
||||||
|
throw new FileFormatException ("Master CRC failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNuFile (byte[] buffer, int ptr)
|
||||||
|
{
|
||||||
|
if (buffer[ptr] == 0x4E && buffer[ptr + 1] == (byte) 0xF5 && buffer[ptr + 2] == 0x46
|
||||||
|
&& buffer[ptr + 3] == (byte) 0xE9 && buffer[ptr + 4] == 0x6C
|
||||||
|
&& buffer[ptr + 5] == (byte) 0xE5)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isBin2 (byte[] buffer, int ptr)
|
||||||
|
{
|
||||||
|
if (buffer[ptr] == 0x0A && buffer[ptr + 1] == 0x47 && buffer[ptr + 2] == 0x4C
|
||||||
|
&& buffer[ptr + 18] == (byte) 0x02)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
text.append (String.format ("Master CRC ..... %,d (%04X)%n", crc, crc));
|
||||||
|
text.append (String.format ("Records ........ %,d%n", totalRecords));
|
||||||
|
text.append (String.format ("Created ........ %s%n", created.format ()));
|
||||||
|
text.append (String.format ("Modified ....... %s%n", modified.format ()));
|
||||||
|
text.append (String.format ("Version ........ %,d%n", version));
|
||||||
|
text.append (String.format ("Master EOF ..... %,d", eof));
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Record
|
||||||
|
{
|
||||||
|
private final int totThreads;
|
||||||
|
private final int crc;
|
||||||
|
private final char separator;
|
||||||
|
private final int fileSystemID;
|
||||||
|
private final int attributes;
|
||||||
|
private final int version;
|
||||||
|
private final int access;
|
||||||
|
private final int fileType;
|
||||||
|
private final int auxType;
|
||||||
|
private final int storType;
|
||||||
|
private final DateTime created;
|
||||||
|
private final DateTime modified;
|
||||||
|
private final DateTime archived;
|
||||||
|
private final int optionSize;
|
||||||
|
private final int fileNameLength;
|
||||||
|
private final String fileName;
|
||||||
|
|
||||||
|
public Record (int dataPtr) throws FileFormatException
|
||||||
|
{
|
||||||
|
// check for NuFX
|
||||||
|
if (!isNuFX (buffer, dataPtr))
|
||||||
|
throw new FileFormatException ("NuFX not found");
|
||||||
|
|
||||||
|
crc = Utility.getWord (buffer, dataPtr + 4);
|
||||||
|
attributes = Utility.getWord (buffer, dataPtr + 6);
|
||||||
|
version = Utility.getWord (buffer, dataPtr + 8);
|
||||||
|
totThreads = Utility.getLong (buffer, dataPtr + 10);
|
||||||
|
fileSystemID = Utility.getWord (buffer, dataPtr + 14);
|
||||||
|
separator = (char) (buffer[dataPtr + 16] & 0x00FF);
|
||||||
|
access = Utility.getLong (buffer, dataPtr + 18);
|
||||||
|
fileType = Utility.getLong (buffer, dataPtr + 22);
|
||||||
|
auxType = Utility.getLong (buffer, dataPtr + 26);
|
||||||
|
storType = Utility.getWord (buffer, dataPtr + 30);
|
||||||
|
created = new DateTime (buffer, dataPtr + 32);
|
||||||
|
modified = new DateTime (buffer, dataPtr + 40);
|
||||||
|
archived = new DateTime (buffer, dataPtr + 48);
|
||||||
|
optionSize = Utility.getWord (buffer, dataPtr + 56);
|
||||||
|
fileNameLength = Utility.getWord (buffer, dataPtr + attributes - 2);
|
||||||
|
|
||||||
|
int len = attributes + fileNameLength - 6;
|
||||||
|
byte[] crcBuffer = new byte[len + totThreads * 16];
|
||||||
|
System.arraycopy (buffer, dataPtr + 6, crcBuffer, 0, crcBuffer.length);
|
||||||
|
|
||||||
|
if (crc != getCRC (crcBuffer, 0))
|
||||||
|
{
|
||||||
|
System.out.println ("***** Header CRC mismatch *****");
|
||||||
|
throw new FileFormatException ("Header CRC failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileNameLength > 0)
|
||||||
|
{
|
||||||
|
int start = dataPtr + attributes;
|
||||||
|
int end = start + fileNameLength;
|
||||||
|
for (int i = start; i < end; i++)
|
||||||
|
buffer[i] &= 0x7F;
|
||||||
|
fileName = new String (buffer, start, fileNameLength);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
fileName = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isNuFX (byte[] buffer, int ptr)
|
||||||
|
{
|
||||||
|
if (buffer[ptr] == 0x4E && buffer[ptr + 1] == (byte) 0xF5 && buffer[ptr + 2] == 0x46
|
||||||
|
&& buffer[ptr + 3] == (byte) 0xD8)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
text.append (String.format ("Header CRC ..... %,d (%04X)%n", crc, crc));
|
||||||
|
text.append (String.format ("Attributes ..... %d%n", attributes));
|
||||||
|
text.append (String.format ("Version ........ %d%n", version));
|
||||||
|
text.append (String.format ("Threads ........ %d%n", totThreads));
|
||||||
|
text.append (String.format ("File sys id .... %d (%s)%n", fileSystemID,
|
||||||
|
fileSystems[fileSystemID]));
|
||||||
|
text.append (String.format ("Separator ...... %s%n", separator));
|
||||||
|
text.append (String.format ("Access ......... %,d%n", access));
|
||||||
|
text.append (String.format ("File type ...... %,d%n", fileType));
|
||||||
|
text.append (String.format ("Aux type ....... %,d%n", auxType));
|
||||||
|
text.append (String.format ("Stor type ...... %,d%n", storType));
|
||||||
|
text.append (String.format ("Created ........ %s%n", created.format ()));
|
||||||
|
text.append (String.format ("Modified ....... %s%n", modified.format ()));
|
||||||
|
text.append (String.format ("Archived ....... %s%n", archived.format ()));
|
||||||
|
text.append (String.format ("Option size .... %,d%n", optionSize));
|
||||||
|
text.append (String.format ("Filename len ... %,d%n", fileNameLength));
|
||||||
|
text.append (String.format ("Filename ....... %s", fileName));
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
136
src/com/bytezone/diskbrowser/Thread.java
Normal file
136
src/com/bytezone/diskbrowser/Thread.java
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
package com.bytezone.diskbrowser;
|
||||||
|
|
||||||
|
import com.bytezone.common.Utility;
|
||||||
|
|
||||||
|
class Thread
|
||||||
|
{
|
||||||
|
private static String[] threadClassText = { "Message", "Control", "Data", "Filename" };
|
||||||
|
private static String[] formatText = { //
|
||||||
|
"Uncompressed", "Huffman squeeze", "LZW/1", "LZW/2", "Unix 12-bit Compress",
|
||||||
|
"Unix 16-bit Compress" };
|
||||||
|
private static String[][] threadKindText = {//
|
||||||
|
{ "ASCII text", "predefined EOF", "IIgs icon" },
|
||||||
|
{ "create directory", "undefined", "undefined" },
|
||||||
|
{ "data fork", "disk image", "resource fork" },
|
||||||
|
{ "filename", "undefined", "undefined" } };
|
||||||
|
|
||||||
|
private final ThreadHeader header;
|
||||||
|
private final byte[] data;
|
||||||
|
private String filename;
|
||||||
|
private String message;
|
||||||
|
private LZW lzw;
|
||||||
|
|
||||||
|
public Thread (byte[] buffer, int offset, int dataOffset)
|
||||||
|
{
|
||||||
|
header = new ThreadHeader (buffer, offset);
|
||||||
|
|
||||||
|
data = new byte[header.compressedEOF];
|
||||||
|
System.arraycopy (buffer, dataOffset, data, 0, data.length);
|
||||||
|
|
||||||
|
switch (header.threadClass)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
if (header.threadKind == 1)
|
||||||
|
message = new String (data, 0, header.uncompressedEOF);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
if (header.threadKind == 1)
|
||||||
|
{
|
||||||
|
if (header.format == 2)
|
||||||
|
lzw = new LZW1 (data);
|
||||||
|
else if (header.format == 3)
|
||||||
|
lzw = new LZW2 (data, header.crc);
|
||||||
|
else if (header.format == 1)
|
||||||
|
{
|
||||||
|
// Huffman Squeeze
|
||||||
|
System.out.println ("Huffman Squeeze format - not written yet");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
if (header.threadKind == 0)
|
||||||
|
filename = new String (data, 0, header.uncompressedEOF);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
System.out.println ("Unknown threadClass: " + header.threadClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getData ()
|
||||||
|
{
|
||||||
|
return hasDisk () ? lzw.getData () : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getCompressedEOF ()
|
||||||
|
{
|
||||||
|
return header.compressedEOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasDisk ()
|
||||||
|
{
|
||||||
|
return lzw != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder (header.toString ());
|
||||||
|
|
||||||
|
if (filename != null)
|
||||||
|
text.append ("\n filename .......... " + filename);
|
||||||
|
else if (message != null)
|
||||||
|
text.append ("\n message ........... " + message);
|
||||||
|
else if (lzw != null)
|
||||||
|
{
|
||||||
|
text.append ("\n");
|
||||||
|
text.append (lzw);
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ThreadHeader
|
||||||
|
{
|
||||||
|
private final int threadClass;
|
||||||
|
private final int format;
|
||||||
|
private final int threadKind;
|
||||||
|
private final int crc;
|
||||||
|
private final int uncompressedEOF;
|
||||||
|
private final int compressedEOF;
|
||||||
|
|
||||||
|
public ThreadHeader (byte[] buffer, int offset)
|
||||||
|
{
|
||||||
|
threadClass = Utility.getWord (buffer, offset);
|
||||||
|
format = Utility.getWord (buffer, offset + 2);
|
||||||
|
threadKind = Utility.getWord (buffer, offset + 4);
|
||||||
|
crc = Utility.getWord (buffer, offset + 6);
|
||||||
|
uncompressedEOF = Utility.getLong (buffer, offset + 8);
|
||||||
|
compressedEOF = Utility.getLong (buffer, offset + 12);
|
||||||
|
// System.out.println (Utility.toHex (buffer, offset, 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
text.append (String.format (" threadClass ....... %d %s%n", threadClass,
|
||||||
|
threadClassText[threadClass]));
|
||||||
|
text.append (String
|
||||||
|
.format (" format ............ %d %s%n", format, formatText[format]));
|
||||||
|
text.append (String.format (" kind .............. %d %s%n", threadKind,
|
||||||
|
threadKindText[threadClass][threadKind]));
|
||||||
|
text.append (String.format (" crc ............... %,d%n", crc));
|
||||||
|
text.append (String.format (" uncompressedEOF ... %,d%n", uncompressedEOF));
|
||||||
|
text.append (String.format (" compressedEOF ..... %,d (%08X)", compressedEOF,
|
||||||
|
compressedEOF));
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
95
src/com/bytezone/diskbrowser/applefile/AbstractFile.java
Executable file
95
src/com/bytezone/diskbrowser/applefile/AbstractFile.java
Executable file
|
@ -0,0 +1,95 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
import com.bytezone.diskbrowser.gui.DataSource;
|
||||||
|
|
||||||
|
public abstract class AbstractFile implements DataSource
|
||||||
|
{
|
||||||
|
public String name;
|
||||||
|
public byte[] buffer;
|
||||||
|
AssemblerProgram assembler;
|
||||||
|
protected BufferedImage image;
|
||||||
|
protected List<HexBlock> hexBlocks = new ArrayList<HexBlock> ();
|
||||||
|
|
||||||
|
public AbstractFile (String name, byte[] buffer)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.buffer = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText () // Override this to get a tailored text representation
|
||||||
|
{
|
||||||
|
return "Name : " + name + "\n\nNo text description";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAssembler ()
|
||||||
|
{
|
||||||
|
if (buffer == null)
|
||||||
|
return "No buffer";
|
||||||
|
if (assembler == null)
|
||||||
|
this.assembler = new AssemblerProgram (name, buffer, 0);
|
||||||
|
return assembler.getText ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHexDump ()
|
||||||
|
{
|
||||||
|
if (hexBlocks.size () > 0)
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
for (HexBlock hb : hexBlocks)
|
||||||
|
{
|
||||||
|
if (hb.title != null)
|
||||||
|
text.append (hb.title + "\n\n");
|
||||||
|
text.append (HexFormatter.format (buffer, hb.ptr, hb.size) + "\n\n");
|
||||||
|
}
|
||||||
|
text.deleteCharAt (text.length () - 1);
|
||||||
|
text.deleteCharAt (text.length () - 1);
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
if (buffer == null || buffer.length == 0)
|
||||||
|
return "No buffer";
|
||||||
|
if (buffer.length <= 99999)
|
||||||
|
return HexFormatter.format (buffer, 0, buffer.length);
|
||||||
|
return HexFormatter.format (buffer, 0, 99999);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BufferedImage getImage ()
|
||||||
|
{
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JComponent getComponent ()
|
||||||
|
{
|
||||||
|
System.out.println ("In AbstractFile.getComponent()");
|
||||||
|
JPanel panel = new JPanel ();
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class HexBlock
|
||||||
|
{
|
||||||
|
public int ptr;
|
||||||
|
public int size;
|
||||||
|
public String title;
|
||||||
|
|
||||||
|
public HexBlock (int ptr, int size, String title)
|
||||||
|
{
|
||||||
|
this.ptr = ptr;
|
||||||
|
this.size = size;
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
32
src/com/bytezone/diskbrowser/applefile/AppleFileSource.java
Executable file
32
src/com/bytezone/diskbrowser/applefile/AppleFileSource.java
Executable file
|
@ -0,0 +1,32 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.disk.DiskAddress;
|
||||||
|
import com.bytezone.diskbrowser.disk.FormattedDisk;
|
||||||
|
import com.bytezone.diskbrowser.gui.DataSource;
|
||||||
|
|
||||||
|
public interface AppleFileSource
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Returns a name that uniquely identifies this object within the disk.
|
||||||
|
*/
|
||||||
|
public String getUniqueName ();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DataSource is implemented by AbstractSector and AbstractFile, and provides
|
||||||
|
* routines to display the data in various formats (text, hex, assembler and
|
||||||
|
* image).
|
||||||
|
*/
|
||||||
|
public DataSource getDataSource ();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns a list of sectors used by this object.
|
||||||
|
*/
|
||||||
|
public List<DiskAddress> getSectors ();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns the actual FormattedDisk that owns this object.
|
||||||
|
*/
|
||||||
|
public FormattedDisk getFormattedDisk ();
|
||||||
|
}
|
29
src/com/bytezone/diskbrowser/applefile/ApplesoftConstants.java
Executable file
29
src/com/bytezone/diskbrowser/applefile/ApplesoftConstants.java
Executable file
|
@ -0,0 +1,29 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
public interface ApplesoftConstants
|
||||||
|
{
|
||||||
|
String[] tokens = { "END", "FOR ", "NEXT ", "DATA ", "INPUT ", "DEL", "DIM ", "READ ", "GR",
|
||||||
|
"TEXT", "PR#", "IN#", "CALL ", "PLOT", "HLIN ", "VLIN ", "HGR2", "HGR",
|
||||||
|
"HCOLOR=", "HPLOT ", "DRAW ", "XDRAW ", "HTAB ", "HOME", "ROT=", "SCALE=",
|
||||||
|
"SHLOAD", "TRACE", "NOTRACE", "NORMAL", "INVERSE", "FLASH", "COLOR=", "POP",
|
||||||
|
"VTAB ", "HIMEM:", "LOMEM:", "ONERR ", "RESUME", "RECALL", "STORE", "SPEED=",
|
||||||
|
"LET ", "GOTO ", "RUN", "IF ", "RESTORE", "& ", "GOSUB ", "RETURN", "REM ",
|
||||||
|
"STOP", "ON ", "WAIT", "LOAD", "SAVE", "DEF", "POKE ", "PRINT ", "CONT",
|
||||||
|
"LIST", "CLEAR", "GET ", "NEW", "TAB(", "TO ", "FN", "SPC(", "THEN ", "AT ",
|
||||||
|
"NOT ", "STEP ", "+ ", "- ", "* ", "/ ", "^ ", "AND ", "OR ", "> ", "= ",
|
||||||
|
"< ", "SGN", "INT", "ABS", "USR", "FRE", "SCRN(", "PDL", "POS ", "SQR", "RND",
|
||||||
|
"LOG", "EXP", "COS", "SIN", "TAN", "ATN", "PEEK", "LEN", "STR$", "VAL", "ASC",
|
||||||
|
"CHR$", "LEFT$", "RIGHT$", "MID$" };
|
||||||
|
|
||||||
|
int[] tokenAddresses = { 0xD870, 0xD766, 0xDCF9, 0xD995, 0xDBB2, 0xF331, 0xDFD9, 0xDBE2, 0xF390,
|
||||||
|
0xF399, 0xF1E5, 0xF1DE, 0xF1D5, 0xF225, 0xF232, 0xF241, 0xF3D8, 0xF3E2,
|
||||||
|
0xF6E9, 0xF6FE, 0xF769, 0xF76F, 0xF7E7, 0xFC58, 0xF721, 0xF727, 0xF775,
|
||||||
|
0xF26D, 0xF26F, 0xF273, 0xF277, 0xF280, 0xF24F, 0xD96B, 0xF256, 0xF286,
|
||||||
|
0xF2A6, 0xF2CB, 0xF318, 0xF3BC, 0xF39F, 0xF262, 0xDA46, 0xD93E, 0xD912,
|
||||||
|
0xD9C9, 0xD849, 0x03F5, 0xD921, 0xD96B, 0xD9DC, 0xD86E, 0xD9EC, 0xE784,
|
||||||
|
0xD8C9, 0xD8B0, 0xE313, 0xE77B, 0xFDAD5, 0xD896, 0xD6A5, 0xD66A, 0xDBA0,
|
||||||
|
0xD649, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xEB90,
|
||||||
|
0xEC23, 0xEBAF, 0x000A, 0xE2DE, 0xD412, 0xDFCD, 0xE2FF, 0xEE8D, 0xEFAE,
|
||||||
|
0xE941, 0xEF09, 0xEFEA, 0xEFF1, 0xF03A, 0xF09E, 0xE764, 0xE6D6, 0xE3C5,
|
||||||
|
0xE707, 0xE6E5, 0xE646, 0xE65A, 0xE686, 0xE691 };
|
||||||
|
}
|
67
src/com/bytezone/diskbrowser/applefile/AssemblerConstants.java
Executable file
67
src/com/bytezone/diskbrowser/applefile/AssemblerConstants.java
Executable file
|
@ -0,0 +1,67 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
public interface AssemblerConstants
|
||||||
|
{
|
||||||
|
// 1A = INC A, 3A = DEC A
|
||||||
|
String[] mnemonics = { "BRK", "ORA", "???", "???", "TSB", "ORA", "ASL", "???", // 00
|
||||||
|
"PHP", "ORA", "ASL", "???", "TSB", "ORA", "ASL", "???", // 08
|
||||||
|
"BPL", "ORA", "ORA", "???", "TRB", "ORA", "ASL", "???", // 10
|
||||||
|
"CLC", "ORA", "INC", "???", "TRB", "ORA", "ASL", "???", // 18
|
||||||
|
"JSR", "AND", "???", "???", "BIT", "AND", "ROL", "???", // 20
|
||||||
|
"PLP", "AND", "ROL", "???", "BIT", "AND", "ROL", "???", // 28
|
||||||
|
"BMI", "AND", "AND", "???", "BIT", "AND", "ROL", "???", // 30
|
||||||
|
"SEC", "AND", "DEC", "???", "BIT", "AND", "ROL", "???", // 38
|
||||||
|
"RTI", "EOR", "???", "???", "???", "EOR", "LSR", "???", // 40
|
||||||
|
"PHA", "EOR", "LSR", "???", "JMP", "EOR", "LSR", "???", // 48
|
||||||
|
"BVC", "EOR", "EOR", "???", "???", "EOR", "LSR", "???", // 50
|
||||||
|
"CLI", "EOR", "PHY", "???", "???", "EOR", "LSR", "???", // 58
|
||||||
|
"RTS", "ADC", "???", "???", "STZ", "ADC", "ROR", "???", // 60
|
||||||
|
"PLA", "ADC", "ROR", "???", "JMP", "ADC", "ROR", "???", // 68
|
||||||
|
"BVS", "ADC", "ADC", "???", "STZ", "ADC", "ROR", "???", // 70
|
||||||
|
"SEI", "ADC", "PLY", "???", "JMP", "ADC", "ROR", "???", // 78
|
||||||
|
"BRA", "STA", "???", "???", "STY", "STA", "STX", "???", // 80
|
||||||
|
"DEY", "BIT", "TXA", "???", "STY", "STA", "STX", "???", // 88
|
||||||
|
"BCC", "STA", "STA", "???", "STY", "STA", "STX", "???", // 90
|
||||||
|
"TYA", "STA", "TXS", "???", "STZ", "STA", "STZ", "???", // 98
|
||||||
|
"LDY", "LDA", "LDX", "???", "LDY", "LDA", "LDX", "???", // A0
|
||||||
|
"TAY", "LDA", "TAX", "???", "LDY", "LDA", "LDX", "???", // A8
|
||||||
|
"BCS", "LDA", "LDA", "???", "LDY", "LDA", "LDX", "???", // B0
|
||||||
|
"CLV", "LDA", "TSX", "???", "LDY", "LDA", "LDX", "???", // B8
|
||||||
|
"CPY", "CMP", "???", "???", "CPY", "CMP", "DEC", "???", // C0
|
||||||
|
"INY", "CMP", "DEX", "???", "CPY", "CMP", "DEC", "???", // C8
|
||||||
|
"BNE", "CMP", "CMP", "???", "???", "CMP", "DEC", "???", // D0
|
||||||
|
"CLD", "CMP", "PHX", "???", "???", "CMP", "DEC", "???", // D8
|
||||||
|
"CPX", "SBC", "???", "???", "CPX", "SBC", "INC", "???", // E0
|
||||||
|
"INX", "SBC", "NOP", "???", "CPX", "SBC", "INC", "???", // E8
|
||||||
|
"BEQ", "SBC", "SBC", "???", "???", "SBC", "INC", "???", // F0
|
||||||
|
"SED", "SBC", "PLX", "???", "???", "SBC", "INC", "???" }; // F8
|
||||||
|
|
||||||
|
byte[] sizes2 = { 1, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, // 00 - 0F
|
||||||
|
2, 2, 2, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0, // 10 - 1F
|
||||||
|
3, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, // 20 - 2F
|
||||||
|
2, 2, 2, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0, // 30 - 3F
|
||||||
|
1, 2, 0, 0, 0, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, // 40 - 4F
|
||||||
|
2, 2, 2, 0, 0, 2, 2, 0, 1, 3, 1, 0, 0, 3, 3, 0, // 50 - 5F
|
||||||
|
1, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, // 60 - 6F
|
||||||
|
2, 2, 2, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0, // 70 - 7F
|
||||||
|
2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, // 80 - 8F
|
||||||
|
2, 2, 2, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0, // 90 - 9F
|
||||||
|
2, 2, 2, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, // A0 - AF
|
||||||
|
2, 2, 2, 0, 2, 2, 2, 0, 1, 3, 1, 0, 3, 3, 3, 0, // B0 - BF
|
||||||
|
2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, // C0 - CF
|
||||||
|
2, 2, 2, 0, 0, 2, 2, 0, 1, 3, 1, 0, 0, 3, 3, 0, // D0 - DF
|
||||||
|
2, 2, 0, 0, 2, 2, 2, 0, 1, 2, 1, 0, 3, 3, 3, 0, // E0 - EF
|
||||||
|
2, 2, 2, 0, 0, 2, 2, 0, 1, 3, 1, 0, 0, 3, 3, 0 }; // F0 - FF
|
||||||
|
|
||||||
|
byte[] sizes = { 1, 1, 2, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2 };
|
||||||
|
|
||||||
|
String[] mode =
|
||||||
|
{ "Implied", "Accumulator", "Immediate", "Absolute", "Absolute, X", "Absolute, Y",
|
||||||
|
"(Absolute, X)", "(Absolute)", "Zero page", "Zero page, X", "Zero page, Y",
|
||||||
|
"(Zero page, X)", "(Zero page), Y", "(Zero page)", "Relative" };
|
||||||
|
|
||||||
|
byte[] chip65c02 =
|
||||||
|
{ 0x04, 0x0C, 0x12, 0x14, 0x1A, 0x1C, 0x32, 0x34, 0x3A, 0x3C, 0x52, 0x5A, 0x64, 0x72,
|
||||||
|
0x74, 0x7A, 0x7C, (byte) 0x80, (byte) 0x89, (byte) 0x92, (byte) 0x9C,
|
||||||
|
(byte) 0x9E, (byte) 0xB2, (byte) 0xD2, (byte) 0xDA, (byte) 0xF2, (byte) 0xFA, };
|
||||||
|
}
|
268
src/com/bytezone/diskbrowser/applefile/AssemblerProgram.java
Executable file
268
src/com/bytezone/diskbrowser/applefile/AssemblerProgram.java
Executable file
|
@ -0,0 +1,268 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.gui.DiskBrowser;
|
||||||
|
|
||||||
|
public class AssemblerProgram extends AbstractFile
|
||||||
|
{
|
||||||
|
private final int loadAddress;
|
||||||
|
private int executeOffset;
|
||||||
|
private static Map<Integer, String> equates;
|
||||||
|
|
||||||
|
public AssemblerProgram (String name, byte[] buffer, int address)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
this.loadAddress = address;
|
||||||
|
|
||||||
|
if (equates == null)
|
||||||
|
getEquates ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public AssemblerProgram (String name, byte[] buffer, int address, int executeOffset)
|
||||||
|
{
|
||||||
|
this (name, buffer, address);
|
||||||
|
this.executeOffset = executeOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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)));
|
||||||
|
pgm.append (String.format ("%n"));
|
||||||
|
|
||||||
|
return pgm.append (getStringBuilder ()).toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringBuilder getStringBuilder ()
|
||||||
|
{
|
||||||
|
if (true)
|
||||||
|
return getStringBuilder2 ();
|
||||||
|
|
||||||
|
StringBuilder pgm = new StringBuilder ();
|
||||||
|
|
||||||
|
int ptr = executeOffset;
|
||||||
|
int address = loadAddress + executeOffset;
|
||||||
|
|
||||||
|
// if the assembly doesn't start at the beginning, just dump the bytes that are skipped
|
||||||
|
for (int i = 0; i < executeOffset; i++)
|
||||||
|
pgm.append (String.format ("%04X: %02X%n", (loadAddress + i), buffer[i]));
|
||||||
|
|
||||||
|
while (ptr < buffer.length)
|
||||||
|
{
|
||||||
|
StringBuilder line = new StringBuilder ();
|
||||||
|
|
||||||
|
AssemblerStatement cmd = new AssemblerStatement (buffer[ptr]);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
line.append (String.format ("%04X: ", address));
|
||||||
|
for (int i = 0; i < cmd.size; i++)
|
||||||
|
line.append (String.format ("%02X ", buffer[ptr + i]));
|
||||||
|
while (line.length () < 20)
|
||||||
|
line.append (" ");
|
||||||
|
line.append (cmd.mnemonic + " " + cmd.operand);
|
||||||
|
if (cmd.offset != 0)
|
||||||
|
{
|
||||||
|
int branch = address + cmd.offset + 2;
|
||||||
|
line.append (String.format ("$%04X", branch < 0 ? branch += 0xFFFF : branch));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd.target > 0
|
||||||
|
&& (cmd.target < loadAddress - 1 || cmd.target > (loadAddress + buffer.length)))
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pgm.append (line.toString () + "\n");
|
||||||
|
address += cmd.size;
|
||||||
|
ptr += cmd.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pgm.length () > 0)
|
||||||
|
pgm.deleteCharAt (pgm.length () - 1);
|
||||||
|
|
||||||
|
return pgm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringBuilder getStringBuilder2 ()
|
||||||
|
{
|
||||||
|
StringBuilder pgm = new StringBuilder ();
|
||||||
|
List<AssemblerStatement> lines = getLines ();
|
||||||
|
|
||||||
|
// if the assembly doesn't start at the beginning, just dump the bytes that are skipped
|
||||||
|
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 ();
|
||||||
|
|
||||||
|
line.append (String.format ("%3.3s %04X: %02X ", getArrow (cmd), cmd.address, cmd.value));
|
||||||
|
|
||||||
|
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);
|
||||||
|
if (cmd.offset != 0)
|
||||||
|
{
|
||||||
|
int branch = cmd.address + cmd.offset + 2;
|
||||||
|
line.append (String.format ("$%04X", branch < 0 ? branch += 0xFFFF : branch));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd.target > 0
|
||||||
|
&& (cmd.target < loadAddress - 1 || cmd.target > (loadAddress + buffer.length)))
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pgm.append (line.toString () + "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pgm.length () > 0)
|
||||||
|
pgm.deleteCharAt (pgm.length () - 1);
|
||||||
|
|
||||||
|
return pgm;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<AssemblerStatement> getLines ()
|
||||||
|
{
|
||||||
|
List<AssemblerStatement> lines = new ArrayList<AssemblerStatement> ();
|
||||||
|
Map<Integer, AssemblerStatement> linesMap = new HashMap<Integer, AssemblerStatement> ();
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (cmd.target >= loadAddress && cmd.target < (loadAddress + buffer.length)
|
||||||
|
&& (cmd.value == 0x4C || cmd.value == 0x6C || cmd.value == 0x20))
|
||||||
|
targets.add (cmd.target);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getArrow (AssemblerStatement cmd)
|
||||||
|
{
|
||||||
|
String arrow = "";
|
||||||
|
if (cmd.value == 0x4C || cmd.value == 0x6C || cmd.value == 0x60 || cmd.offset != 0)
|
||||||
|
arrow = "<--";
|
||||||
|
if (cmd.value == 0x20 && // JSR
|
||||||
|
cmd.target >= loadAddress && cmd.target < (loadAddress + buffer.length))
|
||||||
|
arrow = "<--";
|
||||||
|
if (cmd.isTarget)
|
||||||
|
if (arrow.isEmpty ())
|
||||||
|
arrow = "-->";
|
||||||
|
else
|
||||||
|
arrow = "<->";
|
||||||
|
return arrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAssembler ()
|
||||||
|
{
|
||||||
|
return getStringBuilder ().toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getEquates ()
|
||||||
|
{
|
||||||
|
equates = new HashMap<Integer, String> ();
|
||||||
|
DataInputStream inputEquates =
|
||||||
|
new DataInputStream (DiskBrowser.class.getClassLoader ()
|
||||||
|
.getResourceAsStream ("com/bytezone/diskbrowser/applefile/equates.txt"));
|
||||||
|
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))
|
||||||
|
System.out.println ("Duplicate equate entry : " + address);
|
||||||
|
else
|
||||||
|
equates.put (address, line.substring (6));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
in.close ();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
363
src/com/bytezone/diskbrowser/applefile/AssemblerStatement.java
Executable file
363
src/com/bytezone/diskbrowser/applefile/AssemblerStatement.java
Executable file
|
@ -0,0 +1,363 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class AssemblerStatement
|
||||||
|
{
|
||||||
|
public byte value;
|
||||||
|
public String mnemonic;
|
||||||
|
public String operand;
|
||||||
|
public int size;
|
||||||
|
public int mode;
|
||||||
|
public int opcode;
|
||||||
|
public int target;
|
||||||
|
public int offset;
|
||||||
|
public String comment;
|
||||||
|
public int address;
|
||||||
|
public boolean isTarget;
|
||||||
|
public byte operand1, operand2;
|
||||||
|
|
||||||
|
public static void print ()
|
||||||
|
{
|
||||||
|
AssemblerStatement[] statements = new AssemblerStatement[256];
|
||||||
|
System.out.println ();
|
||||||
|
for (int i = 0; i < 256; i++)
|
||||||
|
{
|
||||||
|
if (i % 16 == 0 && i > 0)
|
||||||
|
System.out.println ();
|
||||||
|
AssemblerStatement as = new AssemblerStatement ((byte) i);
|
||||||
|
statements[i] = as;
|
||||||
|
if (as.size == 1)
|
||||||
|
as.addData ();
|
||||||
|
else if (as.size == 2)
|
||||||
|
as.addData ((byte) 1);
|
||||||
|
else if (as.size == 3)
|
||||||
|
as.addData ((byte) 1, (byte) 1);
|
||||||
|
if ((i / 8) % 2 == 0)
|
||||||
|
System.out.printf ("%02X %15.15s ", i, as);
|
||||||
|
}
|
||||||
|
|
||||||
|
Arrays.sort (statements, new Comparator<AssemblerStatement> ()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public int compare (AssemblerStatement o1, AssemblerStatement o2)
|
||||||
|
{
|
||||||
|
if (o1.mnemonic.equals (o2.mnemonic))
|
||||||
|
return o1.mode == o2.mode ? 0 : o1.mode < o2.mode ? -1 : 1;
|
||||||
|
return o1.mnemonic.compareTo (o2.mnemonic);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
System.out.println ();
|
||||||
|
String lastMnemonic = "";
|
||||||
|
for (AssemblerStatement as : statements)
|
||||||
|
if (as.size > 0)
|
||||||
|
{
|
||||||
|
if (!as.mnemonic.equals (lastMnemonic))
|
||||||
|
{
|
||||||
|
lastMnemonic = as.mnemonic;
|
||||||
|
System.out.println ();
|
||||||
|
}
|
||||||
|
System.out.printf ("%3s %-15s %s%n", as.mnemonic, AssemblerConstants.mode[as.mode], as);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AssemblerStatement (byte opcode)
|
||||||
|
{
|
||||||
|
this.value = opcode;
|
||||||
|
this.opcode = HexFormatter.intValue (opcode);
|
||||||
|
this.mnemonic = AssemblerConstants.mnemonics[this.opcode];
|
||||||
|
this.size = AssemblerConstants.sizes2[this.opcode];
|
||||||
|
this.operand = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addData ()
|
||||||
|
{
|
||||||
|
switch (this.opcode)
|
||||||
|
{
|
||||||
|
case 0x00: // BRK
|
||||||
|
case 0x08: // PHP
|
||||||
|
case 0x18: // CLC
|
||||||
|
case 0x28: // PLP
|
||||||
|
case 0x38: // SEC
|
||||||
|
case 0x40: // RTI
|
||||||
|
case 0x48: // PHA
|
||||||
|
case 0x58: // CLI
|
||||||
|
case 0x5A: // NOP
|
||||||
|
case 0x60: // RTS
|
||||||
|
case 0x68: // PLA
|
||||||
|
case 0x78: // SEI
|
||||||
|
case 0x7A: // NOP
|
||||||
|
case 0x88: // DEY
|
||||||
|
case 0x8A: // TXA
|
||||||
|
case 0x98: // TYA
|
||||||
|
case 0x9A: // TXS
|
||||||
|
case 0xA8: // TAY
|
||||||
|
case 0xAA: // TAX
|
||||||
|
case 0xB8: // CLV
|
||||||
|
case 0xBA: // TSX
|
||||||
|
case 0xC8: // INY
|
||||||
|
case 0xCA: // DEX
|
||||||
|
case 0xD8: // CLD
|
||||||
|
case 0xDA: // NOP
|
||||||
|
case 0xE8: // INX
|
||||||
|
case 0xEA: // NOP
|
||||||
|
case 0xF8: // SED
|
||||||
|
case 0xFA: // NOP
|
||||||
|
mode = 0; // Implied
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x0A: // ASL
|
||||||
|
case 0x1A: // NOP
|
||||||
|
case 0x2A: // ROL
|
||||||
|
case 0x3A: // NOP
|
||||||
|
case 0x4A: // LSR
|
||||||
|
case 0x6A: // ROR
|
||||||
|
mode = 1; // Accumulator
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
System.out.println ("Not found (0) : " + opcode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addData (byte b)
|
||||||
|
{
|
||||||
|
operand1 = b;
|
||||||
|
String address = "$" + HexFormatter.format2 (b);
|
||||||
|
// if (this.mnemonic.equals ("JSR"))
|
||||||
|
// this.target = HexFormatter.intValue (b);
|
||||||
|
|
||||||
|
switch (this.opcode)
|
||||||
|
{
|
||||||
|
case 0x09: // ORA
|
||||||
|
case 0x29: // AND
|
||||||
|
case 0x49: // EOR
|
||||||
|
case 0x69: // ADC
|
||||||
|
case 0x89: // NOP - 65c02
|
||||||
|
case 0xA0: // LDY
|
||||||
|
case 0xA2: // LDX
|
||||||
|
case 0xA9: // LDA
|
||||||
|
case 0xC0: // CPY
|
||||||
|
case 0xC9: // CMP
|
||||||
|
case 0xE0: // CPX
|
||||||
|
case 0xE9: // SBC
|
||||||
|
operand = "#" + address;
|
||||||
|
mode = 2; // Immediate
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x04: // NOP - 65c02
|
||||||
|
case 0x05: // ORA
|
||||||
|
case 0x06: // ASL
|
||||||
|
case 0x14: // NOP - 65c02
|
||||||
|
case 0x24: // BIT
|
||||||
|
case 0x25: // AND
|
||||||
|
case 0x26: // ROL
|
||||||
|
case 0x45: // EOR
|
||||||
|
case 0x46: // LSR
|
||||||
|
case 0x64: // NOP - 65c02
|
||||||
|
case 0x65: // ADC
|
||||||
|
case 0x66: // ROR
|
||||||
|
case 0x84: // STY
|
||||||
|
case 0x85: // STA
|
||||||
|
case 0x86: // STX
|
||||||
|
case 0xA4: // LDY
|
||||||
|
case 0xA5: // LDA
|
||||||
|
case 0xA6: // LDX
|
||||||
|
case 0xC4: // CPY
|
||||||
|
case 0xC5: // CMP
|
||||||
|
case 0xC6: // DEC
|
||||||
|
case 0xE4: // CPX
|
||||||
|
case 0xE5: // SBC
|
||||||
|
case 0xE6: // INC
|
||||||
|
target = HexFormatter.intValue (b);
|
||||||
|
operand = address;
|
||||||
|
mode = 8; // Zero page
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x15: // ORA
|
||||||
|
case 0x16: // ASL
|
||||||
|
case 0x34: // NOP - 65c02
|
||||||
|
case 0x35: // AND
|
||||||
|
case 0x36: // ROL
|
||||||
|
case 0x55: // EOR
|
||||||
|
case 0x56: // LSR
|
||||||
|
case 0x74: // NOP - 65c02
|
||||||
|
case 0x75: // ADC
|
||||||
|
case 0x76: // ROR
|
||||||
|
case 0x94: // STY
|
||||||
|
case 0x95: // STA
|
||||||
|
case 0xB4: // LDY
|
||||||
|
case 0xB5: // LDA
|
||||||
|
case 0xD5: // CMP
|
||||||
|
case 0xD6: // DEC
|
||||||
|
case 0xF5: // SBC
|
||||||
|
case 0xF6: // INC
|
||||||
|
operand = address + ",X";
|
||||||
|
mode = 9; // Zero page, X
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x96: // STX
|
||||||
|
case 0xB6: // LDX
|
||||||
|
operand = address + ",Y";
|
||||||
|
mode = 10; // Zero page, Y
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x01: // ORA
|
||||||
|
case 0x21: // AND
|
||||||
|
case 0x41: // EOR
|
||||||
|
case 0x61: // ADC
|
||||||
|
case 0x81: // STA
|
||||||
|
case 0xA1: // LDA
|
||||||
|
case 0xC1: // CMP
|
||||||
|
case 0xE1: // SEC
|
||||||
|
operand = "(" + address + ",X)";
|
||||||
|
mode = 11; // (Indirect, X)
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x11: // ORA
|
||||||
|
case 0x31: // AND
|
||||||
|
case 0x51: // EOR
|
||||||
|
case 0x71: // ADC
|
||||||
|
case 0x91: // STA
|
||||||
|
case 0xB1: // LDA
|
||||||
|
case 0xD1: // CMP
|
||||||
|
case 0xF1: // SBC
|
||||||
|
operand = "(" + address + "),Y";
|
||||||
|
mode = 12; // (Indirect), Y
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x12: // NOP
|
||||||
|
case 0x32: // NOP
|
||||||
|
case 0x52: // NOP
|
||||||
|
case 0x72: // NOP
|
||||||
|
case 0x92: // NOP
|
||||||
|
case 0xB2: // NOP
|
||||||
|
case 0xD2: // NOP
|
||||||
|
case 0xF2: // NOP
|
||||||
|
operand = "(" + address + ")"; // all 65c02
|
||||||
|
mode = 13; // (zero page)
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x10: // BPL
|
||||||
|
case 0x30: // BMI
|
||||||
|
case 0x50: // BVC
|
||||||
|
case 0x70: // BVS
|
||||||
|
case 0x80: // NOP - 65c02
|
||||||
|
case 0x90: // BCC
|
||||||
|
case 0xB0: // BCS
|
||||||
|
case 0xD0: // BNE
|
||||||
|
case 0xF0: // BEQ
|
||||||
|
offset = b;
|
||||||
|
mode = 14; // relative
|
||||||
|
this.target = HexFormatter.intValue (b);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
System.out.println ("Not found (1) : " + opcode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addData (byte b1, byte b2)
|
||||||
|
{
|
||||||
|
operand1 = b1;
|
||||||
|
operand2 = b2;
|
||||||
|
String address = "$" + HexFormatter.format2 (b2) + HexFormatter.format2 (b1);
|
||||||
|
// if (this.mnemonic.equals ("JSR") || this.mnemonic.equals ("JMP")
|
||||||
|
// || this.mnemonic.equals ("BIT") || this.mnemonic.equals ("STA")
|
||||||
|
// || this.mnemonic.equals ("LDA"))
|
||||||
|
// this.target = HexFormatter.intValue (b1, b2);
|
||||||
|
|
||||||
|
switch (this.opcode)
|
||||||
|
{
|
||||||
|
case 0x0C: // NOP - 65c02
|
||||||
|
case 0x0D: // ORA
|
||||||
|
case 0x0E: // ASL
|
||||||
|
case 0x1C: // NOP - 65c02
|
||||||
|
case 0x20: // JSR
|
||||||
|
case 0x2C: // BIT
|
||||||
|
case 0x2D: // AND
|
||||||
|
case 0x2E: // ROL
|
||||||
|
case 0x4C: // JMP
|
||||||
|
case 0x4D: // EOR
|
||||||
|
case 0x4E: // LSR
|
||||||
|
case 0x6D: // ADC
|
||||||
|
case 0x6E: // ROR
|
||||||
|
case 0x8C: // STY
|
||||||
|
case 0x8D: // STA
|
||||||
|
case 0x8E: // STX
|
||||||
|
case 0x9C: // NOP - 65c02
|
||||||
|
case 0xAC: // LDY
|
||||||
|
case 0xAD: // LDA
|
||||||
|
case 0xAE: // LDX
|
||||||
|
case 0xCC: // CPY
|
||||||
|
case 0xCD: // CMP
|
||||||
|
case 0xCE: // DEC
|
||||||
|
case 0xEC: // CPX
|
||||||
|
case 0xED: // SBC
|
||||||
|
case 0xEE: // INC
|
||||||
|
operand = address;
|
||||||
|
mode = 3; // absolute
|
||||||
|
this.target = HexFormatter.intValue (b1, b2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x1D: // ORA
|
||||||
|
case 0x1E: // ASL
|
||||||
|
case 0x3C: // NOP - 65c02
|
||||||
|
case 0x3D: // AND
|
||||||
|
case 0x3E: // ROL
|
||||||
|
case 0x5D: // EOR
|
||||||
|
case 0x5E: // LSR
|
||||||
|
case 0x7D: // ADC
|
||||||
|
case 0x7E: // ROR
|
||||||
|
case 0x9D: // STA
|
||||||
|
case 0x9E: // NOP - 65c02
|
||||||
|
case 0xBC: // LDY
|
||||||
|
case 0xBD: // LDA
|
||||||
|
case 0xDD: // CMP
|
||||||
|
case 0xDE: // DEC
|
||||||
|
case 0xFD: // SBC
|
||||||
|
case 0xFE: // INC
|
||||||
|
operand = address + ",X";
|
||||||
|
mode = 4; // absolute, X
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x19: // ORA
|
||||||
|
case 0x39: // AND
|
||||||
|
case 0x59: // EOR
|
||||||
|
case 0x79: // ADC
|
||||||
|
case 0x99: // STA
|
||||||
|
case 0xB9: // LDA
|
||||||
|
case 0xBE: // LDX
|
||||||
|
case 0xD9: // CMP
|
||||||
|
case 0xF9: // SBC
|
||||||
|
operand = address + ",Y";
|
||||||
|
mode = 5; // absolute, Y
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x7C: // NOP - 65c02
|
||||||
|
operand = "(" + address + ",X)";
|
||||||
|
mode = 6; // (absolute, X)
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0x6C: // JMP
|
||||||
|
operand = "(" + address + ")";
|
||||||
|
mode = 7; // (absolute)
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
System.out.println ("Not found (2) : " + opcode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
if (offset == 0)
|
||||||
|
return String.format ("%d %3s %-10s %02X", size, mnemonic, operand, value);
|
||||||
|
return String.format ("%d %3s %-10s %02X", size, mnemonic, operand + "+" + offset, value);
|
||||||
|
}
|
||||||
|
}
|
658
src/com/bytezone/diskbrowser/applefile/BasicProgram.java
Normal file
658
src/com/bytezone/diskbrowser/applefile/BasicProgram.java
Normal file
|
@ -0,0 +1,658 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class BasicProgram extends AbstractFile
|
||||||
|
{
|
||||||
|
private static final byte ASCII_QUOTE = 0x22;
|
||||||
|
private static final byte ASCII_COLON = 0x3A;
|
||||||
|
private static final byte ASCII_SEMI_COLON = 0x3B;
|
||||||
|
|
||||||
|
private static final byte TOKEN_FOR = (byte) 0x81;
|
||||||
|
private static final byte TOKEN_NEXT = (byte) 0x82;
|
||||||
|
private static final byte TOKEN_LET = (byte) 0xAA;
|
||||||
|
private static final byte TOKEN_GOTO = (byte) 0xAB;
|
||||||
|
private static final byte TOKEN_IF = (byte) 0xAD;
|
||||||
|
private static final byte TOKEN_GOSUB = (byte) 0xB0;
|
||||||
|
private static final byte TOKEN_REM = (byte) 0xB2;
|
||||||
|
private static final byte TOKEN_PRINT = (byte) 0xBA;
|
||||||
|
private static final byte TOKEN_THEN = (byte) 0xC4;
|
||||||
|
private static final byte TOKEN_EQUALS = (byte) 0xD0;
|
||||||
|
|
||||||
|
private final List<SourceLine> sourceLines = new ArrayList<SourceLine> ();
|
||||||
|
private final int endPtr;
|
||||||
|
private final Set<Integer> gotoLines = new HashSet<Integer> ();
|
||||||
|
private final Set<Integer> gosubLines = new HashSet<Integer> ();
|
||||||
|
|
||||||
|
boolean splitRem = false; // should be a user preference
|
||||||
|
boolean alignAssign = true; // should be a user preference
|
||||||
|
boolean showTargets = true; // should be a user preference
|
||||||
|
boolean showHeader = true; // should be a user preference
|
||||||
|
boolean onlyShowTargetLineNumbers = false; // should be a user preference
|
||||||
|
int wrapPrintAt = 40;
|
||||||
|
int wrapRemAt = 60;
|
||||||
|
|
||||||
|
public BasicProgram (String name, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
|
||||||
|
int ptr = 0;
|
||||||
|
int prevOffset = 0;
|
||||||
|
|
||||||
|
int max = buffer.length - 4; // need at least 4 bytes to make a SourceLine
|
||||||
|
while (ptr < max)
|
||||||
|
{
|
||||||
|
int offset = HexFormatter.intValue (buffer[ptr], buffer[ptr + 1]);
|
||||||
|
if (offset <= prevOffset)
|
||||||
|
break;
|
||||||
|
|
||||||
|
SourceLine line = new SourceLine (ptr);
|
||||||
|
sourceLines.add (line);
|
||||||
|
ptr += line.length;
|
||||||
|
prevOffset = offset;
|
||||||
|
}
|
||||||
|
endPtr = ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
StringBuilder fullText = new StringBuilder ();
|
||||||
|
Stack<String> loopVariables = new Stack<String> ();
|
||||||
|
if (showHeader)
|
||||||
|
addHeader (fullText);
|
||||||
|
int alignPos = 0;
|
||||||
|
StringBuilder text;
|
||||||
|
int baseOffset = showTargets ? 12 : 8;
|
||||||
|
|
||||||
|
for (SourceLine line : sourceLines)
|
||||||
|
{
|
||||||
|
text = new StringBuilder (getBase (line) + " ");
|
||||||
|
|
||||||
|
int indent = loopVariables.size (); // each full line starts at the loop indent
|
||||||
|
int ifIndent = 0; // IF statement(s) limit back indentation by NEXT
|
||||||
|
|
||||||
|
for (SubLine subline : line.sublines)
|
||||||
|
{
|
||||||
|
// Allow empty statements (caused by a single colon)
|
||||||
|
if (subline.isEmpty ())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// A REM statement might conceal an assembler routine - see P.CREATE on Diags2E.DSK
|
||||||
|
if (subline.is (TOKEN_REM) && subline.containsToken ())
|
||||||
|
{
|
||||||
|
int address = subline.getAddress () + 1; // skip the REM token
|
||||||
|
fullText.append (text
|
||||||
|
+ String.format ("REM - Inline assembler @ $%02X (%d)%n", address, address));
|
||||||
|
String padding = " ".substring (0, text.length () + 2);
|
||||||
|
for (String asm : subline.getAssembler ())
|
||||||
|
fullText.append (padding + asm + "\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce the indent by each NEXT, but only as far as the IF indent allows
|
||||||
|
if (subline.is (TOKEN_NEXT))
|
||||||
|
{
|
||||||
|
popLoopVariables (loopVariables, subline);
|
||||||
|
indent = Math.max (ifIndent, loopVariables.size ());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Are we joining REM lines with the previous subline?
|
||||||
|
if (!splitRem && subline.isJoinableRem ())
|
||||||
|
{
|
||||||
|
// Join this REM statement to the previous line, so no indenting
|
||||||
|
fullText.deleteCharAt (fullText.length () - 1); // remove newline
|
||||||
|
fullText.append (" ");
|
||||||
|
}
|
||||||
|
// ... otherwise do all the indenting and showing of targets etc.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Prepare target indicators for subsequent sublines (ie no line number)
|
||||||
|
if (showTargets && !subline.isFirst ())
|
||||||
|
if (subline.is (TOKEN_GOSUB))
|
||||||
|
text.append ("<<--");
|
||||||
|
else if (subline.is (TOKEN_GOTO) || subline.isImpliedGoto ())
|
||||||
|
text.append (" <--");
|
||||||
|
|
||||||
|
// Align assign statements if required
|
||||||
|
if (alignAssign)
|
||||||
|
alignPos = alignEqualsPosition (subline, alignPos);
|
||||||
|
|
||||||
|
int column = indent * 2 + baseOffset;
|
||||||
|
while (text.length () < column)
|
||||||
|
text.append (" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the current text, then reset it
|
||||||
|
int pos = subline.is (TOKEN_REM) ? 0 : alignPos;
|
||||||
|
String lineText = subline.getAlignedText (pos);
|
||||||
|
|
||||||
|
// if (subline.is (TOKEN_REM) && lineText.length () > wrapRemAt + 4)
|
||||||
|
// {
|
||||||
|
// System.out.println (subline.getAlignedText (pos));
|
||||||
|
// String copy = lineText.substring (4);
|
||||||
|
// text.append ("REM ");
|
||||||
|
// int inset = text.length ();
|
||||||
|
// System.out.println (inset);
|
||||||
|
// List<String> remarks = splitRemark (copy, wrapRemAt);
|
||||||
|
// for (String remark : remarks)
|
||||||
|
// text.append (" ".substring (0, inset) + remark);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
text.append (lineText);
|
||||||
|
|
||||||
|
// Check for a wrapable PRINT statement (see FROM MACHINE LANGUAGE TO BASIC on DOSToolkit2eB.dsk)
|
||||||
|
if (subline.is (TOKEN_PRINT) && wrapPrintAt > 0 && countChars (text, ASCII_QUOTE) == 2
|
||||||
|
&& countChars (text, ASCII_SEMI_COLON) == 0)
|
||||||
|
{
|
||||||
|
int first = text.indexOf ("\"");
|
||||||
|
int last = text.indexOf ("\"", first + 1);
|
||||||
|
if ((last - first) > wrapPrintAt)
|
||||||
|
{
|
||||||
|
int ptr = first + wrapPrintAt;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
fullText.append (text.substring (0, ptr)
|
||||||
|
+ "\n ".substring (0, first + 1));
|
||||||
|
text.delete (0, ptr);
|
||||||
|
ptr = wrapPrintAt;
|
||||||
|
} while (text.length () > wrapPrintAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fullText.append (text + "\n");
|
||||||
|
text.setLength (0);
|
||||||
|
|
||||||
|
// Calculate indent changes that take effect after the current subline
|
||||||
|
if (subline.is (TOKEN_IF))
|
||||||
|
ifIndent = ++indent;
|
||||||
|
else if (subline.is (TOKEN_FOR))
|
||||||
|
{
|
||||||
|
loopVariables.push (subline.forVariable);
|
||||||
|
++indent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset the alignment value if we just left an IF - the indentation will be different now.
|
||||||
|
if (ifIndent > 0)
|
||||||
|
alignPos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fullText.deleteCharAt (fullText.length () - 1); // remove last newline
|
||||||
|
return fullText.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> splitRemark (String remark, int wrapLength)
|
||||||
|
{
|
||||||
|
List<String> remarks = new ArrayList<String> ();
|
||||||
|
while (remark.length () > wrapLength)
|
||||||
|
{
|
||||||
|
int max = Math.min (wrapLength, remark.length () - 1);
|
||||||
|
while (max > 0 && remark.charAt (max) != ' ')
|
||||||
|
--max;
|
||||||
|
System.out.println (remark.substring (0, max));
|
||||||
|
remarks.add (remark.substring (0, max) + "\n");
|
||||||
|
if (max == 0)
|
||||||
|
break;
|
||||||
|
remark = remark.substring (max + 1);
|
||||||
|
}
|
||||||
|
remarks.add (remark);
|
||||||
|
System.out.println (remark);
|
||||||
|
return remarks;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int countChars (StringBuilder text, byte ch)
|
||||||
|
{
|
||||||
|
int total = 0;
|
||||||
|
for (int i = 0; i < text.length (); i++)
|
||||||
|
if (text.charAt (i) == ch)
|
||||||
|
total++;
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getBase (SourceLine line)
|
||||||
|
{
|
||||||
|
if (!showTargets)
|
||||||
|
return String.format (" %5d", line.lineNumber);
|
||||||
|
|
||||||
|
String lineNumberText = String.format ("%5d", line.lineNumber);
|
||||||
|
SubLine subline = line.sublines.get (0);
|
||||||
|
String c1 = " ", c2 = " ";
|
||||||
|
if (subline.is (TOKEN_GOSUB))
|
||||||
|
c1 = "<<";
|
||||||
|
if (subline.is (TOKEN_GOTO))
|
||||||
|
c1 = " <";
|
||||||
|
if (gotoLines.contains (line.lineNumber))
|
||||||
|
c2 = "> ";
|
||||||
|
if (gosubLines.contains (line.lineNumber))
|
||||||
|
c2 = ">>";
|
||||||
|
if (c1.equals (" ") && !c2.equals (" "))
|
||||||
|
c1 = "--";
|
||||||
|
if (!c1.equals (" ") && c2.equals (" "))
|
||||||
|
c2 = "--";
|
||||||
|
if (onlyShowTargetLineNumbers && !c2.startsWith (">"))
|
||||||
|
lineNumberText = "";
|
||||||
|
return String.format ("%s%s %s", c1, c2, lineNumberText);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decide whether the current subline needs to be aligned on its equals sign. If so,
|
||||||
|
// and the column hasn't been calculated, read ahead to find the highest position.
|
||||||
|
private int alignEqualsPosition (SubLine subline, int currentAlignPosition)
|
||||||
|
{
|
||||||
|
if (subline.assignEqualPos > 0) // does the line have an equals sign?
|
||||||
|
{
|
||||||
|
if (currentAlignPosition == 0)
|
||||||
|
currentAlignPosition = findHighest (subline); // examine following sublines for alignment
|
||||||
|
return currentAlignPosition;
|
||||||
|
}
|
||||||
|
return 0; // reset it
|
||||||
|
}
|
||||||
|
|
||||||
|
// The IF processing is so that any assignment that is being aligned doesn't continue
|
||||||
|
// to the next full line (because the indentation has changed).
|
||||||
|
private int findHighest (SubLine startSubline)
|
||||||
|
{
|
||||||
|
boolean started = false;
|
||||||
|
int highestAssign = startSubline.assignEqualPos;
|
||||||
|
fast: for (SourceLine line : sourceLines)
|
||||||
|
{
|
||||||
|
boolean inIf = false;
|
||||||
|
for (SubLine subline : line.sublines)
|
||||||
|
{
|
||||||
|
if (started)
|
||||||
|
{
|
||||||
|
// Stop when we come to a line without an equals sign (except for non-split REMs).
|
||||||
|
// Lines that start with a REM always break.
|
||||||
|
if (subline.assignEqualPos == 0
|
||||||
|
// && (splitRem || !subline.is (TOKEN_REM) || subline.isFirst ()))
|
||||||
|
&& (splitRem || !subline.isJoinableRem ()))
|
||||||
|
break fast; // of champions
|
||||||
|
|
||||||
|
if (subline.assignEqualPos > highestAssign)
|
||||||
|
highestAssign = subline.assignEqualPos;
|
||||||
|
}
|
||||||
|
else if (subline == startSubline)
|
||||||
|
started = true;
|
||||||
|
else if (subline.is (TOKEN_IF))
|
||||||
|
inIf = true;
|
||||||
|
}
|
||||||
|
if (started && inIf)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return highestAssign;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHexDump ()
|
||||||
|
{
|
||||||
|
if (buffer.length < 2)
|
||||||
|
return super.getHexDump ();
|
||||||
|
|
||||||
|
StringBuilder pgm = new StringBuilder ();
|
||||||
|
if (showHeader)
|
||||||
|
addHeader (pgm);
|
||||||
|
|
||||||
|
int ptr = 0;
|
||||||
|
int offset = HexFormatter.intValue (buffer[0], buffer[1]);
|
||||||
|
int programLoadAddress = offset - getLineLength (0);
|
||||||
|
|
||||||
|
while (ptr <= endPtr) // stop at the same place as the source listing
|
||||||
|
{
|
||||||
|
int length = getLineLength (ptr);
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
pgm.append (HexFormatter.formatNoHeader (buffer, ptr, 2, programLoadAddress));
|
||||||
|
ptr += 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ptr + length < buffer.length)
|
||||||
|
pgm.append (HexFormatter.formatNoHeader (buffer, ptr, length, programLoadAddress)
|
||||||
|
+ "\n\n");
|
||||||
|
ptr += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ptr < buffer.length)
|
||||||
|
{
|
||||||
|
int length = buffer.length - ptr;
|
||||||
|
pgm.append ("\n\n");
|
||||||
|
pgm.append (HexFormatter.formatNoHeader (buffer, ptr, length, programLoadAddress));
|
||||||
|
}
|
||||||
|
|
||||||
|
return pgm.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addHeader (StringBuilder pgm)
|
||||||
|
{
|
||||||
|
pgm.append ("Name : " + name + "\n");
|
||||||
|
pgm.append ("Length : $" + HexFormatter.format4 (buffer.length));
|
||||||
|
pgm.append (" (" + buffer.length + ")\n");
|
||||||
|
|
||||||
|
int programLoadAddress = getLoadAddress ();
|
||||||
|
pgm.append ("Load at : $" + HexFormatter.format4 (programLoadAddress));
|
||||||
|
pgm.append (" (" + programLoadAddress + ")\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getLoadAddress ()
|
||||||
|
{
|
||||||
|
int programLoadAddress = 0;
|
||||||
|
if (buffer.length > 1)
|
||||||
|
{
|
||||||
|
int offset = HexFormatter.intValue (buffer[0], buffer[1]);
|
||||||
|
programLoadAddress = offset - getLineLength (0);
|
||||||
|
}
|
||||||
|
return programLoadAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getLineLength (int ptr)
|
||||||
|
{
|
||||||
|
int offset = HexFormatter.intValue (buffer[ptr], buffer[ptr + 1]);
|
||||||
|
if (offset == 0)
|
||||||
|
return 0;
|
||||||
|
ptr += 4;
|
||||||
|
int length = 5;
|
||||||
|
|
||||||
|
while (ptr < buffer.length && buffer[ptr++] != 0)
|
||||||
|
length++;
|
||||||
|
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void popLoopVariables (Stack<String> loopVariables, SubLine subline)
|
||||||
|
{
|
||||||
|
if (subline.nextVariables.length == 0) // naked NEXT
|
||||||
|
{
|
||||||
|
if (loopVariables.size () > 0)
|
||||||
|
loopVariables.pop ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
for (String variable : subline.nextVariables)
|
||||||
|
// e.g. NEXT X,Y,Z
|
||||||
|
while (loopVariables.size () > 0)
|
||||||
|
if (sameVariable (variable, loopVariables.pop ()))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean sameVariable (String v1, String v2)
|
||||||
|
{
|
||||||
|
if (v1.equals (v2))
|
||||||
|
return true;
|
||||||
|
if (v1.length () >= 2 && v2.length () >= 2 && v1.charAt (0) == v2.charAt (0)
|
||||||
|
&& v1.charAt (1) == v2.charAt (1))
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SourceLine
|
||||||
|
{
|
||||||
|
List<SubLine> sublines = new ArrayList<SubLine> ();
|
||||||
|
int lineNumber;
|
||||||
|
int linePtr;
|
||||||
|
int length;
|
||||||
|
|
||||||
|
public SourceLine (int ptr)
|
||||||
|
{
|
||||||
|
linePtr = ptr;
|
||||||
|
lineNumber = HexFormatter.intValue (buffer[ptr + 2], buffer[ptr + 3]);
|
||||||
|
|
||||||
|
int startPtr = ptr += 4;
|
||||||
|
boolean inString = false; // can toggle
|
||||||
|
boolean inRemark = false; // can only go false -> true
|
||||||
|
byte b;
|
||||||
|
|
||||||
|
while ((b = buffer[ptr++]) != 0)
|
||||||
|
{
|
||||||
|
switch (b)
|
||||||
|
{
|
||||||
|
// break IF statements into two sublines (allows for easier line indenting)
|
||||||
|
case TOKEN_IF:
|
||||||
|
if (!inString && !inRemark)
|
||||||
|
{
|
||||||
|
// skip to THEN or GOTO - if not found then it's an error
|
||||||
|
while (buffer[ptr] != TOKEN_THEN && buffer[ptr] != TOKEN_GOTO
|
||||||
|
&& buffer[ptr] != 0)
|
||||||
|
ptr++;
|
||||||
|
|
||||||
|
// keep THEN with the IF
|
||||||
|
if (buffer[ptr] == TOKEN_THEN)
|
||||||
|
++ptr;
|
||||||
|
|
||||||
|
// create subline from the condition (and THEN if it exists)
|
||||||
|
sublines.add (new SubLine (this, startPtr, ptr - startPtr));
|
||||||
|
startPtr = ptr;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// end of subline, so add it, advance startPtr and continue
|
||||||
|
case ASCII_COLON:
|
||||||
|
if (!inString && !inRemark)
|
||||||
|
{
|
||||||
|
sublines.add (new SubLine (this, startPtr, ptr - startPtr));
|
||||||
|
startPtr = ptr;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TOKEN_REM:
|
||||||
|
if (!inString && !inRemark)
|
||||||
|
inRemark = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ASCII_QUOTE:
|
||||||
|
if (!inRemark)
|
||||||
|
inString = !inString;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// add whatever is left
|
||||||
|
sublines.add (new SubLine (this, startPtr, ptr - startPtr));
|
||||||
|
this.length = ptr - linePtr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SubLine
|
||||||
|
{
|
||||||
|
SourceLine parent;
|
||||||
|
int startPtr;
|
||||||
|
int length;
|
||||||
|
String[] nextVariables;
|
||||||
|
String forVariable = "";
|
||||||
|
int targetLine = -1;
|
||||||
|
|
||||||
|
// used for aligning the equals sign
|
||||||
|
int assignEqualPos;
|
||||||
|
|
||||||
|
public SubLine (SourceLine parent, int startPtr, int length)
|
||||||
|
{
|
||||||
|
this.parent = parent;
|
||||||
|
this.startPtr = startPtr;
|
||||||
|
this.length = length;
|
||||||
|
|
||||||
|
byte b = buffer[startPtr];
|
||||||
|
if ((b & 0x80) > 0) // token
|
||||||
|
{
|
||||||
|
switch (b)
|
||||||
|
{
|
||||||
|
case TOKEN_FOR:
|
||||||
|
int p = startPtr + 1;
|
||||||
|
while (buffer[p] != TOKEN_EQUALS)
|
||||||
|
forVariable += (char) buffer[p++];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TOKEN_NEXT:
|
||||||
|
if (length == 2) // no variables
|
||||||
|
nextVariables = new String[0];
|
||||||
|
else
|
||||||
|
{
|
||||||
|
String varList = new String (buffer, startPtr + 1, length - 2);
|
||||||
|
nextVariables = varList.split (",");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TOKEN_LET:
|
||||||
|
recordEqualsPosition ();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TOKEN_GOTO:
|
||||||
|
String target = new String (buffer, startPtr + 1, length - 2);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
gotoLines.add (Integer.parseInt (target));
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e)
|
||||||
|
{
|
||||||
|
System.out.println ("Error parsing : GOTO " + target + " in "
|
||||||
|
+ parent.lineNumber);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TOKEN_GOSUB:
|
||||||
|
String target2 = new String (buffer, startPtr + 1, length - 2);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
gosubLines.add (Integer.parseInt (target2));
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e)
|
||||||
|
{
|
||||||
|
System.out.println ("Error parsing : GOSUB " + target2 + " in "
|
||||||
|
+ parent.lineNumber);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (b >= 48 && b <= 57) // numeric, so must be a line number
|
||||||
|
{
|
||||||
|
String target = new String (buffer, startPtr, length - 1);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
targetLine = Integer.parseInt (target);
|
||||||
|
gotoLines.add (targetLine);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e)
|
||||||
|
{
|
||||||
|
System.out.println (target);
|
||||||
|
System.out.println (HexFormatter.format (buffer, startPtr, length - 1));
|
||||||
|
System.out.println (e.toString ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (alignAssign)
|
||||||
|
recordEqualsPosition ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isImpliedGoto ()
|
||||||
|
{
|
||||||
|
byte b = buffer[startPtr];
|
||||||
|
if ((b & 0x80) > 0) // token
|
||||||
|
return false;
|
||||||
|
return (b >= 48 && b <= 57);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record the position of the equals sign so it can be aligned with adjacent lines.
|
||||||
|
private void recordEqualsPosition ()
|
||||||
|
{
|
||||||
|
int p = startPtr + 1;
|
||||||
|
int max = startPtr + length;
|
||||||
|
while (buffer[p] != TOKEN_EQUALS && p < max)
|
||||||
|
p++;
|
||||||
|
if (buffer[p] == TOKEN_EQUALS)
|
||||||
|
assignEqualPos = toString ().indexOf ('='); // use expanded line
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isJoinableRem ()
|
||||||
|
{
|
||||||
|
return is (TOKEN_REM) && !isFirst ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFirst ()
|
||||||
|
{
|
||||||
|
return (parent.linePtr + 4) == startPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean is (byte token)
|
||||||
|
{
|
||||||
|
return buffer[startPtr] == token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEmpty ()
|
||||||
|
{
|
||||||
|
return length == 1 && buffer[startPtr] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsToken ()
|
||||||
|
{
|
||||||
|
// ignore first byte, check the rest for tokens
|
||||||
|
for (int p = startPtr + 1, max = startPtr + length; p < max; p++)
|
||||||
|
if ((buffer[p] & 0x80) > 0)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAddress ()
|
||||||
|
{
|
||||||
|
return getLoadAddress () + startPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAlignedText (int alignPosition)
|
||||||
|
{
|
||||||
|
StringBuilder line = toStringBuilder ();
|
||||||
|
|
||||||
|
while (alignPosition-- > assignEqualPos)
|
||||||
|
line.insert (assignEqualPos, ' ');
|
||||||
|
|
||||||
|
return line.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
// A REM statement might conceal an assembler routine
|
||||||
|
public String[] getAssembler ()
|
||||||
|
{
|
||||||
|
byte[] buffer2 = new byte[length - 1];
|
||||||
|
System.arraycopy (buffer, startPtr + 1, buffer2, 0, buffer2.length);
|
||||||
|
AssemblerProgram program =
|
||||||
|
new AssemblerProgram ("REM assembler", buffer2, getAddress () + 1);
|
||||||
|
return program.getAssembler ().split ("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
return toStringBuilder ().toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public StringBuilder toStringBuilder ()
|
||||||
|
{
|
||||||
|
StringBuilder line = new StringBuilder ();
|
||||||
|
|
||||||
|
// All sublines end with 0 or : except IF lines that are split into two
|
||||||
|
int max = startPtr + length - 1;
|
||||||
|
if (buffer[max] == 0)
|
||||||
|
--max;
|
||||||
|
|
||||||
|
for (int p = startPtr; p <= max; p++)
|
||||||
|
{
|
||||||
|
byte b = buffer[p];
|
||||||
|
if ((b & 0x80) > 0) // token
|
||||||
|
{
|
||||||
|
if (line.length () > 0 && line.charAt (line.length () - 1) != ' ')
|
||||||
|
line.append (' ');
|
||||||
|
int val = b & 0x7F;
|
||||||
|
if (val < ApplesoftConstants.tokens.length)
|
||||||
|
line.append (ApplesoftConstants.tokens[b & 0x7F]);
|
||||||
|
// else
|
||||||
|
// System.out.println ("Bad value : " + val + " " + line.toString () + " "
|
||||||
|
// + parent.lineNumber);
|
||||||
|
}
|
||||||
|
else if (b < 32) // CTRL character
|
||||||
|
line.append ("^" + (char) (b + 64)); // would be better in inverse text
|
||||||
|
else
|
||||||
|
line.append ((char) b);
|
||||||
|
}
|
||||||
|
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
src/com/bytezone/diskbrowser/applefile/BootSector.java
Normal file
48
src/com/bytezone/diskbrowser/applefile/BootSector.java
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.disk.AbstractSector;
|
||||||
|
import com.bytezone.diskbrowser.disk.Disk;
|
||||||
|
|
||||||
|
public class BootSector extends AbstractSector
|
||||||
|
{
|
||||||
|
AssemblerProgram assembler;
|
||||||
|
String name; // DOS or Prodos
|
||||||
|
|
||||||
|
public BootSector (Disk disk, byte[] buffer, String name)
|
||||||
|
{
|
||||||
|
super (disk, buffer);
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String createText ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
if (assembler == null)
|
||||||
|
{
|
||||||
|
// The first byte in the buffer is the number of sectors to read in (minus 1)
|
||||||
|
int sectors = buffer[0] & 0xFF;
|
||||||
|
// System.out.printf ("Sectors to read : %d%n", (sectors + 1));
|
||||||
|
if (sectors > 0)
|
||||||
|
{
|
||||||
|
int bufferSize = buffer.length * (sectors + 1);
|
||||||
|
byte[] newBuffer = new byte[bufferSize];
|
||||||
|
System.arraycopy (buffer, 0, newBuffer, 0, buffer.length);
|
||||||
|
|
||||||
|
for (int i = 0; i < sectors; i++)
|
||||||
|
{
|
||||||
|
byte[] buf = disk.readSector (i + 1);
|
||||||
|
// System.out.printf ("%d %d %d%n", buf.length, buffer.length, newBuffer.length);
|
||||||
|
System.arraycopy (buf, 0, newBuffer, i * buf.length, buf.length);
|
||||||
|
}
|
||||||
|
buffer = newBuffer;
|
||||||
|
}
|
||||||
|
assembler = new AssemblerProgram (name + " Boot Loader", buffer, 0x800, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
text.append (assembler.getText ());
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
31
src/com/bytezone/diskbrowser/applefile/Charset.java
Executable file
31
src/com/bytezone/diskbrowser/applefile/Charset.java
Executable file
|
@ -0,0 +1,31 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class Charset extends AbstractFile
|
||||||
|
{
|
||||||
|
public Charset (String name, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
for (int i = 0; i < buffer.length; i += 8)
|
||||||
|
{
|
||||||
|
for (int line = 7; line >= 0; line--)
|
||||||
|
{
|
||||||
|
int value = HexFormatter.intValue (buffer[i + line]);
|
||||||
|
for (int bit = 0; bit < 8; bit++)
|
||||||
|
{
|
||||||
|
text.append ((value & 0x01) == 1 ? "X" : ".");
|
||||||
|
value >>= 1;
|
||||||
|
}
|
||||||
|
text.append ("\n");
|
||||||
|
}
|
||||||
|
text.append ("\n");
|
||||||
|
}
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
180
src/com/bytezone/diskbrowser/applefile/Command.java
Normal file
180
src/com/bytezone/diskbrowser/applefile/Command.java
Normal file
|
@ -0,0 +1,180 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
// Brendan Robert's code from JACE
|
||||||
|
|
||||||
|
public class Command
|
||||||
|
{
|
||||||
|
public static enum TOKEN
|
||||||
|
{
|
||||||
|
END ((byte) 0x080, "END"),
|
||||||
|
FOR ((byte) 0x081, "FOR"),
|
||||||
|
NEXT ((byte) 0x082, "NEXT"),
|
||||||
|
DATA ((byte) 0x083, "DATA"),
|
||||||
|
INPUT ((byte) 0x084, "INPUT"),
|
||||||
|
DEL ((byte) 0x085, "DEL"),
|
||||||
|
DIM ((byte) 0x086, "DIM"),
|
||||||
|
READ ((byte) 0x087, "READ"),
|
||||||
|
GR ((byte) 0x088, "GR"),
|
||||||
|
TEXT ((byte) 0x089, "TEXT"),
|
||||||
|
PR ((byte) 0x08A, "PR#"),
|
||||||
|
IN ((byte) 0x08B, "IN#"),
|
||||||
|
CALL ((byte) 0x08C, "CALL"),
|
||||||
|
PLOT ((byte) 0x08D, "PLOT"),
|
||||||
|
HLIN ((byte) 0x08E, "HLIN"),
|
||||||
|
VLIN ((byte) 0x08F, "VLIN"),
|
||||||
|
HGR2 ((byte) 0x090, "HGR2"),
|
||||||
|
HGR ((byte) 0x091, "HGR"),
|
||||||
|
HCOLOR ((byte) 0x092, "HCOLOR="),
|
||||||
|
HPLOT ((byte) 0x093, "HPLOT"),
|
||||||
|
DRAW ((byte) 0x094, "DRAW"),
|
||||||
|
XDRAW ((byte) 0x095, "XDRAW"),
|
||||||
|
HTAB ((byte) 0x096, "HTAB"),
|
||||||
|
HOME ((byte) 0x097, "HOME"),
|
||||||
|
ROT ((byte) 0x098, "ROT="),
|
||||||
|
SCALE ((byte) 0x099, "SCALE="),
|
||||||
|
SHLOAD ((byte) 0x09A, "SHLOAD"),
|
||||||
|
TRACE ((byte) 0x09B, "TRACE"),
|
||||||
|
NOTRACE ((byte) 0x09C, "NOTRACE"),
|
||||||
|
NORMAL ((byte) 0x09D, "NORMAL"),
|
||||||
|
INVERSE ((byte) 0x09E, "INVERSE"),
|
||||||
|
FLASH ((byte) 0x09F, "FLASH"),
|
||||||
|
COLOR ((byte) 0x0A0, "COLOR="),
|
||||||
|
POP ((byte) 0x0A1, "POP"),
|
||||||
|
VTAB ((byte) 0x0A2, "VTAB"),
|
||||||
|
HIMEM ((byte) 0x0A3, "HIMEM:"),
|
||||||
|
LOMEM ((byte) 0x0A4, "LOMEM:"),
|
||||||
|
ONERR ((byte) 0x0A5, "ONERR"),
|
||||||
|
RESUME ((byte) 0x0A6, "RESUME"),
|
||||||
|
RECALL ((byte) 0x0A7, "RECALL"),
|
||||||
|
STORE ((byte) 0x0A8, "STORE"),
|
||||||
|
SPEED ((byte) 0x0A9, "SPEED="),
|
||||||
|
LET ((byte) 0x0AA, "LET"),
|
||||||
|
GOTO ((byte) 0x0AB, "GOTO"),
|
||||||
|
RUN ((byte) 0x0AC, "RUN"),
|
||||||
|
IF ((byte) 0x0AD, "IF"),
|
||||||
|
RESTORE ((byte) 0x0AE, "RESTORE"),
|
||||||
|
AMPERSAND ((byte) 0x0AF, "&"),
|
||||||
|
GOSUB ((byte) 0x0B0, "GOSUB"),
|
||||||
|
RETURN ((byte) 0x0B1, "RETURN"),
|
||||||
|
REM ((byte) 0x0B2, "REM"),
|
||||||
|
STOP ((byte) 0x0B3, "STOP"),
|
||||||
|
ONGOTO ((byte) 0x0B4, "ON"),
|
||||||
|
WAIT ((byte) 0x0B5, "WAIT"),
|
||||||
|
LOAD ((byte) 0x0B6, "LOAD"),
|
||||||
|
SAVE ((byte) 0x0B7, "SAVE"),
|
||||||
|
DEF ((byte) 0x0B8, "DEF"),
|
||||||
|
POKE ((byte) 0x0B9, "POKE"),
|
||||||
|
PRINT ((byte) 0x0BA, "PRINT"),
|
||||||
|
CONT ((byte) 0x0BB, "CONT"),
|
||||||
|
LIST ((byte) 0x0BC, "LIST"),
|
||||||
|
CLEAR ((byte) 0x0BD, "CLEAR"),
|
||||||
|
GET ((byte) 0x0BE, "GET"),
|
||||||
|
NEW ((byte) 0x0BF, "NEW"),
|
||||||
|
TAB ((byte) 0x0C0, "TAB("),
|
||||||
|
TO ((byte) 0x0C1, "TO"),
|
||||||
|
FN ((byte) 0x0C2, "FN"),
|
||||||
|
SPC ((byte) 0x0c3, "SPC"),
|
||||||
|
THEN ((byte) 0x0c4, "THEN"),
|
||||||
|
AT ((byte) 0x0c5, "AT"),
|
||||||
|
NOT ((byte) 0x0c6, "NOT"),
|
||||||
|
STEP ((byte) 0x0c7, "STEP"),
|
||||||
|
PLUS ((byte) 0x0c8, "+"),
|
||||||
|
MINUS ((byte) 0x0c9, "-"),
|
||||||
|
MULTIPLY ((byte) 0x0Ca, "*"),
|
||||||
|
DIVIDE ((byte) 0x0Cb, "/"),
|
||||||
|
POWER ((byte) 0x0Cc, "^"),
|
||||||
|
AND ((byte) 0x0Cd, "AND"),
|
||||||
|
OR ((byte) 0x0Ce, "OR"),
|
||||||
|
GREATER ((byte) 0x0CF, ">"),
|
||||||
|
EQUAL ((byte) 0x0d0, "="),
|
||||||
|
LESS ((byte) 0x0d1, "<"),
|
||||||
|
SGN ((byte) 0x0D2, "SGN"),
|
||||||
|
INT ((byte) 0x0D3, "INT"),
|
||||||
|
ABS ((byte) 0x0D4, "ABS"),
|
||||||
|
USR ((byte) 0x0D5, "USR"),
|
||||||
|
FRE ((byte) 0x0D6, "FRE"),
|
||||||
|
SCREEN ((byte) 0x0D7, "SCRN("),
|
||||||
|
PDL ((byte) 0x0D8, "PDL"),
|
||||||
|
POS ((byte) 0x0D9, "POS"),
|
||||||
|
SQR ((byte) 0x0DA, "SQR"),
|
||||||
|
RND ((byte) 0x0DB, "RND"),
|
||||||
|
LOG ((byte) 0x0DC, "LOG"),
|
||||||
|
EXP ((byte) 0x0DD, "EXP"),
|
||||||
|
COS ((byte) 0x0DE, "COS"),
|
||||||
|
SIN ((byte) 0x0DF, "SIN"),
|
||||||
|
TAN ((byte) 0x0E0, "TAN"),
|
||||||
|
ATN ((byte) 0x0E1, "ATN"),
|
||||||
|
PEEK ((byte) 0x0E2, "PEEK"),
|
||||||
|
LEN ((byte) 0x0E3, "LEN"),
|
||||||
|
STR ((byte) 0x0E4, "STR$"),
|
||||||
|
VAL ((byte) 0x0E5, "VAL"),
|
||||||
|
ASC ((byte) 0x0E6, "ASC"),
|
||||||
|
CHR ((byte) 0x0E7, "CHR$"),
|
||||||
|
LEFT ((byte) 0x0E8, "LEFT$"),
|
||||||
|
RIGHT ((byte) 0x0E9, "RIGHT$"),
|
||||||
|
MID ((byte) 0x0EA, "MID$");
|
||||||
|
private final String str;
|
||||||
|
private final byte b;
|
||||||
|
|
||||||
|
TOKEN (byte b, String str)
|
||||||
|
{
|
||||||
|
this.b = b;
|
||||||
|
this.str = str;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TOKEN fromByte (byte b)
|
||||||
|
{
|
||||||
|
for (TOKEN t : values ())
|
||||||
|
if (t.b == b)
|
||||||
|
return t;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ByteOrToken
|
||||||
|
{
|
||||||
|
byte b;
|
||||||
|
TOKEN t;
|
||||||
|
boolean isToken = false;
|
||||||
|
|
||||||
|
public ByteOrToken (byte b)
|
||||||
|
{
|
||||||
|
TOKEN t = TOKEN.fromByte (b);
|
||||||
|
if (t != null)
|
||||||
|
{
|
||||||
|
isToken = true;
|
||||||
|
this.t = t;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isToken = false;
|
||||||
|
this.b = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
return isToken ? " " + t.toString () + " " : String.valueOf ((char) b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<ByteOrToken> parts = new ArrayList<ByteOrToken> ();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
String out = "";
|
||||||
|
for (ByteOrToken p : parts)
|
||||||
|
out += p.toString ();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
32
src/com/bytezone/diskbrowser/applefile/DefaultAppleFile.java
Executable file
32
src/com/bytezone/diskbrowser/applefile/DefaultAppleFile.java
Executable file
|
@ -0,0 +1,32 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
public class DefaultAppleFile extends AbstractFile
|
||||||
|
{
|
||||||
|
String text;
|
||||||
|
|
||||||
|
public DefaultAppleFile (String name, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultAppleFile (String name, byte[] buffer, String text)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
this.text = "Name : " + name + "\n\n" + text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setText (String text)
|
||||||
|
{
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
if (text != null)
|
||||||
|
return text;
|
||||||
|
if (buffer == null)
|
||||||
|
return "Invalid file : " + name;
|
||||||
|
return super.getText ();
|
||||||
|
}
|
||||||
|
}
|
25
src/com/bytezone/diskbrowser/applefile/ErrorMessageFile.java
Normal file
25
src/com/bytezone/diskbrowser/applefile/ErrorMessageFile.java
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
public class ErrorMessageFile extends AbstractFile
|
||||||
|
{
|
||||||
|
String text;
|
||||||
|
|
||||||
|
public ErrorMessageFile (String name, byte[] buffer, Exception e)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
text.append ("Oops! : " + e.toString () + "\n\n");
|
||||||
|
for (StackTraceElement ste : e.getStackTrace ())
|
||||||
|
text.append (ste + "\n");
|
||||||
|
if (text.length () > 0)
|
||||||
|
text.deleteCharAt (text.length () - 1);
|
||||||
|
this.text = text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
262
src/com/bytezone/diskbrowser/applefile/HiResImage.java
Executable file
262
src/com/bytezone/diskbrowser/applefile/HiResImage.java
Executable file
|
@ -0,0 +1,262 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.awt.image.DataBuffer;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class HiResImage extends AbstractFile
|
||||||
|
{
|
||||||
|
int fileType;
|
||||||
|
int auxType;
|
||||||
|
byte[] unpackedBuffer;
|
||||||
|
|
||||||
|
public HiResImage (String name, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
if (name.equals ("FLY LOGO") || name.equals ("BIGBAT.PAC")) // && reportedLength == 0x14FA)
|
||||||
|
{
|
||||||
|
this.buffer = unscrunch (buffer);
|
||||||
|
}
|
||||||
|
if (isGif (buffer))
|
||||||
|
makeGif ();
|
||||||
|
else
|
||||||
|
makeScreen1 (buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HiResImage (String name, byte[] buffer, int fileType, int auxType)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
this.fileType = fileType;
|
||||||
|
this.auxType = auxType;
|
||||||
|
|
||||||
|
if (fileType == 192 && auxType == 1)
|
||||||
|
{
|
||||||
|
unpackedBuffer = unpackBytes (buffer);
|
||||||
|
makeScreen2 (unpackedBuffer);
|
||||||
|
}
|
||||||
|
if (fileType == 192 && auxType == 2)
|
||||||
|
{
|
||||||
|
System.out.println ("yippee - Preferred picture format - " + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeScreen1 (byte[] buffer)
|
||||||
|
{
|
||||||
|
int rows = buffer.length <= 8192 ? 192 : 384;
|
||||||
|
image = new BufferedImage (280, rows, BufferedImage.TYPE_BYTE_GRAY);
|
||||||
|
|
||||||
|
DataBuffer db = image.getRaster ().getDataBuffer ();
|
||||||
|
|
||||||
|
int element = 0;
|
||||||
|
byte[] mask = { 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
|
||||||
|
|
||||||
|
for (int z = 0; z < rows / 192; z++)
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
for (int j = 0; j < 8; j++)
|
||||||
|
for (int k = 0; k < 8; k++)
|
||||||
|
{
|
||||||
|
int base = i * 0x28 + j * 0x80 + k * 0x400 + z * 0x2000;
|
||||||
|
int max = Math.min (base + 40, buffer.length);
|
||||||
|
for (int ptr = base; ptr < max; ptr++)
|
||||||
|
{
|
||||||
|
byte val = buffer[ptr];
|
||||||
|
if (val == 0) // no pixels set
|
||||||
|
{
|
||||||
|
element += 7;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int bit = 6; bit >= 0; bit--)
|
||||||
|
{
|
||||||
|
if ((val & mask[bit]) > 0)
|
||||||
|
db.setElem (element, 255);
|
||||||
|
element++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeScreen2 (byte[] buffer)
|
||||||
|
{
|
||||||
|
// System.out.println (HexFormatter.format (buffer, 32000, 640));
|
||||||
|
// for (int table = 0; table < 200; table++)
|
||||||
|
// {
|
||||||
|
// System.out.println (HexFormatter.format (buffer, ptr, 32));
|
||||||
|
// for (int color = 0; color < 16; color++)
|
||||||
|
// {
|
||||||
|
// int red = buffer[ptr++] & 0x0F;
|
||||||
|
// int green = (buffer[ptr] & 0xF0) >> 4;
|
||||||
|
// int blue = buffer[ptr++] & 0x0F;
|
||||||
|
// Color c = new Color (red, green, blue);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
image = new BufferedImage (320, 200, BufferedImage.TYPE_BYTE_GRAY);
|
||||||
|
DataBuffer db = image.getRaster ().getDataBuffer ();
|
||||||
|
|
||||||
|
int element = 0;
|
||||||
|
int ptr = 0;
|
||||||
|
for (int row = 0; row < 200; row++)
|
||||||
|
for (int col = 0; col < 160; col++)
|
||||||
|
{
|
||||||
|
int pix1 = (buffer[ptr] & 0xF0) >> 4;
|
||||||
|
int pix2 = buffer[ptr] & 0x0F;
|
||||||
|
if (pix1 > 0)
|
||||||
|
db.setElem (element, 255);
|
||||||
|
if (pix2 > 0)
|
||||||
|
db.setElem (element + 1, 255);
|
||||||
|
element += 2;
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Beagle Bros routine to expand a hi-res screen
|
||||||
|
private byte[] unscrunch (byte[] src)
|
||||||
|
{
|
||||||
|
// byte[] dst = new byte[src.length < 0x2000 ? 0x2000 : 0x4000];
|
||||||
|
byte[] dst = new byte[0x2000];
|
||||||
|
int p1 = 0;
|
||||||
|
int p2 = 0;
|
||||||
|
// while (p1 < dst.length && p2 < src.length)
|
||||||
|
while (p1 < dst.length)
|
||||||
|
{
|
||||||
|
// System.out.printf ("%04X %04X%n", p1, p2);
|
||||||
|
byte b = src[p2++];
|
||||||
|
if ((b == (byte) 0x80) || (b == (byte) 0xFF))
|
||||||
|
{
|
||||||
|
b &= 0x7F;
|
||||||
|
int rpt = src[p2++];
|
||||||
|
for (int i = 0; i < rpt; i++)
|
||||||
|
dst[p1++] = b;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
dst[p1++] = b;
|
||||||
|
}
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void makeGif ()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
image = ImageIO.read (new ByteArrayInputStream (buffer));
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] unpackBytes (byte[] buffer)
|
||||||
|
{
|
||||||
|
// routine found here - http://kpreid.livejournal.com/4319.html
|
||||||
|
|
||||||
|
byte[] newBuf = new byte[32768];
|
||||||
|
byte[] fourBuf = new byte[4];
|
||||||
|
|
||||||
|
// System.out.println (HexFormatter.format (buffer));
|
||||||
|
|
||||||
|
int ptr = 0, newPtr = 0;
|
||||||
|
while (ptr < buffer.length)
|
||||||
|
{
|
||||||
|
int type = (buffer[ptr] & 0xC0) >> 6;
|
||||||
|
int count = (buffer[ptr++] & 0x3F) + 1;
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
while (count-- != 0)
|
||||||
|
newBuf[newPtr++] = buffer[ptr++];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
byte b = buffer[ptr++];
|
||||||
|
while (count-- != 0)
|
||||||
|
newBuf[newPtr++] = b;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
fourBuf[i] = buffer[ptr++];
|
||||||
|
while (count-- != 0)
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
newBuf[newPtr++] = fourBuf[i];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
b = buffer[ptr++];
|
||||||
|
count *= 4;
|
||||||
|
while (count-- != 0)
|
||||||
|
newBuf[newPtr++] = b;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newBuf;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
String auxText = "";
|
||||||
|
StringBuilder text = new StringBuilder ("Image File : " + name);
|
||||||
|
text.append (String.format ("%nFile type : $%02X", fileType));
|
||||||
|
|
||||||
|
switch (fileType)
|
||||||
|
{
|
||||||
|
case 8:
|
||||||
|
if (auxType < 0x4000)
|
||||||
|
auxText = "Graphics File";
|
||||||
|
else if (auxType == 0x4000)
|
||||||
|
auxText = "Packed Hi-Res File";
|
||||||
|
else if (auxType == 0x4001)
|
||||||
|
auxText = "Packed Double Hi-Res File";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 192:
|
||||||
|
if (auxType == 1)
|
||||||
|
{
|
||||||
|
if (unpackedBuffer == null)
|
||||||
|
unpackedBuffer = unpackBytes (buffer);
|
||||||
|
auxText = "Packed Super Hi-Res Image";
|
||||||
|
}
|
||||||
|
else if (auxType == 2)
|
||||||
|
auxText = "Super Hi-Res Image";
|
||||||
|
else if (auxType == 3)
|
||||||
|
auxText = "Packed QuickDraw II PICT File";
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 193:
|
||||||
|
if (auxType == 0)
|
||||||
|
auxText = "Super Hi-res Screen Image";
|
||||||
|
else if (auxType == 1)
|
||||||
|
auxText = "QuickDraw PICT File";
|
||||||
|
else if (auxType == 2)
|
||||||
|
auxText = "Super Hi-Res 3200 color image";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!auxText.isEmpty ())
|
||||||
|
text.append (String.format ("%nAux type : $%04X %s", auxType, auxText));
|
||||||
|
|
||||||
|
text.append (String.format ("%nFile size : %,d", buffer.length));
|
||||||
|
if (unpackedBuffer != null)
|
||||||
|
{
|
||||||
|
text.append (String.format ("%nUnpacked : %,d%n%n", unpackedBuffer.length));
|
||||||
|
text.append (HexFormatter.format (unpackedBuffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isGif (byte[] buffer)
|
||||||
|
{
|
||||||
|
if (buffer.length < 6)
|
||||||
|
return false;
|
||||||
|
String text = new String (buffer, 0, 6);
|
||||||
|
return text.equals ("GIF89a") || text.equals ("GIF87a");
|
||||||
|
}
|
||||||
|
}
|
201
src/com/bytezone/diskbrowser/applefile/IconFile.java
Normal file
201
src/com/bytezone/diskbrowser/applefile/IconFile.java
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class IconFile extends AbstractFile
|
||||||
|
{
|
||||||
|
private final int iBlkNext;
|
||||||
|
private final int iBlkID;
|
||||||
|
private final int iBlkPath;
|
||||||
|
private final String iBlkName;
|
||||||
|
private final List<Icon> icons = new ArrayList<IconFile.Icon> ();
|
||||||
|
|
||||||
|
public IconFile (String name, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
|
||||||
|
iBlkNext = HexFormatter.getLong (buffer, 0);
|
||||||
|
iBlkID = HexFormatter.getWord (buffer, 4);
|
||||||
|
iBlkPath = HexFormatter.getLong (buffer, 6);
|
||||||
|
iBlkName = HexFormatter.getHexString (buffer, 10, 16);
|
||||||
|
|
||||||
|
int ptr = 26;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int dataLen = HexFormatter.getWord (buffer, ptr);
|
||||||
|
if (dataLen == 0)
|
||||||
|
break;
|
||||||
|
icons.add (new Icon (buffer, ptr));
|
||||||
|
ptr += dataLen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ("Name : " + name + "\n\n");
|
||||||
|
|
||||||
|
text.append (String.format ("Next Icon file .. %d%n", iBlkNext));
|
||||||
|
text.append (String.format ("Block ID ........ %d%n", iBlkID));
|
||||||
|
text.append (String.format ("Block path ...... %d%n", iBlkPath));
|
||||||
|
text.append (String.format ("Block name ...... %s%n", iBlkName));
|
||||||
|
|
||||||
|
text.append ("\n");
|
||||||
|
for (Icon icon : icons)
|
||||||
|
{
|
||||||
|
text.append (icon);
|
||||||
|
text.append ("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
class Icon
|
||||||
|
{
|
||||||
|
byte[] buffer;
|
||||||
|
int iDataLen;
|
||||||
|
String pathName;
|
||||||
|
String dataName;
|
||||||
|
int iDataType;
|
||||||
|
int iDataAux;
|
||||||
|
Image largeImage;
|
||||||
|
Image smallImage;
|
||||||
|
|
||||||
|
public Icon (byte[] fullBuffer, int ptr)
|
||||||
|
{
|
||||||
|
iDataLen = HexFormatter.getWord (fullBuffer, ptr);
|
||||||
|
|
||||||
|
buffer = new byte[iDataLen];
|
||||||
|
System.arraycopy (fullBuffer, ptr, buffer, 0, buffer.length);
|
||||||
|
|
||||||
|
int len = buffer[2] & 0xFF;
|
||||||
|
pathName = new String (buffer, 3, len);
|
||||||
|
|
||||||
|
len = buffer[66] & 0xFF;
|
||||||
|
dataName = new String (buffer, 67, len);
|
||||||
|
|
||||||
|
iDataType = HexFormatter.getWord (buffer, 82);
|
||||||
|
iDataAux = HexFormatter.getWord (buffer, 84);
|
||||||
|
|
||||||
|
largeImage = new Image (buffer, 86);
|
||||||
|
smallImage = new Image (buffer, 86 + largeImage.size ());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
text.append (String.format ("Data length .. %04X%n", iDataLen));
|
||||||
|
text.append (String.format ("Path name .... %s%n", pathName));
|
||||||
|
text.append (String.format ("Data name .... %s%n", dataName));
|
||||||
|
text.append ("\n");
|
||||||
|
text.append (largeImage);
|
||||||
|
text.append ("\n");
|
||||||
|
text.append ("\n");
|
||||||
|
text.append (smallImage);
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Image
|
||||||
|
{
|
||||||
|
int iconType;
|
||||||
|
int iconSize;
|
||||||
|
int iconHeight;
|
||||||
|
int iconWidth;
|
||||||
|
byte[] main;
|
||||||
|
byte[] mask;
|
||||||
|
|
||||||
|
public Image (byte[] buffer, int ptr)
|
||||||
|
{
|
||||||
|
iconType = HexFormatter.getWord (buffer, ptr);
|
||||||
|
iconSize = HexFormatter.getWord (buffer, ptr + 2);
|
||||||
|
iconHeight = HexFormatter.getWord (buffer, ptr + 4);
|
||||||
|
iconWidth = HexFormatter.getWord (buffer, ptr + 6);
|
||||||
|
|
||||||
|
main = new byte[iconSize];
|
||||||
|
mask = new byte[iconSize];
|
||||||
|
|
||||||
|
System.arraycopy (buffer, ptr + 8, main, 0, iconSize);
|
||||||
|
System.arraycopy (buffer, ptr + 8 + iconSize, mask, 0, iconSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size ()
|
||||||
|
{
|
||||||
|
return 8 + iconSize * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
text.append (String.format ("Icon type .... %04X%n", iconType));
|
||||||
|
text.append (String.format ("Icon size .... %d%n", iconSize));
|
||||||
|
text.append (String.format ("Icon height .. %d%n", iconHeight));
|
||||||
|
text.append (String.format ("Icon width ... %d%n%n", iconWidth));
|
||||||
|
appendIcon (text, main);
|
||||||
|
text.append ("\n\n");
|
||||||
|
appendIcon (text, mask);
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Offset Color RGB Mini-Palette
|
||||||
|
|
||||||
|
0 Black 000 0
|
||||||
|
1 Blue 00F 1
|
||||||
|
2 Yellow FF0 2
|
||||||
|
3 White FFF 3
|
||||||
|
4 Black 000 0
|
||||||
|
5 Red D00 1
|
||||||
|
6 Green 0E0 2
|
||||||
|
7 White FFF 3
|
||||||
|
|
||||||
|
8 Black 000 0
|
||||||
|
9 Blue 00F 1
|
||||||
|
10 Yellow FF0 2
|
||||||
|
11 White FFF 3
|
||||||
|
12 Black 000 0
|
||||||
|
13 Red D00 1
|
||||||
|
14 Green 0E0 2
|
||||||
|
15 White FFF 3
|
||||||
|
|
||||||
|
The displayMode word bits are defined as:
|
||||||
|
|
||||||
|
Bit 0 selectedIconBit 1 = invert image before copying
|
||||||
|
Bit 1 openIconBit 1 = copy light-gray pattern instead of image
|
||||||
|
Bit 2 offLineBit 1 = AND light-gray pattern to image being copied
|
||||||
|
Bits 3-7 reserved.
|
||||||
|
Bits 8-11 foreground color to apply to black part of black & white icons
|
||||||
|
Bits 12-15 background color to apply to white part of black & white icons
|
||||||
|
|
||||||
|
Bits 0-2 can occur at once and are tested in the order 1-2-0.
|
||||||
|
|
||||||
|
"Color is only applied to the black and white icons if bits 15-8 are not all 0.
|
||||||
|
Colored pixels in an icon are inverted by black pixels becoming white and any
|
||||||
|
other color of pixel becoming black."
|
||||||
|
*/
|
||||||
|
|
||||||
|
private void appendIcon (StringBuilder text, byte[] buffer)
|
||||||
|
{
|
||||||
|
int rowBytes = 1 + (iconWidth - 1) / 2;
|
||||||
|
for (int i = 0; i < main.length; i += rowBytes)
|
||||||
|
{
|
||||||
|
for (int ptr = i, max = i + rowBytes; ptr < max; ptr++)
|
||||||
|
{
|
||||||
|
int left = (byte) ((buffer[ptr] & 0xF0) >> 4);
|
||||||
|
int right = (byte) (buffer[ptr] & 0x0F);
|
||||||
|
text.append (String.format ("%X %X ", left, right));
|
||||||
|
}
|
||||||
|
text.append ("\n");
|
||||||
|
}
|
||||||
|
if (text.length () > 0)
|
||||||
|
text.deleteCharAt (text.length () - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
240
src/com/bytezone/diskbrowser/applefile/IntegerBasicProgram.java
Executable file
240
src/com/bytezone/diskbrowser/applefile/IntegerBasicProgram.java
Executable file
|
@ -0,0 +1,240 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class IntegerBasicProgram extends AbstractFile
|
||||||
|
{
|
||||||
|
private static String[] tokens =
|
||||||
|
{ "?", "?", "?", " : ", "?", "?", "?", "?", "?", "?", "?", "?", "CLR", "?", "?", "?",
|
||||||
|
"HIMEM: ", "LOMEM: ", " + ", " - ", " * ", " / ", " = ", " # ", " >= ", " > ",
|
||||||
|
" <= ", " <> ", " < ", " AND ", " OR ", " MOD ", "^", "+", "(", ",", " THEN ",
|
||||||
|
" THEN ", ",", ",", "\"", "\"", "(", "!", "!", "(", "PEEK ", "RND ", "SGN",
|
||||||
|
"ABS", "PDL", "RNDX", "(", "+", "-", "NOT ", "(", "=", "#", "LEN(", "ASC(",
|
||||||
|
"SCRN(", ",", "(", "$", "$", "(", ", ", ",", ";", ";", ";", ",", ",", ",",
|
||||||
|
"TEXT", "GR ", "CALL ", "DIM ", "DIM ", "TAB ", "END", "INPUT ", "INPUT ",
|
||||||
|
"INPUT ", "FOR ", " = ", " TO ", " STEP ", "NEXT ", ",", "RETURN", "GOSUB ",
|
||||||
|
"REM ", "LET ", "GOTO ", "IF ", "PRINT ", "PRINT ", "PRINT", "POKE ", ",",
|
||||||
|
"COLOR=", "PLOT", ",", "HLIN", ",", " AT ", "VLIN ", ",", " AT ", "VTAB ", " = ",
|
||||||
|
" = ", ")", ")", "LIST ", ",", "LIST ", "POP ", "NODSP ", "NODSP ", "NOTRACE ",
|
||||||
|
"DSP ", "DSP ", "TRACE ", "PR#", "IN#", };
|
||||||
|
|
||||||
|
public IntegerBasicProgram (String name, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
StringBuilder pgm = new StringBuilder ();
|
||||||
|
pgm.append ("Name : " + name + "\n");
|
||||||
|
pgm.append ("Length : $" + HexFormatter.format4 (buffer.length) + " (" + buffer.length
|
||||||
|
+ ")\n\n");
|
||||||
|
int ptr = 0;
|
||||||
|
boolean looksLikeAssembler = checkForAssembler (); // this can probably go
|
||||||
|
boolean looksLikeSCAssembler = checkForSCAssembler ();
|
||||||
|
|
||||||
|
while (ptr < buffer.length)
|
||||||
|
{
|
||||||
|
int lineLength = HexFormatter.intValue (buffer[ptr]);
|
||||||
|
/*
|
||||||
|
* It appears that lines ending in 00 are S-C Assembler programs, and
|
||||||
|
* lines ending in 01 are Integer Basic programs.
|
||||||
|
*/
|
||||||
|
int p2 = ptr + lineLength - 1;
|
||||||
|
if (p2 < 0 || (buffer[p2] != 1 && buffer[p2] != 0))
|
||||||
|
{
|
||||||
|
pgm.append ("\nPossible assembler code follows\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (lineLength <= 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (looksLikeSCAssembler)
|
||||||
|
appendSCAssembler (pgm, ptr, lineLength);
|
||||||
|
else if (looksLikeAssembler)
|
||||||
|
appendAssembler (pgm, ptr, lineLength);
|
||||||
|
else
|
||||||
|
appendInteger (pgm, ptr, lineLength);
|
||||||
|
|
||||||
|
pgm.append ("\n");
|
||||||
|
ptr += lineLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ptr < buffer.length)
|
||||||
|
{
|
||||||
|
int address = HexFormatter.intValue (buffer[ptr + 2], buffer[ptr + 3]);
|
||||||
|
int remainingBytes = buffer.length - ptr - 5;
|
||||||
|
byte[] newBuffer = new byte[remainingBytes];
|
||||||
|
System.arraycopy (buffer, ptr + 4, newBuffer, 0, remainingBytes);
|
||||||
|
AssemblerProgram ap = new AssemblerProgram ("embedded", newBuffer, address);
|
||||||
|
pgm.append ("\n" + ap.getText () + "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
pgm.deleteCharAt (pgm.length () - 1);
|
||||||
|
return pgm.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendAssembler (StringBuilder pgm, int ptr, int lineLength)
|
||||||
|
{
|
||||||
|
for (int i = ptr + 3; i < ptr + lineLength - 1; i++)
|
||||||
|
{
|
||||||
|
if ((buffer[i] & 0x80) == 0x80)
|
||||||
|
{
|
||||||
|
int spaces = buffer[i] & 0x0F;
|
||||||
|
for (int j = 0; j < spaces; j++)
|
||||||
|
pgm.append (' ');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int b = HexFormatter.intValue (buffer[i]);
|
||||||
|
pgm.append ((char) b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkForAssembler ()
|
||||||
|
{
|
||||||
|
int ptr = 0;
|
||||||
|
|
||||||
|
while (ptr < buffer.length)
|
||||||
|
{
|
||||||
|
int lineLength = HexFormatter.intValue (buffer[ptr]);
|
||||||
|
int p2 = ptr + lineLength - 1;
|
||||||
|
if (p2 < 0 || (buffer[p2] != 1 && buffer[p2] != 0))
|
||||||
|
break;
|
||||||
|
if (lineLength <= 0) // in case of looping bug
|
||||||
|
break;
|
||||||
|
// check for comments
|
||||||
|
if (buffer[ptr + 3] == 0x3B || buffer[ptr + 3] == 0x2A)
|
||||||
|
return true;
|
||||||
|
ptr += lineLength;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkForSCAssembler ()
|
||||||
|
{
|
||||||
|
int lineLength = HexFormatter.intValue (buffer[0]);
|
||||||
|
if (lineLength <= 0)
|
||||||
|
return false;
|
||||||
|
return buffer[lineLength - 1] == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendSCAssembler (StringBuilder pgm, int ptr, int lineLength)
|
||||||
|
{
|
||||||
|
int lineNumber =
|
||||||
|
HexFormatter.intValue (buffer[ptr + 2]) * 256
|
||||||
|
+ HexFormatter.intValue (buffer[ptr + 1]);
|
||||||
|
pgm.append (String.format ("%4d: ", lineNumber));
|
||||||
|
int p2 = ptr + 3;
|
||||||
|
while (buffer[p2] != 0)
|
||||||
|
{
|
||||||
|
if (buffer[p2] == (byte) 0xC0)
|
||||||
|
{
|
||||||
|
int repeat = buffer[p2 + 1];
|
||||||
|
for (int i = 0; i < repeat; i++)
|
||||||
|
pgm.append ((char) buffer[p2 + 2]);
|
||||||
|
p2 += 2;
|
||||||
|
}
|
||||||
|
else if ((buffer[p2] & 0x80) != 0)
|
||||||
|
{
|
||||||
|
int spaces = buffer[p2] & 0x7F;
|
||||||
|
for (int i = 0; i < spaces; i++)
|
||||||
|
pgm.append (' ');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
pgm.append ((char) buffer[p2]);
|
||||||
|
p2++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void appendInteger (StringBuilder pgm, int ptr, int lineLength)
|
||||||
|
{
|
||||||
|
int lineNumber = HexFormatter.intValue (buffer[ptr + 1], buffer[ptr + 2]);
|
||||||
|
|
||||||
|
boolean inString = false;
|
||||||
|
boolean inRemark = false;
|
||||||
|
|
||||||
|
String lineText = String.format ("%5d ", lineNumber);
|
||||||
|
int lineTab = lineText.length ();
|
||||||
|
pgm.append (lineText);
|
||||||
|
|
||||||
|
for (int p = ptr + 3; p < ptr + lineLength - 1; p++)
|
||||||
|
{
|
||||||
|
int b = HexFormatter.intValue (buffer[p]);
|
||||||
|
|
||||||
|
if (b == 0x03 // token for colon (:)
|
||||||
|
&& !inString && !inRemark && buffer[p + 1] != 1) // not end of
|
||||||
|
// line
|
||||||
|
{
|
||||||
|
pgm.append (":\n" + " ".substring (0, lineTab));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b >= 0xB0 && b <= 0xB9 // numeric literal
|
||||||
|
&& (buffer[p - 1] & 0x80) == 0 // not a variable name
|
||||||
|
&& !inString && !inRemark)
|
||||||
|
{
|
||||||
|
pgm.append (HexFormatter.intValue (buffer[p + 1], buffer[p + 2]));
|
||||||
|
p += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (b >= 128)
|
||||||
|
{
|
||||||
|
b -= 128;
|
||||||
|
if (b >= 32)
|
||||||
|
pgm.append ((char) b);
|
||||||
|
else
|
||||||
|
pgm.append ("<ctrl-" + (char) (b + 64) + ">");
|
||||||
|
}
|
||||||
|
else if (!tokens[b].equals ("?"))
|
||||||
|
{
|
||||||
|
pgm.append (tokens[b]);
|
||||||
|
if ((b == 40 || b == 41) && !inRemark) // double quotes
|
||||||
|
inString = !inString;
|
||||||
|
if (b == 0x5D)
|
||||||
|
inRemark = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
pgm.append (" ." + HexFormatter.format2 (b) + ". ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHexDump ()
|
||||||
|
{
|
||||||
|
if (false)
|
||||||
|
return super.getHexDump ();
|
||||||
|
|
||||||
|
StringBuffer pgm = new StringBuffer ();
|
||||||
|
|
||||||
|
pgm.append ("Name : " + name + "\n");
|
||||||
|
pgm.append ("Length : $" + HexFormatter.format4 (buffer.length) + " (" + buffer.length
|
||||||
|
+ ")\n\n");
|
||||||
|
|
||||||
|
int ptr = 0;
|
||||||
|
|
||||||
|
while (ptr < buffer.length)
|
||||||
|
{
|
||||||
|
int lineLength = HexFormatter.intValue (buffer[ptr]);
|
||||||
|
int p2 = ptr + lineLength - 1;
|
||||||
|
if (p2 < 0 || buffer[p2] > 1)
|
||||||
|
{
|
||||||
|
System.out.println ("invalid line");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pgm.append (HexFormatter.formatNoHeader (buffer, ptr, lineLength));
|
||||||
|
pgm.append ("\n");
|
||||||
|
if (lineLength <= 0)
|
||||||
|
{
|
||||||
|
System.out.println ("looping");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ptr += lineLength;
|
||||||
|
pgm.append ("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pgm.length () > 0)
|
||||||
|
pgm.deleteCharAt (pgm.length () - 1);
|
||||||
|
|
||||||
|
return pgm.toString ();
|
||||||
|
}
|
||||||
|
}
|
93
src/com/bytezone/diskbrowser/applefile/LodeRunner.java
Normal file
93
src/com/bytezone/diskbrowser/applefile/LodeRunner.java
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class LodeRunner extends AbstractFile
|
||||||
|
{
|
||||||
|
public LodeRunner (String name, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
text.append ("Lode Runner Level\n\n");
|
||||||
|
|
||||||
|
for (int level = 0; level < 150; level++)
|
||||||
|
{
|
||||||
|
int ptr = level * 256 + 226;
|
||||||
|
String levelName = "";
|
||||||
|
if (buffer[ptr] != 0 && buffer[ptr] != (byte) 0xFF)
|
||||||
|
levelName = HexFormatter.sanitiseString (buffer, ptr, 15);
|
||||||
|
text.append (String.format ("Level %d %s%n%n", level + 1, levelName));
|
||||||
|
|
||||||
|
ptr = 0;
|
||||||
|
for (int i = level * 256, max = i + 224; i < max; i++)
|
||||||
|
{
|
||||||
|
String val = String.format ("%02X", buffer[i]);
|
||||||
|
text = addPosition (text, val.charAt (0));
|
||||||
|
text.append (' ');
|
||||||
|
text = addPosition (text, val.charAt (1));
|
||||||
|
text.append (' ');
|
||||||
|
if (++ptr % 14 == 0)
|
||||||
|
text.append ("\n");
|
||||||
|
}
|
||||||
|
text.append ("\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private StringBuilder addPosition (StringBuilder text, char c)
|
||||||
|
{
|
||||||
|
switch (c)
|
||||||
|
{
|
||||||
|
case '0':
|
||||||
|
text.append (' '); // space
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '1':
|
||||||
|
text.append ('-'); // diggable floor
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '2':
|
||||||
|
text.append ('='); // undiggable floor
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '3':
|
||||||
|
text.append ('+'); // ladder
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '4':
|
||||||
|
text.append ('^'); // hand over hand bar
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '5':
|
||||||
|
text.append ('~'); // trap door
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '6':
|
||||||
|
text.append ('#'); // hidden ladder
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '7':
|
||||||
|
text.append ('$'); // gold
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '8':
|
||||||
|
text.append ('*'); // enemy
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '9':
|
||||||
|
text.append ('x'); // player
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
text.append (c);
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
95
src/com/bytezone/diskbrowser/applefile/MerlinSource.java
Normal file
95
src/com/bytezone/diskbrowser/applefile/MerlinSource.java
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
public class MerlinSource extends AbstractFile
|
||||||
|
{
|
||||||
|
int ptr;
|
||||||
|
private static int[] tabs = { 12, 19, 35 };
|
||||||
|
private static int TAB_POS = tabs[2];
|
||||||
|
private final int recordLength;
|
||||||
|
private final int eof;
|
||||||
|
|
||||||
|
// Source : Prodos text file
|
||||||
|
public MerlinSource (String name, byte[] buffer, int recordLength, int eof)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
this.eof = eof;
|
||||||
|
this.recordLength = recordLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source : Dos binary file
|
||||||
|
public MerlinSource (String name, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
this.eof = 0;
|
||||||
|
this.recordLength = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
text.append ("Name : " + name + "\n");
|
||||||
|
if (recordLength > 0) // a prodos text file
|
||||||
|
{
|
||||||
|
text.append (String.format ("Record length : %,8d%n", recordLength));
|
||||||
|
text.append (String.format ("End of file : %,8d%n", eof));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
text.append (String.format ("End of file : %,8d%n", buffer.length));
|
||||||
|
text.append ("\n");
|
||||||
|
|
||||||
|
ptr = 0;
|
||||||
|
while (ptr < buffer.length && buffer[ptr] != 0)
|
||||||
|
text.append (getLine () + "\n");
|
||||||
|
if (text.length () > 0)
|
||||||
|
text.deleteCharAt (text.length () - 1);
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getLine ()
|
||||||
|
{
|
||||||
|
StringBuilder line = new StringBuilder ();
|
||||||
|
boolean comment = false;
|
||||||
|
boolean string = false;
|
||||||
|
while (ptr < buffer.length)
|
||||||
|
{
|
||||||
|
int val = buffer[ptr++] & 0x7F;
|
||||||
|
if (val == 0x0D)
|
||||||
|
break;
|
||||||
|
if (val == '*' && line.length () == 0)
|
||||||
|
comment = true;
|
||||||
|
if (val == '"')
|
||||||
|
string = !string;
|
||||||
|
if (val == ';' && !comment)
|
||||||
|
{
|
||||||
|
comment = true;
|
||||||
|
while (line.length () < TAB_POS)
|
||||||
|
line.append (' ');
|
||||||
|
}
|
||||||
|
if (val == ' ' && !comment && !string)
|
||||||
|
{
|
||||||
|
line = tab (line);
|
||||||
|
if (line.length () >= tabs[2])
|
||||||
|
comment = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
line.append ((char) val);
|
||||||
|
}
|
||||||
|
return line.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private StringBuilder tab (StringBuilder text)
|
||||||
|
{
|
||||||
|
int nextTab = 0;
|
||||||
|
for (int tab : tabs)
|
||||||
|
if (text.length () < tab)
|
||||||
|
{
|
||||||
|
nextTab = tab;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
while (text.length () < nextTab)
|
||||||
|
text.append (' ');
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
69
src/com/bytezone/diskbrowser/applefile/PascalCode.java
Executable file
69
src/com/bytezone/diskbrowser/applefile/PascalCode.java
Executable file
|
@ -0,0 +1,69 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class PascalCode extends AbstractFile implements PascalConstants, Iterable<PascalSegment>
|
||||||
|
{
|
||||||
|
List<PascalSegment> segments = new ArrayList<PascalSegment> (16);
|
||||||
|
String codeName;
|
||||||
|
String comment;
|
||||||
|
|
||||||
|
public static void print ()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 216; i++)
|
||||||
|
System.out.printf ("%3d %d %3s %s%n", i + 128, PascalConstants.mnemonicSize[i],
|
||||||
|
PascalConstants.mnemonics[i], PascalConstants.descriptions[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PascalCode (String name, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
int nonameCounter = 0;
|
||||||
|
|
||||||
|
// Build segment list (up to 16 segments)
|
||||||
|
for (int i = 0; i < 16; i++)
|
||||||
|
{
|
||||||
|
codeName = HexFormatter.getString (buffer, 0x40 + i * 8, 8).trim ();
|
||||||
|
int size = HexFormatter.intValue (buffer[i * 4 + 2], buffer[i * 4 + 3]);
|
||||||
|
if (size > 0)
|
||||||
|
{
|
||||||
|
if (codeName.length () == 0)
|
||||||
|
codeName = "<NULL" + nonameCounter++ + ">";
|
||||||
|
segments.add (new PascalSegment (codeName, buffer, i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
comment = HexFormatter.getPascalString (buffer, 0x1B0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder (getHeader ());
|
||||||
|
|
||||||
|
text.append ("Segment Dictionary\n==================\n\n");
|
||||||
|
|
||||||
|
text.append ("Slot Addr Len Len Name Kind"
|
||||||
|
+ " Text Seg# Mtyp Vers I/S\n");
|
||||||
|
text.append ("---- ---- ----- ----- -------- ---------------"
|
||||||
|
+ " ---- ---- ---- ---- ---\n");
|
||||||
|
|
||||||
|
for (PascalSegment segment : segments)
|
||||||
|
text.append (segment.toText () + "\n");
|
||||||
|
text.append ("\nComment : " + comment + "\n\n");
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getHeader ()
|
||||||
|
{
|
||||||
|
return "Name : " + name + "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterator<PascalSegment> iterator ()
|
||||||
|
{
|
||||||
|
return segments.iterator ();
|
||||||
|
}
|
||||||
|
}
|
329
src/com/bytezone/diskbrowser/applefile/PascalCodeStatement.java
Executable file
329
src/com/bytezone/diskbrowser/applefile/PascalCodeStatement.java
Executable file
|
@ -0,0 +1,329 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class PascalCodeStatement implements PascalConstants
|
||||||
|
{
|
||||||
|
private static final String[] compValue =
|
||||||
|
{ "invalid", "", "REAL", "", "STR", "", "BOOL", "", "POWR", "", "BYT", "", "WORD" };
|
||||||
|
|
||||||
|
int length;
|
||||||
|
int val;
|
||||||
|
int p1, p2, p3;
|
||||||
|
String mnemonic;
|
||||||
|
String extras = "";
|
||||||
|
String description;
|
||||||
|
String text;
|
||||||
|
int ptr; // temp
|
||||||
|
byte[] buffer;
|
||||||
|
boolean jumpTarget;
|
||||||
|
List<Jump> jumps = new ArrayList<Jump> ();
|
||||||
|
|
||||||
|
public PascalCodeStatement (byte[] buffer, int ptr, int procPtr)
|
||||||
|
{
|
||||||
|
this.ptr = ptr;
|
||||||
|
this.buffer = buffer;
|
||||||
|
length = 1;
|
||||||
|
val = buffer[ptr] & 0xFF;
|
||||||
|
if (val <= 127)
|
||||||
|
{
|
||||||
|
mnemonic = "SLDC";
|
||||||
|
extras = "#" + val;
|
||||||
|
description = "Short load constant - push #" + val;
|
||||||
|
}
|
||||||
|
else if (val >= 248)
|
||||||
|
{
|
||||||
|
mnemonic = "SIND";
|
||||||
|
extras = "#" + (val - 248);
|
||||||
|
description = "Short index load - push word *ToS + #" + (val - 248);
|
||||||
|
}
|
||||||
|
else if (val >= 232)
|
||||||
|
{
|
||||||
|
mnemonic = "SLDO";
|
||||||
|
extras = "#" + (val - 231);
|
||||||
|
description = "Short load global - push BASE + #" + (val - 231);
|
||||||
|
}
|
||||||
|
else if (val >= 216)
|
||||||
|
{
|
||||||
|
mnemonic = "SLDL";
|
||||||
|
extras = "#" + (val - 215);
|
||||||
|
description = "Short load local - push MP + #" + (val - 215);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mnemonic = mnemonics[val - 128];
|
||||||
|
description = descriptions[val - 128];
|
||||||
|
|
||||||
|
length = mnemonicSize[val - 128];
|
||||||
|
if (length != 1)
|
||||||
|
{
|
||||||
|
switch (val)
|
||||||
|
{
|
||||||
|
// W1, W2, W3, <table> - word aligned case jump
|
||||||
|
case 172: //XJP
|
||||||
|
int padding = (ptr % 2) == 0 ? 1 : 0;
|
||||||
|
p1 = getWord (buffer, ptr + padding + 1);
|
||||||
|
p2 = getWord (buffer, ptr + padding + 3);
|
||||||
|
p3 = getWord (buffer, ptr + padding + 5);
|
||||||
|
length = (p2 - p1 + 1) * 2 + 7 + padding;
|
||||||
|
setParameters (p1, p2, String.format ("%04X", p3));
|
||||||
|
int v = p1;
|
||||||
|
int min = ptr + padding + 7;
|
||||||
|
int max = min + (p2 - p1) * 2;
|
||||||
|
for (int i = min; i <= max; i += 2)
|
||||||
|
{
|
||||||
|
jumps.add (new Jump (i, i - HexFormatter.intValue (buffer[i], buffer[i + 1]), v++));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// UB, <block> - word aligned
|
||||||
|
case 179: //LDC
|
||||||
|
p1 = buffer[ptr + 1] & 0xFF;
|
||||||
|
padding = ptr % 2 == 0 ? 0 : 1;
|
||||||
|
length = p1 * 2 + padding + 2;
|
||||||
|
setParameters (p1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// UB, <chars>
|
||||||
|
case 166: // LSA
|
||||||
|
case 208: // LPA
|
||||||
|
p1 = buffer[ptr + 1] & 0xFF;
|
||||||
|
length = p1 + 2;
|
||||||
|
if (val == 166)
|
||||||
|
{
|
||||||
|
text = HexFormatter.getPascalString (buffer, ptr + 1);
|
||||||
|
description += ": " + text;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// W
|
||||||
|
case 199: // LDCI
|
||||||
|
p1 = getWord (buffer, ptr + 1);
|
||||||
|
setParameters (p1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// B
|
||||||
|
case 162: // INC
|
||||||
|
case 163: // IND
|
||||||
|
case 164: // IXA
|
||||||
|
case 165: // LAO
|
||||||
|
case 168: // MOV
|
||||||
|
case 169: // LDO
|
||||||
|
case 171: // SRO
|
||||||
|
case 198: // LLA
|
||||||
|
case 202: // LDL
|
||||||
|
case 204: // STL
|
||||||
|
case 213: // BPT
|
||||||
|
length = getLengthOfB (buffer[ptr + 1]) + 1;
|
||||||
|
p1 = getValueOfB (buffer, ptr + 1, length - 1);
|
||||||
|
setParameters (p1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// DB, B or UB, B
|
||||||
|
case 157: // LDE
|
||||||
|
case 167: // LAE
|
||||||
|
case 178: // LDA
|
||||||
|
case 182: // LOD
|
||||||
|
case 184: // STR
|
||||||
|
case 209: // STE
|
||||||
|
length = getLengthOfB (buffer[ptr + 2]) + 2;
|
||||||
|
p1 = buffer[ptr + 1] & 0xFF;
|
||||||
|
p2 = getValueOfB (buffer, ptr + 2, length - 2);
|
||||||
|
setParameters (p1, p2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// UB1, UB2
|
||||||
|
case 192: // IXP
|
||||||
|
case 205: // CXP
|
||||||
|
p1 = buffer[ptr + 1] & 0xFF;
|
||||||
|
p2 = buffer[ptr + 2] & 0xFF;
|
||||||
|
setParameters (p1, p2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// SB or DB
|
||||||
|
case 161: // FJP
|
||||||
|
case 173: // RNP
|
||||||
|
case 185: // UJP
|
||||||
|
case 193: // RBP
|
||||||
|
case 211: // EFJ
|
||||||
|
case 212: // NFJ
|
||||||
|
p1 = buffer[ptr + 1];
|
||||||
|
if (val == 173 || val == 193) // return from procedure
|
||||||
|
setParameters (p1);
|
||||||
|
else if (p1 < 0)
|
||||||
|
{
|
||||||
|
// look up jump table entry
|
||||||
|
int address = procPtr + p1;
|
||||||
|
int ptr2 = address - ((buffer[address + 1] & 0xFF) * 256 + (buffer[address] & 0xFF));
|
||||||
|
extras = String.format ("$%04X", ptr2);
|
||||||
|
jumps.add (new Jump (ptr, ptr2));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int address = ptr + length + p1;
|
||||||
|
extras = String.format ("$%04X", address);
|
||||||
|
jumps.add (new Jump (ptr, address));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
// UB
|
||||||
|
case 160: // AOJ
|
||||||
|
case 170: // SAS
|
||||||
|
case 174: // CIP
|
||||||
|
case 188: // LDM
|
||||||
|
case 189: // STM
|
||||||
|
case 194: // CBP
|
||||||
|
case 206: // CLP
|
||||||
|
case 207: // CGP
|
||||||
|
p1 = buffer[ptr + 1] & 0xFF;
|
||||||
|
setParameters (p1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// CSP
|
||||||
|
case 158:
|
||||||
|
p1 = buffer[ptr + 1];
|
||||||
|
description = "Call standard procedure - " + CSP[p1];
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Non-integer comparisons
|
||||||
|
case 175:
|
||||||
|
case 176:
|
||||||
|
case 177:
|
||||||
|
case 180:
|
||||||
|
case 181:
|
||||||
|
case 183:
|
||||||
|
p1 = buffer[ptr + 1]; // 2/4/6/8/10/12
|
||||||
|
if (p1 < 0 || p1 >= compValue.length)
|
||||||
|
{
|
||||||
|
System.out.printf ("%d %d %d%n", val, p1, ptr);
|
||||||
|
mnemonic += "******************************";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
mnemonic += compValue[p1];
|
||||||
|
if (p1 == 10 || p1 == 12)
|
||||||
|
{
|
||||||
|
length = getLengthOfB (buffer[ptr + 2]) + 2;
|
||||||
|
p2 = getValueOfB (buffer, ptr + 2, length - 2);
|
||||||
|
setParameters (p2);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
System.out.println ("Forgot : " + val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getWord (byte[] buffer, int ptr)
|
||||||
|
{
|
||||||
|
return (buffer[ptr + 1] & 0xFF) * 256 + (buffer[ptr] & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getLengthOfB (byte b)
|
||||||
|
{
|
||||||
|
return (b & 0x80) == 0x80 ? 2 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getValueOfB (byte[] buffer, int ptr, int length)
|
||||||
|
{
|
||||||
|
if (length == 2)
|
||||||
|
return (buffer[ptr] & 0x7F) * 256 + (buffer[ptr + 1] & 0xFF);
|
||||||
|
return buffer[ptr] & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setParameters (int p1)
|
||||||
|
{
|
||||||
|
description = description.replaceFirst (":1", p1 + "");
|
||||||
|
extras = "#" + p1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setParameters (int p1, int p2)
|
||||||
|
{
|
||||||
|
setParameters (p1);
|
||||||
|
extras += ", #" + p2;
|
||||||
|
description = description.replaceFirst (":2", p2 + "");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setParameters (int p1, int p2, String p3)
|
||||||
|
{
|
||||||
|
setParameters (p1, p2);
|
||||||
|
description = description.replaceFirst (":3", p3);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
String hex = getHex (buffer, ptr, length > 4 ? 4 : length);
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
text.append (String.format ("%2s%05X: %-11s %-6s %-8s %s%n", jumpTarget ? "->" : "", ptr,
|
||||||
|
hex, mnemonic, extras, description));
|
||||||
|
if (length > 4)
|
||||||
|
{
|
||||||
|
int bytesLeft = length - 4;
|
||||||
|
int jmp = 0;
|
||||||
|
int p = ptr + 4;
|
||||||
|
while (bytesLeft > 0)
|
||||||
|
{
|
||||||
|
String line = getHex (buffer, p, (bytesLeft > 4) ? 4 : bytesLeft);
|
||||||
|
text.append (" " + line);
|
||||||
|
if (jumps.size () > 0)
|
||||||
|
{
|
||||||
|
if (jmp < jumps.size ())
|
||||||
|
text.append (" " + jumps.get (jmp++));
|
||||||
|
if (jmp < jumps.size ())
|
||||||
|
text.append (" " + jumps.get (jmp++));
|
||||||
|
}
|
||||||
|
text.append ("\n");
|
||||||
|
bytesLeft -= 4;
|
||||||
|
p += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getHex (byte[] buffer, int offset, int length)
|
||||||
|
{
|
||||||
|
if ((offset + length) >= buffer.length)
|
||||||
|
{
|
||||||
|
System.out.println ("too many");
|
||||||
|
return "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
|
||||||
|
}
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
text.append (String.format ("%02X ", buffer[offset + i]));
|
||||||
|
if (text.length () > 0)
|
||||||
|
text.deleteCharAt (text.length () - 1);
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
class Jump
|
||||||
|
{
|
||||||
|
int addressFrom;
|
||||||
|
int addressTo;
|
||||||
|
boolean caseJump;
|
||||||
|
int caseValue;
|
||||||
|
|
||||||
|
public Jump (int addressFrom, int addressTo)
|
||||||
|
{
|
||||||
|
this.addressFrom = addressFrom;
|
||||||
|
this.addressTo = addressTo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Jump (int addressFrom, int addressTo, int value)
|
||||||
|
{
|
||||||
|
this (addressFrom, addressTo);
|
||||||
|
this.caseValue = value;
|
||||||
|
this.caseJump = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
if (caseJump)
|
||||||
|
return String.format ("%3d: %04X", caseValue, addressTo);
|
||||||
|
return String.format ("%04X", addressTo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
118
src/com/bytezone/diskbrowser/applefile/PascalConstants.java
Executable file
118
src/com/bytezone/diskbrowser/applefile/PascalConstants.java
Executable file
|
@ -0,0 +1,118 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
public interface PascalConstants
|
||||||
|
{
|
||||||
|
static String[] mnemonics =
|
||||||
|
{ "ABI", "ABR", "ADI", "ADR", "LAND", "DIF", "DVI", "DVR", "CHK", "FLO", "FLT",
|
||||||
|
"INN", "INT", "LOR", "MODI", "MPI", "MPR", "NGI", "NGR", "LNOT", "SRS", "SBI",
|
||||||
|
"SBR", "SGS", "SQI", "SQR", "STO", "IXS", "UNI", "LDE", "CSP", "LDCN", "ADJ",
|
||||||
|
"FJP", "INC", "IND", "IXA", "LAO", "LSA", "LAE", "MOV", "LDO", "SAS", "SRO", "XJP",
|
||||||
|
"RNP", "CIP", "EQU", "GEQ", "GRT", "LDA", "LDC", "LEQ", "LES", "LOD", "NEQ", "STR",
|
||||||
|
"UJP", "LDP", "STP", "LDM", "STM", "LDB", "STB", "IXP", "RBP", "CBP", "EQUI",
|
||||||
|
"GEQI", "GRTI", "LLA", "LDCI", "LEQI", "LESI", "LDL", "NEQI", "STL", "CXP", "CLP",
|
||||||
|
"CGP", "LPA", "STE", "???", "EFJ", "NFJ", "BPT", "XIT", "NOP" };
|
||||||
|
|
||||||
|
static int[] mnemonicSize =
|
||||||
|
//
|
||||||
|
// 128 - 155
|
||||||
|
// 156 - 183
|
||||||
|
// 184 - 211
|
||||||
|
// 212 - 239
|
||||||
|
// 240 - 255
|
||||||
|
|
||||||
|
{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 3, 2, 1, 2, 2, 2, 2, 2, 2, 0, 3, 2, 2, 2, 2, 0, 2, 2, 2, 2, 2, 3, 0, 2, 2, 3, 2,
|
||||||
|
3, 2, 1, 1, 2, 2, 1, 1, 3, 2, 2, 1, 1, 1, 2, 3, 1, 1, 2, 1, 2, 3, 2, 2, 0, 3, 1, 2,
|
||||||
|
2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
|
||||||
|
|
||||||
|
static String[] descriptions =
|
||||||
|
{
|
||||||
|
"Absolute value of integer - push ABS(ToS)",
|
||||||
|
"Absolute value of real - push abs((real)ToS)",
|
||||||
|
"Add integers (tos + tos-1)",
|
||||||
|
"Add reals - push ToS + ToS-1",
|
||||||
|
"Logical AND",
|
||||||
|
"Set difference - push difference of sets ToS-1 and ToS",
|
||||||
|
"Divide integers - push ToS-1 / ToS",
|
||||||
|
"Divide reals - push ToS-1 / ToS",
|
||||||
|
"Check subrange bounds - assert ToS-1 <= ToS-2 <= ToS, pop ToS, pop ToS-1",
|
||||||
|
"Float next-to-ToS - push integer ToS-1 after converting to a real",
|
||||||
|
"Float ToS - push integer ToS after converting to a float",
|
||||||
|
"Set Membership - if int ToS-1 is in set ToS, push true, else push false",
|
||||||
|
"Set Intersection - push TOS AND TOS-1",
|
||||||
|
"Logical OR",
|
||||||
|
"Modulo integers - push ToS-1 % ToS",
|
||||||
|
"Multiply TOS by TOS-1",
|
||||||
|
"Multiply reals - push ToS-1 * ToS",
|
||||||
|
"Negate Integer - push two's complement of ToS",
|
||||||
|
"Negate real - push -((real)ToS)",
|
||||||
|
"Logical Not - push one's complement of ToS",
|
||||||
|
"Build a subrange set",
|
||||||
|
"Subtract Integers push ToS-1 - ToS",
|
||||||
|
"Subtract reals - push ToS-1 - ToS",
|
||||||
|
"Build a singleton set",
|
||||||
|
"Square integer - push ToS ^ 2",
|
||||||
|
"Square real - push ToS ^ 2",
|
||||||
|
"Store indirect word - store ToS into word pointed to by ToS-1",
|
||||||
|
"Index string array - push &(*ToS-1 + ToS)",
|
||||||
|
"Set union - push union of sets ToS OR ToS-1",
|
||||||
|
"Load extended word - push word at segment :1+:2",
|
||||||
|
"Call Standard Procedure #:1 - ",
|
||||||
|
"Load Constant NIL",
|
||||||
|
"Adjust set",
|
||||||
|
"Jump if ToS false",
|
||||||
|
"Increment field ptr - push ToS+:1",
|
||||||
|
"Static index and load word",
|
||||||
|
"Compute word pointer from ToS-1 + ToS * :1 words",
|
||||||
|
"Load Global - push (BASE+:1)",
|
||||||
|
"Load constant string address",
|
||||||
|
"Load extended address - push address of word at segment :1+:2",
|
||||||
|
"Move words - transfer :1 words from *ToS to *ToS-1",
|
||||||
|
"Load Global Word - push BASE+:1",
|
||||||
|
"String Assign",
|
||||||
|
"Store TOS into BASE+:1",
|
||||||
|
"Case Jump - :1::2, Error: :3",
|
||||||
|
"Return from non-base procedure (pass :1 words)",
|
||||||
|
"Call intermediate procedure #:1",
|
||||||
|
"ToS-1 == ToS",
|
||||||
|
"ToS-1 >= ToS",
|
||||||
|
"ToS-1 > ToS",
|
||||||
|
"Load Intermediate Address - push :1th activation record +:2 bytes",
|
||||||
|
"Load multi-word constant - :1 words",
|
||||||
|
"ToS-1 <= ToS",
|
||||||
|
"ToS-1 < ToS",
|
||||||
|
"Load Intermediate Word - push :1th activation record +:2 bytes",
|
||||||
|
"ToS-1 <> ToS",
|
||||||
|
"Store intermediate word - store TOS into :2, traverse :1",
|
||||||
|
"Unconditional jump",
|
||||||
|
"Load Packed Field - push *ToS",
|
||||||
|
"Store into packed field",
|
||||||
|
"Load multiple words - push block of unsigned bytes at *ToS",
|
||||||
|
"Store multiple words - store block of UB at ToS to *ToS-1",
|
||||||
|
"Load Byte - index the byte pointer ToS-1 by integer index ToS and push that byte",
|
||||||
|
"Store Byte - index the byte pointer ToS-2 by integer index ToS-1 and move ToS to that location",
|
||||||
|
"Index packed array - do complicated stuff with :1 and :2",
|
||||||
|
"Return from base procedure (pass :1 words)",
|
||||||
|
"Call Base Procedure :1 at lex level -1 or 0", "Compare Integer : ToS-1 = ToS",
|
||||||
|
"Compare Integer : TOS-1 >= TOS", "Compare Integer : TOS-1 > ToS",
|
||||||
|
"Load Local Address - push MP+:1", "Load Word - push #:1",
|
||||||
|
"Compare Integer : TOS-1 <= TOS", "Compare Integer : TOS-1 < ToS",
|
||||||
|
"Load Local Word - push MP+:1", "Compare Integer : TOS-1 <> TOS",
|
||||||
|
"Store Local Word - store ToS into MP+:1",
|
||||||
|
"Call external procedure #:2 in segment #:1", "Call local procedure #:1",
|
||||||
|
"Call global procedure #:1", "Load a packed array - use :1 and :2",
|
||||||
|
"Store extended word - store ToS into word at segment :1+:2", "210 ",
|
||||||
|
"Equal false jump - jump :1 if ToS-1 <> ToS",
|
||||||
|
"Not equal false jump - jump :1 if ToS-1 == ToS",
|
||||||
|
"Breakpoint - not used (does NOP)", "Exit OS - cold boot", "No-op" };
|
||||||
|
|
||||||
|
static String[] CSP =
|
||||||
|
{ "000", "NEW", "MVL", "MVR", "EXIT", "", "", "IDS", "TRS", "TIM", "FLC", "SCN", "",
|
||||||
|
"", "", "", "", "", "", "", "", "021", "TNC", "RND", "", "", "", "", "", "", "",
|
||||||
|
"MRK", "RLS", "33", "34", "POT", "36", "37", "38", "39", "40" };
|
||||||
|
|
||||||
|
static String[] SegmentKind =
|
||||||
|
{ "Linked", "HostSeg", "SegProc", "UnitSeg", "SeprtSeg", "UnlinkedIntrins",
|
||||||
|
"LinkedIntrins", "DataSeg" };
|
||||||
|
}
|
29
src/com/bytezone/diskbrowser/applefile/PascalInfo.java
Normal file
29
src/com/bytezone/diskbrowser/applefile/PascalInfo.java
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
public class PascalInfo extends AbstractFile
|
||||||
|
{
|
||||||
|
|
||||||
|
public PascalInfo (String name, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder (getHeader ());
|
||||||
|
|
||||||
|
for (int i = 0; i < buffer.length; i++)
|
||||||
|
if (buffer[i] == 0x0D)
|
||||||
|
text.append ("\n");
|
||||||
|
else
|
||||||
|
text.append ((char) buffer[i]);
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getHeader ()
|
||||||
|
{
|
||||||
|
return "Name : " + name + "\n\n";
|
||||||
|
}
|
||||||
|
}
|
175
src/com/bytezone/diskbrowser/applefile/PascalProcedure.java
Executable file
175
src/com/bytezone/diskbrowser/applefile/PascalProcedure.java
Executable file
|
@ -0,0 +1,175 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
import com.bytezone.diskbrowser.applefile.PascalCodeStatement.Jump;
|
||||||
|
|
||||||
|
public class PascalProcedure
|
||||||
|
{
|
||||||
|
// all procedures have these fields
|
||||||
|
byte[] buffer;
|
||||||
|
int procOffset;
|
||||||
|
int offset;
|
||||||
|
int slot;
|
||||||
|
boolean valid;
|
||||||
|
|
||||||
|
// only valid procedures have these fields
|
||||||
|
int procedureNo;
|
||||||
|
int procLevel;
|
||||||
|
int codeStart;
|
||||||
|
int codeEnd;
|
||||||
|
int parmSize;
|
||||||
|
int dataSize;
|
||||||
|
List<PascalCodeStatement> statements = new ArrayList<PascalCodeStatement> ();
|
||||||
|
AssemblerProgram assembler;
|
||||||
|
int jumpTable = -8;
|
||||||
|
|
||||||
|
public PascalProcedure (byte[] buffer, int slot)
|
||||||
|
{
|
||||||
|
this.buffer = buffer;
|
||||||
|
this.slot = slot;
|
||||||
|
int p = buffer.length - 2 - slot * 2;
|
||||||
|
offset = HexFormatter.intValue (buffer[p], buffer[p + 1]);
|
||||||
|
procOffset = p - offset;
|
||||||
|
valid = procOffset > 0;
|
||||||
|
|
||||||
|
if (valid)
|
||||||
|
{
|
||||||
|
procedureNo = buffer[procOffset] & 0xFF;
|
||||||
|
procLevel = buffer[procOffset + 1];
|
||||||
|
codeStart = HexFormatter.intValue (buffer[procOffset - 2], buffer[procOffset - 1]);
|
||||||
|
codeEnd = HexFormatter.intValue (buffer[procOffset - 4], buffer[procOffset - 3]);
|
||||||
|
parmSize = HexFormatter.intValue (buffer[procOffset - 6], buffer[procOffset - 5]);
|
||||||
|
dataSize = HexFormatter.intValue (buffer[procOffset - 8], buffer[procOffset - 7]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void decode ()
|
||||||
|
{
|
||||||
|
if (statements.size () > 0 || assembler != null)
|
||||||
|
return;
|
||||||
|
int ptr = procOffset - codeStart - 2;
|
||||||
|
int max = procOffset + jumpTable;
|
||||||
|
|
||||||
|
if (codeEnd == 0)
|
||||||
|
{
|
||||||
|
int len = codeStart + jumpTable + 2;
|
||||||
|
if (len > 0)
|
||||||
|
{
|
||||||
|
byte[] asmBuf = new byte[len];
|
||||||
|
System.arraycopy (buffer, ptr, asmBuf, 0, len);
|
||||||
|
assembler = new AssemblerProgram ("Proc", asmBuf, ptr);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (ptr < max)
|
||||||
|
{
|
||||||
|
PascalCodeStatement cs = new PascalCodeStatement (buffer, ptr, procOffset);
|
||||||
|
if (cs.length <= 0)
|
||||||
|
{
|
||||||
|
System.out.println ("error - length <= 0 : " + cs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
statements.add (cs);
|
||||||
|
if (cs.val == 185 || cs.val == 161)
|
||||||
|
if (cs.p1 < jumpTable)
|
||||||
|
{
|
||||||
|
jumpTable = cs.p1;
|
||||||
|
max = procOffset + jumpTable;
|
||||||
|
}
|
||||||
|
ptr += cs.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tidy up left-over bytes at the end
|
||||||
|
if (statements.size () > 1)
|
||||||
|
{
|
||||||
|
PascalCodeStatement lastStatement = statements.get (statements.size () - 1);
|
||||||
|
PascalCodeStatement secondLastStatement = statements.get (statements.size () - 2);
|
||||||
|
if (lastStatement.val == 0
|
||||||
|
&& (secondLastStatement.val == 0xD6 || secondLastStatement.val == 0xC1 || secondLastStatement.val == 0xAD))
|
||||||
|
statements.remove (statements.size () - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark statements that are jump targets
|
||||||
|
int actualEnd = procOffset - codeEnd - 4;
|
||||||
|
for (PascalCodeStatement cs : statements)
|
||||||
|
{
|
||||||
|
if (cs.ptr == actualEnd)
|
||||||
|
{
|
||||||
|
cs.jumpTarget = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (Jump cj : cs.jumps)
|
||||||
|
for (PascalCodeStatement cs2 : statements)
|
||||||
|
if (cs2.ptr == cj.addressTo)
|
||||||
|
{
|
||||||
|
cs2.jumpTarget = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PascalCodeStatement> extractStrings ()
|
||||||
|
{
|
||||||
|
decode ();
|
||||||
|
List<PascalCodeStatement> strings = new ArrayList<PascalCodeStatement> ();
|
||||||
|
for (PascalCodeStatement cs : statements)
|
||||||
|
if (cs.val == 166)
|
||||||
|
strings.add (cs);
|
||||||
|
return strings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
if (!valid)
|
||||||
|
return "";
|
||||||
|
decode ();
|
||||||
|
|
||||||
|
StringBuilder text = new StringBuilder ("\nProcedure Header\n================\n\n");
|
||||||
|
|
||||||
|
if (false)
|
||||||
|
text.append (HexFormatter.format (buffer, procOffset + jumpTable, 2 - jumpTable) + "\n\n");
|
||||||
|
|
||||||
|
text.append (String.format ("Level.......%5d %02X%n", procLevel, procLevel & 0xFF));
|
||||||
|
text.append (String.format ("Proc no.....%5d %02X%n", procedureNo, procedureNo));
|
||||||
|
text.append (String.format ("Code entry..%5d %04X (%04X - %04X = %04X)%n", codeStart,
|
||||||
|
codeStart, (procOffset - 2), codeStart, (procOffset - codeStart - 2)));
|
||||||
|
text.append (String.format ("Code exit...%5d %04X", codeEnd, codeEnd));
|
||||||
|
if (codeEnd > 0)
|
||||||
|
text.append (String.format (" (%04X - %04X = %04X)%n", (procOffset - 4), codeEnd,
|
||||||
|
(procOffset - codeEnd - 4)));
|
||||||
|
else
|
||||||
|
text.append (String.format ("%n"));
|
||||||
|
text.append (String.format ("Parm size...%5d %04X%n", parmSize, parmSize));
|
||||||
|
text.append (String.format ("Data size...%5d %04X%n%n", dataSize, dataSize));
|
||||||
|
|
||||||
|
text.append ("Procedure Code\n==============\n\n");
|
||||||
|
|
||||||
|
int ptr = procOffset - codeStart - 2;
|
||||||
|
if (false)
|
||||||
|
text.append (HexFormatter.format (buffer, ptr, codeStart + jumpTable + 2) + "\n\n");
|
||||||
|
|
||||||
|
if (codeEnd == 0)
|
||||||
|
text.append (assembler.getAssembler () + "\n");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (PascalCodeStatement cs : statements)
|
||||||
|
text.append (cs);
|
||||||
|
|
||||||
|
if (jumpTable < -8 && false)
|
||||||
|
{
|
||||||
|
text.append ("\nJump table:\n");
|
||||||
|
for (int i = procOffset + jumpTable; i < procOffset - 8; i += 2)
|
||||||
|
{
|
||||||
|
ptr = i - ((buffer[i + 1] & 0xFF) * 256 + (buffer[i] & 0xFF));
|
||||||
|
text.append (String.format ("%05X : %02X %02X --> %04X%n", i, buffer[i], buffer[i + 1],
|
||||||
|
ptr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
134
src/com/bytezone/diskbrowser/applefile/PascalSegment.java
Executable file
134
src/com/bytezone/diskbrowser/applefile/PascalSegment.java
Executable file
|
@ -0,0 +1,134 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.FileFormatException;
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class PascalSegment extends AbstractFile implements PascalConstants
|
||||||
|
{
|
||||||
|
int segmentNoHeader;
|
||||||
|
int segmentNoBody;
|
||||||
|
public final int blockNo;
|
||||||
|
public final int size;
|
||||||
|
List<PascalProcedure> procedures;
|
||||||
|
int segKind;
|
||||||
|
int textAddress;
|
||||||
|
int machineType;
|
||||||
|
int version;
|
||||||
|
int intrinsSegs1;
|
||||||
|
int intrinsSegs2;
|
||||||
|
int slot;
|
||||||
|
int totalProcedures;
|
||||||
|
|
||||||
|
public PascalSegment (String name, byte[] fullBuffer, int seq)
|
||||||
|
{
|
||||||
|
super (name, fullBuffer); // sets this.buffer to the full buffer temporarily
|
||||||
|
this.slot = seq;
|
||||||
|
this.blockNo = HexFormatter.intValue (fullBuffer[seq * 4], fullBuffer[seq * 4 + 1]);
|
||||||
|
this.size = HexFormatter.intValue (fullBuffer[seq * 4 + 2], fullBuffer[seq * 4 + 3]);
|
||||||
|
this.segmentNoHeader = fullBuffer[0x100 + seq * 2];
|
||||||
|
segKind =
|
||||||
|
HexFormatter.intValue (fullBuffer[0xC0 + seq * 2], fullBuffer[0xC0 + seq * 2 + 1]);
|
||||||
|
textAddress =
|
||||||
|
HexFormatter.intValue (fullBuffer[0xE0 + seq * 2], fullBuffer[0xE0 + seq * 2 + 1]);
|
||||||
|
int flags = fullBuffer[0x101 + seq * 2] & 0xFF;
|
||||||
|
machineType = flags & 0x0F;
|
||||||
|
version = (flags & 0xD0) >> 5;
|
||||||
|
intrinsSegs1 =
|
||||||
|
HexFormatter.intValue (fullBuffer[0x120 + seq * 4], fullBuffer[0x120 + seq * 4 + 1]);
|
||||||
|
intrinsSegs2 =
|
||||||
|
HexFormatter.intValue (fullBuffer[0x120 + seq * 4 + 2],
|
||||||
|
fullBuffer[0x120 + seq * 4 + 3]);
|
||||||
|
|
||||||
|
int offset = blockNo * 512;
|
||||||
|
if (offset < fullBuffer.length)
|
||||||
|
{
|
||||||
|
buffer = new byte[size]; // replaces this.buffer with the segment buffer only
|
||||||
|
System.arraycopy (fullBuffer, blockNo * 512, buffer, 0, size);
|
||||||
|
totalProcedures = buffer[size - 1] & 0xFF;
|
||||||
|
segmentNoBody = buffer[size - 2] & 0xFF;
|
||||||
|
if (segmentNoBody != segmentNoHeader)
|
||||||
|
System.out.println ("Segment number mismatch : " + segmentNoBody + " / "
|
||||||
|
+ segmentNoHeader);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
System.out.println ("Error in blocksize for pascal disk");
|
||||||
|
throw new FileFormatException ("Error in PascalSegment");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildProcedureList ()
|
||||||
|
{
|
||||||
|
procedures = new ArrayList<PascalProcedure> (totalProcedures);
|
||||||
|
|
||||||
|
for (int i = 1; i <= totalProcedures; i++)
|
||||||
|
procedures.add (new PascalProcedure (buffer, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toText ()
|
||||||
|
{
|
||||||
|
return String
|
||||||
|
.format (" %2d %02X %04X %,6d %-8s %-15s %3d %3d %d %d %d %d",
|
||||||
|
slot, blockNo, size, size, name, SegmentKind[segKind], textAddress,
|
||||||
|
segmentNoHeader, machineType, version, intrinsSegs1, intrinsSegs2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
if (procedures == null)
|
||||||
|
buildProcedureList ();
|
||||||
|
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
String title = "Segment - " + name;
|
||||||
|
text.append (title + "\n"
|
||||||
|
+ "===============================".substring (0, title.length ()) + "\n\n");
|
||||||
|
String warning =
|
||||||
|
segmentNoBody == segmentNoHeader ? "" : " (" + segmentNoHeader + " in header)";
|
||||||
|
text.append (String.format ("Address........ %02X%n", blockNo));
|
||||||
|
text.append (String.format ("Length......... %04X%n", buffer.length));
|
||||||
|
text.append (String.format ("Machine type... %d%n", machineType));
|
||||||
|
text.append (String.format ("Version........ %d%n", version));
|
||||||
|
text.append (String.format ("Segment........ %d%s%n", segmentNoBody, warning));
|
||||||
|
text.append (String.format ("Total procs.... %d%n", procedures.size ()));
|
||||||
|
|
||||||
|
text.append ("\nProcedure Dictionary\n====================\n\n");
|
||||||
|
|
||||||
|
int len = procedures.size () * 2 + 2;
|
||||||
|
if (false)
|
||||||
|
text.append (HexFormatter.format (buffer, buffer.length - len, len) + "\n\n");
|
||||||
|
|
||||||
|
text.append ("Proc Offset Lvl Entry Exit Parm Data Proc header\n");
|
||||||
|
text.append ("---- ------ --- ----- ---- ---- ---- --------------------\n");
|
||||||
|
for (PascalProcedure procedure : procedures)
|
||||||
|
{
|
||||||
|
if (procedure.valid)
|
||||||
|
{
|
||||||
|
int address = size - procedure.slot * 2 - 2;
|
||||||
|
text.append (String
|
||||||
|
.format (" %2d %04X %3d %04X %04X %04X %04X (%04X - %04X = %04X)%n",
|
||||||
|
procedure.procedureNo, procedure.offset, procedure.procLevel,
|
||||||
|
procedure.codeStart, procedure.codeEnd, procedure.parmSize,
|
||||||
|
procedure.dataSize, address, procedure.offset, procedure.procOffset));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
text.append (String.format (" %2d %04X%n", procedure.slot, procedure.offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
text.append ("\nStrings\n=======\n");
|
||||||
|
for (PascalProcedure pp : procedures)
|
||||||
|
{
|
||||||
|
List<PascalCodeStatement> strings = pp.extractStrings ();
|
||||||
|
for (PascalCodeStatement cs : strings)
|
||||||
|
text.append (String.format (" %2d %04X %s%n", pp.procedureNo, cs.ptr, cs.text));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (PascalProcedure procedure : procedures)
|
||||||
|
if (procedure.valid)
|
||||||
|
text.append (procedure);
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
50
src/com/bytezone/diskbrowser/applefile/PascalText.java
Executable file
50
src/com/bytezone/diskbrowser/applefile/PascalText.java
Executable file
|
@ -0,0 +1,50 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
public class PascalText extends AbstractFile
|
||||||
|
{
|
||||||
|
public PascalText (String name, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder (getHeader ());
|
||||||
|
|
||||||
|
int ptr = 0x400;
|
||||||
|
while (ptr < buffer.length)
|
||||||
|
{
|
||||||
|
if (buffer[ptr] == 0x00)
|
||||||
|
{
|
||||||
|
++ptr;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (buffer[ptr] == 0x10)
|
||||||
|
{
|
||||||
|
int tab = buffer[ptr + 1] - 0x20;
|
||||||
|
while (tab-- > 0)
|
||||||
|
text.append (" ");
|
||||||
|
ptr += 2;
|
||||||
|
}
|
||||||
|
String line = getLine (ptr);
|
||||||
|
text.append (line + "\n");
|
||||||
|
ptr += line.length () + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getHeader ()
|
||||||
|
{
|
||||||
|
return "Name : " + name + "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getLine (int ptr)
|
||||||
|
{
|
||||||
|
StringBuilder line = new StringBuilder ();
|
||||||
|
while (buffer[ptr] != 0x0D)
|
||||||
|
line.append ((char) buffer[ptr++]);
|
||||||
|
return line.toString ();
|
||||||
|
}
|
||||||
|
}
|
150
src/com/bytezone/diskbrowser/applefile/ShapeTable.java
Executable file
150
src/com/bytezone/diskbrowser/applefile/ShapeTable.java
Executable file
|
@ -0,0 +1,150 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class ShapeTable extends AbstractFile
|
||||||
|
{
|
||||||
|
private static final int SIZE = 400;
|
||||||
|
|
||||||
|
public ShapeTable (String name, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
StringBuffer text = new StringBuffer ();
|
||||||
|
int totalShapes = buffer[0] & 0xFF;
|
||||||
|
int startPos = SIZE / 2;
|
||||||
|
|
||||||
|
for (int i = 0; i < totalShapes; i++)
|
||||||
|
{
|
||||||
|
int offset = HexFormatter.intValue (buffer[i * 2 + 2], buffer[i * 2 + 3]);
|
||||||
|
int[][] grid = new int[SIZE][SIZE];
|
||||||
|
int row = startPos;
|
||||||
|
int col = row;
|
||||||
|
if (i > 0)
|
||||||
|
text.append ("\n");
|
||||||
|
text.append ("Shape " + i + " :\n");
|
||||||
|
|
||||||
|
while (buffer[offset] != 0)
|
||||||
|
{
|
||||||
|
int value = buffer[offset++] & 0xFF;
|
||||||
|
int v1 = value >> 6;
|
||||||
|
int v2 = (value & 0x38) >> 3;
|
||||||
|
int v3 = value & 0x07;
|
||||||
|
// System.out.printf ("%02X %02X %02X %02X%n", value, v1, v2, v3);
|
||||||
|
|
||||||
|
if (v3 >= 4)
|
||||||
|
{
|
||||||
|
grid[row][col] = 1;
|
||||||
|
grid[0][col]++;
|
||||||
|
grid[row][0]++;
|
||||||
|
}
|
||||||
|
if (v3 == 0 || v3 == 4)
|
||||||
|
row--;
|
||||||
|
else if (v3 == 1 || v3 == 5)
|
||||||
|
col++;
|
||||||
|
else if (v3 == 2 || v3 == 6)
|
||||||
|
row++;
|
||||||
|
else
|
||||||
|
col--;
|
||||||
|
|
||||||
|
if (v2 >= 4)
|
||||||
|
{
|
||||||
|
grid[row][col] = 1;
|
||||||
|
grid[0][col]++;
|
||||||
|
grid[row][0]++;
|
||||||
|
}
|
||||||
|
if (v2 == 0 && v1 != 0)
|
||||||
|
row--;
|
||||||
|
else if (v2 == 4)
|
||||||
|
row--;
|
||||||
|
else if (v2 == 1 || v2 == 5)
|
||||||
|
col++;
|
||||||
|
else if (v2 == 2 || v2 == 6)
|
||||||
|
row++;
|
||||||
|
else if (v2 == 3 || v2 == 7)
|
||||||
|
col--;
|
||||||
|
|
||||||
|
if (v1 == 1)
|
||||||
|
col++;
|
||||||
|
else if (v1 == 2)
|
||||||
|
row++;
|
||||||
|
else if (v1 == 3)
|
||||||
|
col--;
|
||||||
|
|
||||||
|
}
|
||||||
|
text.append ("\n");
|
||||||
|
|
||||||
|
int minRow = startPos, maxRow = startPos;
|
||||||
|
int minCol = startPos, maxCol = startPos;
|
||||||
|
for (row = 1; row < grid.length; row++)
|
||||||
|
{
|
||||||
|
if (grid[row][0] > 0)
|
||||||
|
{
|
||||||
|
if (row < minRow)
|
||||||
|
minRow = row;
|
||||||
|
if (row > maxRow)
|
||||||
|
maxRow = row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (col = 1; col < grid[0].length; col++)
|
||||||
|
{
|
||||||
|
if (grid[0][col] > 0)
|
||||||
|
{
|
||||||
|
if (col < minCol)
|
||||||
|
minCol = col;
|
||||||
|
if (col > maxCol)
|
||||||
|
maxCol = col;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (row = minRow; row <= maxRow; row++)
|
||||||
|
{
|
||||||
|
for (col = minCol; col <= maxCol; col++)
|
||||||
|
{
|
||||||
|
if (col == startPos && row == startPos)
|
||||||
|
text.append (grid[row][col] > 0 ? " @" : " .");
|
||||||
|
else if (grid[row][col] == 0)
|
||||||
|
text.append (" ");
|
||||||
|
else
|
||||||
|
text.append (" X");
|
||||||
|
}
|
||||||
|
text.append ("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
text.deleteCharAt (text.length () - 1);
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isShapeTable (byte[] buffer)
|
||||||
|
{
|
||||||
|
if (buffer.length == 0 || buffer[buffer.length - 1] != 0)
|
||||||
|
return false;
|
||||||
|
int totalShapes = buffer[0] & 0xFF;
|
||||||
|
if (totalShapes == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int lastOffset = 0;
|
||||||
|
for (int i = 0; i < totalShapes; i++)
|
||||||
|
{
|
||||||
|
// check index table entry is inside the file
|
||||||
|
int ptr = i * 2 + 2;
|
||||||
|
if (ptr >= buffer.length - 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// check index points inside the file
|
||||||
|
int offset = HexFormatter.intValue (buffer[ptr], buffer[ptr + 1]);
|
||||||
|
if (offset == 0 || offset < lastOffset || offset >= buffer.length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
lastOffset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
46
src/com/bytezone/diskbrowser/applefile/SimpleText.java
Executable file
46
src/com/bytezone/diskbrowser/applefile/SimpleText.java
Executable file
|
@ -0,0 +1,46 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
public class SimpleText extends AbstractFile
|
||||||
|
{
|
||||||
|
|
||||||
|
public SimpleText (String name, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
text.append ("Name : " + name + "\n");
|
||||||
|
text.append (String.format ("End of file : %,8d%n%n", buffer.length));
|
||||||
|
|
||||||
|
int ptr = 0;
|
||||||
|
while (ptr < buffer.length)
|
||||||
|
{
|
||||||
|
String line = getLine (ptr);
|
||||||
|
text.append (line + "\n");
|
||||||
|
ptr += line.length () + 1;
|
||||||
|
if (ptr < buffer.length && buffer[ptr] == 0x0A)
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getLine (int ptr)
|
||||||
|
{
|
||||||
|
StringBuilder line = new StringBuilder ();
|
||||||
|
while (ptr < buffer.length && buffer[ptr] != 0x0D)
|
||||||
|
line.append ((char) buffer[ptr++]);
|
||||||
|
return line.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isHTML (byte[] buffer)
|
||||||
|
{
|
||||||
|
String text = new String (buffer, 0, buffer.length);
|
||||||
|
if (text.indexOf ("HTML") > 0 || text.indexOf ("html") > 0)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
149
src/com/bytezone/diskbrowser/applefile/SimpleText2.java
Executable file
149
src/com/bytezone/diskbrowser/applefile/SimpleText2.java
Executable file
|
@ -0,0 +1,149 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class SimpleText2 extends AbstractFile
|
||||||
|
{
|
||||||
|
List<Integer> lineStarts = new ArrayList<Integer> ();
|
||||||
|
int loadAddress;
|
||||||
|
boolean showByte = false;
|
||||||
|
|
||||||
|
public SimpleText2 (String name, byte[] buffer, int loadAddress)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
this.loadAddress = loadAddress;
|
||||||
|
|
||||||
|
// store a pointer to each new line
|
||||||
|
int ptr = 0;
|
||||||
|
while (buffer[ptr] != -1)
|
||||||
|
{
|
||||||
|
int length = buffer[ptr] & 0xFF;
|
||||||
|
lineStarts.add (ptr);
|
||||||
|
ptr += length + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
text.append ("Name : " + name + "\n");
|
||||||
|
text.append (String.format ("Length : $%04X (%d)%n", buffer.length, buffer.length));
|
||||||
|
text.append (String.format ("Load at : $%04X%n%n", loadAddress));
|
||||||
|
|
||||||
|
for (Integer i : lineStarts)
|
||||||
|
text.append (String.format ("%05X %s%n", i, getLine (i)));
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHexDump ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
for (Integer i : lineStarts)
|
||||||
|
text.append (HexFormatter.formatNoHeader (buffer, i, (buffer[i] & 0xFF) + 1) + "\n");
|
||||||
|
text.append (HexFormatter.formatNoHeader (buffer, buffer.length - 2, 2) + "\n");
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert buffer to text, ignore line-break at the end
|
||||||
|
private String getLine (int ptr)
|
||||||
|
{
|
||||||
|
StringBuilder line = new StringBuilder ();
|
||||||
|
int length = buffer[ptr] & 0xFF;
|
||||||
|
while (--length > 0)
|
||||||
|
{
|
||||||
|
int val = buffer[++ptr] & 0xFF;
|
||||||
|
if (val == 0xBB)
|
||||||
|
{
|
||||||
|
while (line.length () < 35)
|
||||||
|
line.append (' ');
|
||||||
|
line.append (';');
|
||||||
|
}
|
||||||
|
else if (val >= 0x80)
|
||||||
|
{
|
||||||
|
while (line.length () < 10)
|
||||||
|
line.append (' ');
|
||||||
|
if (val == 0xDC)
|
||||||
|
line.append (String.format ("EQU", val));
|
||||||
|
else if (val == 0xD0)
|
||||||
|
line.append (String.format ("STA", val));
|
||||||
|
else if (val == 0xD2)
|
||||||
|
line.append (String.format ("STY", val));
|
||||||
|
else if (val == 0xD4)
|
||||||
|
line.append (String.format ("LSR", val));
|
||||||
|
else if (val == 0xD5)
|
||||||
|
line.append (String.format ("ROR", val));
|
||||||
|
else if (val == 0xD7)
|
||||||
|
line.append (String.format ("ASL", val));
|
||||||
|
else if (val == 0xD9)
|
||||||
|
line.append (String.format ("EQ ", val));
|
||||||
|
else if (val == 0xDB)
|
||||||
|
line.append (String.format ("TGT", val));
|
||||||
|
else if (val == 0xDA)
|
||||||
|
line.append (String.format ("ORG", val));
|
||||||
|
else if (val == 0xB1)
|
||||||
|
line.append (String.format ("TYA", val));
|
||||||
|
else if (val == 0xC1)
|
||||||
|
line.append (String.format ("AND", val));
|
||||||
|
else if (val == 0xC4)
|
||||||
|
line.append (String.format ("CMP", val));
|
||||||
|
else if (val == 0xC8)
|
||||||
|
line.append (String.format ("EOR", val));
|
||||||
|
else if (val == 0xCA)
|
||||||
|
line.append (String.format ("JMP", val));
|
||||||
|
else if (val == 0xCB)
|
||||||
|
line.append (String.format ("JSR", val));
|
||||||
|
else if (val == 0xCD)
|
||||||
|
line.append (String.format ("LDA", val));
|
||||||
|
else if (val == 0xCE)
|
||||||
|
line.append (String.format ("LDX", val));
|
||||||
|
else if (val == 0xCF)
|
||||||
|
line.append (String.format ("LDY", val));
|
||||||
|
else if (val == 0xA1)
|
||||||
|
line.append (String.format ("PHA", val));
|
||||||
|
else if (val == 0xA2)
|
||||||
|
line.append (String.format ("PLA", val));
|
||||||
|
else if (val == 0xA5)
|
||||||
|
line.append (String.format ("RTS", val));
|
||||||
|
else if (val == 0xA9)
|
||||||
|
line.append (String.format ("SEC", val));
|
||||||
|
else if (val == 0xAD)
|
||||||
|
line.append (String.format ("TAY", val));
|
||||||
|
else if (val == 0x82)
|
||||||
|
line.append (String.format ("BMI", val));
|
||||||
|
else if (val == 0x84)
|
||||||
|
line.append (String.format ("BCS", val));
|
||||||
|
else if (val == 0x85)
|
||||||
|
line.append (String.format ("BPL", val));
|
||||||
|
else if (val == 0x86)
|
||||||
|
line.append (String.format ("BNE", val));
|
||||||
|
else if (val == 0x87)
|
||||||
|
line.append (String.format ("BEQ", val));
|
||||||
|
else if (val == 0x99)
|
||||||
|
line.append (String.format ("CLC", val));
|
||||||
|
else if (val == 0x9C)
|
||||||
|
line.append (String.format ("DEX", val));
|
||||||
|
else if (val == 0x9F)
|
||||||
|
line.append (String.format ("INY", val));
|
||||||
|
else
|
||||||
|
line.append (String.format (".%02X.", val));
|
||||||
|
|
||||||
|
line.append (' ');
|
||||||
|
++ptr;
|
||||||
|
if (buffer[ptr] < 0x20 && showByte)
|
||||||
|
{
|
||||||
|
val = buffer[ptr] & 0xFF;
|
||||||
|
line.append (String.format (".%02X. ", val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
line.append ((char) val);
|
||||||
|
}
|
||||||
|
return line.toString ();
|
||||||
|
}
|
||||||
|
}
|
242
src/com/bytezone/diskbrowser/applefile/StoredVariables.java
Executable file
242
src/com/bytezone/diskbrowser/applefile/StoredVariables.java
Executable file
|
@ -0,0 +1,242 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class StoredVariables extends AbstractFile
|
||||||
|
{
|
||||||
|
public StoredVariables (String name, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
String strValue = null;
|
||||||
|
int intValue = 0;
|
||||||
|
// double doubleValue = 0.0;
|
||||||
|
int strPtr = buffer.length;
|
||||||
|
|
||||||
|
text.append ("File length : " + HexFormatter.format4 (buffer.length));
|
||||||
|
int totalLength = HexFormatter.intValue (buffer[0], buffer[1]);
|
||||||
|
text.append ("\nTotal length : " + HexFormatter.format4 (totalLength));
|
||||||
|
|
||||||
|
int varLength = HexFormatter.intValue (buffer[2], buffer[3]);
|
||||||
|
text.append ("\nVar length : " + HexFormatter.format4 (varLength));
|
||||||
|
text.append ("\n\n");
|
||||||
|
|
||||||
|
// list simple variables
|
||||||
|
|
||||||
|
int ptr = 5;
|
||||||
|
while (ptr < varLength + 5)
|
||||||
|
{
|
||||||
|
String variableName = getVariableName (buffer[ptr], buffer[ptr + 1]);
|
||||||
|
text.append (variableName);
|
||||||
|
|
||||||
|
char suffix = variableName.charAt (variableName.length () - 1);
|
||||||
|
if (suffix == '$')
|
||||||
|
{
|
||||||
|
int strLength = HexFormatter.intValue (buffer[ptr + 2]);
|
||||||
|
strPtr -= strLength;
|
||||||
|
strValue = HexFormatter.getString (buffer, strPtr, strLength);
|
||||||
|
text.append (" = " + strValue);
|
||||||
|
}
|
||||||
|
else if (suffix == '%')
|
||||||
|
{
|
||||||
|
intValue = HexFormatter.intValue (buffer[ptr + 3], buffer[ptr + 2]);
|
||||||
|
if ((buffer[ptr + 2] & 0x80) > 0)
|
||||||
|
intValue -= 65536;
|
||||||
|
text.append (" = " + intValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (hasValue (ptr + 2))
|
||||||
|
{
|
||||||
|
String value = HexFormatter.floatValue (buffer, ptr + 2) + "";
|
||||||
|
if (value.endsWith (".0"))
|
||||||
|
text.append (" = " + value.substring (0, value.length () - 2));
|
||||||
|
else
|
||||||
|
text.append (" = " + value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
text.append ("\n");
|
||||||
|
ptr += 7;
|
||||||
|
}
|
||||||
|
listArrays (text, ptr, totalLength, strPtr);
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getVariableName (byte b1, byte b2)
|
||||||
|
{
|
||||||
|
char c1, c2, suffix;
|
||||||
|
|
||||||
|
if ((b1 & 0x80) > 0) // integer
|
||||||
|
{
|
||||||
|
c1 = (char) (b1 & 0x7F);
|
||||||
|
c2 = (char) (b2 & 0x7F);
|
||||||
|
suffix = '%';
|
||||||
|
}
|
||||||
|
else if ((b2 & 0x80) > 0) // string
|
||||||
|
{
|
||||||
|
c1 = (char) b1;
|
||||||
|
c2 = (char) (b2 & 0x7F);
|
||||||
|
suffix = '$';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
c1 = (char) b1;
|
||||||
|
c2 = (char) b2;
|
||||||
|
suffix = ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuffer variableName = new StringBuffer ();
|
||||||
|
variableName.append (c1);
|
||||||
|
if (c2 > 32)
|
||||||
|
variableName.append (c2);
|
||||||
|
if (suffix != ' ')
|
||||||
|
variableName.append (suffix);
|
||||||
|
|
||||||
|
return variableName.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getDimensionText (int[] values)
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ("(");
|
||||||
|
for (int i = 0; i < values.length; i++)
|
||||||
|
{
|
||||||
|
text.append (values[i]);
|
||||||
|
if (i < values.length - 1)
|
||||||
|
text.append (',');
|
||||||
|
}
|
||||||
|
return text.append (')').toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void listArrays (StringBuilder text, int ptr, int totalLength, int strPtr)
|
||||||
|
{
|
||||||
|
while (ptr < totalLength + 5)
|
||||||
|
{
|
||||||
|
String variableName = getVariableName (buffer[ptr], buffer[ptr + 1]);
|
||||||
|
text.append ("\n");
|
||||||
|
int offset = HexFormatter.intValue (buffer[ptr + 2], buffer[ptr + 3]);
|
||||||
|
int dimensions = HexFormatter.intValue (buffer[ptr + 4]);
|
||||||
|
int[] dimensionSizes = new int[dimensions];
|
||||||
|
int totalElements = 0;
|
||||||
|
for (int i = 0; i < dimensions; i++)
|
||||||
|
{
|
||||||
|
int p = i * 2 + 5 + ptr;
|
||||||
|
int elements = HexFormatter.intValue (buffer[p + 1], buffer[p]);
|
||||||
|
dimensionSizes[dimensions - i - 1] = elements - 1;
|
||||||
|
if (totalElements == 0)
|
||||||
|
totalElements = elements;
|
||||||
|
else
|
||||||
|
totalElements *= elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
int headerSize = 5 + dimensions * 2;
|
||||||
|
int elementSize = (offset - headerSize) / totalElements;
|
||||||
|
|
||||||
|
int p = ptr + headerSize;
|
||||||
|
int[] values = new int[dimensions];
|
||||||
|
for (int i = 0; i < values.length; i++)
|
||||||
|
values[i] = 0;
|
||||||
|
out: while (true)
|
||||||
|
{
|
||||||
|
text.append (variableName + " " + getDimensionText (values) + " = ");
|
||||||
|
if (elementSize == 2)
|
||||||
|
{
|
||||||
|
int intValue = HexFormatter.intValue (buffer[p + 1], buffer[p]);
|
||||||
|
if ((buffer[p] & 0x80) > 0)
|
||||||
|
intValue -= 65536;
|
||||||
|
text.append (intValue + "\n");
|
||||||
|
}
|
||||||
|
else if (elementSize == 3)
|
||||||
|
{
|
||||||
|
int strLength = HexFormatter.intValue (buffer[p]);
|
||||||
|
if (strLength > 0)
|
||||||
|
{
|
||||||
|
strPtr -= strLength;
|
||||||
|
text.append (HexFormatter.getString (buffer, strPtr, strLength));
|
||||||
|
}
|
||||||
|
text.append ("\n");
|
||||||
|
}
|
||||||
|
else if (elementSize == 5)
|
||||||
|
{
|
||||||
|
if (hasValue (p))
|
||||||
|
text.append (HexFormatter.floatValue (buffer, p));
|
||||||
|
text.append ("\n");
|
||||||
|
}
|
||||||
|
p += elementSize;
|
||||||
|
int cp = 0;
|
||||||
|
while (++values[cp] > dimensionSizes[cp])
|
||||||
|
{
|
||||||
|
values[cp++] = 0;
|
||||||
|
if (cp >= values.length)
|
||||||
|
break out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ptr += offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasValue (int p)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 5; i++)
|
||||||
|
if (buffer[p + i] != 0)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHexDump ()
|
||||||
|
{
|
||||||
|
StringBuffer text = new StringBuffer ();
|
||||||
|
|
||||||
|
text.append ("File length : " + HexFormatter.format4 (buffer.length));
|
||||||
|
int totalLength = HexFormatter.intValue (buffer[0], buffer[1]);
|
||||||
|
text.append ("\nTotal length : " + HexFormatter.format4 (totalLength));
|
||||||
|
|
||||||
|
int varLength = HexFormatter.intValue (buffer[2], buffer[3]);
|
||||||
|
text.append ("\nVar length : " + HexFormatter.format4 (varLength));
|
||||||
|
|
||||||
|
int unknown = HexFormatter.intValue (buffer[4]);
|
||||||
|
text.append ("\nUnknown : " + HexFormatter.format2 (unknown));
|
||||||
|
text.append ("\n\n");
|
||||||
|
|
||||||
|
int ptr = 5;
|
||||||
|
text.append ("Simple variables : \n\n");
|
||||||
|
while (ptr < varLength + 5)
|
||||||
|
{
|
||||||
|
text.append (HexFormatter.format (buffer, ptr, 7, false, 0) + "\n");
|
||||||
|
ptr += 7;
|
||||||
|
}
|
||||||
|
text.append ("\nArrays : \n\n");
|
||||||
|
while (ptr < totalLength + 5)
|
||||||
|
{
|
||||||
|
int offset = HexFormatter.intValue (buffer[ptr + 2], buffer[ptr + 3]);
|
||||||
|
int dimensions = HexFormatter.intValue (buffer[ptr + 4]);
|
||||||
|
int[] dimensionSizes = new int[dimensions];
|
||||||
|
int totalElements = 0;
|
||||||
|
for (int i = 0; i < dimensions; i++)
|
||||||
|
{
|
||||||
|
int p = i * 2 + 5 + ptr;
|
||||||
|
int elements = HexFormatter.intValue (buffer[p + 1], buffer[p]);
|
||||||
|
dimensionSizes[dimensions - i - 1] = elements;
|
||||||
|
if (totalElements == 0)
|
||||||
|
totalElements = elements;
|
||||||
|
else
|
||||||
|
totalElements *= elements;
|
||||||
|
}
|
||||||
|
int headerSize = 5 + dimensions * 2;
|
||||||
|
text.append (HexFormatter.format (buffer, ptr, headerSize, false, 0) + "\n\n");
|
||||||
|
text.append (HexFormatter.format (buffer, ptr + headerSize, offset - headerSize, false, 0)
|
||||||
|
+ "\n\n");
|
||||||
|
ptr += offset;
|
||||||
|
}
|
||||||
|
text.append ("Strings : \n\n");
|
||||||
|
int length = buffer.length - ptr;
|
||||||
|
text.append (HexFormatter.format (buffer, ptr, length, false, 0) + "\n\n");
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
43
src/com/bytezone/diskbrowser/applefile/TextBuffer.java
Normal file
43
src/com/bytezone/diskbrowser/applefile/TextBuffer.java
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
// only used by Prodos text files - note the fixed block size of 512 - bad!
|
||||||
|
public class TextBuffer
|
||||||
|
{
|
||||||
|
public final byte[] buffer;
|
||||||
|
public final int reclen;
|
||||||
|
public final int firstRecNo;
|
||||||
|
|
||||||
|
public TextBuffer (byte[] tempBuffer, int reclen, int firstBlock)
|
||||||
|
{
|
||||||
|
this.reclen = reclen;
|
||||||
|
|
||||||
|
// calculate recNo of first full record
|
||||||
|
int firstByte = firstBlock * 512; // logical byte #
|
||||||
|
int rem = firstByte % reclen;
|
||||||
|
firstRecNo = firstByte / reclen + (rem > 0 ? 1 : 0);
|
||||||
|
int offset = (rem > 0) ? reclen - rem : 0;
|
||||||
|
|
||||||
|
int availableBytes = tempBuffer.length - offset;
|
||||||
|
int totalRecords = (availableBytes - 1) / reclen + 1;
|
||||||
|
|
||||||
|
// should check whether the two buffers are identical, and maybe skip this
|
||||||
|
// step
|
||||||
|
buffer = new byte[totalRecords * reclen];
|
||||||
|
int copyBytes = Math.min (availableBytes, buffer.length);
|
||||||
|
System.arraycopy (tempBuffer, offset, buffer, 0, copyBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
text.append ("Record length : " + reclen + "\n");
|
||||||
|
text.append ("First record : " + firstRecNo + "\n\n");
|
||||||
|
text.append (HexFormatter.format (buffer, 0, buffer.length) + "\n");
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
159
src/com/bytezone/diskbrowser/applefile/TextFile.java
Executable file
159
src/com/bytezone/diskbrowser/applefile/TextFile.java
Executable file
|
@ -0,0 +1,159 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class TextFile extends AbstractFile
|
||||||
|
{
|
||||||
|
private int recordLength;
|
||||||
|
private List<TextBuffer> buffers; // only used if it is a Prodos text file
|
||||||
|
private int eof;
|
||||||
|
|
||||||
|
public TextFile (String name, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextFile (String name, byte[] buffer, int recordLength, int eof)
|
||||||
|
{
|
||||||
|
this (name, buffer);
|
||||||
|
this.eof = eof;
|
||||||
|
this.recordLength = recordLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextFile (String name, List<TextBuffer> buffers, int recordLength, int eof)
|
||||||
|
{
|
||||||
|
super (name, null);
|
||||||
|
this.buffers = buffers;
|
||||||
|
this.eof = eof;
|
||||||
|
this.recordLength = recordLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHexDump ()
|
||||||
|
{
|
||||||
|
if (buffers == null)
|
||||||
|
return (super.getHexDump ());
|
||||||
|
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
for (TextBuffer tb : buffers)
|
||||||
|
{
|
||||||
|
for (int i = 0, rec = 0; i < tb.buffer.length; i += tb.reclen, rec++)
|
||||||
|
{
|
||||||
|
text.append ("\nRecord #" + (tb.firstRecNo + rec) + "\n");
|
||||||
|
text.append (HexFormatter.format (tb.buffer, i, tb.reclen) + "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
text.append ("Name : " + name + "\n");
|
||||||
|
if (recordLength > 0) // a prodos text file
|
||||||
|
{
|
||||||
|
text.append (String.format ("Record length : %,8d%n", recordLength));
|
||||||
|
text.append (String.format ("End of file : %,8d%n", eof));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
text.append (String.format ("End of file : %,8d%n", buffer.length));
|
||||||
|
text.append ("\n");
|
||||||
|
|
||||||
|
// check whether file is spread over multiple buffers
|
||||||
|
if (buffers != null)
|
||||||
|
return treeFileText (text);
|
||||||
|
|
||||||
|
// check whether the record length is known
|
||||||
|
if (recordLength == 0)
|
||||||
|
return unknownLength (text);
|
||||||
|
|
||||||
|
text.append ("Offset Record Text values\n");
|
||||||
|
text.append ("------ ------- -------------------------------------------------------\n");
|
||||||
|
return knownLength (text, 0).toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String treeFileText (StringBuilder text)
|
||||||
|
{
|
||||||
|
for (TextBuffer tb : buffers)
|
||||||
|
{
|
||||||
|
this.buffer = tb.buffer;
|
||||||
|
knownLength (text, tb.firstRecNo);
|
||||||
|
}
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private StringBuilder knownLength (StringBuilder text, int recNo)
|
||||||
|
{
|
||||||
|
for (int ptr = 0; ptr < buffer.length; ptr += recordLength)
|
||||||
|
{
|
||||||
|
if (buffer[ptr] == 0)
|
||||||
|
{
|
||||||
|
recNo++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int len = buffer.length - ptr;
|
||||||
|
int bytes = len < recordLength ? len : recordLength;
|
||||||
|
|
||||||
|
while (buffer[ptr + bytes - 1] == 0)
|
||||||
|
bytes--;
|
||||||
|
|
||||||
|
text.append (String.format ("%,6d %,8d %s%n", ptr, recNo++,
|
||||||
|
HexFormatter.getString (buffer, ptr, bytes)));
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String unknownLength (StringBuilder text)
|
||||||
|
{
|
||||||
|
int nulls = 0;
|
||||||
|
int ptr = 0;
|
||||||
|
int size = buffer.length;
|
||||||
|
int lastVal = 0;
|
||||||
|
boolean newFormat = true;
|
||||||
|
|
||||||
|
if (newFormat)
|
||||||
|
{
|
||||||
|
text.append ("Offset Text values\n");
|
||||||
|
text.append ("------ -------------------------------------------------------"
|
||||||
|
+ "-------------------\n");
|
||||||
|
if (size == 0)
|
||||||
|
return text.toString ();
|
||||||
|
|
||||||
|
if (buffer[ptr] != 0)
|
||||||
|
text.append (String.format ("%6d ", ptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
while (ptr < size)
|
||||||
|
{
|
||||||
|
int val = buffer[ptr++] & 0x7F; // strip hi-order bit
|
||||||
|
if (val == 0)
|
||||||
|
++nulls;
|
||||||
|
else if (val == 0x0D) // carriage return
|
||||||
|
text.append ("\n");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (nulls > 0)
|
||||||
|
{
|
||||||
|
if (newFormat)
|
||||||
|
text.append (String.format ("%6d ", ptr - 1));
|
||||||
|
else
|
||||||
|
text.append ("\nNew record at : " + (ptr - 1) + "\n");
|
||||||
|
nulls = 0;
|
||||||
|
}
|
||||||
|
else if (lastVal == 0x0D && newFormat)
|
||||||
|
text.append (" ");
|
||||||
|
|
||||||
|
text.append ((char) val);
|
||||||
|
}
|
||||||
|
lastVal = val;
|
||||||
|
}
|
||||||
|
if (text.length () > 0 && text.charAt (text.length () - 1) == '\n')
|
||||||
|
text.deleteCharAt (text.length () - 1);
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
57
src/com/bytezone/diskbrowser/applefile/VisicalcFile.java
Normal file
57
src/com/bytezone/diskbrowser/applefile/VisicalcFile.java
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class VisicalcFile extends AbstractFile
|
||||||
|
{
|
||||||
|
private VisicalcSpreadsheet sheet;
|
||||||
|
|
||||||
|
public VisicalcFile (String name, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
if (sheet == null)
|
||||||
|
sheet = new VisicalcSpreadsheet (buffer);
|
||||||
|
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
text.append ("Visicalc : " + name + "\n");
|
||||||
|
text.append ("Cells : " + sheet.size () + "\n\n");
|
||||||
|
text.append (sheet.getCells ());
|
||||||
|
|
||||||
|
if (false)
|
||||||
|
{
|
||||||
|
text.append ("\n");
|
||||||
|
for (String line : sheet.lines)
|
||||||
|
{
|
||||||
|
text.append ("\n");
|
||||||
|
text.append (line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isVisicalcFile (byte[] buffer)
|
||||||
|
{
|
||||||
|
if (false)
|
||||||
|
System.out.println (HexFormatter.format (buffer));
|
||||||
|
int firstByte = buffer[0] & 0xFF;
|
||||||
|
if (firstByte != 0xBE && firstByte != 0xAF)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
int last = buffer.length - 1;
|
||||||
|
|
||||||
|
while (buffer[last] == 0)
|
||||||
|
last--;
|
||||||
|
|
||||||
|
if (buffer[last] != (byte) 0x8D)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
564
src/com/bytezone/diskbrowser/applefile/VisicalcSpreadsheet.java
Normal file
564
src/com/bytezone/diskbrowser/applefile/VisicalcSpreadsheet.java
Normal file
|
@ -0,0 +1,564 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import java.security.InvalidParameterException;
|
||||||
|
import java.text.DecimalFormat;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
import com.bytezone.diskbrowser.applefile.VisicalcSpreadsheet.VisicalcCell;
|
||||||
|
|
||||||
|
public class VisicalcSpreadsheet implements Iterable<VisicalcCell>
|
||||||
|
{
|
||||||
|
private static final Pattern addressPattern = Pattern.compile ("([A-B]?[A-Z])([0-9]{1,3}):");
|
||||||
|
private static final Pattern cellContents = Pattern
|
||||||
|
.compile ("([-+/*]?)(([A-Z]{1,2}[0-9]{1,3})|([0-9.]+)|(@[^-+/*]+))");
|
||||||
|
private static final Pattern functionPattern = Pattern
|
||||||
|
.compile ("\\(([A-B]?[A-Z])([0-9]{1,3})\\.\\.\\.([A-B]?[A-Z])([0-9]{1,3})\\)");
|
||||||
|
|
||||||
|
private final Map<Integer, VisicalcCell> sheet = new TreeMap<Integer, VisicalcCell> ();
|
||||||
|
private final Map<String, Double> functions = new HashMap<String, Double> ();
|
||||||
|
|
||||||
|
final List<String> lines = new ArrayList<String> ();
|
||||||
|
VisicalcCell currentCell = null;
|
||||||
|
int columnWidth = 12;
|
||||||
|
char defaultFormat;
|
||||||
|
|
||||||
|
// Maximum cell = BK254
|
||||||
|
|
||||||
|
//Commands:
|
||||||
|
// /G<address> goto
|
||||||
|
// /B blank the cell
|
||||||
|
// /C clear and home
|
||||||
|
// A-Z label (" to force a label)
|
||||||
|
// 0-9.+-()*/@# value (+ to force a value)
|
||||||
|
|
||||||
|
// /S Storage (ISLDWR)
|
||||||
|
// /SI Storage init
|
||||||
|
// /SS Storage Save
|
||||||
|
// /SL Storage Load
|
||||||
|
// /SD Storage Delete
|
||||||
|
// /SW Storage Write (to cassette)
|
||||||
|
// /SR Storage Read (from cassette)
|
||||||
|
|
||||||
|
// /R Replicate
|
||||||
|
|
||||||
|
// /G Global (CORF)
|
||||||
|
// /GF Global Format (DGILR$*)
|
||||||
|
// /GFI Global Format Integer
|
||||||
|
// /GF$ Global Format Currency
|
||||||
|
// /GC Global Column <width>
|
||||||
|
// /GR Global
|
||||||
|
// /GO Global
|
||||||
|
|
||||||
|
// /T Titles (HVBN)
|
||||||
|
// /TH fix Horizontal Titles
|
||||||
|
// /TV fix Vertical Titles
|
||||||
|
// /TB fix Both Titles
|
||||||
|
// /TN fix Neither
|
||||||
|
|
||||||
|
// /W Window (HV1SU)
|
||||||
|
// /WV Window Vertical (split on cursor column)
|
||||||
|
// /WH Window Horizontal (split on cursor row)
|
||||||
|
|
||||||
|
public VisicalcSpreadsheet (byte[] buffer)
|
||||||
|
{
|
||||||
|
int ptr = 0;
|
||||||
|
int last = buffer.length - 1;
|
||||||
|
|
||||||
|
while (buffer[last] == 0)
|
||||||
|
last--;
|
||||||
|
|
||||||
|
while (ptr <= last)
|
||||||
|
{
|
||||||
|
int endPtr = findEndPtr (buffer, ptr);
|
||||||
|
add (HexFormatter.getString (buffer, ptr, endPtr - ptr));
|
||||||
|
ptr = endPtr + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false)
|
||||||
|
for (VisicalcCell cell : sheet.values ())
|
||||||
|
System.out.println (cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add (String command)
|
||||||
|
{
|
||||||
|
lines.add (command);
|
||||||
|
String data;
|
||||||
|
|
||||||
|
if (command.startsWith (">")) // GOTO cell
|
||||||
|
{
|
||||||
|
int pos = command.indexOf (':'); // end of cell address
|
||||||
|
Matcher m = addressPattern.matcher (command);
|
||||||
|
if (m.find ())
|
||||||
|
{
|
||||||
|
Address address = new Address (m.group (1), m.group (2));
|
||||||
|
VisicalcCell cell = sheet.get (address.sortValue);
|
||||||
|
command = command.substring (pos + 1);
|
||||||
|
|
||||||
|
if (cell == null)
|
||||||
|
{
|
||||||
|
cell = new VisicalcCell (this, address);
|
||||||
|
sheet.put (cell.address.sortValue, cell);
|
||||||
|
currentCell = cell;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
System.out.println ("Found " + cell);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
System.out.printf ("Invalid cell address: %s%n", command);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (command.startsWith ("/")) // command
|
||||||
|
{
|
||||||
|
// System.out.printf ("Cmd: %s%n", command);
|
||||||
|
data = command.substring (1);
|
||||||
|
char subCommand = command.charAt (1);
|
||||||
|
switch (subCommand)
|
||||||
|
{
|
||||||
|
case 'W':
|
||||||
|
System.out.println (" Window command: " + data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'G':
|
||||||
|
System.out.println (" Global command: " + data);
|
||||||
|
if (data.charAt (1) == 'C')
|
||||||
|
columnWidth = Integer.parseInt (data.substring (2));
|
||||||
|
else if (data.charAt (1) == 'F')
|
||||||
|
defaultFormat = data.charAt (2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'T':
|
||||||
|
System.out.println (" Title command: " + data);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
currentCell.doCommand (command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (command.startsWith ("@"))
|
||||||
|
{
|
||||||
|
currentCell.doCommand (command);
|
||||||
|
}
|
||||||
|
else if (command.startsWith ("\""))
|
||||||
|
{
|
||||||
|
currentCell.doCommand (command);
|
||||||
|
}
|
||||||
|
else if (command.startsWith ("+"))
|
||||||
|
{
|
||||||
|
currentCell.doCommand (command);
|
||||||
|
}
|
||||||
|
else if (command.matches ("^[0-9.]+$")) // value
|
||||||
|
{
|
||||||
|
currentCell.doCommand (command);
|
||||||
|
}
|
||||||
|
else if (command.matches ("^[-A-Z]+$")) // label
|
||||||
|
{
|
||||||
|
currentCell.doCommand (command);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
currentCell.doCommand (command); // formula
|
||||||
|
}
|
||||||
|
|
||||||
|
private int findEndPtr (byte[] buffer, int ptr)
|
||||||
|
{
|
||||||
|
while (buffer[ptr] != (byte) 0x8D)
|
||||||
|
ptr++;
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double evaluateFunction (String function)
|
||||||
|
{
|
||||||
|
if (functions.containsKey (function))
|
||||||
|
return functions.get (function);
|
||||||
|
|
||||||
|
Range range = null;
|
||||||
|
Matcher m = functionPattern.matcher (function);
|
||||||
|
while (m.find ())
|
||||||
|
{
|
||||||
|
Address fromAddress = new Address (m.group (1), m.group (2));
|
||||||
|
Address toAddress = new Address (m.group (3), m.group (4));
|
||||||
|
range = new Range (fromAddress, toAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
double result = 0;
|
||||||
|
|
||||||
|
if (function.startsWith ("@SUM"))
|
||||||
|
{
|
||||||
|
for (Address address : range)
|
||||||
|
result += getValue (address);
|
||||||
|
}
|
||||||
|
else if (function.startsWith ("@COUNT"))
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
for (Address address : range)
|
||||||
|
{
|
||||||
|
VisicalcCell cell = getCell (address);
|
||||||
|
if (cell != null && cell.hasValue () && cell.value != 0.0)
|
||||||
|
++count;
|
||||||
|
}
|
||||||
|
result = count;
|
||||||
|
}
|
||||||
|
else if (function.startsWith ("@MIN"))
|
||||||
|
{
|
||||||
|
double min = Double.MAX_VALUE;
|
||||||
|
for (Address address : range)
|
||||||
|
if (min > getValue (address))
|
||||||
|
min = getValue (address);
|
||||||
|
result = min;
|
||||||
|
}
|
||||||
|
else if (function.startsWith ("@MAX"))
|
||||||
|
{
|
||||||
|
double max = Double.MIN_VALUE;
|
||||||
|
for (Address address : range)
|
||||||
|
if (max < getValue (address))
|
||||||
|
max = getValue (address);
|
||||||
|
result = max;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
System.out.println ("Unimplemented function: " + function);
|
||||||
|
// http://www.bricklin.com/history/refcard1.htm
|
||||||
|
// Functions:
|
||||||
|
// @AVERAGE
|
||||||
|
// @NPV
|
||||||
|
// @LOOKUP(v,range)
|
||||||
|
// @NA
|
||||||
|
// @ERROR
|
||||||
|
// @PI
|
||||||
|
// @ABS
|
||||||
|
// @INT
|
||||||
|
// @EXP
|
||||||
|
// @SQRT
|
||||||
|
// @LN
|
||||||
|
// @LOG10
|
||||||
|
// @SIN
|
||||||
|
// @ASIN
|
||||||
|
// @COS
|
||||||
|
// @ACOS
|
||||||
|
// @TAN
|
||||||
|
// @ATAN
|
||||||
|
|
||||||
|
functions.put (function, result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getValue (Address address)
|
||||||
|
{
|
||||||
|
VisicalcCell cell = sheet.get (address.sortValue);
|
||||||
|
return cell == null ? 0.0 : cell.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getValue (String cellName)
|
||||||
|
{
|
||||||
|
Address address = new Address (cellName);
|
||||||
|
return getValue (address);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VisicalcCell getCell (Address address)
|
||||||
|
{
|
||||||
|
return sheet.get (address.sortValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int size ()
|
||||||
|
{
|
||||||
|
return sheet.size ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<VisicalcCell> iterator ()
|
||||||
|
{
|
||||||
|
return sheet.values ().iterator ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCells ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
String format = String.format ("%%-%d.%ds", columnWidth, columnWidth);
|
||||||
|
String currencyFormat = String.format ("%%%d.%ds", columnWidth, columnWidth);
|
||||||
|
String integerFormat = String.format ("%%%d.0f", columnWidth);
|
||||||
|
String numberFormat = String.format ("%%%d.3f", columnWidth);
|
||||||
|
|
||||||
|
DecimalFormat nf = new DecimalFormat ("$#####0.00");
|
||||||
|
// NumberFormat nf = NumberFormat.getCurrencyInstance ();
|
||||||
|
int lastRow = 0;
|
||||||
|
int lastColumn = -1;
|
||||||
|
|
||||||
|
for (VisicalcCell cell : sheet.values ())
|
||||||
|
{
|
||||||
|
while (lastRow < cell.address.row)
|
||||||
|
{
|
||||||
|
text.append ("\n");
|
||||||
|
++lastRow;
|
||||||
|
lastColumn = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (lastColumn < cell.address.column - 1)
|
||||||
|
{
|
||||||
|
text.append (" ".substring (0, columnWidth));
|
||||||
|
++lastColumn;
|
||||||
|
}
|
||||||
|
lastColumn = cell.address.column;
|
||||||
|
|
||||||
|
if (cell.hasValue ())
|
||||||
|
{
|
||||||
|
if (defaultFormat == 'I')
|
||||||
|
text.append (String.format (integerFormat, cell.getValue ()));
|
||||||
|
else if (defaultFormat == '$')
|
||||||
|
text.append (String.format (currencyFormat, nf.format (cell.getValue ())));
|
||||||
|
else
|
||||||
|
text.append (String.format (numberFormat, cell.getValue ()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
text.append (String.format (format, cell.value ()));
|
||||||
|
}
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
class VisicalcCell implements Comparable<VisicalcCell>
|
||||||
|
{
|
||||||
|
private final Address address;
|
||||||
|
private final VisicalcSpreadsheet parent;
|
||||||
|
|
||||||
|
private String label;
|
||||||
|
private double value;
|
||||||
|
private String formula;
|
||||||
|
private char format;
|
||||||
|
private int width;
|
||||||
|
private int columnWidth;
|
||||||
|
private char repeatingChar;
|
||||||
|
private String repeat = "";
|
||||||
|
private boolean valid;
|
||||||
|
|
||||||
|
public VisicalcCell (VisicalcSpreadsheet parent, Address address)
|
||||||
|
{
|
||||||
|
this.parent = parent;
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void doCommand (String command)
|
||||||
|
{
|
||||||
|
if (command.startsWith ("/"))
|
||||||
|
{
|
||||||
|
if (command.charAt (1) == 'F') // format cell
|
||||||
|
{
|
||||||
|
format = command.charAt (2);
|
||||||
|
if (command.length () > 3 && command.charAt (3) == '"')
|
||||||
|
label = command.substring (4);
|
||||||
|
}
|
||||||
|
else if (command.charAt (1) == '-') // repeating label
|
||||||
|
{
|
||||||
|
repeatingChar = command.charAt (2);
|
||||||
|
for (int i = 0; i < 20; i++)
|
||||||
|
repeat += repeatingChar;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
System.out.println ("Unknown command: " + command);
|
||||||
|
}
|
||||||
|
else if (command.startsWith ("\"")) // starts with a quote
|
||||||
|
label = command.substring (1);
|
||||||
|
else if (command.matches ("^[0-9.]+$")) // contains only numbers or .
|
||||||
|
this.value = Float.parseFloat (command);
|
||||||
|
else
|
||||||
|
formula = command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasValue ()
|
||||||
|
{
|
||||||
|
return label == null && repeatingChar == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getValue ()
|
||||||
|
{
|
||||||
|
if (valid || formula == null)
|
||||||
|
return value;
|
||||||
|
|
||||||
|
double result = 0.0;
|
||||||
|
double interim;
|
||||||
|
|
||||||
|
Matcher m = cellContents.matcher (formula);
|
||||||
|
while (m.find ())
|
||||||
|
{
|
||||||
|
valid = true;
|
||||||
|
char operator = m.group (1).isEmpty () ? '+' : m.group (1).charAt (0);
|
||||||
|
|
||||||
|
if (m.group (3) != null) // address
|
||||||
|
interim = parent.getValue (m.group (3));
|
||||||
|
else if (m.group (4) != null) // constant
|
||||||
|
interim = Double.parseDouble (m.group (4));
|
||||||
|
else
|
||||||
|
interim = parent.evaluateFunction (m.group (5)); // function
|
||||||
|
|
||||||
|
if (operator == '+')
|
||||||
|
result += interim;
|
||||||
|
else if (operator == '-')
|
||||||
|
result -= interim;
|
||||||
|
else if (operator == '*')
|
||||||
|
result *= interim;
|
||||||
|
else if (operator == '/')
|
||||||
|
result = interim == 0.0 ? 0 : result / interim;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valid)
|
||||||
|
{
|
||||||
|
value = result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println ("?? " + formula);
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String value ()
|
||||||
|
{
|
||||||
|
if (label != null)
|
||||||
|
return label;
|
||||||
|
if (repeatingChar > 0)
|
||||||
|
return repeat;
|
||||||
|
if (formula != null)
|
||||||
|
if (formula.length () >= 12)
|
||||||
|
return formula.substring (0, 12);
|
||||||
|
else
|
||||||
|
return formula;
|
||||||
|
return value + "";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
String value =
|
||||||
|
repeatingChar == 0 ? label == null ? formula == null ? ", Value: " + this.value
|
||||||
|
: ", Frmla: " + formula : ", Label: " + label : ", Rpeat: " + repeatingChar;
|
||||||
|
String format = this.format == 0 ? "" : ", Format: " + this.format;
|
||||||
|
String width = this.width == 0 ? "" : ", Width: " + this.width;
|
||||||
|
String columnWidth = this.columnWidth == 0 ? "" : ", Col Width: " + this.columnWidth;
|
||||||
|
return String.format ("[Cell:%5s%s%s%s%s]", address, format, width, columnWidth, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo (VisicalcCell o)
|
||||||
|
{
|
||||||
|
return address.compareTo (o.address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Range implements Iterable<Address>
|
||||||
|
{
|
||||||
|
Address from, to;
|
||||||
|
List<Address> range = new ArrayList<Address> ();
|
||||||
|
|
||||||
|
public Range (Address from, Address to)
|
||||||
|
{
|
||||||
|
this.from = from;
|
||||||
|
this.to = to;
|
||||||
|
|
||||||
|
range.add (from);
|
||||||
|
|
||||||
|
if (from.row == to.row)
|
||||||
|
{
|
||||||
|
while (from.compareTo (to) < 0)
|
||||||
|
{
|
||||||
|
from = from.nextColumn ();
|
||||||
|
range.add (from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (from.column == to.column)
|
||||||
|
{
|
||||||
|
while (from.compareTo (to) < 0)
|
||||||
|
{
|
||||||
|
from = from.nextRow ();
|
||||||
|
range.add (from);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw new InvalidParameterException ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
return String.format (" %s -> %s", from.text, to.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<Address> iterator ()
|
||||||
|
{
|
||||||
|
return range.iterator ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Address implements Comparable<Address>
|
||||||
|
{
|
||||||
|
int row, column;
|
||||||
|
int sortValue;
|
||||||
|
String text;
|
||||||
|
|
||||||
|
public Address (String column, String row)
|
||||||
|
{
|
||||||
|
set (column, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Address (int column, int row)
|
||||||
|
{
|
||||||
|
assert column <= 64;
|
||||||
|
assert row <= 255;
|
||||||
|
this.row = row;
|
||||||
|
this.column = column;
|
||||||
|
sortValue = row * 64 + column;
|
||||||
|
|
||||||
|
int col1 = column / 26;
|
||||||
|
int col2 = column % 26;
|
||||||
|
String col =
|
||||||
|
col1 > 0 ? (char) ('@' + col1) + ('A' + col2) + "" : (char) ('A' + col2) + "";
|
||||||
|
text = col + (row + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Address (String address)
|
||||||
|
{
|
||||||
|
if (address.charAt (1) < 'A')
|
||||||
|
set (address.substring (0, 1), address.substring (1));
|
||||||
|
else
|
||||||
|
set (address.substring (0, 2), address.substring (2));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void set (String sCol, String sRow)
|
||||||
|
{
|
||||||
|
if (sCol.length () == 1)
|
||||||
|
column = sCol.charAt (0) - 'A';
|
||||||
|
else if (sCol.length () == 2)
|
||||||
|
column = (sCol.charAt (0) - '@') * 26 + sCol.charAt (1) - 'A';
|
||||||
|
else
|
||||||
|
System.out.println ("Bollocks");
|
||||||
|
|
||||||
|
row = Integer.parseInt (sRow) - 1;
|
||||||
|
sortValue = row * 64 + column;
|
||||||
|
text = sCol + sRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Address nextRow ()
|
||||||
|
{
|
||||||
|
Address next = new Address (column, row + 1);
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Address nextColumn ()
|
||||||
|
{
|
||||||
|
Address next = new Address (column + 1, row);
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
return String.format ("%s %d %d %d", text, row, column, sortValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo (Address o)
|
||||||
|
{
|
||||||
|
return sortValue - o.sortValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
51
src/com/bytezone/diskbrowser/applefile/WizardryTitle.java
Executable file
51
src/com/bytezone/diskbrowser/applefile/WizardryTitle.java
Executable file
|
@ -0,0 +1,51 @@
|
||||||
|
package com.bytezone.diskbrowser.applefile;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class WizardryTitle extends AbstractFile
|
||||||
|
{
|
||||||
|
public WizardryTitle (String name, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
int size = 20;
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
for (int i = 0; i < buffer.length; i += size)
|
||||||
|
{
|
||||||
|
for (int line = 0; line < size; line++)
|
||||||
|
{
|
||||||
|
int p = i + line;
|
||||||
|
if (p >= buffer.length)
|
||||||
|
break;
|
||||||
|
int value = HexFormatter.intValue (buffer[p]);
|
||||||
|
text = decode2 (value, text);
|
||||||
|
}
|
||||||
|
text.append ("\n");
|
||||||
|
}
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private StringBuilder decode (int value, StringBuilder text)
|
||||||
|
{
|
||||||
|
for (int bit = 0; bit < 8; bit++)
|
||||||
|
{
|
||||||
|
text.append ((value & 0x01) == 1 ? "X" : " ");
|
||||||
|
value >>= 1;
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private StringBuilder decode2 (int value, StringBuilder text)
|
||||||
|
{
|
||||||
|
for (int bit = 7; bit >= 0; bit--)
|
||||||
|
{
|
||||||
|
text.append ((value & 0x01) == 1 ? "X" : " ");
|
||||||
|
value >>= 1;
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
158
src/com/bytezone/diskbrowser/applefile/equates.txt
Normal file
158
src/com/bytezone/diskbrowser/applefile/equates.txt
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
* Zero page
|
||||||
|
|
||||||
|
0020 WNDLFT
|
||||||
|
0021 WNDWDTH
|
||||||
|
0022 WNDTOP
|
||||||
|
0023 WNDBTM
|
||||||
|
0024 CH
|
||||||
|
0025 CV
|
||||||
|
0026 GBAS-LO
|
||||||
|
0027 GBAS-HI
|
||||||
|
0028 BAS-LO
|
||||||
|
0029 BAS-HI
|
||||||
|
0032 INVFLG
|
||||||
|
0035 YSAV1
|
||||||
|
0036 CSWL
|
||||||
|
0037 CSHW
|
||||||
|
0044 A5L - volume number?
|
||||||
|
004E RND-LO
|
||||||
|
004F RND-HI
|
||||||
|
0050 LINNUM
|
||||||
|
0073 HIMEM
|
||||||
|
009D FAC
|
||||||
|
009E FAC mantissa hi order
|
||||||
|
009F FAC mantissa mid order hi
|
||||||
|
00B1 CHRGET
|
||||||
|
00B7 CHRGOT
|
||||||
|
00B8 TXTPTR
|
||||||
|
|
||||||
|
0200 Input buffer
|
||||||
|
|
||||||
|
03D0 Applesoft warm start
|
||||||
|
03EA VECT
|
||||||
|
A56E catalog routine
|
||||||
|
|
||||||
|
C000 KYBD - last key pressed
|
||||||
|
C010 STROBE - Clear KYBD
|
||||||
|
C050 TXTCLR - Display Graphics
|
||||||
|
C051 TXTSET - Display Text
|
||||||
|
C052 MIXCLR - Display Full Screen
|
||||||
|
C053 MIXSET - Display Split Screen
|
||||||
|
C054 TXTPAGE1 - Display Page 1
|
||||||
|
C055 TXTPAGE2 - If 80STORE Off: Display Page 2, If 80STORE On: Read/Write Aux Display Mem
|
||||||
|
C056 LORES - Display LoRes Graphics
|
||||||
|
C057 HIRES - Display HiRes Graphics
|
||||||
|
|
||||||
|
C080 Read RAM bank 2; no write
|
||||||
|
C081 ROMIN - Read ROM; write RAM bank 2
|
||||||
|
C082 Read ROM; no write
|
||||||
|
C083 LCBANK2 - Read/write RAM bank 2
|
||||||
|
C084 Read RAM bank 2; no write
|
||||||
|
C085 ROMIN - Read ROM; write RAM bank 2
|
||||||
|
C086 Read ROM; no write
|
||||||
|
C087 LCBANK2 - Read/write RAM bank 2
|
||||||
|
C088 Read RAM bank 1; no write
|
||||||
|
C089 Read ROM; write RAM bank 1
|
||||||
|
C08A Read ROM; no write
|
||||||
|
C08B Read/write RAM bank 1
|
||||||
|
C08C Read RAM bank 1; no write
|
||||||
|
C08D Read ROM; write RAM bank 1
|
||||||
|
C08E Read ROM; no write
|
||||||
|
C08F Read/write RAM bank 1
|
||||||
|
|
||||||
|
D52C INLIN numeric input
|
||||||
|
DB3A STROUT - output a string
|
||||||
|
DB5C output a character
|
||||||
|
DD67 FRMNUM
|
||||||
|
DD7B FRMEVAL
|
||||||
|
DEBE CHKCOM
|
||||||
|
DEC0 SYNCHR
|
||||||
|
DEC9 syntax error
|
||||||
|
DFE3 PTRGET
|
||||||
|
|
||||||
|
E053 find a variable
|
||||||
|
E10C convert FP to INT
|
||||||
|
E2F2 convert ACC to FP
|
||||||
|
E301 SNGFLT
|
||||||
|
E3E7 FPSTR2
|
||||||
|
E6F8 GETBYTE
|
||||||
|
E74C COMBYTE
|
||||||
|
E752 GETADR - get from FAC to LINNUM
|
||||||
|
E7A7 FSUB
|
||||||
|
E7BE FADD
|
||||||
|
E8D5 OVERFLOW
|
||||||
|
E913 ONE
|
||||||
|
E941 FLOG
|
||||||
|
E97F FMULT
|
||||||
|
E9E3 CONUPK
|
||||||
|
EA39 MUL10
|
||||||
|
EA66 FDIV
|
||||||
|
EAE1 DIVERR
|
||||||
|
EAF9 MOVEFM - move (A,Y) to FAC
|
||||||
|
EB2B MOVEMF
|
||||||
|
EB93 FLOAT
|
||||||
|
EBA0 FLOAT1 - integer to FAC ($9D-$A2)
|
||||||
|
EBB2 FCOMP
|
||||||
|
EBF2 QINT
|
||||||
|
EC23 FINT
|
||||||
|
EC4A FIN
|
||||||
|
ED24 LINPRNT - print a decimal number
|
||||||
|
ED2E PRNTFAC
|
||||||
|
ED34 FOUT - FAC to FBUFFR ($100-$110)
|
||||||
|
EE8D SQR
|
||||||
|
EE97 FPWRT
|
||||||
|
EED0 NEGOP
|
||||||
|
EF09 FEXP
|
||||||
|
EFAE RND
|
||||||
|
EFEA FCOS
|
||||||
|
EFF1 FSIN
|
||||||
|
|
||||||
|
F03A FTAN
|
||||||
|
F066 PIHALF
|
||||||
|
F09E FATN
|
||||||
|
F411 map x,y location on hi-res 1 ??
|
||||||
|
F467 LEFT EQU
|
||||||
|
F48A RIGHT EQU
|
||||||
|
F4D5 UP EQU
|
||||||
|
F504 DOWN EQU
|
||||||
|
F6B9 HFNS
|
||||||
|
F940 PRINTYX
|
||||||
|
F941 PRINTAX - print a hex number
|
||||||
|
FAA6 reboot DOS
|
||||||
|
FAFF 0 = Autostart ROM, 1 = Old Monitor
|
||||||
|
FB1E PREAD - read game paddle
|
||||||
|
FB2F initialise text screen
|
||||||
|
FB39 text mode - SETTXT
|
||||||
|
FB5B TABV - monitor tab routine
|
||||||
|
FC66 CURSDWN - move cursor down
|
||||||
|
FBF4 CURSRIT - move cursor right
|
||||||
|
FC10 CURSLFT - move cursor left
|
||||||
|
FC1A CURSUP - move cursor up
|
||||||
|
FB6F set powerup checksum
|
||||||
|
FBC1 BASCALC - calculate video address
|
||||||
|
FBDD BEEP
|
||||||
|
FC22 VTAB
|
||||||
|
FC42 CLREOP - clear to end of page
|
||||||
|
FC58 HOME - clear screen
|
||||||
|
FC62 CR
|
||||||
|
FC9C CLREOL
|
||||||
|
FCA8 WAIT 1/2(26+27A+5A^2) microseconds
|
||||||
|
FD0C RDKEY - Blink cursor
|
||||||
|
FD1B KEYIN - Increment RNDL,H while polling keyboard
|
||||||
|
FD35 RDCHAR - Call RDKEY
|
||||||
|
FD6A GETLN
|
||||||
|
FD75 NXTCHAR
|
||||||
|
FD8B CROUT1 - generate a return with clear
|
||||||
|
FDDA PRBYTE - print A in hex
|
||||||
|
FDED COUT - output a character
|
||||||
|
FDF0 COUT1 - output a character to screen
|
||||||
|
FD8E CROUT - generate a return
|
||||||
|
FE2C move a block of memory
|
||||||
|
FE89 disconnect DOS from I/O links
|
||||||
|
FE93 disconnect DOS from I/O links
|
||||||
|
FF3A BELL
|
||||||
|
FF3F SAVE
|
||||||
|
FF4A RESTORE
|
||||||
|
FF59 Monitor cold entry point
|
||||||
|
FFA7 GETNUM - move num to A2L.A2H
|
||||||
|
FFC7 ZMODE - monitor get ASCII return
|
185
src/com/bytezone/diskbrowser/appleworks/AppleworksADBFile.java
Normal file
185
src/com/bytezone/diskbrowser/appleworks/AppleworksADBFile.java
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
package com.bytezone.diskbrowser.appleworks;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
import com.bytezone.diskbrowser.applefile.AbstractFile;
|
||||||
|
|
||||||
|
public class AppleworksADBFile extends AbstractFile
|
||||||
|
{
|
||||||
|
static final String line = "-------------------------------------------------------"
|
||||||
|
+ "-----------------------------------\n";
|
||||||
|
|
||||||
|
private final int headerSize;
|
||||||
|
private final int cursorDirectionSRL;
|
||||||
|
private final char cursorDirectionMRL;
|
||||||
|
private final char currentDisplay;
|
||||||
|
final int categories;
|
||||||
|
private final int totalReports;
|
||||||
|
private final int totalRecords;
|
||||||
|
private final int dbMinVersion;
|
||||||
|
final String[] categoryNames;
|
||||||
|
int maxCategoryName;
|
||||||
|
|
||||||
|
private final int[] columnWidthsMRL = new int[30];
|
||||||
|
private final int[] columnCategoryMRL = new int[30];
|
||||||
|
private final int[] rowPositionSRL = new int[30];
|
||||||
|
private final int[] columnPositionSRL = new int[30];
|
||||||
|
private final int[] categorySRL = new int[30];
|
||||||
|
|
||||||
|
private final int firstFrozenColumn;
|
||||||
|
private final int lastFrozenColumn;
|
||||||
|
private final int leftmostActiveColumn;
|
||||||
|
private final int totalCategoriesMRL;
|
||||||
|
|
||||||
|
private final int[] selectionRules = new int[3];
|
||||||
|
private final int[] testTypes = new int[3];
|
||||||
|
private final int[] continuation = new int[3];
|
||||||
|
private final String[] comparison = new String[3];
|
||||||
|
|
||||||
|
private final List<Report> reports = new ArrayList<Report> ();
|
||||||
|
final List<Record> records = new ArrayList<Record> ();
|
||||||
|
private final Record standardRecord;
|
||||||
|
|
||||||
|
public AppleworksADBFile (String name, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
|
||||||
|
dbMinVersion = buffer[218] & 0xFF;
|
||||||
|
|
||||||
|
headerSize = HexFormatter.getWord (buffer, 0);
|
||||||
|
cursorDirectionSRL = buffer[30];
|
||||||
|
cursorDirectionMRL = (char) buffer[31];
|
||||||
|
currentDisplay = (char) buffer[34];
|
||||||
|
categories = buffer[35] & 0xFF;
|
||||||
|
categoryNames = new String[categories];
|
||||||
|
|
||||||
|
totalReports = buffer[38] & 0xFF;
|
||||||
|
int recs = HexFormatter.getWord (buffer, 36);
|
||||||
|
totalRecords = dbMinVersion == 0 ? recs : recs & 0x7FFF;
|
||||||
|
|
||||||
|
for (int i = 0; i < 30; i++)
|
||||||
|
{
|
||||||
|
columnWidthsMRL[i] = buffer[42 + i] & 0xFF;
|
||||||
|
columnCategoryMRL[i] = buffer[78 + i] & 0xFF;
|
||||||
|
columnPositionSRL[i] = buffer[114 + i] & 0xFF;
|
||||||
|
rowPositionSRL[i] = buffer[150 + i] & 0xFF;
|
||||||
|
categorySRL[i] = buffer[186 + i] & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
firstFrozenColumn = buffer[219] & 0xFF;
|
||||||
|
lastFrozenColumn = buffer[220] & 0xFF;
|
||||||
|
leftmostActiveColumn = buffer[221] & 0xFF;
|
||||||
|
totalCategoriesMRL = buffer[222] & 0xFF;
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
selectionRules[i] = HexFormatter.getWord (buffer, 223 + i * 2);
|
||||||
|
testTypes[i] = HexFormatter.getWord (buffer, 229 + i * 2);
|
||||||
|
continuation[i] = HexFormatter.getWord (buffer, 235 + i * 2);
|
||||||
|
comparison[i] = new String (buffer, 241 + i * 20, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ptr = 357;
|
||||||
|
for (int i = 0; i < categoryNames.length; i++)
|
||||||
|
{
|
||||||
|
categoryNames[i] = new String (buffer, ptr + 1, buffer[ptr] & 0xFF);
|
||||||
|
if (categoryNames[i].length () > maxCategoryName)
|
||||||
|
maxCategoryName = categoryNames[i].length ();
|
||||||
|
ptr += 22;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int reportNo = 0; reportNo < totalReports; reportNo++)
|
||||||
|
{
|
||||||
|
int reportFormat = (char) buffer[ptr + 214];
|
||||||
|
if (reportFormat == 'H')
|
||||||
|
reports.add (new TableReport (this, buffer, ptr));
|
||||||
|
else if (reportFormat == 'V')
|
||||||
|
reports.add (new LabelReport (this, buffer, ptr));
|
||||||
|
else
|
||||||
|
System.out.println ("Bollocks - report format not H or V : " + reportFormat);
|
||||||
|
ptr += 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
int length = HexFormatter.getWord (buffer, ptr);
|
||||||
|
ptr += 2;
|
||||||
|
|
||||||
|
if (length == 0)
|
||||||
|
standardRecord = null;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
standardRecord = new Record (this, buffer, ptr);
|
||||||
|
ptr += length;
|
||||||
|
|
||||||
|
for (int recordNo = 0; recordNo < totalRecords; recordNo++)
|
||||||
|
{
|
||||||
|
length = HexFormatter.getWord (buffer, ptr);
|
||||||
|
ptr += 2;
|
||||||
|
if (length == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
records.add (new Record (this, buffer, ptr));
|
||||||
|
ptr += length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
text.append (String.format ("Header size ........ %d%n", headerSize));
|
||||||
|
text.append (String
|
||||||
|
.format ("SRL cursor ......... %d (1=default, 2=left->right, top->bottom)%n",
|
||||||
|
cursorDirectionSRL));
|
||||||
|
text.append (String.format ("MRL cursor ......... %s (D=down, R=right)%n",
|
||||||
|
cursorDirectionMRL));
|
||||||
|
text.append (String.format ("Display ............ %s (R=SRL, /=MRL)%n", currentDisplay));
|
||||||
|
text.append (String.format ("Categories ......... %d%n", categories));
|
||||||
|
text.append (String.format ("Reports ............ %d%n", totalReports));
|
||||||
|
text.append (String.format ("Records ............ %d%n", totalRecords));
|
||||||
|
text.append (String.format ("Min version ........ %d%n", dbMinVersion));
|
||||||
|
text.append (String.format ("1st Frozen col ..... %d%n", firstFrozenColumn));
|
||||||
|
text.append (String.format ("Last Frozen col .... %d%n", lastFrozenColumn));
|
||||||
|
text.append (String.format ("Left active col .... %d%n", leftmostActiveColumn));
|
||||||
|
text.append (String.format ("MRL categories ..... %d%n", totalCategoriesMRL));
|
||||||
|
|
||||||
|
text.append ("\n Categories:\n");
|
||||||
|
for (int i = 0; i < categories; i++)
|
||||||
|
text.append (String.format (" %2d %-30s %n", (i + 1), categoryNames[i]));
|
||||||
|
text.append ("\n");
|
||||||
|
|
||||||
|
for (Report report : reports)
|
||||||
|
{
|
||||||
|
text.append (report);
|
||||||
|
text.append ("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Report report : reports)
|
||||||
|
{
|
||||||
|
text.append (report.getText ());
|
||||||
|
text.append ("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (reports.size () == 0)
|
||||||
|
{
|
||||||
|
text.append (line);
|
||||||
|
for (Record record : records)
|
||||||
|
{
|
||||||
|
text.append (record.getReportLine () + "\n");
|
||||||
|
text.append (line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
removeTrailing (text, '\n');
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeTrailing (StringBuilder text, char c)
|
||||||
|
{
|
||||||
|
while (text.charAt (text.length () - 1) == c)
|
||||||
|
text.deleteCharAt (text.length () - 1);
|
||||||
|
}
|
||||||
|
}
|
308
src/com/bytezone/diskbrowser/appleworks/AppleworksSSFile.java
Normal file
308
src/com/bytezone/diskbrowser/appleworks/AppleworksSSFile.java
Normal file
|
@ -0,0 +1,308 @@
|
||||||
|
package com.bytezone.diskbrowser.appleworks;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
import com.bytezone.diskbrowser.applefile.AbstractFile;
|
||||||
|
|
||||||
|
public class AppleworksSSFile extends AbstractFile
|
||||||
|
{
|
||||||
|
Header header;
|
||||||
|
List<Row> rows = new ArrayList<Row> ();
|
||||||
|
|
||||||
|
public AppleworksSSFile (String name, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
|
||||||
|
header = new Header ();
|
||||||
|
|
||||||
|
int ptr = header.ssMinVers == 0 ? 300 : 302;
|
||||||
|
while (ptr < buffer.length)
|
||||||
|
{
|
||||||
|
int length = HexFormatter.getWord (buffer, ptr);
|
||||||
|
|
||||||
|
if (length == 0xFFFF)
|
||||||
|
break;
|
||||||
|
|
||||||
|
ptr += 2;
|
||||||
|
Row row = new Row (ptr);
|
||||||
|
rows.add (row);
|
||||||
|
ptr += length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder (header.toString ());
|
||||||
|
|
||||||
|
for (Row row : rows)
|
||||||
|
{
|
||||||
|
text.append ("\n");
|
||||||
|
for (Cell cell : row.cells)
|
||||||
|
text.append (cell);
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getCellName (int row, int column)
|
||||||
|
{
|
||||||
|
char c1 = (char) ('A' + column / 26 - 1);
|
||||||
|
char c2 = (char) ('A' + column % 26);
|
||||||
|
return "" + (c1 == '@' ? "" : c1) + c2 + row;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Header
|
||||||
|
{
|
||||||
|
private final int[] columnWidths = new int[127];
|
||||||
|
private final char calcOrder;
|
||||||
|
private final char calcFrequency;
|
||||||
|
private final int lastRow;
|
||||||
|
private final int lastColumn;
|
||||||
|
private final char windowLayout;
|
||||||
|
private final boolean windowSynch;
|
||||||
|
private final Window currentWindow;
|
||||||
|
private final Window secondWindow;
|
||||||
|
private final boolean cellProtection;
|
||||||
|
private final int platenWidth;
|
||||||
|
private final int leftMargin;
|
||||||
|
private final int rightMargin;
|
||||||
|
private final int charsPerInch;
|
||||||
|
|
||||||
|
private final int paperLength;
|
||||||
|
private final int topMargin;
|
||||||
|
private final int bottomMargin;
|
||||||
|
private final int linesPerInch;
|
||||||
|
private final char spacing;
|
||||||
|
|
||||||
|
private final byte[] printerCodes = new byte[14];
|
||||||
|
private final boolean printDash;
|
||||||
|
private final boolean printHeader;
|
||||||
|
private final boolean zoomed;
|
||||||
|
|
||||||
|
private final int ssMinVers;
|
||||||
|
|
||||||
|
public Header ()
|
||||||
|
{
|
||||||
|
int ptr = 4;
|
||||||
|
for (int i = 0; i < columnWidths.length; i++)
|
||||||
|
columnWidths[i] = buffer[ptr++] & 0xFF;
|
||||||
|
|
||||||
|
calcOrder = (char) buffer[131];
|
||||||
|
calcFrequency = (char) buffer[132];
|
||||||
|
lastRow = HexFormatter.getWord (buffer, 133);
|
||||||
|
lastColumn = buffer[135] & 0xFF;
|
||||||
|
windowLayout = (char) buffer[136];
|
||||||
|
windowSynch = buffer[137] != 0;
|
||||||
|
|
||||||
|
currentWindow = new Window (138);
|
||||||
|
secondWindow = new Window (162);
|
||||||
|
|
||||||
|
cellProtection = buffer[213] != 0;
|
||||||
|
platenWidth = buffer[215] & 0xFF;
|
||||||
|
leftMargin = buffer[216] & 0xFF;
|
||||||
|
rightMargin = buffer[217] & 0xFF;
|
||||||
|
charsPerInch = buffer[218] & 0xFF;
|
||||||
|
|
||||||
|
paperLength = buffer[219] & 0xFF;
|
||||||
|
topMargin = buffer[220] & 0xFF;
|
||||||
|
bottomMargin = buffer[221] & 0xFF;
|
||||||
|
linesPerInch = buffer[222] & 0xFF;
|
||||||
|
spacing = (char) buffer[223];
|
||||||
|
|
||||||
|
System.arraycopy (buffer, 224, printerCodes, 0, printerCodes.length);
|
||||||
|
|
||||||
|
printDash = buffer[238] != 0;
|
||||||
|
printHeader = buffer[239] != 0;
|
||||||
|
zoomed = buffer[240] != 0;
|
||||||
|
|
||||||
|
ssMinVers = buffer[242];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
text.append (String.format ("Calc order ..... %s %n", calcOrder));
|
||||||
|
text.append (String.format ("Calc freq ...... %s %n", calcFrequency));
|
||||||
|
text.append (String.format ("Last row ....... %d %n", lastRow));
|
||||||
|
text.append (String.format ("Last column .... %d %n", lastColumn));
|
||||||
|
text.append (String.format ("Window layout .. %s %n", windowLayout));
|
||||||
|
text.append (String.format ("Window synch ... %s %n", windowSynch));
|
||||||
|
text.append (String.format ("Min version .... %s %n%n", ssMinVers));
|
||||||
|
|
||||||
|
String[] s1 = currentWindow.toString ().split ("\n");
|
||||||
|
String[] s2 = secondWindow.toString ().split ("\n");
|
||||||
|
for (int i = 0; i < s1.length; i++)
|
||||||
|
text.append (String.format ("%-30s %-30s%n", s1[i], s2[i]));
|
||||||
|
text.append ("\n");
|
||||||
|
|
||||||
|
text.append (String.format ("Cell protect ... %s %n", cellProtection));
|
||||||
|
text.append (String.format ("Platen width ... %d %n", platenWidth));
|
||||||
|
text.append (String.format ("Left margin .... %d %n", leftMargin));
|
||||||
|
text.append (String.format ("Right margin ... %d %n", rightMargin));
|
||||||
|
text.append (String.format ("Chars per inch . %d %n", charsPerInch));
|
||||||
|
|
||||||
|
text.append (String.format ("Paper length ... %d %n", paperLength));
|
||||||
|
text.append (String.format ("Top margin ..... %d %n", topMargin));
|
||||||
|
text.append (String.format ("Bottom margin .. %d %n", bottomMargin));
|
||||||
|
text.append (String.format ("Lines per inch . %d %n", linesPerInch));
|
||||||
|
text.append (String.format ("Spacing ........ %s %n", spacing));
|
||||||
|
|
||||||
|
String prC = HexFormatter.getHexString (printerCodes);
|
||||||
|
text.append (String.format ("Printer codes .. %s %n", prC));
|
||||||
|
text.append (String.format ("Print dash ..... %s %n", printDash));
|
||||||
|
text.append (String.format ("Print header ... %s %n", printHeader));
|
||||||
|
text.append (String.format ("Zoomed ......... %s %n", zoomed));
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Window
|
||||||
|
{
|
||||||
|
private final int justification;
|
||||||
|
private final CellFormat format;
|
||||||
|
private final int r1;
|
||||||
|
private final int c1;
|
||||||
|
private final int r2;
|
||||||
|
private final int c2;
|
||||||
|
private final int r3;
|
||||||
|
private final int c3;
|
||||||
|
private final int r4;
|
||||||
|
private final int c4;
|
||||||
|
private final int r5;
|
||||||
|
private final int c5;
|
||||||
|
private final int r6;
|
||||||
|
private final int c6;
|
||||||
|
private final int r7;
|
||||||
|
private final int c7;
|
||||||
|
private final int bodyRows;
|
||||||
|
private final boolean rightColumnNotDisplayed;
|
||||||
|
private final boolean topTitleSwitch;
|
||||||
|
private final boolean sideTitleSwitch;
|
||||||
|
|
||||||
|
public Window (int offset)
|
||||||
|
{
|
||||||
|
justification = buffer[offset] & 0xFF;
|
||||||
|
|
||||||
|
format = new CellFormat (buffer[offset + 1], buffer[offset + 2]);
|
||||||
|
|
||||||
|
r1 = buffer[offset + 3] & 0xFF;
|
||||||
|
c1 = buffer[offset + 4] & 0xFF;
|
||||||
|
r2 = HexFormatter.getWord (buffer, offset + 5);
|
||||||
|
c2 = buffer[offset + 7] & 0xFF;
|
||||||
|
r3 = HexFormatter.getWord (buffer, offset + 8);
|
||||||
|
c3 = buffer[offset + 10] & 0xFF;
|
||||||
|
r4 = HexFormatter.getWord (buffer, offset + 11);
|
||||||
|
c4 = buffer[offset + 13] & 0xFF;
|
||||||
|
r5 = buffer[offset + 14] & 0xFF;
|
||||||
|
c5 = buffer[offset + 15] & 0xFF;
|
||||||
|
r6 = HexFormatter.getWord (buffer, offset + 16);
|
||||||
|
c6 = buffer[offset + 18] & 0xFF;
|
||||||
|
r7 = buffer[offset + 19] & 0xFF;
|
||||||
|
c7 = buffer[offset + 20] & 0xFF;
|
||||||
|
|
||||||
|
bodyRows = buffer[offset + 21] & 0xFF;
|
||||||
|
rightColumnNotDisplayed = buffer[offset + 21] != 0;
|
||||||
|
|
||||||
|
int flags = buffer[offset + 23] & 0xFF;
|
||||||
|
topTitleSwitch = (flags & 0x80) != 0;
|
||||||
|
sideTitleSwitch = (flags & 0x40) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
text.append (String.format ("Justification .. %s %n", justification));
|
||||||
|
text.append (String.format ("Format ......... %s %n", format.mask ()));
|
||||||
|
text.append (String.format ("Decimals ....... %s %n", format.decimals));
|
||||||
|
text.append (String.format ("Top line ....... %d %n", r1));
|
||||||
|
text.append (String.format ("Left column .... %d %n", c1));
|
||||||
|
text.append (String.format ("Title top line . %d %n", r2));
|
||||||
|
text.append (String.format ("Title left col . %d %n", c2));
|
||||||
|
text.append (String.format ("Top line ....... %d %n", r3));
|
||||||
|
text.append (String.format ("Left column .... %d %n", c3));
|
||||||
|
text.append (String.format ("Title top line . %d %n", r4));
|
||||||
|
text.append (String.format ("Title left col . %d %n", c4));
|
||||||
|
text.append (String.format ("Title top line . %d %n", r5));
|
||||||
|
text.append (String.format ("Title left col . %d %n", c5));
|
||||||
|
text.append (String.format ("Top line ....... %d %n", r6));
|
||||||
|
text.append (String.format ("Left column .... %d %n", c6));
|
||||||
|
text.append (String.format ("Title top line . %d %n", r7));
|
||||||
|
text.append (String.format ("Title left col . %d %n", c7));
|
||||||
|
text.append (String.format ("Body rows ...... %d %n", bodyRows));
|
||||||
|
text.append (String.format ("Right col hidden %s %n", rightColumnNotDisplayed));
|
||||||
|
text.append (String.format ("Top title sw ... %s %n", topTitleSwitch));
|
||||||
|
text.append (String.format ("Left title sw .. %s %n", sideTitleSwitch));
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Row
|
||||||
|
{
|
||||||
|
private final int rowNumber;
|
||||||
|
private final List<Cell> cells = new ArrayList<Cell> ();
|
||||||
|
|
||||||
|
public Row (int ptr)
|
||||||
|
{
|
||||||
|
rowNumber = HexFormatter.getWord (buffer, ptr);
|
||||||
|
ptr += 2; // first control byte
|
||||||
|
|
||||||
|
int column = 0;
|
||||||
|
int val;
|
||||||
|
|
||||||
|
while ((val = buffer[ptr++] & 0xFF) != 0xFF)
|
||||||
|
{
|
||||||
|
if (val > 0x80)
|
||||||
|
{
|
||||||
|
column += (val - 0x80); // skip columns
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ptr >= buffer.length)
|
||||||
|
{
|
||||||
|
System.out.println ("too long for buffer");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int b1 = buffer[ptr] & 0xFF;
|
||||||
|
|
||||||
|
if ((b1 & 0xE0) == 0 || (b1 & 0xA0) == 0x20) // Label - 000 or 0.1
|
||||||
|
cells.add (new CellLabel (buffer, rowNumber, column++, ptr, val));
|
||||||
|
else if ((b1 & 0xA0) == 0xA0) // Constant - 1.1
|
||||||
|
{
|
||||||
|
if (val > 0)
|
||||||
|
cells.add (new CellConstant (buffer, rowNumber, column++, ptr, val));
|
||||||
|
}
|
||||||
|
else if ((b1 & 0xA0) == 0x80) // Value - 1.0
|
||||||
|
cells.add (new CellValue (buffer, rowNumber, column++, ptr, val));
|
||||||
|
else
|
||||||
|
System.out.println ("Unknown Cell value : " + val);
|
||||||
|
|
||||||
|
ptr += val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
text.append (String.format ("Row number ..... %s %n", rowNumber));
|
||||||
|
for (Cell cell : cells)
|
||||||
|
{
|
||||||
|
text.append (cell);
|
||||||
|
text.append ("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
187
src/com/bytezone/diskbrowser/appleworks/AppleworksWPFile.java
Executable file
187
src/com/bytezone/diskbrowser/appleworks/AppleworksWPFile.java
Executable file
|
@ -0,0 +1,187 @@
|
||||||
|
package com.bytezone.diskbrowser.appleworks;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.applefile.AbstractFile;
|
||||||
|
|
||||||
|
public class AppleworksWPFile extends AbstractFile
|
||||||
|
{
|
||||||
|
Header header;
|
||||||
|
|
||||||
|
public AppleworksWPFile (String name, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (name, buffer);
|
||||||
|
|
||||||
|
header = new Header ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
int leftMargin = header.leftMargin;
|
||||||
|
int rightMargin;
|
||||||
|
int topMargin;
|
||||||
|
int bottomMargin;
|
||||||
|
int paperLength;
|
||||||
|
int indent;
|
||||||
|
|
||||||
|
int ptr = 300; // skip the header
|
||||||
|
StringBuilder text = new StringBuilder (header.toString ());
|
||||||
|
text.append ("\n");
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int b1 = buffer[ptr] & 0xFF;
|
||||||
|
int b2 = buffer[ptr + 1] & 0xFF;
|
||||||
|
// System.out.printf ("%02X%02X %n", (buffer[ptr] & 0xFF), (buffer[ptr + 1] & 0xFF));
|
||||||
|
|
||||||
|
if (b1 == 0xFF && b2 == 0xFF)
|
||||||
|
break;
|
||||||
|
|
||||||
|
switch (b2)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
int len = b1;
|
||||||
|
int b3 = buffer[ptr + 2] & 0xFF;
|
||||||
|
int b4 = buffer[ptr + 3] & 0xFF;
|
||||||
|
|
||||||
|
int lineMargin = b3 & 0x7F;
|
||||||
|
boolean containsTabs = (b3 & 0x80) != 0;
|
||||||
|
int textLen = b4 & 0x7F;
|
||||||
|
boolean cr = (b4 & 0x80) != 0;
|
||||||
|
|
||||||
|
// System.out.printf ("%02X %02X %d %d %s %s%n", b3, b4, margin, textLen,
|
||||||
|
// containsTabs, cr);
|
||||||
|
if (b3 == 0xFF)
|
||||||
|
text.append ("--------- Ruler ----------\n");
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// left margin
|
||||||
|
for (int i = 0; i < leftMargin; i++)
|
||||||
|
text.append (" ");
|
||||||
|
for (int i = 0; i < lineMargin; i++)
|
||||||
|
text.append (" ");
|
||||||
|
|
||||||
|
// check for tabs (I'm guessing about how this works)
|
||||||
|
if (false)
|
||||||
|
{
|
||||||
|
while (buffer[ptr + 4] == 0x16) // tab character
|
||||||
|
{
|
||||||
|
ptr++;
|
||||||
|
len--;
|
||||||
|
while (buffer[ptr + 4] == 0x17) // tab fill character
|
||||||
|
{
|
||||||
|
text.append (" ");
|
||||||
|
ptr++;
|
||||||
|
len--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text.append (new String (buffer, ptr + 4, len - 2));
|
||||||
|
ptr += len;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
StringBuilder line = new StringBuilder ();
|
||||||
|
int p = ptr + 4;
|
||||||
|
ptr += len;
|
||||||
|
len -= 2;
|
||||||
|
|
||||||
|
while (--len >= 0)
|
||||||
|
{
|
||||||
|
char c = (char) buffer[p++];
|
||||||
|
if (c >= 0x20)
|
||||||
|
line.append (c);
|
||||||
|
else if (c == 0x17)
|
||||||
|
line.append (' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
text.append (line.toString ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
text.append ("\n");
|
||||||
|
|
||||||
|
if (cr)
|
||||||
|
text.append ("\n");
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xD0:
|
||||||
|
text.append ("\n");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xD9:
|
||||||
|
leftMargin = b1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xDA:
|
||||||
|
rightMargin = b1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xDE:
|
||||||
|
indent = b1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xE2:
|
||||||
|
paperLength = b1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xE3:
|
||||||
|
topMargin = b1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 0xE4:
|
||||||
|
bottomMargin = b1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
System.out.printf ("Unknown value : %02X %02X%n", b1, b2);
|
||||||
|
}
|
||||||
|
ptr += 2;
|
||||||
|
}
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Header
|
||||||
|
{
|
||||||
|
private final char[] tabStops = new char[80];
|
||||||
|
private final String tabs;
|
||||||
|
private final boolean zoom;
|
||||||
|
private final boolean paginated;
|
||||||
|
private final int leftMargin;
|
||||||
|
private final boolean mailMerge;
|
||||||
|
private final int sfMinVers;
|
||||||
|
|
||||||
|
private final boolean multipleRulers;
|
||||||
|
|
||||||
|
public Header ()
|
||||||
|
{
|
||||||
|
assert buffer[4] == 0x4F;
|
||||||
|
|
||||||
|
int ptr = 5;
|
||||||
|
for (int i = 0; i < 80; i++)
|
||||||
|
tabStops[i] = (char) buffer[ptr++];
|
||||||
|
|
||||||
|
tabs = new String (tabStops);
|
||||||
|
zoom = buffer[85] != 0;
|
||||||
|
paginated = buffer[90] != 0;
|
||||||
|
leftMargin = buffer[91] & 0xFF;
|
||||||
|
mailMerge = buffer[92] != 0;
|
||||||
|
|
||||||
|
multipleRulers = buffer[176] != 0;
|
||||||
|
sfMinVers = buffer[183] & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
text.append (String.format ("Tabs ......... %s %n", tabs));
|
||||||
|
text.append (String.format ("Zoom ......... %s %n", zoom));
|
||||||
|
text.append (String.format ("Mail merge ... %s %n", mailMerge));
|
||||||
|
text.append (String.format ("Left margin .. %d %n", leftMargin));
|
||||||
|
text.append (String.format ("Min version .. %d %n", sfMinVers));
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
src/com/bytezone/diskbrowser/appleworks/Cell.java
Normal file
31
src/com/bytezone/diskbrowser/appleworks/Cell.java
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package com.bytezone.diskbrowser.appleworks;
|
||||||
|
|
||||||
|
class Cell
|
||||||
|
{
|
||||||
|
final String cellName;
|
||||||
|
final int row;
|
||||||
|
final int column;
|
||||||
|
String value;
|
||||||
|
String type;
|
||||||
|
|
||||||
|
static String getCellName (int row, int column)
|
||||||
|
{
|
||||||
|
char c1 = (char) ('A' + column / 26 - 1);
|
||||||
|
char c2 = (char) ('A' + column % 26);
|
||||||
|
return "" + (c1 == '@' ? "" : c1) + c2 + row;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cell (int row, int column, int offset, int length)
|
||||||
|
{
|
||||||
|
this.row = row;
|
||||||
|
this.column = column;
|
||||||
|
|
||||||
|
cellName = getCellName (row, column);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
return String.format ("%5s : %s %s%n", cellName, type, value);
|
||||||
|
}
|
||||||
|
}
|
21
src/com/bytezone/diskbrowser/appleworks/CellAddress.java
Normal file
21
src/com/bytezone/diskbrowser/appleworks/CellAddress.java
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package com.bytezone.diskbrowser.appleworks;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class CellAddress
|
||||||
|
{
|
||||||
|
int colRef;
|
||||||
|
int rowRef;
|
||||||
|
|
||||||
|
public CellAddress (byte[] buffer, int offset)
|
||||||
|
{
|
||||||
|
colRef = buffer[offset];
|
||||||
|
rowRef = HexFormatter.getSignedWord (buffer[offset + 1], buffer[offset + 2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
return String.format ("[Row=%04d, Col=%04d]", rowRef, colRef);
|
||||||
|
}
|
||||||
|
}
|
40
src/com/bytezone/diskbrowser/appleworks/CellConstant.java
Normal file
40
src/com/bytezone/diskbrowser/appleworks/CellConstant.java
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package com.bytezone.diskbrowser.appleworks;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class CellConstant extends Cell
|
||||||
|
{
|
||||||
|
double saneDouble;
|
||||||
|
CellFormat format;
|
||||||
|
|
||||||
|
public CellConstant (byte[] buffer, int row, int column, int offset, int length)
|
||||||
|
{
|
||||||
|
super (row, column, offset, length);
|
||||||
|
|
||||||
|
type = "Const";
|
||||||
|
|
||||||
|
// assert length == 10;
|
||||||
|
|
||||||
|
if (length != 10)
|
||||||
|
{
|
||||||
|
System.out.println ("Spreadsheet CellConstant with length != 10");
|
||||||
|
System.out.printf ("Row %d, Col %d, Length %d %n", row, column, length);
|
||||||
|
System.out.println (HexFormatter.format (buffer, offset, length));
|
||||||
|
type = "*** Invalid Constant ***";
|
||||||
|
value = "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
long bits = 0;
|
||||||
|
for (int i = 9; i >= 2; i--)
|
||||||
|
{
|
||||||
|
bits <<= 8;
|
||||||
|
bits |= buffer[offset + i] & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
saneDouble = Double.longBitsToDouble (bits);
|
||||||
|
format = new CellFormat (buffer[offset], buffer[offset + 1]);
|
||||||
|
value = String.format (format.mask (), saneDouble).trim ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
src/com/bytezone/diskbrowser/appleworks/CellFormat.java
Normal file
50
src/com/bytezone/diskbrowser/appleworks/CellFormat.java
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package com.bytezone.diskbrowser.appleworks;
|
||||||
|
|
||||||
|
public class CellFormat
|
||||||
|
{
|
||||||
|
boolean labelAllowed;
|
||||||
|
boolean valueAllowed;
|
||||||
|
boolean display;
|
||||||
|
boolean standard;
|
||||||
|
boolean fixed;
|
||||||
|
boolean dollars;
|
||||||
|
boolean commas;
|
||||||
|
boolean percent;
|
||||||
|
boolean appropriate;
|
||||||
|
int decimals;
|
||||||
|
|
||||||
|
public CellFormat (byte format)
|
||||||
|
{
|
||||||
|
display = (format & 0x40) == 0;
|
||||||
|
labelAllowed = (format & 0x10) == 0;
|
||||||
|
valueAllowed = (format & 0x08) == 0;
|
||||||
|
|
||||||
|
int formatting = format & 0x07;
|
||||||
|
|
||||||
|
standard = formatting == 1;
|
||||||
|
fixed = formatting == 2;
|
||||||
|
dollars = formatting == 3;
|
||||||
|
commas = formatting == 4;
|
||||||
|
percent = formatting == 5;
|
||||||
|
appropriate = formatting == 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CellFormat (byte format, byte decimals)
|
||||||
|
{
|
||||||
|
this (format);
|
||||||
|
this.decimals = decimals & 0x07;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String mask ()
|
||||||
|
{
|
||||||
|
String fmt = dollars ? "$%" : "%";
|
||||||
|
if (commas)
|
||||||
|
fmt += ",";
|
||||||
|
fmt += "12." + decimals;
|
||||||
|
fmt += "f";
|
||||||
|
if (percent)
|
||||||
|
fmt += "%%";
|
||||||
|
|
||||||
|
return fmt;
|
||||||
|
}
|
||||||
|
}
|
59
src/com/bytezone/diskbrowser/appleworks/CellFormula.java
Normal file
59
src/com/bytezone/diskbrowser/appleworks/CellFormula.java
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package com.bytezone.diskbrowser.appleworks;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class CellFormula
|
||||||
|
{
|
||||||
|
private static String[] tokens = {//
|
||||||
|
"@Deg", "@Rad", "@Pi", "@True", "@False", "@Not", "@IsBlank", "@IsNA", "@IsError",
|
||||||
|
"@Exp", "@Ln", "@Log", "@Cos", "@Sin", "@Tan", "@ACos", "@ASin", "@ATan2",
|
||||||
|
"@ATan", "@Mod", "@FV", "@PV", "@PMT", "@Term", "@Rate", "@Round", "@Or", "@And",
|
||||||
|
"@Sum", "@Avg", "@Choose", "@Count", "@Error", "@IRR", "@If", "@Int", "@Lookup",
|
||||||
|
"@Max", "@Min", "@NA", "@NPV", "@Sqrt", "@Abs", "", "<>", ">=", "<=", "=", ">",
|
||||||
|
"<", ",", "^", ")", "-", "+", "/", "*", "(", "-", "+", "..." };
|
||||||
|
String value;
|
||||||
|
|
||||||
|
public CellFormula (Cell cell, byte[] buffer, int offset, int length)
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
int value = buffer[offset + i] & 0xFF;
|
||||||
|
if (value < 0xFD)
|
||||||
|
{
|
||||||
|
String token = tokens[value - 0xC0];
|
||||||
|
text.append (token);
|
||||||
|
if (value == 0xE0 || value == 0xE7)
|
||||||
|
i += 3;
|
||||||
|
}
|
||||||
|
else if (value == 0xFD)
|
||||||
|
{
|
||||||
|
double d = HexFormatter.getSANEDouble (buffer, offset + i + 1);
|
||||||
|
String num = String.format ("%f", d).trim ();
|
||||||
|
while (num.endsWith ("0"))
|
||||||
|
num = num.substring (0, num.length () - 1);
|
||||||
|
if (num.endsWith ("."))
|
||||||
|
num = num.substring (0, num.length () - 1);
|
||||||
|
text.append (num);
|
||||||
|
i += 8;
|
||||||
|
}
|
||||||
|
else if (value == 0xFE)
|
||||||
|
{
|
||||||
|
CellAddress address = new CellAddress (buffer, offset + i + 1);
|
||||||
|
String cellName =
|
||||||
|
Cell.getCellName (cell.row + address.rowRef, cell.column + address.colRef);
|
||||||
|
i += 3;
|
||||||
|
text.append (cellName);
|
||||||
|
}
|
||||||
|
else if (value == 0xFF)
|
||||||
|
{
|
||||||
|
int len = buffer[offset + i + 1] & 0xFF;
|
||||||
|
String word = new String (buffer, offset + i + 2, len);
|
||||||
|
i += len + 1;
|
||||||
|
System.out.println ("Word: " + word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value = text.toString ();
|
||||||
|
}
|
||||||
|
}
|
30
src/com/bytezone/diskbrowser/appleworks/CellLabel.java
Normal file
30
src/com/bytezone/diskbrowser/appleworks/CellLabel.java
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package com.bytezone.diskbrowser.appleworks;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class CellLabel extends Cell
|
||||||
|
{
|
||||||
|
boolean propagated;
|
||||||
|
String label;
|
||||||
|
|
||||||
|
public CellLabel (byte[] buffer, int row, int column, int offset, int length)
|
||||||
|
{
|
||||||
|
super (row, column, offset, length);
|
||||||
|
|
||||||
|
int b1 = buffer[offset] & 0xFF;
|
||||||
|
|
||||||
|
// label = new String (buffer, offset + 1, length - 1);
|
||||||
|
|
||||||
|
// MOUSE.TEXT.SS/TAWUG.22/TAWUG 21 to 25.2mg has funny characters
|
||||||
|
label = HexFormatter.sanitiseString (buffer, offset + 1, length - 1);
|
||||||
|
|
||||||
|
// int columnWidth = header.columnWidths[column];
|
||||||
|
|
||||||
|
value = "[" + label + "]";
|
||||||
|
type = "Label";
|
||||||
|
propagated = (b1 & 0xA0) == 0x20;
|
||||||
|
|
||||||
|
if (propagated)
|
||||||
|
value += "+";
|
||||||
|
}
|
||||||
|
}
|
36
src/com/bytezone/diskbrowser/appleworks/CellValue.java
Normal file
36
src/com/bytezone/diskbrowser/appleworks/CellValue.java
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package com.bytezone.diskbrowser.appleworks;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class CellValue extends Cell
|
||||||
|
{
|
||||||
|
CellFormat format;
|
||||||
|
CellFormula formula;
|
||||||
|
boolean lastEvalNA;
|
||||||
|
boolean lastEvalError;
|
||||||
|
double saneDouble;
|
||||||
|
|
||||||
|
public CellValue (byte[] buffer, int row, int column, int offset, int length)
|
||||||
|
{
|
||||||
|
super (row, column, offset, length);
|
||||||
|
|
||||||
|
type = "Value";
|
||||||
|
|
||||||
|
// if (header.ssMinVers != 0)
|
||||||
|
// {
|
||||||
|
// System.out.println ("AppleWorks v" + header.ssMinVers + " required!");
|
||||||
|
// value = HexFormatter.getHexString (buffer, offset, length);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
{
|
||||||
|
format = new CellFormat (buffer[offset]);
|
||||||
|
int b1 = buffer[offset + 1] & 0xFF;
|
||||||
|
lastEvalNA = (b1 & 0x40) != 0;
|
||||||
|
lastEvalError = (b1 & 0x20) != 0;
|
||||||
|
saneDouble = HexFormatter.getSANEDouble (buffer, offset + 2);
|
||||||
|
value = String.format (format.mask (), saneDouble).trim ();
|
||||||
|
formula = new CellFormula (this, buffer, offset + 10, length - 10);
|
||||||
|
value = String.format ("%-15s %s", value, formula.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
src/com/bytezone/diskbrowser/appleworks/LabelReport.java
Normal file
16
src/com/bytezone/diskbrowser/appleworks/LabelReport.java
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package com.bytezone.diskbrowser.appleworks;
|
||||||
|
|
||||||
|
class LabelReport extends Report
|
||||||
|
{
|
||||||
|
|
||||||
|
public LabelReport (AppleworksADBFile parent, byte[] buffer, int offset)
|
||||||
|
{
|
||||||
|
super (parent, buffer, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
return "Skipping vertical report\n";
|
||||||
|
}
|
||||||
|
}
|
178
src/com/bytezone/diskbrowser/appleworks/Record.java
Normal file
178
src/com/bytezone/diskbrowser/appleworks/Record.java
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
package com.bytezone.diskbrowser.appleworks;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
class Record
|
||||||
|
{
|
||||||
|
AppleworksADBFile parent;
|
||||||
|
int length;
|
||||||
|
List<String> items = new ArrayList<String> ();
|
||||||
|
Map<Integer, Double> calculatedItems = new HashMap<Integer, Double> ();// move to TableReport
|
||||||
|
|
||||||
|
public Record (AppleworksADBFile parent, byte[] buffer, int ptr)
|
||||||
|
{
|
||||||
|
this.parent = parent;
|
||||||
|
int count;
|
||||||
|
|
||||||
|
while ((count = buffer[ptr++] & 0xFF) != 0xFF)
|
||||||
|
{
|
||||||
|
if (count < 0x80) // size of category data
|
||||||
|
{
|
||||||
|
if (buffer[ptr] == (byte) 0xC0) // date
|
||||||
|
{
|
||||||
|
String year = new String (buffer, ptr + 1, 2);
|
||||||
|
int month = buffer[ptr + 3] - '@';
|
||||||
|
String day = new String (buffer, ptr + 4, 2).trim ();
|
||||||
|
if (day.length () == 1)
|
||||||
|
day = "0" + day;
|
||||||
|
items.add (String.format ("%2s/%02d/%2s", year, month, day));
|
||||||
|
}
|
||||||
|
else if (buffer[ptr] == (byte) 0xD4) // time
|
||||||
|
{
|
||||||
|
int hour = buffer[ptr + 1] - '@';
|
||||||
|
String minute = new String (buffer, ptr + 2, 2);
|
||||||
|
items.add (String.format ("%02d:%s", hour, minute));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
items.add (new String (buffer, ptr, count));
|
||||||
|
|
||||||
|
ptr += count;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
while (count-- > 0x80)
|
||||||
|
items.add ("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.size () > parent.categories)
|
||||||
|
System.out.println ("Too many items");
|
||||||
|
|
||||||
|
while (items.size () < parent.categories)
|
||||||
|
items.add ("");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getItem (int index)
|
||||||
|
{
|
||||||
|
return items.get (index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double calculateItem (int pos, int name, String condition)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// System.out.printf ("%nCalculating %d (%s): %s%n", pos, (char) name, condition);
|
||||||
|
Pattern p = Pattern.compile ("([A-Za-z]{1,2})(([-+*/]([A-Za-z]{1,2}|[0-9]))*)");
|
||||||
|
Matcher m = p.matcher (condition);
|
||||||
|
if (m.matches ())
|
||||||
|
{
|
||||||
|
String init = m.group (1);
|
||||||
|
String rest = m.group (2);
|
||||||
|
|
||||||
|
double val = Double.parseDouble (valueOf (init.charAt (0)));
|
||||||
|
|
||||||
|
Pattern p2 = Pattern.compile ("([-+*/])(([A-Za-z]{1,2})|([0-9]{1,6}))");
|
||||||
|
Matcher m2 = p2.matcher (rest);
|
||||||
|
|
||||||
|
while (m2.find ())
|
||||||
|
{
|
||||||
|
String operator = m2.group (1).trim ();
|
||||||
|
|
||||||
|
double nextVal;
|
||||||
|
if (m2.group (3) != null)
|
||||||
|
nextVal = Double.parseDouble (valueOf (m2.group (3).charAt (0)));
|
||||||
|
else
|
||||||
|
nextVal = Double.parseDouble (m2.group (4));
|
||||||
|
|
||||||
|
if (operator.equals ("+"))
|
||||||
|
val += nextVal;
|
||||||
|
else if (operator.equals ("-"))
|
||||||
|
val -= nextVal;
|
||||||
|
else if (operator.equals ("/"))
|
||||||
|
val /= nextVal;
|
||||||
|
else if (operator.equals ("*"))
|
||||||
|
val *= nextVal;
|
||||||
|
else
|
||||||
|
System.out.println ("Unknown operator : " + operator);
|
||||||
|
}
|
||||||
|
|
||||||
|
calculatedItems.put (name, val);
|
||||||
|
// System.out.printf ("Putting %s : %f%n", (char) name, val);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace ();
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String valueOf (int field)
|
||||||
|
{
|
||||||
|
int itemNo = field - 'A';
|
||||||
|
|
||||||
|
if (itemNo > 26) // lowercase - calculated
|
||||||
|
{
|
||||||
|
if (calculatedItems.containsKey (field))
|
||||||
|
{
|
||||||
|
// System.out.printf (" Value of : [%s]", (char) field);
|
||||||
|
// System.out.println (" -> " + calculatedItems.get (field));
|
||||||
|
return Double.toString (calculatedItems.get (field));
|
||||||
|
}
|
||||||
|
System.out.println ("Didn't find : " + field);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemNo < items.size ())
|
||||||
|
{
|
||||||
|
// System.out.printf (" Value of : [%s]", (char) field);
|
||||||
|
// System.out.println (" -> " + items.get (itemNo));
|
||||||
|
return items.get (itemNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.printf (" -> can't find: %d out of %d%n", (itemNo + 1), items.size ());
|
||||||
|
return "0.0";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReportLine (String format)
|
||||||
|
{
|
||||||
|
return String.format (format, (Object[]) items.toArray (new String[items.size ()]));
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getReportLine ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
String format = String.format ("%%-%ds : %%s%%n", parent.maxCategoryName);
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
for (String item : items)
|
||||||
|
{
|
||||||
|
if (count < parent.categoryNames.length)
|
||||||
|
text.append (String.format (format, parent.categoryNames[count++], item));
|
||||||
|
else
|
||||||
|
text.append (item + "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.length () > 0)
|
||||||
|
text.deleteCharAt (text.length () - 1);
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
String format = "%-" + parent.maxCategoryName + "s [%s]%n";
|
||||||
|
|
||||||
|
int category = 0;
|
||||||
|
for (String item : items)
|
||||||
|
text.append (String.format (format, parent.categoryNames[category++], item));
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
182
src/com/bytezone/diskbrowser/appleworks/Report.java
Normal file
182
src/com/bytezone/diskbrowser/appleworks/Report.java
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
package com.bytezone.diskbrowser.appleworks;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
abstract class Report
|
||||||
|
{
|
||||||
|
static final String line = "-------------------------------------------------------"
|
||||||
|
+ "-----------------------------------\n";
|
||||||
|
static final String gap = " "
|
||||||
|
+ " ";
|
||||||
|
static final String[] testText = { "", "=", ">", "<", "?4", "?5", "<>", "?7", "?8", "?9",
|
||||||
|
"?10", "?11", "?12", "?13" };
|
||||||
|
static final String[] continuationText = { "", "And", "Or", "Through" };
|
||||||
|
|
||||||
|
protected final AppleworksADBFile parent;
|
||||||
|
protected final String name;
|
||||||
|
private final char reportFormat;
|
||||||
|
private final char spacing;
|
||||||
|
protected final int categoriesOnThisReport;
|
||||||
|
protected final String titleLine;
|
||||||
|
protected final boolean printHeader;
|
||||||
|
private final int platenWidth;
|
||||||
|
private final int leftMargin;
|
||||||
|
private final int rightMargin;
|
||||||
|
private final int charsPerInch;
|
||||||
|
private final int paperLength;
|
||||||
|
private final int topMargin;
|
||||||
|
private final int bottomMargin;
|
||||||
|
private final int linesPerInch;
|
||||||
|
|
||||||
|
private final int[] selectionRules = new int[3];
|
||||||
|
private final int[] testTypes = new int[3];
|
||||||
|
private final int[] continuation = new int[3];
|
||||||
|
private final String[] comparison = new String[3];
|
||||||
|
|
||||||
|
private final String printerRules;
|
||||||
|
private final boolean printDash;
|
||||||
|
private String fudgeReason;
|
||||||
|
|
||||||
|
public Report (AppleworksADBFile parent, byte[] buffer, int offset)
|
||||||
|
{
|
||||||
|
this.parent = parent;
|
||||||
|
|
||||||
|
name = pascalString (buffer, offset);
|
||||||
|
categoriesOnThisReport = buffer[offset + 200] & 0xFF;
|
||||||
|
platenWidth = buffer[offset + 205] & 0xFF;
|
||||||
|
leftMargin = buffer[offset + 206] & 0xFF;
|
||||||
|
rightMargin = buffer[offset + 207] & 0xFF;
|
||||||
|
charsPerInch = buffer[offset + 208] & 0xFF;
|
||||||
|
paperLength = buffer[offset + 209] & 0xFF;
|
||||||
|
topMargin = buffer[offset + 210] & 0xFF;
|
||||||
|
bottomMargin = buffer[offset + 211] & 0xFF;
|
||||||
|
linesPerInch = buffer[offset + 212] & 0xFF;
|
||||||
|
|
||||||
|
reportFormat = (char) buffer[offset + 214];
|
||||||
|
spacing = (char) buffer[offset + 215];
|
||||||
|
printHeader = buffer[offset + 216] != 0;
|
||||||
|
|
||||||
|
titleLine = pascalString (buffer, offset + 220);
|
||||||
|
int printerLength = buffer[offset + 464] & 0xFF;
|
||||||
|
if (printerLength > 13)
|
||||||
|
System.out.println ("*** Dodgy printer rules ***");
|
||||||
|
|
||||||
|
printerRules = pascalString (buffer, offset + 464);
|
||||||
|
printDash = buffer[offset + 478] != 0;
|
||||||
|
|
||||||
|
int fudge = 0;
|
||||||
|
if (buffer[offset + 480] != 0)
|
||||||
|
{
|
||||||
|
fudge = 1;
|
||||||
|
fudgeReason = "*** Report rules ***";
|
||||||
|
if (buffer[offset + 481] != 0)
|
||||||
|
fudgeReason = "*** Bollocksed ***";
|
||||||
|
}
|
||||||
|
else if (buffer[offset + 464] == 0 && buffer[offset + 465] != 0)
|
||||||
|
{
|
||||||
|
fudgeReason = "*** Printer codes ***";
|
||||||
|
fudge = 1;
|
||||||
|
}
|
||||||
|
else if (buffer[offset + 479] != 0 && buffer[offset + 485] == 0)
|
||||||
|
{
|
||||||
|
fudgeReason = "*** Test codes ***";
|
||||||
|
fudge = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
fudgeReason = "";
|
||||||
|
|
||||||
|
if (false)
|
||||||
|
{
|
||||||
|
System.out.println ("==============================================================");
|
||||||
|
System.out.println ("Header");
|
||||||
|
System.out.println (HexFormatter.formatNoHeader (buffer, offset + 213, 7));
|
||||||
|
System.out.println ();
|
||||||
|
System.out.println ("Title line:");
|
||||||
|
System.out.println (HexFormatter.formatNoHeader (buffer, offset + 220, 81));
|
||||||
|
System.out.println ();
|
||||||
|
System.out.println ("Calculated categories:");
|
||||||
|
System.out.println (HexFormatter.formatNoHeader (buffer, offset + 302, 22));
|
||||||
|
System.out.println (HexFormatter.formatNoHeader (buffer, offset + 324, 32));
|
||||||
|
System.out.println ();
|
||||||
|
System.out.println (HexFormatter.formatNoHeader (buffer, offset + 356, 22));
|
||||||
|
System.out.println (HexFormatter.formatNoHeader (buffer, offset + 378, 32));
|
||||||
|
System.out.println ();
|
||||||
|
System.out.println (HexFormatter.formatNoHeader (buffer, offset + 410, 22));
|
||||||
|
System.out.println (HexFormatter.formatNoHeader (buffer, offset + 432, 32));
|
||||||
|
System.out.println ();
|
||||||
|
System.out.println ("Printer rules:");
|
||||||
|
System.out.println (HexFormatter.formatNoHeader (buffer, offset + 464 + fudge, 14));
|
||||||
|
System.out.println ();
|
||||||
|
System.out.println ("Printer dash:");
|
||||||
|
System.out.println (HexFormatter.formatNoHeader (buffer, offset + 478 + fudge, 1));
|
||||||
|
System.out.println ();
|
||||||
|
System.out.println ("Selection rules:");
|
||||||
|
System.out.println (" categories:");
|
||||||
|
System.out.println (HexFormatter.formatNoHeader (buffer, offset + 479 + fudge, 6));
|
||||||
|
System.out.println (" tests:");
|
||||||
|
System.out.println (HexFormatter.formatNoHeader (buffer, offset + 485 + fudge, 6));
|
||||||
|
System.out.println (" continuation:");
|
||||||
|
System.out.println (HexFormatter.formatNoHeader (buffer, offset + 491 + fudge, 6));
|
||||||
|
System.out.println (" comparison:");
|
||||||
|
System.out.println (HexFormatter.formatNoHeader (buffer, offset + 497 + fudge, 32));
|
||||||
|
System.out.println ();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buffer[offset + 480 + fudge] == 0) // test high byte
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
selectionRules[i] = HexFormatter.getWord (buffer, offset + 479 + i * 2 + fudge);
|
||||||
|
testTypes[i] = HexFormatter.getWord (buffer, offset + 485 + i * 2 + fudge);
|
||||||
|
continuation[i] = HexFormatter.getWord (buffer, offset + 491 + i * 2 + fudge);
|
||||||
|
comparison[i] = pascalString (buffer, offset + 497 + i * 32 + fudge);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
System.out.println ("*** Invalid value in report rules ***");
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract String getText ();
|
||||||
|
|
||||||
|
protected String pascalString (byte[] buffer, int ptr)
|
||||||
|
{
|
||||||
|
return new String (buffer, ptr + 1, buffer[ptr] & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
text.append (String.format ("Report name ........ %s%n", name));
|
||||||
|
text.append (String.format ("Report type ........ %s%n", reportFormat));
|
||||||
|
text.append (String.format ("Spacing ............ %s (Single/Double/Triple)%n", spacing));
|
||||||
|
text.append (String.format ("Print header ....... %s%n", printHeader));
|
||||||
|
text.append (String.format ("Title .............. %s%n", titleLine));
|
||||||
|
text.append (String.format ("L/R/T/B margin ..... %d/%d/%d/%d%n", leftMargin, rightMargin,
|
||||||
|
topMargin, bottomMargin));
|
||||||
|
text.append (String.format ("Categories ......... %s%n", categoriesOnThisReport));
|
||||||
|
text.append (String.format ("Platen width ....... %d%n", platenWidth));
|
||||||
|
text.append (String.format ("Chars per inch ..... %d%n", charsPerInch));
|
||||||
|
text.append (String.format ("Paper length ....... %d%n", paperLength));
|
||||||
|
text.append (String.format ("Lines per inch ..... %d%n", linesPerInch));
|
||||||
|
text.append (String.format ("Print dash ......... %s%n", printDash));
|
||||||
|
text.append (String.format ("Printer rules ...... %s%n", printerRules));
|
||||||
|
|
||||||
|
text.append ("Report rules ....... ");
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
if (selectionRules[i] == 0)
|
||||||
|
break;
|
||||||
|
int category = selectionRules[i] - 1;
|
||||||
|
int test = testTypes[i];
|
||||||
|
int cont = continuation[i];
|
||||||
|
text.append (String.format ("[%s] %s [%s] %s ", parent.categoryNames[category],
|
||||||
|
testText[test], comparison[i], continuationText[cont]));
|
||||||
|
}
|
||||||
|
text.append ("\n");
|
||||||
|
|
||||||
|
if (!fudgeReason.isEmpty ())
|
||||||
|
text.append ("Fudge .............. " + fudgeReason + "\n");
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
211
src/com/bytezone/diskbrowser/appleworks/TableReport.java
Normal file
211
src/com/bytezone/diskbrowser/appleworks/TableReport.java
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
package com.bytezone.diskbrowser.appleworks;
|
||||||
|
|
||||||
|
class TableReport extends Report
|
||||||
|
{
|
||||||
|
private final int[] columnWidths = new int[33];
|
||||||
|
private final int[] spaces = new int[33];
|
||||||
|
private final int[] footTotals = new int[33];
|
||||||
|
private final int[] justification = new int[33];
|
||||||
|
private final int[] reportCategoryNames = new int[33];
|
||||||
|
|
||||||
|
private final int[] calculatedColumn = new int[3];
|
||||||
|
private final String[] calculatedCategory = new String[3];
|
||||||
|
private final String[] calculatedRules = new String[3];
|
||||||
|
|
||||||
|
private final int groupTotalColumn;
|
||||||
|
private final boolean printGroupTotals;
|
||||||
|
|
||||||
|
public TableReport (AppleworksADBFile parent, byte[] buffer, int offset)
|
||||||
|
{
|
||||||
|
super (parent, buffer, offset);
|
||||||
|
|
||||||
|
for (int i = 0; i < categoriesOnThisReport; i++)
|
||||||
|
{
|
||||||
|
columnWidths[i] = buffer[offset + 20 + i] & 0xFF;
|
||||||
|
spaces[i] = buffer[offset + 56 + i] & 0xFF;
|
||||||
|
reportCategoryNames[i] = buffer[offset + 92 + i] & 0xFF;
|
||||||
|
footTotals[i] = buffer[offset + 128 + i] & 0xFF;
|
||||||
|
justification[i] = buffer[offset + 164 + i] & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
calculatedColumn[i] = buffer[offset + 201 + i] & 0xFF;
|
||||||
|
calculatedCategory[i] = pascalString (buffer, offset + 302 + i * 54);
|
||||||
|
calculatedRules[i] = pascalString (buffer, offset + 324 + i * 54);
|
||||||
|
}
|
||||||
|
|
||||||
|
groupTotalColumn = buffer[offset + 204] & 0xFF;
|
||||||
|
printGroupTotals = buffer[offset + 217] != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
if (printHeader && !titleLine.isEmpty ())
|
||||||
|
text.append (titleLine);
|
||||||
|
else
|
||||||
|
text.append ("Report name: " + name);
|
||||||
|
text.append ("\n\n");
|
||||||
|
|
||||||
|
StringBuilder header = new StringBuilder ();
|
||||||
|
StringBuilder underline = new StringBuilder ();
|
||||||
|
|
||||||
|
for (int i = 0; i < categoriesOnThisReport; i++)
|
||||||
|
{
|
||||||
|
int category = reportCategoryNames[i];
|
||||||
|
String categoryName;
|
||||||
|
if (category < 0x7F)
|
||||||
|
categoryName = parent.categoryNames[category - 1];
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int calcField = (category - 0x80);
|
||||||
|
categoryName = calculatedCategory[calcField];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (categoryName.length () > columnWidths[i])
|
||||||
|
categoryName = categoryName.substring (0, columnWidths[i]);
|
||||||
|
|
||||||
|
header.append (categoryName);
|
||||||
|
header.append (gap.substring (0, columnWidths[i] + spaces[i] - categoryName.length ()));
|
||||||
|
underline.append (line.substring (0, columnWidths[i]));
|
||||||
|
underline.append (gap.substring (0, spaces[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
header = trimRight (header);
|
||||||
|
text.append (header.toString ());
|
||||||
|
text.append ("\n");
|
||||||
|
|
||||||
|
underline = trimRight (underline);
|
||||||
|
text.append (underline.toString ());
|
||||||
|
text.append ("\n");
|
||||||
|
|
||||||
|
float[] totals = new float[33];
|
||||||
|
|
||||||
|
for (Record record : parent.records)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < categoriesOnThisReport; i++)
|
||||||
|
{
|
||||||
|
int category = reportCategoryNames[i];
|
||||||
|
String item;
|
||||||
|
if (category < 0x7F)
|
||||||
|
item = record.getItem (category - 1).trim ();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int calcField = (category - 0x80);
|
||||||
|
String cond = calculatedRules[calcField];
|
||||||
|
int col = calculatedColumn[calcField] - 1;
|
||||||
|
String format = "%12." + justification[col] + "f";
|
||||||
|
item =
|
||||||
|
String.format (format, record.calculateItem (calcField, i + 97, cond)).trim ();
|
||||||
|
// System.out.println (item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.length () > columnWidths[i])
|
||||||
|
item = item.substring (0, columnWidths[i]);
|
||||||
|
|
||||||
|
if (footTotals[i] != 0xFF && !item.isEmpty () && !item.equals (" "))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
totals[i] += Float.parseFloat (item);
|
||||||
|
}
|
||||||
|
catch (NumberFormatException e)
|
||||||
|
{
|
||||||
|
// ignore this value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (justification[i] != 0xFF)
|
||||||
|
{
|
||||||
|
text.append (gap.substring (0, columnWidths[i] - item.length ()));
|
||||||
|
text.append (item);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
text.append (item);
|
||||||
|
text.append (gap.substring (0, columnWidths[i] - item.length ()));
|
||||||
|
}
|
||||||
|
|
||||||
|
text.append (gap.substring (0, spaces[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
text = trimRight (text);
|
||||||
|
text.append ("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
text.append (underline.toString ());
|
||||||
|
text.append ("\n");
|
||||||
|
|
||||||
|
StringBuilder totalLine = new StringBuilder ();
|
||||||
|
underline = new StringBuilder ();
|
||||||
|
boolean hasTotals = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < categoriesOnThisReport; i++)
|
||||||
|
{
|
||||||
|
if (footTotals[i] == 0xFF) // not totalled
|
||||||
|
{
|
||||||
|
totalLine.append (gap.substring (0, columnWidths[i]));
|
||||||
|
underline.append (gap.substring (0, columnWidths[i]));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hasTotals = true;
|
||||||
|
String format = "%12." + footTotals[i] + "f";
|
||||||
|
String value = String.format (format, totals[i]).trim ();
|
||||||
|
if (value.length () > columnWidths[i])
|
||||||
|
value = value.substring (0, columnWidths[i]); // wrong end
|
||||||
|
|
||||||
|
totalLine.append (gap.substring (0, columnWidths[i] - value.length ()));
|
||||||
|
totalLine.append (value);
|
||||||
|
underline.append (line.substring (0, columnWidths[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
totalLine.append (gap.substring (0, spaces[i]));
|
||||||
|
underline.append (gap.substring (0, spaces[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasTotals)
|
||||||
|
{
|
||||||
|
text.append (totalLine.toString ());
|
||||||
|
text.append ("\n");
|
||||||
|
text.append (underline.toString ());
|
||||||
|
text.append ("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private StringBuilder trimRight (StringBuilder text)
|
||||||
|
{
|
||||||
|
while (text.length () > 0 && text.charAt (text.length () - 1) == ' ')
|
||||||
|
text.deleteCharAt (text.length () - 1);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder (super.toString ());
|
||||||
|
text.append (String.format ("Calculated ......... %d %d %d%n", calculatedColumn[0],
|
||||||
|
calculatedColumn[1], calculatedColumn[2]));
|
||||||
|
text.append (String.format ("Group total ........ %d%n", groupTotalColumn));
|
||||||
|
text.append (String.format ("Print gr totals .... %s%n", printGroupTotals));
|
||||||
|
text.append (String.format ("Calc category1 ..... %s%n", calculatedCategory[0]));
|
||||||
|
text.append (String.format ("Calc rules1 ........ %s%n", calculatedRules[0]));
|
||||||
|
text.append (String.format ("Calc category2 ..... %s%n", calculatedCategory[1]));
|
||||||
|
text.append (String.format ("Calc rules2 ........ %s%n", calculatedRules[1]));
|
||||||
|
text.append (String.format ("Calc category3 ..... %s%n", calculatedCategory[2]));
|
||||||
|
text.append (String.format ("Calc rules3 ........ %s%n", calculatedRules[2]));
|
||||||
|
|
||||||
|
text.append (String.format ("%n Width Space Name Foot Just%n"));
|
||||||
|
for (int i = 0; i < categoriesOnThisReport; i++)
|
||||||
|
text.append (String.format (" %2d %2d %02X %02X %02X %n", columnWidths[i],
|
||||||
|
spaces[i], reportCategoryNames[i], footTotals[i],
|
||||||
|
justification[i]));
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
13
src/com/bytezone/diskbrowser/catalog/AbstractCatalogCreator.java
Executable file
13
src/com/bytezone/diskbrowser/catalog/AbstractCatalogCreator.java
Executable file
|
@ -0,0 +1,13 @@
|
||||||
|
package com.bytezone.diskbrowser.catalog;
|
||||||
|
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
|
||||||
|
public abstract class AbstractCatalogCreator implements CatalogLister
|
||||||
|
{
|
||||||
|
DefaultMutableTreeNode node;
|
||||||
|
|
||||||
|
public void setNode (DefaultMutableTreeNode node)
|
||||||
|
{
|
||||||
|
this.node = node;
|
||||||
|
}
|
||||||
|
}
|
35
src/com/bytezone/diskbrowser/catalog/AbstractDiskCreator.java
Executable file
35
src/com/bytezone/diskbrowser/catalog/AbstractDiskCreator.java
Executable file
|
@ -0,0 +1,35 @@
|
||||||
|
package com.bytezone.diskbrowser.catalog;
|
||||||
|
|
||||||
|
import java.util.Enumeration;
|
||||||
|
|
||||||
|
import javax.swing.JTree;
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
import javax.swing.tree.DefaultTreeModel;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.disk.FormattedDisk;
|
||||||
|
import com.bytezone.diskbrowser.gui.DiskSelectedEvent;
|
||||||
|
|
||||||
|
public abstract class AbstractDiskCreator implements DiskLister
|
||||||
|
{
|
||||||
|
FormattedDisk disk;
|
||||||
|
|
||||||
|
public void setDisk (FormattedDisk disk)
|
||||||
|
{
|
||||||
|
this.disk = disk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// should return List<AppleFileSource>
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public Enumeration<DefaultMutableTreeNode> getEnumeration ()
|
||||||
|
{
|
||||||
|
JTree tree = disk.getCatalogTree ();
|
||||||
|
DefaultTreeModel treeModel = (DefaultTreeModel) tree.getModel ();
|
||||||
|
DefaultMutableTreeNode node = (DefaultMutableTreeNode) treeModel.getRoot ();
|
||||||
|
return node.breadthFirstEnumeration ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void diskSelected (DiskSelectedEvent e)
|
||||||
|
{
|
||||||
|
setDisk (e.getFormattedDisk ());
|
||||||
|
}
|
||||||
|
}
|
12
src/com/bytezone/diskbrowser/catalog/CatalogLister.java
Executable file
12
src/com/bytezone/diskbrowser/catalog/CatalogLister.java
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
package com.bytezone.diskbrowser.catalog;
|
||||||
|
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
|
||||||
|
public interface CatalogLister
|
||||||
|
{
|
||||||
|
public void setNode (DefaultMutableTreeNode node);
|
||||||
|
|
||||||
|
public void createCatalog ();
|
||||||
|
|
||||||
|
public String getMenuText ();
|
||||||
|
}
|
19
src/com/bytezone/diskbrowser/catalog/DiskLister.java
Executable file
19
src/com/bytezone/diskbrowser/catalog/DiskLister.java
Executable file
|
@ -0,0 +1,19 @@
|
||||||
|
package com.bytezone.diskbrowser.catalog;
|
||||||
|
|
||||||
|
import java.util.Enumeration;
|
||||||
|
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.disk.FormattedDisk;
|
||||||
|
import com.bytezone.diskbrowser.gui.DiskSelectionListener;
|
||||||
|
|
||||||
|
public interface DiskLister extends DiskSelectionListener
|
||||||
|
{
|
||||||
|
public void setDisk (FormattedDisk disk);
|
||||||
|
|
||||||
|
public void createDisk ();
|
||||||
|
|
||||||
|
public Enumeration<DefaultMutableTreeNode> getEnumeration ();
|
||||||
|
|
||||||
|
public String getMenuText ();
|
||||||
|
}
|
52
src/com/bytezone/diskbrowser/catalog/DocumentCreatorFactory.java
Executable file
52
src/com/bytezone/diskbrowser/catalog/DocumentCreatorFactory.java
Executable file
|
@ -0,0 +1,52 @@
|
||||||
|
package com.bytezone.diskbrowser.catalog;
|
||||||
|
|
||||||
|
/*******************************************************************************
|
||||||
|
* Factory object that determines whether iText is available, and creates a
|
||||||
|
* CatalogLister and a DiskLister accordingly. Also links the two xxxListers to
|
||||||
|
* menu items.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.gui.MenuHandler;
|
||||||
|
|
||||||
|
public class DocumentCreatorFactory
|
||||||
|
{
|
||||||
|
public CatalogLister catalogLister;
|
||||||
|
public DiskLister diskLister;
|
||||||
|
|
||||||
|
public DocumentCreatorFactory (MenuHandler mh)
|
||||||
|
{
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// Class.forName ("com.lowagie.text.Document");
|
||||||
|
// catalogLister = new PDFCatalogCreator ();
|
||||||
|
// diskLister = new PDFDiskCreator ();
|
||||||
|
// }
|
||||||
|
// catch (ClassNotFoundException e)
|
||||||
|
{
|
||||||
|
catalogLister = new TextCatalogCreator ();
|
||||||
|
diskLister = new TextDiskCreator ();
|
||||||
|
}
|
||||||
|
|
||||||
|
mh.createCatalogFileItem.setText (catalogLister.getMenuText ());
|
||||||
|
mh.createDiskFileItem.setText (diskLister.getMenuText ());
|
||||||
|
|
||||||
|
mh.createCatalogFileItem.addActionListener (new ActionListener ()
|
||||||
|
{
|
||||||
|
public void actionPerformed (ActionEvent e)
|
||||||
|
{
|
||||||
|
catalogLister.createCatalog ();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mh.createDiskFileItem.addActionListener (new ActionListener ()
|
||||||
|
{
|
||||||
|
public void actionPerformed (ActionEvent e)
|
||||||
|
{
|
||||||
|
diskLister.createDisk ();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
90
src/com/bytezone/diskbrowser/catalog/TextCatalogCreator.java
Executable file
90
src/com/bytezone/diskbrowser/catalog/TextCatalogCreator.java
Executable file
|
@ -0,0 +1,90 @@
|
||||||
|
package com.bytezone.diskbrowser.catalog;
|
||||||
|
|
||||||
|
import java.awt.EventQueue;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.disk.DiskFactory;
|
||||||
|
import com.bytezone.diskbrowser.disk.FormattedDisk;
|
||||||
|
import com.bytezone.diskbrowser.gui.TreeBuilder.FileNode;
|
||||||
|
|
||||||
|
public class TextCatalogCreator extends AbstractCatalogCreator
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void createCatalog ()
|
||||||
|
{
|
||||||
|
Object o = node.getUserObject ();
|
||||||
|
if (!(o instanceof FileNode))
|
||||||
|
{
|
||||||
|
JOptionPane.showMessageDialog (null, "Please select a folder from the Disk Tree",
|
||||||
|
"Info", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
File f = ((FileNode) o).file;
|
||||||
|
final File f2 = new File (f.getAbsolutePath () + "/Catalog.txt");
|
||||||
|
JOptionPane.showMessageDialog (null, "About to create file : " + f2.getAbsolutePath (),
|
||||||
|
"Info", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
|
||||||
|
EventQueue.invokeLater (new Runnable ()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run ()
|
||||||
|
{
|
||||||
|
FileWriter out = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
out = new FileWriter (f2);
|
||||||
|
printDescendants (node, out);
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
JOptionPane.showMessageDialog (null, "Error creating catalog : " + e.getMessage (),
|
||||||
|
"Bugger", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (out != null)
|
||||||
|
out.close ();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void printDescendants (DefaultMutableTreeNode root, FileWriter out)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
Object o = root.getUserObject ();
|
||||||
|
if (o instanceof FileNode)
|
||||||
|
{
|
||||||
|
File f = ((FileNode) root.getUserObject ()).file;
|
||||||
|
if (!f.isDirectory ())
|
||||||
|
{
|
||||||
|
FormattedDisk fd = DiskFactory.createDisk (f.getAbsolutePath ());
|
||||||
|
out.write (fd.getCatalog ().getDataSource ().getText () + String.format ("%n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Enumeration<DefaultMutableTreeNode> children = root.children ();
|
||||||
|
if (children != null)
|
||||||
|
while (children.hasMoreElements ())
|
||||||
|
printDescendants (children.nextElement (), out);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getMenuText ()
|
||||||
|
{
|
||||||
|
return "Create catalog text";
|
||||||
|
}
|
||||||
|
}
|
61
src/com/bytezone/diskbrowser/catalog/TextDiskCreator.java
Executable file
61
src/com/bytezone/diskbrowser/catalog/TextDiskCreator.java
Executable file
|
@ -0,0 +1,61 @@
|
||||||
|
package com.bytezone.diskbrowser.catalog;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.applefile.AppleFileSource;
|
||||||
|
|
||||||
|
public class TextDiskCreator extends AbstractDiskCreator
|
||||||
|
{
|
||||||
|
public void createDisk ()
|
||||||
|
{
|
||||||
|
File f = new File ("D:\\DiskDetails.txt");
|
||||||
|
FileWriter out = null;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
out = new FileWriter (f);
|
||||||
|
printDisk (out);
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace ();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
out.close ();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void printDisk (FileWriter out) throws IOException
|
||||||
|
{
|
||||||
|
Enumeration<DefaultMutableTreeNode> children = getEnumeration ();
|
||||||
|
|
||||||
|
if (children == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (children.hasMoreElements ())
|
||||||
|
{
|
||||||
|
DefaultMutableTreeNode node = (DefaultMutableTreeNode) children.nextElement ();
|
||||||
|
AppleFileSource afs = (AppleFileSource) node.getUserObject ();
|
||||||
|
out.write (afs.getDataSource ().getText () + String.format ("%n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMenuText ()
|
||||||
|
{
|
||||||
|
return "create text disk";
|
||||||
|
}
|
||||||
|
}
|
66
src/com/bytezone/diskbrowser/cpm/CPMDisk.java
Normal file
66
src/com/bytezone/diskbrowser/cpm/CPMDisk.java
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package com.bytezone.diskbrowser.cpm;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
import com.bytezone.diskbrowser.applefile.BootSector;
|
||||||
|
import com.bytezone.diskbrowser.disk.AbstractFormattedDisk;
|
||||||
|
import com.bytezone.diskbrowser.disk.AppleDisk;
|
||||||
|
import com.bytezone.diskbrowser.disk.Disk;
|
||||||
|
import com.bytezone.diskbrowser.disk.DiskAddress;
|
||||||
|
import com.bytezone.diskbrowser.disk.SectorType;
|
||||||
|
|
||||||
|
public class CPMDisk extends AbstractFormattedDisk
|
||||||
|
{
|
||||||
|
private final Color green = new Color (0, 200, 0);
|
||||||
|
public final SectorType catalogSector = new SectorType ("Catalog", green);
|
||||||
|
public final SectorType cpmSector = new SectorType ("CPM", Color.lightGray);
|
||||||
|
|
||||||
|
public CPMDisk (Disk disk)
|
||||||
|
{
|
||||||
|
super (disk);
|
||||||
|
sectorTypesList.add (catalogSector);
|
||||||
|
sectorTypesList.add (cpmSector);
|
||||||
|
|
||||||
|
byte[] sectorBuffer = disk.readSector (0, 0); // Boot sector
|
||||||
|
bootSector = new BootSector (disk, sectorBuffer, "CPM");
|
||||||
|
sectorTypes[0] = cpmSector;
|
||||||
|
|
||||||
|
for (int sector = 0; sector < 8; sector++)
|
||||||
|
{
|
||||||
|
DiskAddress da = disk.getDiskAddress (3, sector);
|
||||||
|
sectorTypes[da.getBlock ()] = catalogSector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DiskAddress> getFileSectors (int fileNo)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isCorrectFormat (AppleDisk disk)
|
||||||
|
{
|
||||||
|
disk.setInterleave (3);
|
||||||
|
|
||||||
|
for (int sector = 0; sector < 8; sector++)
|
||||||
|
{
|
||||||
|
byte[] buffer = disk.readSector (3, sector);
|
||||||
|
for (int i = 0; i < buffer.length; i += 32)
|
||||||
|
{
|
||||||
|
if (buffer[i] != 0 && buffer[i] != (byte) 0xE5)
|
||||||
|
return false;
|
||||||
|
if (buffer[i] == 0)
|
||||||
|
{
|
||||||
|
String filename = HexFormatter.getString (buffer, i + 1, 8);
|
||||||
|
String filetype = HexFormatter.getString (buffer, i + 9, 3);
|
||||||
|
String bytes = HexFormatter.getHexString (buffer, i + 12, 20);
|
||||||
|
System.out.println (filename + " " + filetype + " " + bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
411
src/com/bytezone/diskbrowser/disk/AbstractFormattedDisk.java
Executable file
411
src/com/bytezone/diskbrowser/disk/AbstractFormattedDisk.java
Executable file
|
@ -0,0 +1,411 @@
|
||||||
|
package com.bytezone.diskbrowser.disk;
|
||||||
|
|
||||||
|
import java.awt.AWTEventMulticaster;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.BitSet;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.swing.JTree;
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
import javax.swing.tree.DefaultTreeModel;
|
||||||
|
import javax.swing.tree.TreePath;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.applefile.AbstractFile;
|
||||||
|
import com.bytezone.diskbrowser.applefile.AppleFileSource;
|
||||||
|
import com.bytezone.diskbrowser.applefile.BootSector;
|
||||||
|
import com.bytezone.diskbrowser.gui.DataSource;
|
||||||
|
|
||||||
|
public abstract class AbstractFormattedDisk implements FormattedDisk
|
||||||
|
{
|
||||||
|
protected Disk disk;
|
||||||
|
protected FormattedDisk parent; // used by Dual-dos disks
|
||||||
|
|
||||||
|
protected ActionListener actionListenerList;
|
||||||
|
protected JTree catalogTree;
|
||||||
|
protected Path originalPath;
|
||||||
|
// protected String originalName;
|
||||||
|
|
||||||
|
protected List<SectorType> sectorTypesList = new ArrayList<SectorType> ();
|
||||||
|
protected List<AppleFileSource> fileEntries = new ArrayList<AppleFileSource> ();
|
||||||
|
|
||||||
|
public SectorType[] sectorTypes;
|
||||||
|
|
||||||
|
protected BootSector bootSector;
|
||||||
|
|
||||||
|
public final SectorType emptySector = new SectorType ("Unused (empty)", Color.white);
|
||||||
|
public final SectorType usedSector = new SectorType ("Unused (data)", Color.yellow);
|
||||||
|
// public final SectorType dosSector = new SectorType ("DOS", Color.lightGray);
|
||||||
|
|
||||||
|
protected int falsePositives;
|
||||||
|
protected int falseNegatives;
|
||||||
|
|
||||||
|
protected Dimension gridLayout;
|
||||||
|
|
||||||
|
protected BitSet freeBlocks;
|
||||||
|
protected BitSet usedBlocks; // still to be populated - currently using stillAvailable ()
|
||||||
|
|
||||||
|
public AbstractFormattedDisk (Disk disk)
|
||||||
|
{
|
||||||
|
this.disk = disk;
|
||||||
|
freeBlocks = new BitSet (disk.getTotalBlocks ());
|
||||||
|
usedBlocks = new BitSet (disk.getTotalBlocks ());
|
||||||
|
/*
|
||||||
|
* All formatted disks will have empty and/or used sectors, so set them
|
||||||
|
* here, and let the actual subclass add its sector types later. This list
|
||||||
|
* is used to hold one of each sector type so that the DiskLayoutPanel can
|
||||||
|
* draw its grid and key correctly. Every additional type that the instance
|
||||||
|
* creates should be added here too.
|
||||||
|
*/
|
||||||
|
sectorTypesList.add (emptySector);
|
||||||
|
sectorTypesList.add (usedSector);
|
||||||
|
/*
|
||||||
|
* Hopefully every used sector will be changed by the subclass to something
|
||||||
|
* sensible, but deleted files will always leave the sector as used/unknown
|
||||||
|
* as it contains data.
|
||||||
|
*/
|
||||||
|
setSectorTypes ();
|
||||||
|
setGridLayout ();
|
||||||
|
/*
|
||||||
|
* Create the disk name as the root for the catalog tree. Subclasses will
|
||||||
|
* have to append their catalog entries to this node.
|
||||||
|
*/
|
||||||
|
DefaultAppleFileSource afs =
|
||||||
|
new DefaultAppleFileSource (getName (), disk.toString (), this);
|
||||||
|
DefaultMutableTreeNode root = new DefaultMutableTreeNode (afs);
|
||||||
|
DefaultTreeModel treeModel = new DefaultTreeModel (root);
|
||||||
|
catalogTree = new JTree (treeModel);
|
||||||
|
treeModel.setAsksAllowsChildren (true); // allows empty nodes to appear as folders
|
||||||
|
/*
|
||||||
|
* Add an ActionListener to the disk in case the interleave or blocksize
|
||||||
|
* changes
|
||||||
|
*/
|
||||||
|
disk.addActionListener (new ActionListener ()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void actionPerformed (ActionEvent e)
|
||||||
|
{
|
||||||
|
setSectorTypes ();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSectorTypes ()
|
||||||
|
{
|
||||||
|
sectorTypes = new SectorType[disk.getTotalBlocks ()];
|
||||||
|
|
||||||
|
for (DiskAddress da : disk)
|
||||||
|
sectorTypes[da.getBlock ()] = disk.isSectorEmpty (da) ? emptySector : usedSector;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setGridLayout ()
|
||||||
|
{
|
||||||
|
int totalBlocks = disk.getTotalBlocks ();
|
||||||
|
switch (totalBlocks)
|
||||||
|
{
|
||||||
|
case 280:
|
||||||
|
gridLayout = new Dimension (8, 35);
|
||||||
|
break;
|
||||||
|
case 455:
|
||||||
|
gridLayout = new Dimension (13, 35);
|
||||||
|
break;
|
||||||
|
case 560:
|
||||||
|
gridLayout = new Dimension (16, 35);
|
||||||
|
break;
|
||||||
|
case 1600:
|
||||||
|
gridLayout = new Dimension (16, 100);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
int[] sizes = { 32, 20, 16, 8 };
|
||||||
|
for (int size : sizes)
|
||||||
|
if ((totalBlocks % size) == 0)
|
||||||
|
{
|
||||||
|
gridLayout = new Dimension (size, totalBlocks / size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (gridLayout == null)
|
||||||
|
System.out.println ("Unusable total blocks : " + totalBlocks);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Disk getDisk ()
|
||||||
|
{
|
||||||
|
return disk;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FormattedDisk getParent ()
|
||||||
|
{
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setParent (FormattedDisk disk)
|
||||||
|
{
|
||||||
|
parent = disk;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOriginalPath (Path path)
|
||||||
|
{
|
||||||
|
this.originalPath = path;
|
||||||
|
|
||||||
|
DefaultMutableTreeNode root = (DefaultMutableTreeNode) catalogTree.getModel ().getRoot ();
|
||||||
|
DefaultAppleFileSource afs =
|
||||||
|
new DefaultAppleFileSource (getName (), disk.toString (), this);
|
||||||
|
root.setUserObject (afs);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAbsolutePath ()
|
||||||
|
{
|
||||||
|
if (originalPath != null)
|
||||||
|
return originalPath.toString ();
|
||||||
|
return disk.getFile ().getAbsolutePath ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName ()
|
||||||
|
{
|
||||||
|
if (originalPath != null)
|
||||||
|
return originalPath.getFileName ().toString ();
|
||||||
|
return disk.getFile ().getName ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeFile (AbstractFile file)
|
||||||
|
{
|
||||||
|
System.out.println ("not implemented yet");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AppleFileSource> getCatalogList ()
|
||||||
|
{
|
||||||
|
return fileEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AppleFileSource getFile (String uniqueName)
|
||||||
|
{
|
||||||
|
for (AppleFileSource afs : fileEntries)
|
||||||
|
if (afs.getUniqueName ().equals (uniqueName))
|
||||||
|
return afs;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Catalog Tree routines
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public JTree getCatalogTree ()
|
||||||
|
{
|
||||||
|
return catalogTree;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultMutableTreeNode getCatalogTreeRoot ()
|
||||||
|
{
|
||||||
|
return (DefaultMutableTreeNode) catalogTree.getModel ().getRoot ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void makeNodeVisible (DefaultMutableTreeNode node)
|
||||||
|
{
|
||||||
|
catalogTree.makeVisible (new TreePath (((DefaultTreeModel) catalogTree.getModel ())
|
||||||
|
.getPathToRoot (node)));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DefaultMutableTreeNode findNode (DefaultMutableTreeNode node, String name)
|
||||||
|
{
|
||||||
|
Enumeration<DefaultMutableTreeNode> children = node.breadthFirstEnumeration ();
|
||||||
|
if (children != null)
|
||||||
|
{
|
||||||
|
while (children.hasMoreElements ())
|
||||||
|
{
|
||||||
|
DefaultMutableTreeNode childNode = children.nextElement ();
|
||||||
|
if (childNode.getUserObject ().toString ().indexOf (name) > 0)
|
||||||
|
return childNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
System.out.println ("Node not found : " + name);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* These routines just hand back the information that was created above, and
|
||||||
|
* added to by the subclass.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public SectorType getSectorType (int block)
|
||||||
|
{
|
||||||
|
return getSectorType (disk.getDiskAddress (block));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SectorType getSectorType (int track, int sector)
|
||||||
|
{
|
||||||
|
return getSectorType (disk.getDiskAddress (track, sector));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SectorType getSectorType (DiskAddress da)
|
||||||
|
{
|
||||||
|
return sectorTypes[da.getBlock ()];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SectorType> getSectorTypeList ()
|
||||||
|
{
|
||||||
|
return sectorTypesList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSectorType (int block, SectorType type)
|
||||||
|
{
|
||||||
|
if (block < sectorTypes.length)
|
||||||
|
sectorTypes[block] = type;
|
||||||
|
else
|
||||||
|
System.out.println ("Invalid block number: " + block);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override this so that the correct sector type can be displayed
|
||||||
|
@Override
|
||||||
|
public DataSource getFormattedSector (DiskAddress da)
|
||||||
|
{
|
||||||
|
if (da.getBlock () == 0 && bootSector != null)
|
||||||
|
return bootSector;
|
||||||
|
|
||||||
|
SectorType sectorType = sectorTypes[da.getBlock ()];
|
||||||
|
byte[] buffer = disk.readSector (da);
|
||||||
|
String address = String.format ("%02X %02X", da.getTrack (), da.getSector ());
|
||||||
|
|
||||||
|
if (sectorType == emptySector)
|
||||||
|
return new DefaultSector ("Empty sector at " + address, disk, buffer);
|
||||||
|
if (sectorType == usedSector)
|
||||||
|
return new DefaultSector ("Orphan sector at " + address, disk, buffer);
|
||||||
|
|
||||||
|
return new DefaultSector ("Data sector at " + address, disk, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Override this with something useful
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AppleFileSource getCatalog ()
|
||||||
|
{
|
||||||
|
return new DefaultAppleFileSource (disk.toString (), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSectorFilename (DiskAddress da)
|
||||||
|
{
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int clearOrphans ()
|
||||||
|
{
|
||||||
|
System.out.println ("Not implemented yet");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSectorFree (DiskAddress da)
|
||||||
|
{
|
||||||
|
return freeBlocks.get (da.getBlock ());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSectorFree (int blockNo)
|
||||||
|
{
|
||||||
|
return freeBlocks.get (blockNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// representation of the Free Sector Table
|
||||||
|
@Override
|
||||||
|
public void setSectorFree (int block, boolean free)
|
||||||
|
{
|
||||||
|
if (block < 0 || block >= freeBlocks.size ())
|
||||||
|
{
|
||||||
|
System.out.printf ("Block %d not in range : 0-%d%n", block, freeBlocks.size () - 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// assert block < freeBlocks.size () : String.format ("Set free block # %6d, size %6d",
|
||||||
|
// block, freeBlocks.size ());
|
||||||
|
freeBlocks.set (block, free);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the sector hasn't already been flagged as part of the disk structure
|
||||||
|
@Override
|
||||||
|
public boolean stillAvailable (DiskAddress da)
|
||||||
|
{
|
||||||
|
return sectorTypes[da.getBlock ()] == usedSector
|
||||||
|
|| sectorTypes[da.getBlock ()] == emptySector;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean stillAvailable (int blockNo)
|
||||||
|
{
|
||||||
|
return sectorTypes[blockNo] == usedSector || sectorTypes[blockNo] == emptySector;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void verify ()
|
||||||
|
{
|
||||||
|
System.out.println ("Sectors to clean :");
|
||||||
|
for (int i = 0, max = disk.getTotalBlocks (); i < max; i++)
|
||||||
|
{
|
||||||
|
if (freeBlocks.get (i))
|
||||||
|
{
|
||||||
|
if (sectorTypes[i] == usedSector)
|
||||||
|
System.out.printf ("%04X clean%n", i);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (sectorTypes[i] == usedSector)
|
||||||
|
System.out.printf ("%04X *** error ***%n", i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dimension getGridLayout ()
|
||||||
|
{
|
||||||
|
return gridLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
// VTOC flags sector as free, but it is in use by a file
|
||||||
|
@Override
|
||||||
|
public int falsePositiveBlocks ()
|
||||||
|
{
|
||||||
|
return falsePositives;
|
||||||
|
}
|
||||||
|
|
||||||
|
// VTOC flags sector as in use, but no file is using it (and not in the DOS tracks)
|
||||||
|
@Override
|
||||||
|
public int falseNegativeBlocks ()
|
||||||
|
{
|
||||||
|
return falseNegatives;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addActionListener (ActionListener actionListener)
|
||||||
|
{
|
||||||
|
actionListenerList = AWTEventMulticaster.add (actionListenerList, actionListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeActionListener (ActionListener actionListener)
|
||||||
|
{
|
||||||
|
actionListenerList = AWTEventMulticaster.remove (actionListenerList, actionListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyListeners (String text)
|
||||||
|
{
|
||||||
|
if (actionListenerList != null)
|
||||||
|
actionListenerList.actionPerformed (new ActionEvent (this, ActionEvent.ACTION_PERFORMED,
|
||||||
|
text));
|
||||||
|
}
|
||||||
|
}
|
123
src/com/bytezone/diskbrowser/disk/AbstractSector.java
Executable file
123
src/com/bytezone/diskbrowser/disk/AbstractSector.java
Executable file
|
@ -0,0 +1,123 @@
|
||||||
|
package com.bytezone.diskbrowser.disk;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
import com.bytezone.diskbrowser.applefile.AssemblerProgram;
|
||||||
|
import com.bytezone.diskbrowser.gui.DataSource;
|
||||||
|
|
||||||
|
public abstract class AbstractSector implements DataSource
|
||||||
|
{
|
||||||
|
private static String newLine = String.format ("%n");
|
||||||
|
private static String newLine2 = newLine + newLine;
|
||||||
|
|
||||||
|
public byte[] buffer;
|
||||||
|
protected Disk disk;
|
||||||
|
AssemblerProgram assembler;
|
||||||
|
String description;
|
||||||
|
|
||||||
|
// maybe this should just use a DiskAddress
|
||||||
|
public AbstractSector (Disk disk, byte[] buffer)
|
||||||
|
{
|
||||||
|
this.buffer = buffer;
|
||||||
|
this.disk = disk;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAssembler ()
|
||||||
|
{
|
||||||
|
if (assembler == null)
|
||||||
|
assembler = new AssemblerProgram ("noname", buffer, 0);
|
||||||
|
return assembler.getText ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getHexDump ()
|
||||||
|
{
|
||||||
|
return HexFormatter.format (buffer, 0, buffer.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BufferedImage getImage ()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
if (description == null)
|
||||||
|
description = createText ();
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract String createText ();
|
||||||
|
|
||||||
|
protected StringBuilder getHeader (String title)
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
text.append (title + newLine2);
|
||||||
|
text.append ("Offset Value Description" + newLine);
|
||||||
|
text.append ("======= =========== "
|
||||||
|
+ "===============================================================" + newLine);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JComponent getComponent ()
|
||||||
|
{
|
||||||
|
System.out.println ("In AbstractSector.getComponent()");
|
||||||
|
JPanel panel = new JPanel ();
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addText (StringBuilder text, byte[] b, int offset, int size, String desc)
|
||||||
|
{
|
||||||
|
if ((offset + size - 1) > b.length)
|
||||||
|
{
|
||||||
|
// System.out.printf ("Offset : %d, Size : %d, Buffer : %d%n", offset, size, buffer.length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (size)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
text.append (String
|
||||||
|
.format ("%03X %02X %s%n", offset, b[offset], desc));
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
text.append (String.format ("%03X-%03X %02X %02X %s%n", offset, offset + 1,
|
||||||
|
b[offset], b[offset + 1], desc));
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
text.append (String.format ("%03X-%03X %02X %02X %02X %s%n", offset,
|
||||||
|
offset + 2, b[offset], b[offset + 1], b[offset + 2], desc));
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
text.append (String.format ("%03X-%03X %02X %02X %02X %02X %s%n", offset,
|
||||||
|
offset + 3, b[offset], b[offset + 1], b[offset + 2],
|
||||||
|
b[offset + 3], desc));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
System.out.println ("Invalid length : " + size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addTextAndDecimal (StringBuilder text, byte[] b, int offset, int size,
|
||||||
|
String desc)
|
||||||
|
{
|
||||||
|
if (size == 1)
|
||||||
|
desc += " (" + (b[offset] & 0xFF) + ")";
|
||||||
|
else if (size == 2)
|
||||||
|
desc += " (" + ((b[offset + 1] & 0xFF) * 256 + (b[offset] & 0xFF)) + ")";
|
||||||
|
else if (size == 3)
|
||||||
|
desc +=
|
||||||
|
String.format (" (%,d)", ((b[offset + 2] & 0xFF) * 65536)
|
||||||
|
+ ((b[offset + 1] & 0xFF) * 256) + (b[offset] & 0xFF));
|
||||||
|
addText (text, b, offset, size, desc);
|
||||||
|
}
|
||||||
|
}
|
441
src/com/bytezone/diskbrowser/disk/AppleDisk.java
Executable file
441
src/com/bytezone/diskbrowser/disk/AppleDisk.java
Executable file
|
@ -0,0 +1,441 @@
|
||||||
|
package com.bytezone.diskbrowser.disk;
|
||||||
|
|
||||||
|
import java.awt.AWTEventMulticaster;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.zip.CRC32;
|
||||||
|
import java.util.zip.Checksum;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.FileFormatException;
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
import com.bytezone.diskbrowser.applefile.AppleFileSource;
|
||||||
|
|
||||||
|
public class AppleDisk implements Disk
|
||||||
|
{
|
||||||
|
static final String newLine = String.format ("%n");
|
||||||
|
static final int MAX_INTERLEAVE = 3;
|
||||||
|
|
||||||
|
public final File path;
|
||||||
|
private final byte[] diskBuffer; // contains the disk contents in memory
|
||||||
|
|
||||||
|
private final int tracks; // usually 35 for floppy disks
|
||||||
|
private int sectors; // 8 or 16
|
||||||
|
private int blocks; // 280 or 560
|
||||||
|
|
||||||
|
private final int trackSize; // 4096
|
||||||
|
public int sectorSize; // 256 or 512
|
||||||
|
|
||||||
|
private int interleave = 0;
|
||||||
|
private static int[][] interleaveSector = //
|
||||||
|
{ { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }, // Dos
|
||||||
|
{ 0, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 15 }, // Prodos
|
||||||
|
{ 0, 13, 11, 9, 7, 5, 3, 1, 14, 12, 10, 8, 6, 4, 2, 15 }, // Infocom
|
||||||
|
{ 0, 6, 12, 3, 9, 15, 14, 5, 11, 2, 8, 7, 13, 4, 10, 1 } }; // CPM
|
||||||
|
|
||||||
|
private boolean[] hasData;
|
||||||
|
private ActionListener actionListenerList;
|
||||||
|
private List<DiskAddress> blockList;
|
||||||
|
|
||||||
|
public AppleDisk (File path, int tracks, int sectors) throws FileFormatException
|
||||||
|
{
|
||||||
|
assert (path.exists ()) : "No such path :" + path.getAbsolutePath ();
|
||||||
|
assert (!path.isDirectory ()) : "File is directory :" + path.getAbsolutePath ();
|
||||||
|
assert (path.length () <= Integer.MAX_VALUE) : "File too large";
|
||||||
|
assert (path.length () != 0) : "File empty";
|
||||||
|
|
||||||
|
int skip = 0;
|
||||||
|
|
||||||
|
String name = path.getName ();
|
||||||
|
int pos = name.lastIndexOf ('.');
|
||||||
|
if (pos > 0 && name.substring (pos + 1).equalsIgnoreCase ("2mg"))
|
||||||
|
{
|
||||||
|
byte[] buffer = getPrefix (path);
|
||||||
|
this.blocks = HexFormatter.intValue (buffer[20], buffer[21]); // 1600
|
||||||
|
this.sectorSize = 512;
|
||||||
|
this.trackSize = 8 * sectorSize;
|
||||||
|
tracks = this.blocks / 8;
|
||||||
|
sectors = 8;
|
||||||
|
skip = HexFormatter.intValue (buffer[8], buffer[9]);
|
||||||
|
}
|
||||||
|
else if (pos > 0 && name.substring (pos + 1).equalsIgnoreCase ("HDV"))
|
||||||
|
{
|
||||||
|
this.blocks = (int) path.length () / 4096 * 8; // reduces blocks to a legal multiple
|
||||||
|
this.sectorSize = 512;
|
||||||
|
this.trackSize = sectors * sectorSize;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.blocks = tracks * sectors;
|
||||||
|
this.sectorSize = (int) path.length () / blocks;
|
||||||
|
this.trackSize = sectors * sectorSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (false)
|
||||||
|
{
|
||||||
|
System.out.println ("Tracks : " + tracks);
|
||||||
|
System.out.println ("Sectors : " + sectors);
|
||||||
|
System.out.println ("Blocks : " + blocks);
|
||||||
|
System.out.println ("Sector size : " + sectorSize);
|
||||||
|
System.out.println ("Track size : " + trackSize);
|
||||||
|
System.out.println ();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sectorSize != 256 && sectorSize != 512)
|
||||||
|
{
|
||||||
|
System.out.println ("Invalid sector size : " + sectorSize);
|
||||||
|
new Exception ().printStackTrace ();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sectorSize != 256 && sectorSize != 512)
|
||||||
|
throw new FileFormatException ("Invalid sector size : " + sectorSize);
|
||||||
|
|
||||||
|
this.path = path;
|
||||||
|
this.tracks = tracks;
|
||||||
|
this.sectors = sectors;
|
||||||
|
|
||||||
|
diskBuffer = new byte[tracks * sectors * sectorSize];
|
||||||
|
hasData = new boolean[blocks];
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
BufferedInputStream file = new BufferedInputStream (new FileInputStream (path));
|
||||||
|
if (skip > 0)
|
||||||
|
file.skip (skip);
|
||||||
|
file.read (diskBuffer);
|
||||||
|
file.close ();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace ();
|
||||||
|
System.exit (1);
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSectorsForData ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] getPrefix (File path)
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[64];
|
||||||
|
try
|
||||||
|
{
|
||||||
|
BufferedInputStream file = new BufferedInputStream (new FileInputStream (path));
|
||||||
|
file.read (buffer);
|
||||||
|
file.close ();
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace ();
|
||||||
|
System.exit (1);
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkSectorsForData ()
|
||||||
|
{
|
||||||
|
blockList = null; // force blockList to be rebuilt with the correct number/size of blocks
|
||||||
|
for (DiskAddress da : this)
|
||||||
|
{
|
||||||
|
byte[] buffer = readSector (da);
|
||||||
|
hasData[da.getBlock ()] = false;
|
||||||
|
for (int i = 0; i < sectorSize; i++)
|
||||||
|
if (buffer[i] != 0)
|
||||||
|
{
|
||||||
|
hasData[da.getBlock ()] = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Routines that implement the Disk interface
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSectorsPerTrack ()
|
||||||
|
{
|
||||||
|
return trackSize / sectorSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTrackSize ()
|
||||||
|
{
|
||||||
|
return trackSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBlockSize ()
|
||||||
|
{
|
||||||
|
return sectorSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTotalBlocks ()
|
||||||
|
{
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getTotalTracks ()
|
||||||
|
{
|
||||||
|
return tracks;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSectorEmpty (DiskAddress da)
|
||||||
|
{
|
||||||
|
return !hasData[da.getBlock ()];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSectorEmpty (int block)
|
||||||
|
{
|
||||||
|
return !hasData[block];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSectorEmpty (int track, int sector)
|
||||||
|
{
|
||||||
|
return !hasData[getDiskAddress (track, sector).getBlock ()];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getFile ()
|
||||||
|
{
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] readSector (DiskAddress da)
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[sectorSize];
|
||||||
|
readBuffer (da, buffer, 0);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] readSectors (List<DiskAddress> daList)
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[daList.size () * sectorSize];
|
||||||
|
int bufferOffset = 0;
|
||||||
|
for (DiskAddress da : daList)
|
||||||
|
{
|
||||||
|
if (da != null) // text files may have gaps
|
||||||
|
readBuffer (da, buffer, bufferOffset);
|
||||||
|
bufferOffset += sectorSize;
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] readSector (int track, int sector)
|
||||||
|
{
|
||||||
|
return readSector (getDiskAddress (track, sector));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] readSector (int block)
|
||||||
|
{
|
||||||
|
return readSector (getDiskAddress (block));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int writeSector (DiskAddress da, byte[] buffer)
|
||||||
|
{
|
||||||
|
System.out.println ("Not yet implemented");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setInterleave (int interleave)
|
||||||
|
{
|
||||||
|
assert (interleave >= 0 && interleave <= MAX_INTERLEAVE) : "Invalid interleave";
|
||||||
|
this.interleave = interleave;
|
||||||
|
checkSectorsForData ();
|
||||||
|
if (actionListenerList != null)
|
||||||
|
notifyListeners ("Interleave changed");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getInterleave ()
|
||||||
|
{
|
||||||
|
return interleave;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBlockSize (int size)
|
||||||
|
{
|
||||||
|
assert (size == 256 || size == 512) : "Invalid sector size : " + size;
|
||||||
|
if (sectorSize == size)
|
||||||
|
return;
|
||||||
|
sectorSize = size;
|
||||||
|
sectors = trackSize / sectorSize;
|
||||||
|
blocks = tracks * sectors;
|
||||||
|
System.out.printf ("New blocks: %d%n", blocks);
|
||||||
|
hasData = new boolean[blocks];
|
||||||
|
checkSectorsForData ();
|
||||||
|
if (actionListenerList != null)
|
||||||
|
notifyListeners ("Sector size changed");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DiskAddress getDiskAddress (int block)
|
||||||
|
{
|
||||||
|
if (!isValidAddress (block))
|
||||||
|
{
|
||||||
|
System.out.println ("Invalid block : " + block);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new AppleDiskAddress (block, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DiskAddress> getDiskAddressList (int... blocks)
|
||||||
|
{
|
||||||
|
List<DiskAddress> addressList = new ArrayList<DiskAddress> ();
|
||||||
|
|
||||||
|
for (int block : blocks)
|
||||||
|
{
|
||||||
|
assert (isValidAddress (block)) : "Invalid block : " + block;
|
||||||
|
addressList.add (new AppleDiskAddress (block, this));
|
||||||
|
}
|
||||||
|
return addressList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DiskAddress getDiskAddress (int track, int sector)
|
||||||
|
{
|
||||||
|
// should this return null for invalid addresses?
|
||||||
|
assert (isValidAddress (track, sector)) : "Invalid address : " + track + ", " + sector;
|
||||||
|
return new AppleDiskAddress (track, sector, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValidAddress (int block)
|
||||||
|
{
|
||||||
|
if (block < 0 || block >= this.blocks)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValidAddress (int track, int sector)
|
||||||
|
{
|
||||||
|
if (track < 0 || track >= this.tracks)
|
||||||
|
return false;
|
||||||
|
if (sector < 0 || sector >= this.sectors)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValidAddress (DiskAddress da)
|
||||||
|
{
|
||||||
|
return isValidAddress (da.getTrack (), da.getSector ());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is the only method that transfers data from the disk buffer to an output buffer.
|
||||||
|
* It handles sectors of 256 or 512 bytes, and both linear and interleaved sectors.
|
||||||
|
*/
|
||||||
|
private void readBuffer (DiskAddress da, byte[] buffer, int bufferOffset)
|
||||||
|
{
|
||||||
|
if (da.getDisk () != this)
|
||||||
|
{
|
||||||
|
System.out.println (da.getDisk ());
|
||||||
|
System.out.println (this);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert da.getDisk () == this : "Disk address not applicable to this disk";
|
||||||
|
assert sectorSize == 256 || sectorSize == 512 : "Invalid sector size : " + sectorSize;
|
||||||
|
assert interleave >= 0 && interleave <= MAX_INTERLEAVE : "Invalid interleave : "
|
||||||
|
+ interleave;
|
||||||
|
int diskOffset;
|
||||||
|
|
||||||
|
if (sectorSize == 256)
|
||||||
|
{
|
||||||
|
diskOffset =
|
||||||
|
da.getTrack () * trackSize + interleaveSector[interleave][da.getSector ()]
|
||||||
|
* sectorSize;
|
||||||
|
System.arraycopy (diskBuffer, diskOffset, buffer, bufferOffset, sectorSize);
|
||||||
|
}
|
||||||
|
else if (sectorSize == 512)
|
||||||
|
{
|
||||||
|
diskOffset =
|
||||||
|
da.getTrack () * trackSize + interleaveSector[interleave][da.getSector () * 2]
|
||||||
|
* 256;
|
||||||
|
System.arraycopy (diskBuffer, diskOffset, buffer, bufferOffset, 256);
|
||||||
|
diskOffset =
|
||||||
|
da.getTrack () * trackSize + interleaveSector[interleave][da.getSector () * 2 + 1]
|
||||||
|
* 256;
|
||||||
|
System.arraycopy (diskBuffer, diskOffset, buffer, bufferOffset + 256, 256);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addActionListener (ActionListener actionListener)
|
||||||
|
{
|
||||||
|
actionListenerList = AWTEventMulticaster.add (actionListenerList, actionListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeActionListener (ActionListener actionListener)
|
||||||
|
{
|
||||||
|
actionListenerList = AWTEventMulticaster.remove (actionListenerList, actionListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void notifyListeners (String text)
|
||||||
|
{
|
||||||
|
if (actionListenerList != null)
|
||||||
|
actionListenerList.actionPerformed (new ActionEvent (this, ActionEvent.ACTION_PERFORMED,
|
||||||
|
text));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppleFileSource getDetails ()
|
||||||
|
{
|
||||||
|
return new DefaultAppleFileSource (toString (), path.getName (), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
text.append ("Path............ " + path.getAbsolutePath () + newLine);
|
||||||
|
text.append (String.format ("File size....... %,d%n", path.length ()));
|
||||||
|
text.append ("Tracks.......... " + tracks + newLine);
|
||||||
|
text.append ("Sectors......... " + sectors + newLine);
|
||||||
|
text.append (String.format ("Blocks.......... %,d%n", blocks));
|
||||||
|
text.append ("Sector size..... " + sectorSize + newLine);
|
||||||
|
text.append ("Interleave...... " + interleave + newLine);
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<DiskAddress> iterator ()
|
||||||
|
{
|
||||||
|
if (blockList == null)
|
||||||
|
{
|
||||||
|
blockList = new ArrayList<DiskAddress> (blocks);
|
||||||
|
for (int block = 0; block < blocks; block++)
|
||||||
|
blockList.add (new AppleDiskAddress (block, this));
|
||||||
|
}
|
||||||
|
return blockList.iterator ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getBootChecksum ()
|
||||||
|
{
|
||||||
|
byte[] buffer = readSector (0, 0);
|
||||||
|
Checksum checksum = new CRC32 ();
|
||||||
|
checksum.update (buffer, 0, buffer.length);
|
||||||
|
return checksum.getValue ();
|
||||||
|
}
|
||||||
|
}
|
65
src/com/bytezone/diskbrowser/disk/AppleDiskAddress.java
Executable file
65
src/com/bytezone/diskbrowser/disk/AppleDiskAddress.java
Executable file
|
@ -0,0 +1,65 @@
|
||||||
|
package com.bytezone.diskbrowser.disk;
|
||||||
|
|
||||||
|
public class AppleDiskAddress implements DiskAddress
|
||||||
|
{
|
||||||
|
private final int block;
|
||||||
|
private final int track;
|
||||||
|
private final int sector;
|
||||||
|
public final Disk owner;
|
||||||
|
|
||||||
|
public AppleDiskAddress (int block, Disk owner)
|
||||||
|
{
|
||||||
|
this.owner = owner;
|
||||||
|
this.block = block;
|
||||||
|
int sectorsPerTrack = owner.getSectorsPerTrack ();
|
||||||
|
this.track = block / sectorsPerTrack;
|
||||||
|
this.sector = block % sectorsPerTrack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppleDiskAddress (int track, int sector, Disk owner)
|
||||||
|
{
|
||||||
|
this.owner = owner;
|
||||||
|
this.track = track;
|
||||||
|
this.sector = sector;
|
||||||
|
this.block = track * owner.getSectorsPerTrack () + sector;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
return String.format ("[Block=%3d, Track=%2d, Sector=%2d]", block, track, sector);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int compareTo (DiskAddress that)
|
||||||
|
{
|
||||||
|
return this.block - that.getBlock ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBlock ()
|
||||||
|
{
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSector ()
|
||||||
|
{
|
||||||
|
return sector;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getTrack ()
|
||||||
|
{
|
||||||
|
return track;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Disk getDisk ()
|
||||||
|
{
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals (Object other)
|
||||||
|
{
|
||||||
|
if (other == null || getClass () != other.getClass ())
|
||||||
|
return false;
|
||||||
|
return this.block == ((AppleDiskAddress) other).block;
|
||||||
|
}
|
||||||
|
}
|
52
src/com/bytezone/diskbrowser/disk/DataDisk.java
Executable file
52
src/com/bytezone/diskbrowser/disk/DataDisk.java
Executable file
|
@ -0,0 +1,52 @@
|
||||||
|
package com.bytezone.diskbrowser.disk;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.gui.DataSource;
|
||||||
|
|
||||||
|
public class DataDisk extends AbstractFormattedDisk
|
||||||
|
{
|
||||||
|
// static final byte[] dos = { 0x01, (byte) 0xA5, 0x27, (byte) 0xC9, 0x09 };
|
||||||
|
|
||||||
|
// this should somehow tie in with the checksum from DiskFactory to determine
|
||||||
|
// whether it has a bootloader
|
||||||
|
|
||||||
|
public DataDisk (AppleDisk disk)
|
||||||
|
{
|
||||||
|
super (disk);
|
||||||
|
|
||||||
|
// byte[] buffer = disk.readSector (0, 0); // Boot sector
|
||||||
|
// boolean ok = true;
|
||||||
|
// for (int i = 0; i < dos.length; i++)
|
||||||
|
// if (buffer[i] != dos[i])
|
||||||
|
// {
|
||||||
|
// ok = false;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// if (buffer[0] == 0x01)
|
||||||
|
// {
|
||||||
|
// bootSector = new BootSector (disk, buffer, "DOS");
|
||||||
|
// sectorTypesList.add (dosSector);
|
||||||
|
// sectorTypes[0] = dosSector;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// no files on data disks
|
||||||
|
@Override
|
||||||
|
public List<DiskAddress> getFileSectors (int fileNo)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no files on data disks
|
||||||
|
public DataSource getFile (int fileNo)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
return disk.toString ();
|
||||||
|
}
|
||||||
|
}
|
88
src/com/bytezone/diskbrowser/disk/DefaultAppleFileSource.java
Executable file
88
src/com/bytezone/diskbrowser/disk/DefaultAppleFileSource.java
Executable file
|
@ -0,0 +1,88 @@
|
||||||
|
package com.bytezone.diskbrowser.disk;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.applefile.AppleFileSource;
|
||||||
|
import com.bytezone.diskbrowser.gui.DataSource;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Most AppleFileSource objects are CatalogEntry types. In order to allow Disk
|
||||||
|
* and Volume nodes in the tree to show some text in the centre panel, use a
|
||||||
|
* DefaultAppleFileSource which returns a DefaultDataSource (just some text).
|
||||||
|
*/
|
||||||
|
public class DefaultAppleFileSource implements AppleFileSource
|
||||||
|
{
|
||||||
|
final String title;
|
||||||
|
final DataSource file;
|
||||||
|
final FormattedDisk owner;
|
||||||
|
List<DiskAddress> blocks;
|
||||||
|
|
||||||
|
public DefaultAppleFileSource (String text, FormattedDisk owner)
|
||||||
|
{
|
||||||
|
this ("", text, owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultAppleFileSource (String title, String text, FormattedDisk owner)
|
||||||
|
{
|
||||||
|
this (title, new DefaultDataSource (text), owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultAppleFileSource (String title, DataSource file, FormattedDisk owner)
|
||||||
|
{
|
||||||
|
this.title = title;
|
||||||
|
this.file = file;
|
||||||
|
this.owner = owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DefaultAppleFileSource (String title, DataSource file, FormattedDisk owner,
|
||||||
|
List<DiskAddress> blocks)
|
||||||
|
{
|
||||||
|
this (title, file, owner);
|
||||||
|
this.blocks = blocks;
|
||||||
|
if (file instanceof DefaultDataSource)
|
||||||
|
((DefaultDataSource) file).buffer = owner.getDisk ().readSectors (blocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSectors (List<DiskAddress> blocks)
|
||||||
|
{
|
||||||
|
this.blocks = blocks;
|
||||||
|
if (file instanceof DefaultDataSource)
|
||||||
|
((DefaultDataSource) file).buffer = owner.getDisk ().readSectors (blocks);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DataSource getDataSource ()
|
||||||
|
{
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FormattedDisk getFormattedDisk ()
|
||||||
|
{
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<DiskAddress> getSectors ()
|
||||||
|
{
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* See similar routine in CatalogPanel.DiskNode
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
final int MAX_NAME_LENGTH = 40;
|
||||||
|
final int SUFFIX_LENGTH = 12;
|
||||||
|
final int PREFIX_LENGTH = MAX_NAME_LENGTH - SUFFIX_LENGTH - 3;
|
||||||
|
|
||||||
|
if (title.length () > MAX_NAME_LENGTH)
|
||||||
|
return title.substring (0, PREFIX_LENGTH) + "..."
|
||||||
|
+ title.substring (title.length () - SUFFIX_LENGTH);
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUniqueName ()
|
||||||
|
{
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
}
|
49
src/com/bytezone/diskbrowser/disk/DefaultDataSource.java
Executable file
49
src/com/bytezone/diskbrowser/disk/DefaultDataSource.java
Executable file
|
@ -0,0 +1,49 @@
|
||||||
|
package com.bytezone.diskbrowser.disk;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
import com.bytezone.diskbrowser.gui.DataSource;
|
||||||
|
|
||||||
|
public class DefaultDataSource implements DataSource
|
||||||
|
{
|
||||||
|
public String text;
|
||||||
|
byte[] buffer;
|
||||||
|
|
||||||
|
public DefaultDataSource (String text)
|
||||||
|
{
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAssembler ()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getHexDump ()
|
||||||
|
{
|
||||||
|
if (buffer != null)
|
||||||
|
return HexFormatter.format (buffer, 0, buffer.length);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferedImage getImage ()
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JComponent getComponent ()
|
||||||
|
{
|
||||||
|
System.out.println ("In DefaultDataSource.getComponent()");
|
||||||
|
JPanel panel = new JPanel ();
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
}
|
26
src/com/bytezone/diskbrowser/disk/DefaultSector.java
Executable file
26
src/com/bytezone/diskbrowser/disk/DefaultSector.java
Executable file
|
@ -0,0 +1,26 @@
|
||||||
|
package com.bytezone.diskbrowser.disk;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
|
||||||
|
public class DefaultSector extends AbstractSector
|
||||||
|
{
|
||||||
|
String name;
|
||||||
|
|
||||||
|
public DefaultSector (String name, Disk disk, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (disk, buffer);
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String createText ()
|
||||||
|
{
|
||||||
|
return name + "\n\n" + HexFormatter.format (buffer, 0, buffer.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
60
src/com/bytezone/diskbrowser/disk/Disk.java
Executable file
60
src/com/bytezone/diskbrowser/disk/Disk.java
Executable file
|
@ -0,0 +1,60 @@
|
||||||
|
package com.bytezone.diskbrowser.disk;
|
||||||
|
|
||||||
|
import java.awt.event.ActionListener;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface Disk extends Iterable<DiskAddress>
|
||||||
|
{
|
||||||
|
public long getBootChecksum ();
|
||||||
|
|
||||||
|
public int getTotalBlocks (); // blocks per disk - usually 560 or 280
|
||||||
|
|
||||||
|
public int getTotalTracks (); // usually 35
|
||||||
|
|
||||||
|
public int getBlockSize (); // bytes per block - 256 or 512
|
||||||
|
|
||||||
|
public void setBlockSize (int blockSize);
|
||||||
|
|
||||||
|
public int getTrackSize (); // bytes per track - 4096
|
||||||
|
|
||||||
|
public int getSectorsPerTrack (); // 8 or 16
|
||||||
|
|
||||||
|
public void setInterleave (int interleave);
|
||||||
|
|
||||||
|
public int getInterleave ();
|
||||||
|
|
||||||
|
public DiskAddress getDiskAddress (int block);
|
||||||
|
|
||||||
|
public List<DiskAddress> getDiskAddressList (int... blocks);
|
||||||
|
|
||||||
|
public DiskAddress getDiskAddress (int track, int sector);
|
||||||
|
|
||||||
|
public byte[] readSector (int block);
|
||||||
|
|
||||||
|
public byte[] readSector (int track, int sector);
|
||||||
|
|
||||||
|
public byte[] readSector (DiskAddress da);
|
||||||
|
|
||||||
|
public byte[] readSectors (List<DiskAddress> daList);
|
||||||
|
|
||||||
|
public int writeSector (DiskAddress da, byte[] buffer);
|
||||||
|
|
||||||
|
public boolean isSectorEmpty (DiskAddress da);
|
||||||
|
|
||||||
|
public boolean isSectorEmpty (int block);
|
||||||
|
|
||||||
|
public boolean isSectorEmpty (int track, int sector);
|
||||||
|
|
||||||
|
public boolean isValidAddress (int block);
|
||||||
|
|
||||||
|
public boolean isValidAddress (int track, int sector);
|
||||||
|
|
||||||
|
public boolean isValidAddress (DiskAddress da);
|
||||||
|
|
||||||
|
public File getFile ();
|
||||||
|
|
||||||
|
public void addActionListener (ActionListener listener);
|
||||||
|
|
||||||
|
public void removeActionListener (ActionListener listener);
|
||||||
|
}
|
12
src/com/bytezone/diskbrowser/disk/DiskAddress.java
Executable file
12
src/com/bytezone/diskbrowser/disk/DiskAddress.java
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
package com.bytezone.diskbrowser.disk;
|
||||||
|
|
||||||
|
public interface DiskAddress extends Comparable<DiskAddress>
|
||||||
|
{
|
||||||
|
public int getBlock ();
|
||||||
|
|
||||||
|
public int getTrack ();
|
||||||
|
|
||||||
|
public int getSector ();
|
||||||
|
|
||||||
|
public Disk getDisk ();
|
||||||
|
}
|
362
src/com/bytezone/diskbrowser/disk/DiskFactory.java
Executable file
362
src/com/bytezone/diskbrowser/disk/DiskFactory.java
Executable file
|
@ -0,0 +1,362 @@
|
||||||
|
package com.bytezone.diskbrowser.disk;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.zip.GZIPInputStream;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.FileFormatException;
|
||||||
|
import com.bytezone.diskbrowser.NuFX;
|
||||||
|
import com.bytezone.diskbrowser.cpm.CPMDisk;
|
||||||
|
import com.bytezone.diskbrowser.dos.DosDisk;
|
||||||
|
import com.bytezone.diskbrowser.infocom.InfocomDisk;
|
||||||
|
import com.bytezone.diskbrowser.pascal.PascalDisk;
|
||||||
|
import com.bytezone.diskbrowser.prodos.ProdosDisk;
|
||||||
|
import com.bytezone.diskbrowser.wizardry.WizardryScenarioDisk;
|
||||||
|
|
||||||
|
public class DiskFactory
|
||||||
|
{
|
||||||
|
private static boolean debug = false;
|
||||||
|
|
||||||
|
private DiskFactory ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FormattedDisk createDisk (File file)
|
||||||
|
{
|
||||||
|
return createDisk (file.getAbsolutePath ());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FormattedDisk createDisk (String path)
|
||||||
|
{
|
||||||
|
if (debug)
|
||||||
|
System.out.println ("Factory : " + path);
|
||||||
|
|
||||||
|
File file = new File (path);
|
||||||
|
if (!file.exists ())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
String suffix = path.substring (path.lastIndexOf (".") + 1).toLowerCase ();
|
||||||
|
Boolean compressed = false;
|
||||||
|
Path p = Paths.get (path);
|
||||||
|
|
||||||
|
if (suffix.equalsIgnoreCase ("sdk"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
NuFX nuFX = new NuFX (p);
|
||||||
|
File tmp = File.createTempFile ("sdk", null);
|
||||||
|
FileOutputStream fos = new FileOutputStream (tmp);
|
||||||
|
fos.write (nuFX.getBuffer ());
|
||||||
|
fos.close ();
|
||||||
|
tmp.deleteOnExit ();
|
||||||
|
file = tmp;
|
||||||
|
suffix = "dsk";
|
||||||
|
compressed = true;
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace ();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
catch (FileFormatException e)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (suffix.equalsIgnoreCase ("gz")) // will be .dsk.gz
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
InputStream in = new GZIPInputStream (new FileInputStream (path));
|
||||||
|
File tmp = File.createTempFile ("gzip", null);
|
||||||
|
FileOutputStream fos = new FileOutputStream (tmp);
|
||||||
|
|
||||||
|
int bytesRead;
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
while ((bytesRead = in.read (buffer)) > 0)
|
||||||
|
fos.write (buffer, 0, bytesRead);
|
||||||
|
|
||||||
|
fos.close ();
|
||||||
|
in.close ();
|
||||||
|
tmp.deleteOnExit ();
|
||||||
|
file = tmp;
|
||||||
|
suffix = "dsk";
|
||||||
|
compressed = true;
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
e.printStackTrace ();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FormattedDisk disk = null;
|
||||||
|
FormattedDisk disk2 = null;
|
||||||
|
|
||||||
|
if (suffix.equals ("hdv"))
|
||||||
|
return checkHardDisk (file);
|
||||||
|
|
||||||
|
if (suffix.equals ("2mg"))
|
||||||
|
return check2mgDisk (file);
|
||||||
|
|
||||||
|
// if (((suffix.equals ("po") || suffix.equals ("dsk")) && file.length () > 116480))
|
||||||
|
if (((suffix.equals ("po") || suffix.equals ("dsk")) && file.length () > 143360))
|
||||||
|
{
|
||||||
|
disk = checkHardDisk (file);
|
||||||
|
if (disk != null)
|
||||||
|
{
|
||||||
|
if (compressed)
|
||||||
|
disk.setOriginalPath (p);
|
||||||
|
return disk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long length = file.length ();
|
||||||
|
if (length != 143360 && length != 116480)
|
||||||
|
{
|
||||||
|
System.out.println ("invalid file length : " + file.length ());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sectors = file.length () == 143360 ? 16 : 13;
|
||||||
|
if (true)
|
||||||
|
{
|
||||||
|
AppleDisk appleDisk = new AppleDisk (file, 35, sectors);
|
||||||
|
long checksum = appleDisk.getBootChecksum ();
|
||||||
|
|
||||||
|
if (checksum == 3176296590L || checksum == 108825457L || checksum == 1439356606L
|
||||||
|
|| checksum == 1550012074L || checksum == 1614602459L || checksum == 940889336L
|
||||||
|
|| checksum == 990032697 || checksum == 2936955085L || checksum == 1348415927L
|
||||||
|
|| checksum == 3340889101L || checksum == 18315788L || checksum == 993895235L)
|
||||||
|
{
|
||||||
|
disk = checkDos (file);
|
||||||
|
disk2 = checkProdos (file);
|
||||||
|
if (disk2 != null && disk != null)
|
||||||
|
disk = new DualDosDisk (disk, disk2);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (checksum == 1737448647L || checksum == 170399908L)
|
||||||
|
{
|
||||||
|
disk = checkProdos (file);
|
||||||
|
disk2 = checkDos (file);
|
||||||
|
if (disk2 != null && disk != null)
|
||||||
|
disk = new DualDosDisk (disk, disk2);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (checksum == 2803644711L || checksum == 3317783349L || checksum == 1728863694L
|
||||||
|
|| checksum == 198094178L)
|
||||||
|
disk = checkPascalDisk (file);
|
||||||
|
|
||||||
|
else if (checksum == 3028642627L || checksum == 2070151659L)
|
||||||
|
disk = checkInfocomDisk (file);
|
||||||
|
|
||||||
|
else if (checksum == 1212926910L || checksum == 1365043894L)
|
||||||
|
disk = checkCPMDisk (file);
|
||||||
|
|
||||||
|
if (disk != null)
|
||||||
|
{
|
||||||
|
if (compressed)
|
||||||
|
disk.setOriginalPath (p);
|
||||||
|
return disk;
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty boot sector
|
||||||
|
if (checksum != 227968344L && false)
|
||||||
|
System.out.println ("Unknown checksum : " + checksum + " : " + path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suffix.equals ("dsk") || suffix.equals ("do") || suffix.equals ("d13"))
|
||||||
|
{
|
||||||
|
disk = checkDos (file);
|
||||||
|
if (disk == null)
|
||||||
|
disk = checkProdos (file);
|
||||||
|
else if (sectors == 16)
|
||||||
|
{
|
||||||
|
if (debug)
|
||||||
|
System.out.println ("Checking DualDos disk");
|
||||||
|
disk2 = checkProdos (file);
|
||||||
|
if (disk2 != null)
|
||||||
|
disk = new DualDosDisk (disk, disk2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (suffix.equals ("po"))
|
||||||
|
{
|
||||||
|
disk = checkProdos (file);
|
||||||
|
if (disk == null)
|
||||||
|
disk = checkDos (file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disk == null)
|
||||||
|
disk = checkPascalDisk (file);
|
||||||
|
|
||||||
|
if (disk == null)
|
||||||
|
{
|
||||||
|
disk2 = checkInfocomDisk (file);
|
||||||
|
if (disk2 != null)
|
||||||
|
disk = disk2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disk == null)
|
||||||
|
disk = new DataDisk (new AppleDisk (file, 35, 16));
|
||||||
|
|
||||||
|
if (debug)
|
||||||
|
System.out.println (" Factory creating disk : "
|
||||||
|
+ disk.getDisk ().getFile ().getAbsolutePath ());
|
||||||
|
|
||||||
|
if (disk != null && compressed)
|
||||||
|
disk.setOriginalPath (p);
|
||||||
|
|
||||||
|
return disk;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static DosDisk checkDos (File file)
|
||||||
|
{
|
||||||
|
if (debug)
|
||||||
|
System.out.println ("Checking DOS disk");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int sectors = file.length () == 143360 ? 16 : 13;
|
||||||
|
AppleDisk disk = new AppleDisk (file, 35, sectors);
|
||||||
|
if (DosDisk.isCorrectFormat (disk))
|
||||||
|
return new DosDisk (disk);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
if (debug)
|
||||||
|
System.out.println ("Not a DOS disk");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ProdosDisk checkProdos (File file)
|
||||||
|
{
|
||||||
|
if (debug)
|
||||||
|
System.out.println ("Checking Prodos disk");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AppleDisk disk = new AppleDisk (file, 35, 8);
|
||||||
|
if (ProdosDisk.isCorrectFormat (disk))
|
||||||
|
return new ProdosDisk (disk);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
if (debug)
|
||||||
|
System.out.println ("Not a Prodos disk");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ProdosDisk checkHardDisk (File file)
|
||||||
|
{
|
||||||
|
if (debug)
|
||||||
|
{
|
||||||
|
System.out.println ("\nChecking Prodos hard disk");
|
||||||
|
System.out.printf ("Total blocks : %f%n", (float) file.length () / 512);
|
||||||
|
System.out.printf ("Total tracks : %f%n", (float) file.length () / 4096);
|
||||||
|
System.out.printf ("File length : %d%n", file.length ());
|
||||||
|
System.out.println ();
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes a sector is 512 bytes
|
||||||
|
if ((file.length () % 512) != 0)
|
||||||
|
{
|
||||||
|
if (debug)
|
||||||
|
System.out.printf ("file length not divisible by 512 : %d%n%", file.length ());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// assumes a track is 4096 bytes
|
||||||
|
// if ((file.length () % 4096) != 0)
|
||||||
|
// {
|
||||||
|
// if (debug)
|
||||||
|
// {
|
||||||
|
// System.out.printf ("file length not divisible by 4096 : %d%n%n", file.length ());
|
||||||
|
// int usableLength = (int) (file.length () / 4096);
|
||||||
|
// }
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AppleDisk disk = new AppleDisk (file, (int) file.length () / 4096, 8);
|
||||||
|
if (ProdosDisk.isCorrectFormat (disk))
|
||||||
|
{
|
||||||
|
if (debug)
|
||||||
|
{
|
||||||
|
System.out.println ("Yay, it's a prodos hard disk");
|
||||||
|
System.out.println (disk);
|
||||||
|
}
|
||||||
|
return new ProdosDisk (disk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.printStackTrace ();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debug)
|
||||||
|
System.out.println ("Not a Prodos hard disk");
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FormattedDisk check2mgDisk (File file)
|
||||||
|
{
|
||||||
|
if (debug)
|
||||||
|
System.out.println ("Checking Prodos 2mg disk");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AppleDisk disk = new AppleDisk (file, 0, 0);
|
||||||
|
if (ProdosDisk.isCorrectFormat (disk))
|
||||||
|
return new ProdosDisk (disk);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
if (debug)
|
||||||
|
System.out.println ("Not a Prodos 2mg disk");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FormattedDisk checkPascalDisk (File file)
|
||||||
|
{
|
||||||
|
if (debug)
|
||||||
|
System.out.println ("Checking Pascal disk");
|
||||||
|
AppleDisk disk = new AppleDisk (file, 35, 8);
|
||||||
|
if (!PascalDisk.isCorrectFormat (disk, debug))
|
||||||
|
return null;
|
||||||
|
if (debug)
|
||||||
|
System.out.println ("Pascal disk OK - Checking Wizardry disk");
|
||||||
|
if (WizardryScenarioDisk.isWizardryFormat (disk, debug))
|
||||||
|
return new WizardryScenarioDisk (disk);
|
||||||
|
if (debug)
|
||||||
|
System.out.println ("Not a Wizardry disk");
|
||||||
|
return new PascalDisk (disk);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static InfocomDisk checkInfocomDisk (File file)
|
||||||
|
{
|
||||||
|
if (debug)
|
||||||
|
System.out.println ("Checking Infocom disk");
|
||||||
|
AppleDisk disk = new AppleDisk (file, 35, 16);
|
||||||
|
if (InfocomDisk.isCorrectFormat (disk))
|
||||||
|
return new InfocomDisk (disk);
|
||||||
|
if (debug)
|
||||||
|
System.out.println ("Not an InfocomDisk disk");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CPMDisk checkCPMDisk (File file)
|
||||||
|
{
|
||||||
|
if (debug)
|
||||||
|
System.out.println ("Checking CPM disk");
|
||||||
|
AppleDisk disk = new AppleDisk (file, 35, 16);
|
||||||
|
if (CPMDisk.isCorrectFormat (disk))
|
||||||
|
return new CPMDisk (disk);
|
||||||
|
if (debug)
|
||||||
|
System.out.println ("Not a CPM disk");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
274
src/com/bytezone/diskbrowser/disk/DualDosDisk.java
Executable file
274
src/com/bytezone/diskbrowser/disk/DualDosDisk.java
Executable file
|
@ -0,0 +1,274 @@
|
||||||
|
package com.bytezone.diskbrowser.disk;
|
||||||
|
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.swing.JTree;
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
import javax.swing.tree.DefaultTreeModel;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.applefile.AbstractFile;
|
||||||
|
import com.bytezone.diskbrowser.applefile.AppleFileSource;
|
||||||
|
import com.bytezone.diskbrowser.gui.DataSource;
|
||||||
|
import com.bytezone.diskbrowser.gui.FileSelectedEvent;
|
||||||
|
import com.bytezone.diskbrowser.gui.FileSelectionListener;
|
||||||
|
|
||||||
|
// Apple Assembly Lines disks are dual-dos
|
||||||
|
|
||||||
|
public class DualDosDisk implements FormattedDisk, FileSelectionListener
|
||||||
|
{
|
||||||
|
private final FormattedDisk[] disks = new FormattedDisk[2];
|
||||||
|
private int currentDisk;
|
||||||
|
private final JTree tree;
|
||||||
|
|
||||||
|
public DualDosDisk (FormattedDisk disk0, FormattedDisk disk1)
|
||||||
|
{
|
||||||
|
String diskName = disk0.getDisk ().getFile ().getName ();
|
||||||
|
String text =
|
||||||
|
"This disk contains both DOS and Prodos files. Isn't that clever?\n\n"
|
||||||
|
+ disk0.getDisk () + "\n" + disk1.getDisk ();
|
||||||
|
DefaultMutableTreeNode root =
|
||||||
|
new DefaultMutableTreeNode (new DefaultAppleFileSource (diskName, text, this));
|
||||||
|
DefaultTreeModel treeModel = new DefaultTreeModel (root);
|
||||||
|
tree = new JTree (treeModel);
|
||||||
|
treeModel.setAsksAllowsChildren (true); // allows empty nodes to appear as folders
|
||||||
|
|
||||||
|
this.disks[0] = disk0;
|
||||||
|
this.disks[1] = disk1;
|
||||||
|
disk0.setParent (this);
|
||||||
|
disk1.setParent (this);
|
||||||
|
|
||||||
|
DefaultMutableTreeNode root0 =
|
||||||
|
(DefaultMutableTreeNode) disk0.getCatalogTree ().getModel ().getRoot ();
|
||||||
|
DefaultMutableTreeNode root1 =
|
||||||
|
(DefaultMutableTreeNode) disk1.getCatalogTree ().getModel ().getRoot ();
|
||||||
|
root.add ((DefaultMutableTreeNode) root0.getChildAt (0));
|
||||||
|
root.add ((DefaultMutableTreeNode) root1.getChildAt (0));
|
||||||
|
|
||||||
|
// TreeNode[] nodes = ((DefaultTreeModel) tree.getModel ()).getPathToRoot (child);
|
||||||
|
// tree.setSelectionPath (new TreePath (nodes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JTree getCatalogTree ()
|
||||||
|
{
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DiskAddress> getFileSectors (int fileNo)
|
||||||
|
{
|
||||||
|
return disks[currentDisk].getFileSectors (fileNo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AppleFileSource> getCatalogList ()
|
||||||
|
{
|
||||||
|
return disks[currentDisk].getCatalogList ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataSource getFormattedSector (DiskAddress da)
|
||||||
|
{
|
||||||
|
return disks[currentDisk].getFormattedSector (da);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SectorType getSectorType (DiskAddress da)
|
||||||
|
{
|
||||||
|
return disks[currentDisk].getSectorType (da);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SectorType getSectorType (int track, int sector)
|
||||||
|
{
|
||||||
|
return disks[currentDisk].getSectorType (track, sector);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SectorType getSectorType (int block)
|
||||||
|
{
|
||||||
|
return disks[currentDisk].getSectorType (block);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SectorType> getSectorTypeList ()
|
||||||
|
{
|
||||||
|
return disks[currentDisk].getSectorTypeList ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Disk getDisk ()
|
||||||
|
{
|
||||||
|
return disks[currentDisk].getDisk ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentDisk (AppleFileSource afs)
|
||||||
|
{
|
||||||
|
FormattedDisk fd = afs.getFormattedDisk ();
|
||||||
|
if (disks[0] == fd && currentDisk != 0)
|
||||||
|
currentDisk = 0;
|
||||||
|
else if (disks[1] == fd && currentDisk != 1)
|
||||||
|
currentDisk = 1;
|
||||||
|
|
||||||
|
System.out.println ("AFS : " + afs);
|
||||||
|
System.out.println ("1. Setting current disk to : " + currentDisk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentDiskNo (int n)
|
||||||
|
{
|
||||||
|
currentDisk = n;
|
||||||
|
System.out.println ("2. Setting current disk to : " + currentDisk);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurrentDiskNo ()
|
||||||
|
{
|
||||||
|
return currentDisk;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FormattedDisk getCurrentDisk ()
|
||||||
|
{
|
||||||
|
return disks[currentDisk];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeFile (AbstractFile file)
|
||||||
|
{
|
||||||
|
disks[currentDisk].writeFile (file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AppleFileSource getCatalog ()
|
||||||
|
{
|
||||||
|
return new DefaultAppleFileSource ("text", disks[0].getCatalog ().getDataSource ()
|
||||||
|
.getText ()
|
||||||
|
+ "\n\n" + disks[1].getCatalog ().getDataSource ().getText (), this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AppleFileSource getFile (String uniqueName)
|
||||||
|
{
|
||||||
|
if (true)
|
||||||
|
return disks[currentDisk].getFile (uniqueName);
|
||||||
|
System.out.println ("Searching for : " + uniqueName);
|
||||||
|
for (int i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
AppleFileSource afs = disks[i].getFile (uniqueName);
|
||||||
|
if (afs != null)
|
||||||
|
{
|
||||||
|
setCurrentDiskNo (i);
|
||||||
|
return afs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int clearOrphans ()
|
||||||
|
{
|
||||||
|
return disks[currentDisk].clearOrphans ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSectorFree (DiskAddress da)
|
||||||
|
{
|
||||||
|
return disks[currentDisk].isSectorFree (da);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void verify ()
|
||||||
|
{
|
||||||
|
disks[currentDisk].verify ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean stillAvailable (DiskAddress da)
|
||||||
|
{
|
||||||
|
return disks[currentDisk].stillAvailable (da);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSectorType (int block, SectorType type)
|
||||||
|
{
|
||||||
|
disks[currentDisk].setSectorType (block, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSectorFree (int block, boolean free)
|
||||||
|
{
|
||||||
|
disks[currentDisk].setSectorFree (block, free);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int falseNegativeBlocks ()
|
||||||
|
{
|
||||||
|
return disks[currentDisk].falseNegativeBlocks ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int falsePositiveBlocks ()
|
||||||
|
{
|
||||||
|
return disks[currentDisk].falsePositiveBlocks ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dimension getGridLayout ()
|
||||||
|
{
|
||||||
|
return disks[currentDisk].getGridLayout ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSectorFree (int block)
|
||||||
|
{
|
||||||
|
return disks[currentDisk].isSectorFree (block);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean stillAvailable (int block)
|
||||||
|
{
|
||||||
|
return disks[currentDisk].stillAvailable (block);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOriginalPath (Path path)
|
||||||
|
{
|
||||||
|
disks[currentDisk].setOriginalPath (path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAbsolutePath ()
|
||||||
|
{
|
||||||
|
return disks[currentDisk].getAbsolutePath ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FormattedDisk getParent ()
|
||||||
|
{
|
||||||
|
return disks[currentDisk].getParent ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setParent (FormattedDisk disk)
|
||||||
|
{
|
||||||
|
disks[currentDisk].setParent (disk);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fileSelected (FileSelectedEvent event)
|
||||||
|
{
|
||||||
|
System.out.println ("In DDD - file selected : " + event.file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSectorFilename (DiskAddress da)
|
||||||
|
{
|
||||||
|
return disks[currentDisk].getSectorFilename (da);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName ()
|
||||||
|
{
|
||||||
|
return disks[currentDisk].getName ();
|
||||||
|
}
|
||||||
|
}
|
79
src/com/bytezone/diskbrowser/disk/FormattedDisk.java
Executable file
79
src/com/bytezone/diskbrowser/disk/FormattedDisk.java
Executable file
|
@ -0,0 +1,79 @@
|
||||||
|
package com.bytezone.diskbrowser.disk;
|
||||||
|
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.swing.JTree;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.applefile.AbstractFile;
|
||||||
|
import com.bytezone.diskbrowser.applefile.AppleFileSource;
|
||||||
|
import com.bytezone.diskbrowser.gui.DataSource;
|
||||||
|
|
||||||
|
public interface FormattedDisk
|
||||||
|
{
|
||||||
|
// Methods to be implemented by the implementer
|
||||||
|
public DataSource getFormattedSector (DiskAddress da);
|
||||||
|
|
||||||
|
public List<DiskAddress> getFileSectors (int fileNo);
|
||||||
|
|
||||||
|
// Methods implemented by AbstractFormattedDisk
|
||||||
|
public JTree getCatalogTree (); // each node is an AppleFileSource
|
||||||
|
|
||||||
|
public List<AppleFileSource> getCatalogList ();
|
||||||
|
|
||||||
|
public void writeFile (AbstractFile file);
|
||||||
|
|
||||||
|
public SectorType getSectorType (int track, int sector);
|
||||||
|
|
||||||
|
public SectorType getSectorType (int block);
|
||||||
|
|
||||||
|
public SectorType getSectorType (DiskAddress da);
|
||||||
|
|
||||||
|
public void setSectorType (int block, SectorType type);
|
||||||
|
|
||||||
|
public String getSectorFilename (DiskAddress da);
|
||||||
|
|
||||||
|
public List<SectorType> getSectorTypeList ();
|
||||||
|
|
||||||
|
public Disk getDisk ();
|
||||||
|
|
||||||
|
public FormattedDisk getParent ();
|
||||||
|
|
||||||
|
public void setParent (FormattedDisk disk);
|
||||||
|
|
||||||
|
public AppleFileSource getCatalog ();
|
||||||
|
|
||||||
|
public AppleFileSource getFile (String uniqueName);
|
||||||
|
|
||||||
|
public int clearOrphans ();
|
||||||
|
|
||||||
|
public void setSectorFree (int block, boolean free);
|
||||||
|
|
||||||
|
public boolean isSectorFree (DiskAddress da);
|
||||||
|
|
||||||
|
public boolean isSectorFree (int block);
|
||||||
|
|
||||||
|
public void verify ();
|
||||||
|
|
||||||
|
public boolean stillAvailable (DiskAddress da);
|
||||||
|
|
||||||
|
public boolean stillAvailable (int block);
|
||||||
|
|
||||||
|
public Dimension getGridLayout ();
|
||||||
|
|
||||||
|
public String getAbsolutePath ();
|
||||||
|
|
||||||
|
public void setOriginalPath (Path path);
|
||||||
|
|
||||||
|
// VTOC flags sector as free, but it is in use by a file
|
||||||
|
public int falsePositiveBlocks ();
|
||||||
|
|
||||||
|
// VTOC flags sector as in use, but no file is using it
|
||||||
|
public int falseNegativeBlocks ();
|
||||||
|
|
||||||
|
public String getName ();
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFileTypeList ()
|
||||||
|
// getFiles (FileType type)
|
48
src/com/bytezone/diskbrowser/disk/SectorList.java
Executable file
48
src/com/bytezone/diskbrowser/disk/SectorList.java
Executable file
|
@ -0,0 +1,48 @@
|
||||||
|
package com.bytezone.diskbrowser.disk;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.applefile.AbstractFile;
|
||||||
|
|
||||||
|
public class SectorList extends AbstractFile
|
||||||
|
{
|
||||||
|
List<DiskAddress> sectors;
|
||||||
|
FormattedDisk formattedDisk;
|
||||||
|
|
||||||
|
public SectorList (FormattedDisk formattedDisk, List<DiskAddress> sectors)
|
||||||
|
{
|
||||||
|
super ("noname", null);
|
||||||
|
|
||||||
|
this.sectors = sectors;
|
||||||
|
this.formattedDisk = formattedDisk;
|
||||||
|
|
||||||
|
Disk disk = formattedDisk.getDisk ();
|
||||||
|
int ptr = 0;
|
||||||
|
buffer = new byte[sectors.size () * disk.getBlockSize ()];
|
||||||
|
|
||||||
|
for (DiskAddress da : sectors)
|
||||||
|
{
|
||||||
|
byte[] tempBuffer = disk.readSector (da);
|
||||||
|
System.arraycopy (tempBuffer, 0, buffer, ptr, disk.getBlockSize ());
|
||||||
|
ptr += disk.getBlockSize ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getText ()
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ("Block Sector Type Owner\n");
|
||||||
|
text.append ("----- ------------------ ---------------------------------------------\n");
|
||||||
|
|
||||||
|
for (DiskAddress da : sectors)
|
||||||
|
{
|
||||||
|
SectorType sectorType = formattedDisk.getSectorType (da);
|
||||||
|
String owner = formattedDisk.getSectorFilename (da);
|
||||||
|
if (owner == null)
|
||||||
|
owner = "";
|
||||||
|
text.append (String.format (" %04X %-18s %s%n", da.getBlock (), sectorType.name, owner));
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
65
src/com/bytezone/diskbrowser/disk/SectorListConverter.java
Normal file
65
src/com/bytezone/diskbrowser/disk/SectorListConverter.java
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package com.bytezone.diskbrowser.disk;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class SectorListConverter
|
||||||
|
{
|
||||||
|
public final List<DiskAddress> sectors;
|
||||||
|
public final String sectorText;
|
||||||
|
|
||||||
|
public SectorListConverter (String text, Disk disk)
|
||||||
|
{
|
||||||
|
sectors = new ArrayList<DiskAddress> ();
|
||||||
|
sectorText = text;
|
||||||
|
|
||||||
|
String[] blocks = text.split (";");
|
||||||
|
for (String s : blocks)
|
||||||
|
{
|
||||||
|
int pos = s.indexOf ('-');
|
||||||
|
if (pos > 0)
|
||||||
|
{
|
||||||
|
int lo = Integer.parseInt (s.substring (0, pos));
|
||||||
|
int hi = Integer.parseInt (s.substring (pos + 1));
|
||||||
|
for (int i = lo; i <= hi; i++)
|
||||||
|
sectors.add (disk.getDiskAddress (i));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
sectors.add (disk.getDiskAddress (Integer.parseInt (s)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SectorListConverter (List<DiskAddress> sectors)
|
||||||
|
{
|
||||||
|
this.sectors = sectors;
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
|
||||||
|
int firstBlock = -2;
|
||||||
|
int runLength = 0;
|
||||||
|
|
||||||
|
for (DiskAddress da : sectors)
|
||||||
|
{
|
||||||
|
if (da.getBlock () == firstBlock + 1 + runLength)
|
||||||
|
{
|
||||||
|
++runLength;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstBlock >= 0)
|
||||||
|
addToText (text, firstBlock, runLength);
|
||||||
|
|
||||||
|
firstBlock = da.getBlock ();
|
||||||
|
runLength = 0;
|
||||||
|
}
|
||||||
|
addToText (text, firstBlock, runLength);
|
||||||
|
sectorText = text.deleteCharAt (text.length () - 1).toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addToText (StringBuilder text, int firstBlock, int runLength)
|
||||||
|
{
|
||||||
|
if (runLength == 0)
|
||||||
|
text.append (firstBlock + ";");
|
||||||
|
else
|
||||||
|
text.append (firstBlock + "-" + (firstBlock + runLength) + ";");
|
||||||
|
}
|
||||||
|
}
|
21
src/com/bytezone/diskbrowser/disk/SectorType.java
Executable file
21
src/com/bytezone/diskbrowser/disk/SectorType.java
Executable file
|
@ -0,0 +1,21 @@
|
||||||
|
package com.bytezone.diskbrowser.disk;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
|
||||||
|
public class SectorType
|
||||||
|
{
|
||||||
|
public final String name;
|
||||||
|
public final Color colour;
|
||||||
|
|
||||||
|
public SectorType (String name, Color colour)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.colour = colour;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
return String.format ("[SectorType : %s, %s]", name, colour);
|
||||||
|
}
|
||||||
|
}
|
277
src/com/bytezone/diskbrowser/dos/AbstractCatalogEntry.java
Normal file
277
src/com/bytezone/diskbrowser/dos/AbstractCatalogEntry.java
Normal file
|
@ -0,0 +1,277 @@
|
||||||
|
package com.bytezone.diskbrowser.dos;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
import com.bytezone.diskbrowser.applefile.*;
|
||||||
|
import com.bytezone.diskbrowser.disk.Disk;
|
||||||
|
import com.bytezone.diskbrowser.disk.DiskAddress;
|
||||||
|
import com.bytezone.diskbrowser.disk.FormattedDisk;
|
||||||
|
import com.bytezone.diskbrowser.dos.DosDisk.FileType;
|
||||||
|
import com.bytezone.diskbrowser.gui.DataSource;
|
||||||
|
|
||||||
|
abstract class AbstractCatalogEntry implements AppleFileSource
|
||||||
|
{
|
||||||
|
Disk disk;
|
||||||
|
DosDisk dosDisk;
|
||||||
|
String name;
|
||||||
|
String catalogName;
|
||||||
|
|
||||||
|
FileType fileType;
|
||||||
|
int reportedSize;
|
||||||
|
boolean locked;
|
||||||
|
protected DataSource appleFile;
|
||||||
|
|
||||||
|
protected DiskAddress catalogSectorDA;
|
||||||
|
protected final List<DiskAddress> dataSectors = new ArrayList<DiskAddress> ();
|
||||||
|
protected final List<DiskAddress> tsSectors = new ArrayList<DiskAddress> ();
|
||||||
|
|
||||||
|
public AbstractCatalogEntry (DosDisk dosDisk, DiskAddress catalogSector, byte[] entryBuffer)
|
||||||
|
{
|
||||||
|
this.dosDisk = dosDisk;
|
||||||
|
this.disk = dosDisk.getDisk ();
|
||||||
|
this.catalogSectorDA = catalogSector;
|
||||||
|
reportedSize = HexFormatter.intValue (entryBuffer[33], entryBuffer[34]);
|
||||||
|
int type = entryBuffer[2] & 0xFF;
|
||||||
|
locked = (type & 0x80) > 0;
|
||||||
|
this.disk = dosDisk.getDisk ();
|
||||||
|
|
||||||
|
if ((type & 0x7F) == 0)
|
||||||
|
fileType = FileType.Text;
|
||||||
|
else if ((type & 0x01) > 0)
|
||||||
|
fileType = FileType.IntegerBasic;
|
||||||
|
else if ((type & 0x02) > 0)
|
||||||
|
fileType = FileType.ApplesoftBasic;
|
||||||
|
else if ((type & 0x04) > 0)
|
||||||
|
fileType = FileType.Binary;
|
||||||
|
else if ((type & 0x08) > 0)
|
||||||
|
fileType = FileType.SS;
|
||||||
|
else if ((type & 0x10) > 0)
|
||||||
|
fileType = FileType.Relocatable;
|
||||||
|
else if ((type & 0x20) > 0)
|
||||||
|
fileType = FileType.AA;
|
||||||
|
else if ((type & 0x40) > 0)
|
||||||
|
fileType = FileType.BB;
|
||||||
|
else
|
||||||
|
System.out.println ("Unknown file type : " + (type & 0x7F));
|
||||||
|
|
||||||
|
name = getName ("", entryBuffer);
|
||||||
|
// CATALOG command only formats the LO byte - see Beneath Apple DOS pp4-6
|
||||||
|
String base =
|
||||||
|
String.format ("%s%s %03d ", (locked) ? "*" : " ", getFileType (),
|
||||||
|
(entryBuffer[33] & 0xFF));
|
||||||
|
catalogName = getName (base, entryBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getName (String base, byte[] buffer)
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder (base);
|
||||||
|
int max = buffer[0] == (byte) 0xFF ? 32 : 33;
|
||||||
|
for (int i = 3; i < max; i++)
|
||||||
|
{
|
||||||
|
int c = buffer[i] & 0xFF;
|
||||||
|
if (c == 136 && !base.isEmpty ()) // allow backspaces
|
||||||
|
{
|
||||||
|
if (text.length () > 0)
|
||||||
|
text.deleteCharAt (text.length () - 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c > 127)
|
||||||
|
c -= c < 160 ? 64 : 128;
|
||||||
|
if (c < 32)
|
||||||
|
text.append ("^" + (char) (c + 64)); // non-printable ascii
|
||||||
|
else
|
||||||
|
text.append ((char) c); // standard ascii
|
||||||
|
}
|
||||||
|
while (text.length () > 0 && text.charAt (text.length () - 1) == ' ')
|
||||||
|
text.deleteCharAt (text.length () - 1); // rtrim()
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getFileType ()
|
||||||
|
{
|
||||||
|
switch (this.fileType)
|
||||||
|
{
|
||||||
|
case Text:
|
||||||
|
return "T";
|
||||||
|
case IntegerBasic:
|
||||||
|
return "I";
|
||||||
|
case ApplesoftBasic:
|
||||||
|
return "A";
|
||||||
|
case Binary:
|
||||||
|
return "B";
|
||||||
|
case SS: // what is this?
|
||||||
|
return "S";
|
||||||
|
case Relocatable:
|
||||||
|
return "R";
|
||||||
|
case AA: // what is this?
|
||||||
|
return "A";
|
||||||
|
case BB: // what is this?
|
||||||
|
return "B";
|
||||||
|
default:
|
||||||
|
System.out.println ("Unknown file type : " + fileType);
|
||||||
|
return "?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// maybe this should be in the FormattedDisk
|
||||||
|
// maybe DiskAddress should have a 'valid' flag
|
||||||
|
protected DiskAddress getValidAddress (byte[] buffer, int offset)
|
||||||
|
{
|
||||||
|
if (disk.isValidAddress (buffer[offset], buffer[offset + 1]))
|
||||||
|
return disk.getDiskAddress (buffer[offset], buffer[offset + 1]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataSource getDataSource ()
|
||||||
|
{
|
||||||
|
if (appleFile != null)
|
||||||
|
return appleFile;
|
||||||
|
|
||||||
|
byte[] buffer = disk.readSectors (dataSectors);
|
||||||
|
int reportedLength;
|
||||||
|
if (buffer.length == 0)
|
||||||
|
{
|
||||||
|
appleFile = new DefaultAppleFile (name, buffer);
|
||||||
|
return appleFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
switch (this.fileType)
|
||||||
|
{
|
||||||
|
case Text:
|
||||||
|
if (VisicalcFile.isVisicalcFile (buffer))
|
||||||
|
appleFile = new VisicalcFile (name, buffer);
|
||||||
|
else
|
||||||
|
appleFile = new TextFile (name, buffer);
|
||||||
|
break;
|
||||||
|
case IntegerBasic:
|
||||||
|
reportedLength = HexFormatter.intValue (buffer[0], buffer[1]);
|
||||||
|
byte[] exactBuffer = new byte[reportedLength];
|
||||||
|
System.arraycopy (buffer, 2, exactBuffer, 0, reportedLength);
|
||||||
|
appleFile = new IntegerBasicProgram (name, exactBuffer);
|
||||||
|
break;
|
||||||
|
case ApplesoftBasic:
|
||||||
|
reportedLength = HexFormatter.intValue (buffer[0], buffer[1]);
|
||||||
|
exactBuffer = new byte[reportedLength];
|
||||||
|
if (reportedLength > buffer.length)
|
||||||
|
reportedLength = buffer.length - 2;
|
||||||
|
System.arraycopy (buffer, 2, exactBuffer, 0, reportedLength);
|
||||||
|
// appleFile = new ApplesoftBasicProgram (name, exactBuffer);
|
||||||
|
appleFile = new BasicProgram (name, exactBuffer);
|
||||||
|
break;
|
||||||
|
case Binary: // binary file
|
||||||
|
case Relocatable: // relocatable binary file
|
||||||
|
if (buffer.length == 0)
|
||||||
|
appleFile = new AssemblerProgram (name, buffer, 0);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int loadAddress = HexFormatter.intValue (buffer[0], buffer[1]);
|
||||||
|
reportedLength = HexFormatter.intValue (buffer[2], buffer[3]);
|
||||||
|
if (reportedLength == 0)
|
||||||
|
{
|
||||||
|
System.out.println (name.trim () + " reported length : 0 - reverting to "
|
||||||
|
+ (buffer.length - 4));
|
||||||
|
reportedLength = buffer.length - 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// buffer is a multiple of the block size, so it usually needs to be reduced
|
||||||
|
if ((reportedLength + 4) <= buffer.length)
|
||||||
|
exactBuffer = new byte[reportedLength];
|
||||||
|
else
|
||||||
|
exactBuffer = new byte[buffer.length - 4]; // reported length is too long
|
||||||
|
System.arraycopy (buffer, 4, exactBuffer, 0, exactBuffer.length);
|
||||||
|
|
||||||
|
if (ShapeTable.isShapeTable (exactBuffer))
|
||||||
|
appleFile = new ShapeTable (name, exactBuffer);
|
||||||
|
else if (HiResImage.isGif (exactBuffer))
|
||||||
|
appleFile = new HiResImage (name, exactBuffer);
|
||||||
|
else if (loadAddress == 0x2000 || loadAddress == 0x4000)
|
||||||
|
{
|
||||||
|
if ((reportedLength > 0x1F00 && reportedLength <= 0x4000)
|
||||||
|
|| ((name.equals ("FLY LOGO") && reportedLength == 0x14FA)))
|
||||||
|
appleFile = new HiResImage (name, exactBuffer);
|
||||||
|
// else if
|
||||||
|
// appleFile = new HiResImage (name, unscrunch (exactBuffer));
|
||||||
|
else
|
||||||
|
appleFile = new AssemblerProgram (name, exactBuffer, loadAddress);
|
||||||
|
}
|
||||||
|
else if (name.endsWith (".S"))
|
||||||
|
appleFile = new MerlinSource (name, exactBuffer);
|
||||||
|
else
|
||||||
|
appleFile = new AssemblerProgram (name, exactBuffer, loadAddress);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SS: // what is this?
|
||||||
|
System.out.println ("SS file");
|
||||||
|
appleFile = new DefaultAppleFile (name, buffer);
|
||||||
|
break;
|
||||||
|
case AA: // what is this?
|
||||||
|
System.out.println ("AA file");
|
||||||
|
appleFile = new DefaultAppleFile (name, buffer);
|
||||||
|
break;
|
||||||
|
case BB: // what is this?
|
||||||
|
int loadAddress = HexFormatter.intValue (buffer[0], buffer[1]);
|
||||||
|
reportedLength = HexFormatter.intValue (buffer[2], buffer[3]);
|
||||||
|
exactBuffer = new byte[reportedLength];
|
||||||
|
System.arraycopy (buffer, 4, exactBuffer, 0, reportedLength);
|
||||||
|
appleFile = new SimpleText2 (name, exactBuffer, loadAddress);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
System.out.println ("Unknown file type : " + fileType);
|
||||||
|
appleFile = new DefaultAppleFile (name, buffer);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
appleFile = new ErrorMessageFile (name, buffer, e);
|
||||||
|
e.printStackTrace ();
|
||||||
|
}
|
||||||
|
return appleFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean contains (DiskAddress da)
|
||||||
|
{
|
||||||
|
for (DiskAddress sector : tsSectors)
|
||||||
|
if (sector.compareTo (da) == 0)
|
||||||
|
return true;
|
||||||
|
for (DiskAddress sector : dataSectors)
|
||||||
|
// random access files may have gaps, and thus null sectors
|
||||||
|
if (sector != null && sector.compareTo (da) == 0)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUniqueName ()
|
||||||
|
{
|
||||||
|
// this might not be unique if the file has been deleted
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FormattedDisk getFormattedDisk ()
|
||||||
|
{
|
||||||
|
return dosDisk;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DiskAddress> getSectors ()
|
||||||
|
{
|
||||||
|
List<DiskAddress> sectors = new ArrayList<DiskAddress> ();
|
||||||
|
sectors.add (catalogSectorDA);
|
||||||
|
sectors.addAll (tsSectors);
|
||||||
|
sectors.addAll (dataSectors);
|
||||||
|
return sectors;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
return catalogName;
|
||||||
|
}
|
||||||
|
}
|
135
src/com/bytezone/diskbrowser/dos/CatalogEntry.java
Normal file
135
src/com/bytezone/diskbrowser/dos/CatalogEntry.java
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
package com.bytezone.diskbrowser.dos;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
import com.bytezone.diskbrowser.disk.DiskAddress;
|
||||||
|
import com.bytezone.diskbrowser.dos.DosDisk.FileType;
|
||||||
|
|
||||||
|
class CatalogEntry extends AbstractCatalogEntry
|
||||||
|
{
|
||||||
|
int textFileGaps;
|
||||||
|
int length;
|
||||||
|
int address;
|
||||||
|
|
||||||
|
public CatalogEntry (DosDisk dosDisk, DiskAddress catalogSector, byte[] entryBuffer)
|
||||||
|
{
|
||||||
|
super (dosDisk, catalogSector, entryBuffer); // build lists of ts and data sectors
|
||||||
|
if (reportedSize > 0 && disk.isValidAddress (entryBuffer[0], entryBuffer[1]))
|
||||||
|
{
|
||||||
|
// Get address of first TS-list sector
|
||||||
|
DiskAddress da = disk.getDiskAddress (entryBuffer[0], entryBuffer[1]);
|
||||||
|
|
||||||
|
// Loop through all TS-list sectors
|
||||||
|
loop: while (da.getBlock () > 0)
|
||||||
|
{
|
||||||
|
if (dosDisk.stillAvailable (da))
|
||||||
|
dosDisk.sectorTypes[da.getBlock ()] = dosDisk.tsListSector;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
System.out.print ("Attempt to assign TS sector to occupied sector : " + da);
|
||||||
|
System.out.println (" from " + name);
|
||||||
|
}
|
||||||
|
tsSectors.add (da);
|
||||||
|
byte[] sectorBuffer = disk.readSector (da);
|
||||||
|
|
||||||
|
int startPtr = 12;
|
||||||
|
// the tsList *should* start at 0xC0, but some disks start in the unused bytes
|
||||||
|
if (false)
|
||||||
|
for (int i = 7; i < startPtr; i++)
|
||||||
|
if (sectorBuffer[i] != 0)
|
||||||
|
{
|
||||||
|
startPtr = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = startPtr, max = disk.getBlockSize (); i < max; i += 2)
|
||||||
|
{
|
||||||
|
da = getValidAddress (sectorBuffer, i);
|
||||||
|
if (da == null)
|
||||||
|
{
|
||||||
|
System.out.print ("T/S list in sector " + i);
|
||||||
|
System.out.printf (" contains an invalid address : %02X, %02X (file %s)%n",
|
||||||
|
sectorBuffer[i], sectorBuffer[i + 1], name.trim ());
|
||||||
|
break loop;
|
||||||
|
}
|
||||||
|
if (da.getBlock () == 0)
|
||||||
|
{
|
||||||
|
if (fileType != FileType.Text)
|
||||||
|
break;
|
||||||
|
++textFileGaps;
|
||||||
|
dataSectors.add (null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dataSectors.add (da);
|
||||||
|
if (dosDisk.stillAvailable (da))
|
||||||
|
dosDisk.sectorTypes[da.getBlock ()] = dosDisk.dataSector;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
System.out.print ("Attempt to assign Data sector to occupied sector : " + da);
|
||||||
|
System.out.println (" from " + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
da = getValidAddress (sectorBuffer, 1);
|
||||||
|
if (da == null)
|
||||||
|
{
|
||||||
|
System.out.print ("Next T/S list in sector " + da);
|
||||||
|
System.out.printf (" is invalid : %02X, %02X%n", sectorBuffer[1], sectorBuffer[2]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove trailing empty sectors
|
||||||
|
if (fileType == FileType.Text)
|
||||||
|
{
|
||||||
|
while (dataSectors.size () > 0)
|
||||||
|
{
|
||||||
|
DiskAddress da = dataSectors.get (dataSectors.size () - 1);
|
||||||
|
if (da == null)
|
||||||
|
{
|
||||||
|
dataSectors.remove (dataSectors.size () - 1);
|
||||||
|
--textFileGaps;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the file length
|
||||||
|
if (dataSectors.size () > 0 && fileType != FileType.Text)
|
||||||
|
{
|
||||||
|
byte[] buffer = disk.readSector (dataSectors.get (0));
|
||||||
|
switch (fileType)
|
||||||
|
{
|
||||||
|
case IntegerBasic:
|
||||||
|
// length = HexFormatter.intValue (buffer[0], buffer[1]);
|
||||||
|
// break;
|
||||||
|
case ApplesoftBasic:
|
||||||
|
length = HexFormatter.intValue (buffer[0], buffer[1]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
address = HexFormatter.intValue (buffer[0], buffer[1]);
|
||||||
|
length = HexFormatter.intValue (buffer[2], buffer[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDetails ()
|
||||||
|
{
|
||||||
|
int actualSize = dataSectors.size () + tsSectors.size () - textFileGaps;
|
||||||
|
String addressText = address == 0 ? "" : String.format ("$%4X", address);
|
||||||
|
String lengthText = length == 0 ? "" : String.format ("$%4X %,6d", length, length);
|
||||||
|
String message = "";
|
||||||
|
String lockedFlag = (locked) ? "*" : " ";
|
||||||
|
if (reportedSize != actualSize)
|
||||||
|
message += "Bad size (" + reportedSize + ") ";
|
||||||
|
if (dataSectors.size () == 0)
|
||||||
|
message += "No data ";
|
||||||
|
return String.format ("%1s %1s %03d %-30.30s %-5s %-13s %2d %3d %s", lockedFlag,
|
||||||
|
getFileType (), actualSize, name, addressText, lengthText,
|
||||||
|
tsSectors.size (), (dataSectors.size () - textFileGaps),
|
||||||
|
message.trim ());
|
||||||
|
}
|
||||||
|
}
|
105
src/com/bytezone/diskbrowser/dos/DeletedCatalogEntry.java
Normal file
105
src/com/bytezone/diskbrowser/dos/DeletedCatalogEntry.java
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
package com.bytezone.diskbrowser.dos;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.applefile.DefaultAppleFile;
|
||||||
|
import com.bytezone.diskbrowser.disk.DiskAddress;
|
||||||
|
import com.bytezone.diskbrowser.gui.DataSource;
|
||||||
|
|
||||||
|
class DeletedCatalogEntry extends AbstractCatalogEntry
|
||||||
|
{
|
||||||
|
boolean allSectorsAvailable = true;
|
||||||
|
boolean debug = false;
|
||||||
|
|
||||||
|
public DeletedCatalogEntry (DosDisk dosDisk, DiskAddress catalogSector, byte[] entryBuffer)
|
||||||
|
{
|
||||||
|
super (dosDisk, catalogSector, entryBuffer);
|
||||||
|
// reportedSize = HexFormatter.intValue (entryBuffer[33], entryBuffer[34]);
|
||||||
|
if (debug)
|
||||||
|
{
|
||||||
|
System.out.println ("Deleted file : " + name);
|
||||||
|
System.out.printf ("Reported size : %d%n", reportedSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reportedSize <= 1 || !disk.isValidAddress (entryBuffer[32], entryBuffer[1]))
|
||||||
|
{
|
||||||
|
if (debug)
|
||||||
|
System.out.println ("invalid catalog entry");
|
||||||
|
allSectorsAvailable = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get address of first TS-list sector
|
||||||
|
DiskAddress da = disk.getDiskAddress (entryBuffer[32], entryBuffer[1]);
|
||||||
|
int totalBlocks = 0;
|
||||||
|
|
||||||
|
// Loop through all TS-list sectors
|
||||||
|
loop: while (da.getBlock () > 0)
|
||||||
|
{
|
||||||
|
if (!dosDisk.stillAvailable (da))
|
||||||
|
{
|
||||||
|
allSectorsAvailable = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tsSectors.add (da);
|
||||||
|
totalBlocks++;
|
||||||
|
|
||||||
|
byte[] sectorBuffer = disk.readSector (da);
|
||||||
|
for (int i = 12, max = disk.getBlockSize (); i < max; i += 2)
|
||||||
|
{
|
||||||
|
da = getValidAddress (sectorBuffer, i);
|
||||||
|
if (da == null)
|
||||||
|
break loop;
|
||||||
|
if (da.getBlock () > 0 && debug)
|
||||||
|
System.out.println (da);
|
||||||
|
|
||||||
|
if (da.getBlock () > 0)
|
||||||
|
{
|
||||||
|
if (!dosDisk.stillAvailable (da))
|
||||||
|
{
|
||||||
|
allSectorsAvailable = false;
|
||||||
|
break loop;
|
||||||
|
}
|
||||||
|
dataSectors.add (da);
|
||||||
|
totalBlocks++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
da = getValidAddress (sectorBuffer, 1);
|
||||||
|
|
||||||
|
if (da == null)
|
||||||
|
{
|
||||||
|
System.out.printf ("Next T/S list in sector %s is invalid : %02X, %02X%n", da,
|
||||||
|
sectorBuffer[1], sectorBuffer[2]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (debug)
|
||||||
|
System.out.printf ("Total blocks recoverable : %d%n", totalBlocks);
|
||||||
|
if (totalBlocks != reportedSize)
|
||||||
|
allSectorsAvailable = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getUniqueName ()
|
||||||
|
{
|
||||||
|
// name might not be unique if the file has been deleted
|
||||||
|
return "!" + name;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataSource getDataSource ()
|
||||||
|
{
|
||||||
|
if (!allSectorsAvailable && appleFile == null)
|
||||||
|
{
|
||||||
|
DefaultAppleFile daf = new DefaultAppleFile (name, null);
|
||||||
|
daf.setText ("This file cannot be recovered");
|
||||||
|
appleFile = daf;
|
||||||
|
}
|
||||||
|
return super.getDataSource ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDetails ()
|
||||||
|
{
|
||||||
|
return String.format ("%-30s %s", name, allSectorsAvailable ? "Recoverable"
|
||||||
|
: "Not recoverable");
|
||||||
|
}
|
||||||
|
}
|
105
src/com/bytezone/diskbrowser/dos/DosCatalogSector.java
Executable file
105
src/com/bytezone/diskbrowser/dos/DosCatalogSector.java
Executable file
|
@ -0,0 +1,105 @@
|
||||||
|
package com.bytezone.diskbrowser.dos;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
import com.bytezone.diskbrowser.disk.AbstractSector;
|
||||||
|
import com.bytezone.diskbrowser.disk.Disk;
|
||||||
|
|
||||||
|
class DosCatalogSector extends AbstractSector
|
||||||
|
{
|
||||||
|
private static final String[] fileTypes = { "Text file", "Integer Basic program",
|
||||||
|
"Applesoft Basic program", "Binary file",
|
||||||
|
"SS file", "Relocatable file", "AA file",
|
||||||
|
"BB file" };
|
||||||
|
private static int CATALOG_ENTRY_SIZE = 35;
|
||||||
|
|
||||||
|
public DosCatalogSector (Disk disk, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (disk, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String createText ()
|
||||||
|
{
|
||||||
|
StringBuilder text = getHeader ("Catalog Sector");
|
||||||
|
addText (text, buffer, 0, 1, "Not used");
|
||||||
|
addText (text, buffer, 1, 2, "Next catalog track/sector");
|
||||||
|
addText (text, buffer, 3, 4, "Not used");
|
||||||
|
addText (text, buffer, 7, 4, "Not used");
|
||||||
|
|
||||||
|
for (int i = 11; i <= 255; i += CATALOG_ENTRY_SIZE)
|
||||||
|
{
|
||||||
|
text.append ("\n");
|
||||||
|
if (true)
|
||||||
|
{
|
||||||
|
if (buffer[i] == (byte) 0xFF)
|
||||||
|
{
|
||||||
|
addText (text,
|
||||||
|
buffer,
|
||||||
|
i + 0,
|
||||||
|
2,
|
||||||
|
"DEL: file @ " + HexFormatter.format2 (buffer[i + 32]) + " "
|
||||||
|
+ HexFormatter.format2 (buffer[i + 1]));
|
||||||
|
addText (text, buffer, i + 2, 1, "DEL: File type " + getType (buffer[i + 2]));
|
||||||
|
if (buffer[i + 3] == 0)
|
||||||
|
addText (text, buffer, i + 3, 4, "");
|
||||||
|
else
|
||||||
|
addText (text, buffer, i + 3, 4, "DEL: " + getName (buffer, i));
|
||||||
|
addTextAndDecimal (text, buffer, i + 33, 2, "DEL: Sector count");
|
||||||
|
}
|
||||||
|
else if (buffer[i] > 0)
|
||||||
|
{
|
||||||
|
addText (text, buffer, i + 0, 2, "TS list track/sector");
|
||||||
|
addText (text, buffer, i + 2, 1, "File type " + getType (buffer[i + 2]));
|
||||||
|
if (buffer[i + 3] == 0)
|
||||||
|
addText (text, buffer, i + 3, 4, "");
|
||||||
|
else
|
||||||
|
addText (text, buffer, i + 3, 4, getName (buffer, i));
|
||||||
|
addTextAndDecimal (text, buffer, i + 33, 2, "Sector count");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
addText (text, buffer, i + 0, 2, "");
|
||||||
|
addText (text, buffer, i + 2, 1, "");
|
||||||
|
addText (text, buffer, i + 3, 4, "");
|
||||||
|
addText (text, buffer, i + 33, 2, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
text.deleteCharAt (text.length () - 1);
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getName (byte[] buffer, int offset)
|
||||||
|
{
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
int max = buffer[offset] == (byte) 0xFF ? 32 : 33;
|
||||||
|
for (int i = 3; i < max; i++)
|
||||||
|
{
|
||||||
|
int c = HexFormatter.intValue (buffer[i + offset]);
|
||||||
|
if (c == 136)
|
||||||
|
{
|
||||||
|
if (text.length () > 0)
|
||||||
|
text.deleteCharAt (text.length () - 1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (c > 127)
|
||||||
|
c -= c < 160 ? 64 : 128;
|
||||||
|
if (c < 32) // non-printable
|
||||||
|
text.append ("^" + (char) (c + 64));
|
||||||
|
else
|
||||||
|
text.append ((char) c); // standard ascii
|
||||||
|
}
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getType (byte value)
|
||||||
|
{
|
||||||
|
int type = value & 0x7F;
|
||||||
|
boolean locked = (value & 0x80) > 0;
|
||||||
|
int val = 7;
|
||||||
|
for (int i = 64; i > type; val--, i /= 2)
|
||||||
|
;
|
||||||
|
return "(" + fileTypes[val] + (locked ? ", locked)" : ", unlocked)");
|
||||||
|
}
|
||||||
|
}
|
407
src/com/bytezone/diskbrowser/dos/DosDisk.java
Executable file
407
src/com/bytezone/diskbrowser/dos/DosDisk.java
Executable file
|
@ -0,0 +1,407 @@
|
||||||
|
package com.bytezone.diskbrowser.dos;
|
||||||
|
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.applefile.AppleFileSource;
|
||||||
|
import com.bytezone.diskbrowser.applefile.BootSector;
|
||||||
|
import com.bytezone.diskbrowser.disk.*;
|
||||||
|
import com.bytezone.diskbrowser.gui.DataSource;
|
||||||
|
|
||||||
|
public class DosDisk extends AbstractFormattedDisk
|
||||||
|
{
|
||||||
|
private static final int ENTRY_SIZE = 35;
|
||||||
|
private static final int CATALOG_TRACK = 17;
|
||||||
|
|
||||||
|
// private static final boolean CHECK_SELF_POINTER = true;
|
||||||
|
|
||||||
|
private final DosVTOCSector dosVTOCSector;
|
||||||
|
private final Color green = new Color (0, 200, 0);
|
||||||
|
private final DefaultMutableTreeNode volumeNode;
|
||||||
|
|
||||||
|
private int freeSectors;
|
||||||
|
private int usedSectors;
|
||||||
|
|
||||||
|
public final SectorType vtocSector = new SectorType ("VTOC", Color.magenta);
|
||||||
|
public final SectorType catalogSector = new SectorType ("Catalog", green);
|
||||||
|
public final SectorType tsListSector = new SectorType ("TSList", Color.blue);
|
||||||
|
public final SectorType dataSector = new SectorType ("Data", Color.red);
|
||||||
|
public final SectorType dosSector = new SectorType ("DOS", Color.lightGray);
|
||||||
|
|
||||||
|
protected List<AppleFileSource> deletedFileEntries = new ArrayList<AppleFileSource> ();
|
||||||
|
|
||||||
|
enum FileType
|
||||||
|
{
|
||||||
|
Text, ApplesoftBasic, IntegerBasic, Binary, Relocatable, SS, AA, BB
|
||||||
|
}
|
||||||
|
|
||||||
|
public DosDisk (Disk disk)
|
||||||
|
{
|
||||||
|
super (disk);
|
||||||
|
|
||||||
|
sectorTypesList.add (dosSector);
|
||||||
|
sectorTypesList.add (vtocSector);
|
||||||
|
sectorTypesList.add (catalogSector);
|
||||||
|
sectorTypesList.add (tsListSector);
|
||||||
|
sectorTypesList.add (dataSector);
|
||||||
|
|
||||||
|
byte[] sectorBuffer = disk.readSector (0, 0); // Boot sector
|
||||||
|
bootSector = new BootSector (disk, sectorBuffer, "DOS");
|
||||||
|
|
||||||
|
sectorBuffer = disk.readSector (CATALOG_TRACK, 0); // VTOC
|
||||||
|
dosVTOCSector = new DosVTOCSector (this, disk, sectorBuffer);
|
||||||
|
|
||||||
|
DiskAddress catalogStart = disk.getDiskAddress (sectorBuffer[1], sectorBuffer[2]);
|
||||||
|
|
||||||
|
if (dosVTOCSector.sectorSize != disk.getBlockSize ())
|
||||||
|
System.out.println ("Invalid sector size : " + dosVTOCSector.sectorSize);
|
||||||
|
if (dosVTOCSector.maxSectors != disk.getSectorsPerTrack ())
|
||||||
|
System.out.println ("Invalid sectors per track : " + dosVTOCSector.maxSectors);
|
||||||
|
|
||||||
|
sectorTypes[CATALOG_TRACK * dosVTOCSector.maxSectors] = vtocSector;
|
||||||
|
|
||||||
|
// assert (maxTracks == disk.getTotalTracks ());
|
||||||
|
// assert (dosVTOCSector.maxSectors == disk.getSectorsPerTrack ());
|
||||||
|
// assert (sectorSize == disk.getBlockSize ()); HFSAssembler.dsk fails this
|
||||||
|
// assert (catalogStart.getTrack () == CATALOG_TRACK);
|
||||||
|
|
||||||
|
// arcboot.dsk starts the catalog at 17/13
|
||||||
|
// assert (catalogStart.getSector () == 15);
|
||||||
|
|
||||||
|
// build list of CatalogEntry objects
|
||||||
|
DefaultMutableTreeNode rootNode = getCatalogTreeRoot ();
|
||||||
|
volumeNode = new DefaultMutableTreeNode ();
|
||||||
|
DefaultMutableTreeNode deletedFilesNode = new DefaultMutableTreeNode ();
|
||||||
|
rootNode.add (volumeNode);
|
||||||
|
|
||||||
|
// flag the catalog sectors before any file mistakenly grabs them
|
||||||
|
DiskAddress da = disk.getDiskAddress (catalogStart.getBlock ());
|
||||||
|
do
|
||||||
|
{
|
||||||
|
if (!disk.isValidAddress (da))
|
||||||
|
break;
|
||||||
|
sectorBuffer = disk.readSector (da);
|
||||||
|
if (!disk.isValidAddress (sectorBuffer[1], sectorBuffer[2]))
|
||||||
|
break;
|
||||||
|
|
||||||
|
// The first byte is officially unused, but it always seems to contain 0x00 or 0xFF
|
||||||
|
// See beautifulboot.dsk.
|
||||||
|
if (sectorBuffer[0] != 0 && (sectorBuffer[0] & 0xFF) != 0xFF && false)
|
||||||
|
{
|
||||||
|
System.out.println ("Dos catalog sector buffer byte #0 invalid : " + sectorBuffer[0]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
sectorTypes[da.getBlock ()] = catalogSector;
|
||||||
|
|
||||||
|
int track = sectorBuffer[1] & 0xFF;
|
||||||
|
int sector = sectorBuffer[2] & 0xFF;
|
||||||
|
if (!disk.isValidAddress (track, sector))
|
||||||
|
break;
|
||||||
|
|
||||||
|
// int thisBlock = da.getBlock ();
|
||||||
|
da = disk.getDiskAddress (track, sector);
|
||||||
|
|
||||||
|
// if (CHECK_SELF_POINTER && da.getBlock () == thisBlock)
|
||||||
|
// break;
|
||||||
|
|
||||||
|
} while (da.getBlock () != 0);
|
||||||
|
|
||||||
|
// same loop, but now all the catalog sectors are properly flagged
|
||||||
|
da = disk.getDiskAddress (catalogStart.getBlock ());
|
||||||
|
loop: do
|
||||||
|
{
|
||||||
|
if (!disk.isValidAddress (da))
|
||||||
|
break;
|
||||||
|
sectorBuffer = disk.readSector (da);
|
||||||
|
if (!disk.isValidAddress (sectorBuffer[1], sectorBuffer[2]))
|
||||||
|
break;
|
||||||
|
|
||||||
|
for (int ptr = 11; ptr < 256; ptr += ENTRY_SIZE)
|
||||||
|
{
|
||||||
|
if (sectorBuffer[ptr] == 0) // empty slot, no more catalog entries
|
||||||
|
continue;
|
||||||
|
// break loop;
|
||||||
|
|
||||||
|
byte[] entry = new byte[ENTRY_SIZE];
|
||||||
|
System.arraycopy (sectorBuffer, ptr, entry, 0, ENTRY_SIZE);
|
||||||
|
|
||||||
|
if (entry[0] == (byte) 0xFF) // deleted file
|
||||||
|
{
|
||||||
|
DeletedCatalogEntry deletedCatalogEntry = new DeletedCatalogEntry (this, da, entry);
|
||||||
|
deletedFileEntries.add (deletedCatalogEntry);
|
||||||
|
DefaultMutableTreeNode node = new DefaultMutableTreeNode (deletedCatalogEntry);
|
||||||
|
node.setAllowsChildren (false);
|
||||||
|
deletedFilesNode.add (node);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CatalogEntry catalogEntry = new CatalogEntry (this, da, entry);
|
||||||
|
fileEntries.add (catalogEntry);
|
||||||
|
DefaultMutableTreeNode node = new DefaultMutableTreeNode (catalogEntry);
|
||||||
|
node.setAllowsChildren (false);
|
||||||
|
volumeNode.add (node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int track = sectorBuffer[1] & 0xFF;
|
||||||
|
int sector = sectorBuffer[2] & 0xFF;
|
||||||
|
if (!disk.isValidAddress (track, sector))
|
||||||
|
break;
|
||||||
|
|
||||||
|
// int thisBlock = da.getBlock ();
|
||||||
|
da = disk.getDiskAddress (sectorBuffer[1], sectorBuffer[2]);
|
||||||
|
|
||||||
|
// if (CHECK_SELF_POINTER && da.getBlock () == thisBlock)
|
||||||
|
// break;
|
||||||
|
|
||||||
|
} while (da.getBlock () != 0);
|
||||||
|
|
||||||
|
// add up all the free and used sectors, and label DOS sectors while we're here
|
||||||
|
int lastDosSector = dosVTOCSector.maxSectors * 3; // first three tracks
|
||||||
|
for (DiskAddress da2 : disk)
|
||||||
|
{
|
||||||
|
int blockNo = da2.getBlock ();
|
||||||
|
if (blockNo < lastDosSector) // in the DOS region
|
||||||
|
{
|
||||||
|
if (freeBlocks.get (blockNo)) // according to the VTOC
|
||||||
|
++freeSectors;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++usedSectors;
|
||||||
|
if (sectorTypes[blockNo] == usedSector)
|
||||||
|
sectorTypes[blockNo] = dosSector;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (stillAvailable (da2)) // free or used, ie not specifically labelled
|
||||||
|
++freeSectors;
|
||||||
|
else
|
||||||
|
++usedSectors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (freeBlocks.get (blockNo) && !stillAvailable (da2))
|
||||||
|
falsePositives++;
|
||||||
|
if (!freeBlocks.get (blockNo) && stillAvailable (da2))
|
||||||
|
falseNegatives++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deletedFilesNode.getDepth () > 0)
|
||||||
|
{
|
||||||
|
rootNode.add (deletedFilesNode);
|
||||||
|
deletedFilesNode.setUserObject (getDeletedList ());
|
||||||
|
makeNodeVisible (deletedFilesNode.getFirstLeaf ());
|
||||||
|
}
|
||||||
|
volumeNode.setUserObject (getCatalog ());
|
||||||
|
makeNodeVisible (volumeNode.getFirstLeaf ());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOriginalPath (Path path)
|
||||||
|
{
|
||||||
|
super.setOriginalPath (path);
|
||||||
|
volumeNode.setUserObject (getCatalog ()); // this has already been set in the constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
// Beagle Bros FRAMEUP disk only has one catalog block
|
||||||
|
// ARCBOOT.DSK has a catalog which starts at sector 0C
|
||||||
|
public static boolean isCorrectFormat (AppleDisk disk)
|
||||||
|
{
|
||||||
|
disk.setInterleave (0);
|
||||||
|
int catalogBlocks = checkFormat (disk);
|
||||||
|
if (catalogBlocks > 3)
|
||||||
|
return true;
|
||||||
|
disk.setInterleave (1);
|
||||||
|
int cb2 = checkFormat (disk);
|
||||||
|
// if (cb2 > catalogBlocks)
|
||||||
|
if (cb2 > 3)
|
||||||
|
return true;
|
||||||
|
disk.setInterleave (2);
|
||||||
|
if (false)
|
||||||
|
{
|
||||||
|
int cb3 = checkFormat (disk);
|
||||||
|
if (cb3 > 3)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (catalogBlocks > 0)
|
||||||
|
{
|
||||||
|
disk.setInterleave (0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (cb2 > 0)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int checkFormat (AppleDisk disk)
|
||||||
|
{
|
||||||
|
byte[] buffer = disk.readSector (0x11, 0x00);
|
||||||
|
|
||||||
|
// DISCCOMMANDER.DSK uses track 0x17 for the catalog
|
||||||
|
// if (buffer[1] != 0x11) // first catalog track
|
||||||
|
// return 0;
|
||||||
|
|
||||||
|
if (buffer[53] != 16 && buffer[53] != 13) // tracks per sector
|
||||||
|
{
|
||||||
|
// System.out.println ("VTOC tracks per sector : " + (buffer[53 & 0xFF]));
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (buffer[49] < -1 || buffer[49] > 1) // direction of next file save
|
||||||
|
{
|
||||||
|
System.out.println ("Bad direction : " + buffer[49]);
|
||||||
|
// Visicalc data disk had 0xF8
|
||||||
|
// return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int version = buffer[3];
|
||||||
|
if (version < -1 || version > 4)
|
||||||
|
{
|
||||||
|
System.out.println ("Bad version : " + buffer[3]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return countCatalogBlocks (disk, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int countCatalogBlocks (AppleDisk disk, byte[] buffer)
|
||||||
|
{
|
||||||
|
DiskAddress catalogStart = disk.getDiskAddress (buffer[1], buffer[2]);
|
||||||
|
// int catalogBlocks = 0;
|
||||||
|
DiskAddress da = disk.getDiskAddress (catalogStart.getBlock ());
|
||||||
|
List<DiskAddress> catalogAddresses = new ArrayList<DiskAddress> ();
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// System.out.printf ("%5d %s%n", catalogBlocks, da);
|
||||||
|
if (!disk.isValidAddress (da))
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (catalogAddresses.contains (da))
|
||||||
|
{
|
||||||
|
System.out.println ("Catalog looping");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer = disk.readSector (da);
|
||||||
|
if (!disk.isValidAddress (buffer[1], buffer[2]))
|
||||||
|
{
|
||||||
|
System.out.printf ("Invalid address : %02X / %02X%n", buffer[1], buffer[2]);
|
||||||
|
// System.out.println (HexFormatter.format (buffer));
|
||||||
|
// System.out.println (da);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
catalogAddresses.add (da);
|
||||||
|
// catalogBlocks++;
|
||||||
|
// if (catalogBlocks > 1000) // looping
|
||||||
|
// {
|
||||||
|
// System.out.println ("Disk appears to be looping in countCatalogBlocks()");
|
||||||
|
// return 0;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// int thisBlock = da.getBlock ();
|
||||||
|
da = disk.getDiskAddress (buffer[1], buffer[2]);
|
||||||
|
|
||||||
|
// if (CHECK_SELF_POINTER && da.getBlock () == thisBlock)
|
||||||
|
// break;
|
||||||
|
|
||||||
|
} while (da.getBlock () != 0);
|
||||||
|
|
||||||
|
// if (catalogBlocks != catalogAddresses.size ())
|
||||||
|
// System.out.printf ("CB: %d, size: %d%n", catalogBlocks, catalogAddresses.size ());
|
||||||
|
return catalogAddresses.size ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
StringBuffer text = new StringBuffer (dosVTOCSector.toString ());
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DataSource getFormattedSector (DiskAddress da)
|
||||||
|
{
|
||||||
|
SectorType type = sectorTypes[da.getBlock ()];
|
||||||
|
if (type == vtocSector)
|
||||||
|
return dosVTOCSector;
|
||||||
|
if (da.getBlock () == 0)
|
||||||
|
return bootSector;
|
||||||
|
|
||||||
|
byte[] buffer = disk.readSector (da);
|
||||||
|
String address = String.format ("%02X %02X", da.getTrack (), da.getSector ());
|
||||||
|
|
||||||
|
if (type == tsListSector)
|
||||||
|
return new DosTSListSector (getSectorFilename (da), disk, buffer);
|
||||||
|
if (type == catalogSector)
|
||||||
|
return new DosCatalogSector (disk, buffer);
|
||||||
|
if (type == dataSector)
|
||||||
|
return new DefaultSector ("Data Sector at " + address + " : " + getSectorFilename (da),
|
||||||
|
disk, buffer);
|
||||||
|
if (type == dosSector)
|
||||||
|
return new DefaultSector ("DOS sector at " + address, disk, buffer);
|
||||||
|
return super.getFormattedSector (da);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getSectorFilename (DiskAddress da)
|
||||||
|
{
|
||||||
|
for (AppleFileSource ce : fileEntries)
|
||||||
|
if (((CatalogEntry) ce).contains (da))
|
||||||
|
return ((CatalogEntry) ce).name;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DiskAddress> getFileSectors (int fileNo)
|
||||||
|
{
|
||||||
|
if (fileEntries.size () > 0 && fileEntries.size () > fileNo)
|
||||||
|
return fileEntries.get (fileNo).getSectors ();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AppleFileSource getCatalog ()
|
||||||
|
{
|
||||||
|
String newLine = String.format ("%n");
|
||||||
|
String line =
|
||||||
|
"- --- --- ------------------------------ ----- -------------"
|
||||||
|
+ " -- ---- ----------------" + newLine;
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
text.append (String.format ("Disk : %s%n%n", getAbsolutePath ()));
|
||||||
|
text.append ("L Typ Len Name Addr"
|
||||||
|
+ " Length TS Data Comment" + newLine);
|
||||||
|
text.append (line);
|
||||||
|
|
||||||
|
for (AppleFileSource ce : fileEntries)
|
||||||
|
text.append (((CatalogEntry) ce).getDetails () + newLine);
|
||||||
|
|
||||||
|
text.append (line);
|
||||||
|
text.append (String
|
||||||
|
.format (" Free sectors: %3d Used sectors: %3d Total sectors: %3d",
|
||||||
|
dosVTOCSector.freeSectors, dosVTOCSector.usedSectors,
|
||||||
|
(dosVTOCSector.freeSectors + dosVTOCSector.usedSectors)));
|
||||||
|
if (dosVTOCSector.freeSectors != freeSectors)
|
||||||
|
text.append (String
|
||||||
|
.format ("%nActual: Free sectors: %3d Used sectors: %3d Total sectors: %3d",
|
||||||
|
freeSectors, usedSectors, (usedSectors + freeSectors)));
|
||||||
|
return new DefaultAppleFileSource ("Volume " + dosVTOCSector.volume, text.toString (),
|
||||||
|
this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private AppleFileSource getDeletedList ()
|
||||||
|
{
|
||||||
|
StringBuilder text =
|
||||||
|
new StringBuilder ("List of files that were deleted from this disk\n");
|
||||||
|
|
||||||
|
for (AppleFileSource afs : deletedFileEntries)
|
||||||
|
text.append (((DeletedCatalogEntry) afs).getDetails () + "\n");
|
||||||
|
|
||||||
|
return new DefaultAppleFileSource ("Deleted files", text.toString (), this);
|
||||||
|
}
|
||||||
|
}
|
78
src/com/bytezone/diskbrowser/dos/DosTSListSector.java
Executable file
78
src/com/bytezone/diskbrowser/dos/DosTSListSector.java
Executable file
|
@ -0,0 +1,78 @@
|
||||||
|
package com.bytezone.diskbrowser.dos;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
import com.bytezone.diskbrowser.disk.AbstractSector;
|
||||||
|
import com.bytezone.diskbrowser.disk.Disk;
|
||||||
|
import com.bytezone.diskbrowser.disk.DiskAddress;
|
||||||
|
|
||||||
|
class DosTSListSector extends AbstractSector
|
||||||
|
{
|
||||||
|
String name;
|
||||||
|
|
||||||
|
public DosTSListSector (String name, Disk disk, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (disk, buffer);
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValid (DosDisk dosDisk)
|
||||||
|
{
|
||||||
|
System.out.println ("Validating TS List sector");
|
||||||
|
|
||||||
|
// what is the count of blocks? does it match? this sector can't tell, there
|
||||||
|
// might be more than one TS list
|
||||||
|
|
||||||
|
// validate the sector, throw an exception if invalid
|
||||||
|
for (int i = 12; i < buffer.length; i += 2)
|
||||||
|
{
|
||||||
|
DiskAddress da = getValidAddress (buffer, i);
|
||||||
|
if (da == null)
|
||||||
|
{
|
||||||
|
System.out.println ("Invalid sector address : null");
|
||||||
|
break; // throw exception
|
||||||
|
}
|
||||||
|
|
||||||
|
if (da.getBlock () > 0 && dosDisk.stillAvailable (da))
|
||||||
|
{
|
||||||
|
System.out.println ("Invalid sector address : " + da);
|
||||||
|
break; // throw exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is in too many places
|
||||||
|
protected DiskAddress getValidAddress (byte[] buffer, int offset)
|
||||||
|
{
|
||||||
|
if (disk.isValidAddress (buffer[offset], buffer[offset + 1]))
|
||||||
|
return disk.getDiskAddress (buffer[offset], buffer[offset + 1]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String createText ()
|
||||||
|
{
|
||||||
|
StringBuilder text = getHeader ("TS List Sector : " + name);
|
||||||
|
addText (text, buffer, 0, 1, "Not used");
|
||||||
|
addText (text, buffer, 1, 2, "Next TS list track/sector");
|
||||||
|
addText (text, buffer, 3, 2, "Not used");
|
||||||
|
addTextAndDecimal (text, buffer, 5, 2, "Sector base number");
|
||||||
|
addText (text, buffer, 7, 4, "Not used");
|
||||||
|
addText (text, buffer, 11, 1, "Not used");
|
||||||
|
|
||||||
|
String message;
|
||||||
|
int sectorBase = HexFormatter.intValue (buffer[5], buffer[6]);
|
||||||
|
|
||||||
|
for (int i = 12; i <= 255; i += 2)
|
||||||
|
{
|
||||||
|
if (buffer[i] == 0 && buffer[i + 1] == 0)
|
||||||
|
message = "";
|
||||||
|
else
|
||||||
|
message = "Track/sector of file sector " + ((i - 10) / 2 + sectorBase);
|
||||||
|
addText (text, buffer, i, 2, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
text.deleteCharAt (text.length () - 1);
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
146
src/com/bytezone/diskbrowser/dos/DosVTOCSector.java
Executable file
146
src/com/bytezone/diskbrowser/dos/DosVTOCSector.java
Executable file
|
@ -0,0 +1,146 @@
|
||||||
|
package com.bytezone.diskbrowser.dos;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.HexFormatter;
|
||||||
|
import com.bytezone.diskbrowser.disk.AbstractSector;
|
||||||
|
import com.bytezone.diskbrowser.disk.Disk;
|
||||||
|
|
||||||
|
class DosVTOCSector extends AbstractSector
|
||||||
|
{
|
||||||
|
DosDisk parentDisk;
|
||||||
|
int volume;
|
||||||
|
int DOSVersion;
|
||||||
|
int maxTSPairs;
|
||||||
|
int lastAllocTrack;
|
||||||
|
int direction;
|
||||||
|
int freeSectors;
|
||||||
|
int usedSectors;
|
||||||
|
int sectorSize;
|
||||||
|
int maxSectors;
|
||||||
|
int maxTracks;
|
||||||
|
|
||||||
|
public DosVTOCSector (DosDisk parentDisk, Disk disk, byte[] buffer)
|
||||||
|
{
|
||||||
|
super (disk, buffer);
|
||||||
|
|
||||||
|
this.parentDisk = parentDisk;
|
||||||
|
DOSVersion = buffer[3];
|
||||||
|
volume = HexFormatter.intValue (buffer[6]);
|
||||||
|
maxTSPairs = buffer[39];
|
||||||
|
lastAllocTrack = buffer[48];
|
||||||
|
direction = buffer[49];
|
||||||
|
maxTracks = buffer[52] & 0xFF;
|
||||||
|
maxSectors = buffer[53] & 0xFF;
|
||||||
|
sectorSize = HexFormatter.intValue (buffer[54], buffer[55]);
|
||||||
|
flagSectors ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String createText ()
|
||||||
|
{
|
||||||
|
StringBuilder text = getHeader ("VTOC Sector");
|
||||||
|
addText (text, buffer, 0, 1, "Not used");
|
||||||
|
addText (text, buffer, 1, 2, "First directory track/sector");
|
||||||
|
addText (text, buffer, 3, 1, "DOS release number");
|
||||||
|
addText (text, buffer, 4, 2, "Not used");
|
||||||
|
addTextAndDecimal (text, buffer, 6, 1, "Diskette volume");
|
||||||
|
addTextAndDecimal (text, buffer, 39, 1, "Maximum TS pairs");
|
||||||
|
addText (text, buffer, 40, 4, "Not used");
|
||||||
|
addText (text, buffer, 44, 4, "Not used");
|
||||||
|
addText (text, buffer, 48, 1, "Last allocated track");
|
||||||
|
addText (text, buffer, 49, 1, "Direction to look when allocating the next file");
|
||||||
|
addText (text, buffer, 50, 2, "Not used");
|
||||||
|
addTextAndDecimal (text, buffer, 52, 1, "Maximum tracks");
|
||||||
|
addTextAndDecimal (text, buffer, 53, 1, "Maximum sectors");
|
||||||
|
addTextAndDecimal (text, buffer, 54, 2, "Bytes per sector");
|
||||||
|
|
||||||
|
for (int i = 56; i <= 0xc3; i += 4)
|
||||||
|
{
|
||||||
|
String extra;
|
||||||
|
if (i <= 64)
|
||||||
|
extra = "(DOS)";
|
||||||
|
else if (i == 124)
|
||||||
|
extra = "(VTOC and Catalog)";
|
||||||
|
else
|
||||||
|
extra = "";
|
||||||
|
addText (text,
|
||||||
|
buffer,
|
||||||
|
i,
|
||||||
|
4,
|
||||||
|
String.format ("Track %02X %s %s", (i - 56) / 4,
|
||||||
|
getBitmap (buffer[i], buffer[i + 1]), extra));
|
||||||
|
}
|
||||||
|
|
||||||
|
text.deleteCharAt (text.length () - 1);
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getBitmap (byte left, byte right)
|
||||||
|
{
|
||||||
|
int base = maxSectors == 13 ? 3 : 0;
|
||||||
|
right >>= base;
|
||||||
|
StringBuilder text = new StringBuilder ();
|
||||||
|
for (int i = base; i < 8; i++)
|
||||||
|
{
|
||||||
|
if ((right & 0x01) == 1)
|
||||||
|
text.append (".");
|
||||||
|
else
|
||||||
|
text.append ("X");
|
||||||
|
right >>= 1;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
if ((left & 0x01) == 1)
|
||||||
|
text.append (".");
|
||||||
|
else
|
||||||
|
text.append ("X");
|
||||||
|
left >>= 1;
|
||||||
|
}
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void flagSectors ()
|
||||||
|
{
|
||||||
|
int block = 0;
|
||||||
|
int base = maxSectors == 13 ? 3 : 0;
|
||||||
|
for (int i = 56; i <= 0xc3; i += 4)
|
||||||
|
{
|
||||||
|
block = check (buffer[i + 1], block, base);
|
||||||
|
block = check (buffer[i], block, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int check (byte b, int block, int base)
|
||||||
|
{
|
||||||
|
b >>= base;
|
||||||
|
for (int i = base; i < 8; i++)
|
||||||
|
{
|
||||||
|
if ((b & 0x01) == 1)
|
||||||
|
{
|
||||||
|
parentDisk.setSectorFree (block, true);
|
||||||
|
++freeSectors;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
parentDisk.setSectorFree (block, false);
|
||||||
|
++usedSectors;
|
||||||
|
}
|
||||||
|
block++;
|
||||||
|
b >>= 1;
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
StringBuffer text = new StringBuffer ();
|
||||||
|
text.append ("DOS version : 3." + DOSVersion);
|
||||||
|
text.append ("\nVolume : " + volume);
|
||||||
|
text.append ("\nMax TS pairs : " + maxTSPairs);
|
||||||
|
text.append ("\nLast allocated T : " + lastAllocTrack);
|
||||||
|
text.append ("\nDirection : " + direction);
|
||||||
|
|
||||||
|
return text.toString ();
|
||||||
|
}
|
||||||
|
}
|
60
src/com/bytezone/diskbrowser/gui/AboutAction.java
Executable file
60
src/com/bytezone/diskbrowser/gui/AboutAction.java
Executable file
|
@ -0,0 +1,60 @@
|
||||||
|
package com.bytezone.diskbrowser.gui;
|
||||||
|
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import javax.swing.Action;
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
|
||||||
|
import com.bytezone.common.DefaultAction;
|
||||||
|
|
||||||
|
public class AboutAction extends DefaultAction
|
||||||
|
{
|
||||||
|
public AboutAction ()
|
||||||
|
{
|
||||||
|
super ("About...", "Display build information", "/com/bytezone/diskbrowser/icons/");
|
||||||
|
putValue (Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke ("alt A"));
|
||||||
|
putValue (Action.MNEMONIC_KEY, KeyEvent.VK_A);
|
||||||
|
|
||||||
|
setIcon (Action.SMALL_ICON, "information_16.png");
|
||||||
|
setIcon (Action.LARGE_ICON_KEY, "information_32.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed (ActionEvent e)
|
||||||
|
{
|
||||||
|
about ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void about ()
|
||||||
|
{
|
||||||
|
int build = 0;
|
||||||
|
String buildDate = "<no date>";
|
||||||
|
Properties props = new Properties ();
|
||||||
|
InputStream in = this.getClass ().getResourceAsStream ("build.properties");
|
||||||
|
if (in != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
props.load (in);
|
||||||
|
in.close ();
|
||||||
|
build = Integer.parseInt (props.getProperty ("build.number"));
|
||||||
|
buildDate = props.getProperty ("build.date");
|
||||||
|
}
|
||||||
|
catch (IOException e1)
|
||||||
|
{
|
||||||
|
System.out.println ("Properties file not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JOptionPane.showMessageDialog (null,
|
||||||
|
"Author - Denis Molony\nBuild #"
|
||||||
|
+ String.format ("%d", build) + " - " + buildDate
|
||||||
|
+ "\n" + "\nContact - dmolony@iinet.net.au",
|
||||||
|
"About DiskBrowser", JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
160
src/com/bytezone/diskbrowser/gui/AbstractTab.java
Executable file
160
src/com/bytezone/diskbrowser/gui/AbstractTab.java
Executable file
|
@ -0,0 +1,160 @@
|
||||||
|
package com.bytezone.diskbrowser.gui;
|
||||||
|
|
||||||
|
/***********************************************************************************************
|
||||||
|
* Parent class of FileSystemTab and AppleDiskTab.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
***********************************************************************************************/
|
||||||
|
|
||||||
|
import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER;
|
||||||
|
import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Cursor;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JScrollPane;
|
||||||
|
import javax.swing.JTree;
|
||||||
|
import javax.swing.tree.*;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.gui.RedoHandler.RedoData;
|
||||||
|
import com.bytezone.diskbrowser.gui.TreeBuilder.FileNode;
|
||||||
|
|
||||||
|
abstract class AbstractTab extends JPanel implements Tab
|
||||||
|
{
|
||||||
|
private final static Cursor handCursor = new Cursor (Cursor.HAND_CURSOR);
|
||||||
|
private final List<MouseAdapter> adapters = new ArrayList<MouseAdapter> ();
|
||||||
|
private Font font;
|
||||||
|
private final JScrollPane scrollpane;
|
||||||
|
final DiskAndFileSelector eventHandler;
|
||||||
|
final RedoHandler navMan;
|
||||||
|
final RedoData redoData;
|
||||||
|
protected JTree tree;
|
||||||
|
|
||||||
|
public AbstractTab (RedoHandler navMan, DiskAndFileSelector selector, Font font)
|
||||||
|
{
|
||||||
|
super (new BorderLayout ());
|
||||||
|
this.eventHandler = selector;
|
||||||
|
this.font = font;
|
||||||
|
this.navMan = navMan;
|
||||||
|
this.redoData = navMan.createData ();
|
||||||
|
|
||||||
|
scrollpane = new JScrollPane (null, VERTICAL_SCROLLBAR_ALWAYS, HORIZONTAL_SCROLLBAR_NEVER);
|
||||||
|
scrollpane.setBorder (null);
|
||||||
|
add (scrollpane, BorderLayout.CENTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setTree (JTree tree)
|
||||||
|
{
|
||||||
|
this.tree = tree;
|
||||||
|
tree.setFont (font);
|
||||||
|
scrollpane.setViewportView (tree);
|
||||||
|
TreeSelectionModel tsm = tree.getSelectionModel ();
|
||||||
|
tsm.setSelectionMode (TreeSelectionModel.SINGLE_TREE_SELECTION);
|
||||||
|
|
||||||
|
if (adapters.size () > 0)
|
||||||
|
restoreAdapters ();
|
||||||
|
else
|
||||||
|
addTreeMouseListener (new CursorHandler ());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setTreeFont (Font font)
|
||||||
|
{
|
||||||
|
tree.setFont (font);
|
||||||
|
this.font = font;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addTreeMouseListener (MouseAdapter adapter)
|
||||||
|
{
|
||||||
|
tree.addMouseListener (adapter);
|
||||||
|
adapters.add (adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restoreAdapters ()
|
||||||
|
{
|
||||||
|
for (MouseAdapter ma : adapters)
|
||||||
|
tree.addMouseListener (ma);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Object getSelectedObject ()
|
||||||
|
{
|
||||||
|
DefaultMutableTreeNode node =
|
||||||
|
(DefaultMutableTreeNode) tree.getLastSelectedPathComponent ();
|
||||||
|
return node == null ? null : node.getUserObject ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DefaultMutableTreeNode getRootNode ()
|
||||||
|
{
|
||||||
|
return (DefaultMutableTreeNode) tree.getModel ().getRoot ();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DefaultMutableTreeNode findNode (int nodeNo)
|
||||||
|
{
|
||||||
|
DefaultMutableTreeNode rootNode = getRootNode ();
|
||||||
|
Enumeration<DefaultMutableTreeNode> children = rootNode.breadthFirstEnumeration ();
|
||||||
|
int count = 0;
|
||||||
|
DefaultMutableTreeNode selectNode = null;
|
||||||
|
while (children.hasMoreElements () && ++count <= nodeNo)
|
||||||
|
selectNode = children.nextElement ();
|
||||||
|
return selectNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected DefaultMutableTreeNode findFirstLeafNode ()
|
||||||
|
{
|
||||||
|
DefaultMutableTreeNode rootNode = getRootNode ();
|
||||||
|
Enumeration<DefaultMutableTreeNode> children = rootNode.depthFirstEnumeration ();
|
||||||
|
DefaultMutableTreeNode selectNode = null;
|
||||||
|
while (children.hasMoreElements ())
|
||||||
|
{
|
||||||
|
selectNode = children.nextElement ();
|
||||||
|
if (selectNode.isLeaf ())
|
||||||
|
{
|
||||||
|
FileNode node = (FileNode) selectNode.getUserObject ();
|
||||||
|
if (node.file.isFile ())
|
||||||
|
return selectNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger the TreeSelectionListener set by the real Tab (if the value is different)
|
||||||
|
protected void showNode (DefaultMutableTreeNode showNode)
|
||||||
|
{
|
||||||
|
TreePath tp = getPathToNode (showNode);
|
||||||
|
tree.setSelectionPath (tp);
|
||||||
|
if (!tree.isVisible (tp))
|
||||||
|
tree.scrollPathToVisible (tp);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected TreePath getPathToNode (DefaultMutableTreeNode selectNode)
|
||||||
|
{
|
||||||
|
DefaultTreeModel treeModel = (DefaultTreeModel) tree.getModel ();
|
||||||
|
TreeNode[] nodes = treeModel.getPathToRoot (selectNode);
|
||||||
|
return new TreePath (nodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CursorHandler extends MouseAdapter
|
||||||
|
{
|
||||||
|
private Cursor oldCursor;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseEntered (MouseEvent e)
|
||||||
|
{
|
||||||
|
oldCursor = getCursor ();
|
||||||
|
setCursor (handCursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseExited (MouseEvent e)
|
||||||
|
{
|
||||||
|
setCursor (oldCursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
163
src/com/bytezone/diskbrowser/gui/AppleDiskTab.java
Executable file
163
src/com/bytezone/diskbrowser/gui/AppleDiskTab.java
Executable file
|
@ -0,0 +1,163 @@
|
||||||
|
package com.bytezone.diskbrowser.gui;
|
||||||
|
|
||||||
|
/***********************************************************************************************
|
||||||
|
* JPanel used to display a scrolling JTree containing details of a single disk. The JTree
|
||||||
|
* consists entirely of AppleFileSource objects. Any number of these objects are contained
|
||||||
|
* in Catalog Panel, along with a single FileSystemTab.
|
||||||
|
***********************************************************************************************/
|
||||||
|
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
|
||||||
|
import javax.swing.JTree;
|
||||||
|
import javax.swing.event.TreeSelectionEvent;
|
||||||
|
import javax.swing.event.TreeSelectionListener;
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.applefile.AppleFileSource;
|
||||||
|
import com.bytezone.diskbrowser.disk.DiskFactory;
|
||||||
|
import com.bytezone.diskbrowser.disk.FormattedDisk;
|
||||||
|
import com.bytezone.diskbrowser.gui.RedoHandler.RedoEvent;
|
||||||
|
|
||||||
|
class AppleDiskTab extends AbstractTab
|
||||||
|
{
|
||||||
|
FormattedDisk disk;
|
||||||
|
|
||||||
|
public AppleDiskTab (FormattedDisk disk, DiskAndFileSelector selector, RedoHandler navMan,
|
||||||
|
Font font, FileSelectedEvent event)
|
||||||
|
{
|
||||||
|
super (navMan, selector, font);
|
||||||
|
create (disk);
|
||||||
|
navMan.fileSelected (event);
|
||||||
|
// System.out.println ("restoring a previous disk with a file selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppleDiskTab (FormattedDisk disk, DiskAndFileSelector selector, RedoHandler navMan,
|
||||||
|
Font font, SectorSelectedEvent event)
|
||||||
|
{
|
||||||
|
super (navMan, selector, font);
|
||||||
|
create (disk);
|
||||||
|
navMan.sectorSelected (event);
|
||||||
|
// System.out.println ("restoring a previous disk with a sector selected");
|
||||||
|
}
|
||||||
|
|
||||||
|
// This constructor is only called when lastFileUsed is not null, but the disk
|
||||||
|
// couldn't find the file entry. Either the file has been deleted, or it is a disk
|
||||||
|
// with redefined files (Wizardry, Infocom etc).
|
||||||
|
public AppleDiskTab (FormattedDisk disk, DiskAndFileSelector selector, RedoHandler navMan,
|
||||||
|
Font font, String lastFileUsed)
|
||||||
|
{
|
||||||
|
super (navMan, selector, font);
|
||||||
|
create (disk);
|
||||||
|
// System.out.println ("ooh - couldn't find the previous file");
|
||||||
|
DefaultMutableTreeNode node = findNode (lastFileUsed);
|
||||||
|
if (node != null)
|
||||||
|
{
|
||||||
|
AppleFileSource afs = (AppleFileSource) node.getUserObject ();
|
||||||
|
FileSelectedEvent event = new FileSelectedEvent (this, afs);
|
||||||
|
navMan.fileSelected (event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// User is selecting a new disk from the catalog
|
||||||
|
public AppleDiskTab (FormattedDisk disk, DiskAndFileSelector selector, RedoHandler navMan,
|
||||||
|
Font font)
|
||||||
|
{
|
||||||
|
super (navMan, selector, font);
|
||||||
|
create (disk);
|
||||||
|
|
||||||
|
AppleFileSource afs = (AppleFileSource) findNode (2).getUserObject (); // select Catalog
|
||||||
|
if (afs == null)
|
||||||
|
afs = (AppleFileSource) findNode (1).getUserObject (); // select Disk
|
||||||
|
navMan.fileSelected (new FileSelectedEvent (this, afs));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void create (FormattedDisk disk)
|
||||||
|
{
|
||||||
|
this.disk = disk;
|
||||||
|
setTree (disk.getCatalogTree ());
|
||||||
|
setSelectionListener (tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void activate ()
|
||||||
|
{
|
||||||
|
// System.out.println ("=========== Activating AppleDiskTab =============");
|
||||||
|
eventHandler.redo = true;
|
||||||
|
eventHandler.fireDiskSelectionEvent (disk);
|
||||||
|
eventHandler.redo = false;
|
||||||
|
tree.setSelectionPath (null); // turn off any current selection to force an event
|
||||||
|
navMan.setCurrentData (redoData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void refresh () // called when the user gives ALT-R command
|
||||||
|
{
|
||||||
|
Object o = getSelectedObject ();
|
||||||
|
String currentFile = (o == null) ? null : ((AppleFileSource) o).getUniqueName ();
|
||||||
|
disk = DiskFactory.createDisk (disk.getAbsolutePath ());
|
||||||
|
setTree (disk.getCatalogTree ());
|
||||||
|
setSelectionListener (tree);
|
||||||
|
selectNode (currentFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectNode (String nodeName)
|
||||||
|
{
|
||||||
|
DefaultMutableTreeNode selectNode = null;
|
||||||
|
if (nodeName != null)
|
||||||
|
selectNode = findNode (nodeName);
|
||||||
|
if (selectNode == null)
|
||||||
|
selectNode = findNode (2);
|
||||||
|
if (selectNode != null)
|
||||||
|
showNode (selectNode);
|
||||||
|
else
|
||||||
|
System.out.println ("First node not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
void redoEvent (RedoEvent event)
|
||||||
|
{
|
||||||
|
selectNode (((FileSelectedEvent) event.value).file.getUniqueName ());
|
||||||
|
}
|
||||||
|
|
||||||
|
private DefaultMutableTreeNode findNode (String nodeName)
|
||||||
|
{
|
||||||
|
DefaultMutableTreeNode rootNode = getRootNode ();
|
||||||
|
Enumeration<DefaultMutableTreeNode> children = rootNode.breadthFirstEnumeration ();
|
||||||
|
while (children.hasMoreElements ())
|
||||||
|
{
|
||||||
|
DefaultMutableTreeNode node = children.nextElement ();
|
||||||
|
Object o = node.getUserObject ();
|
||||||
|
if (o instanceof AppleFileSource)
|
||||||
|
{
|
||||||
|
AppleFileSource afs = (AppleFileSource) node.getUserObject ();
|
||||||
|
if (nodeName.equals (afs.getUniqueName ()))
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains (FormattedDisk disk)
|
||||||
|
{
|
||||||
|
return this.disk.getAbsolutePath ().equals (disk.getAbsolutePath ());
|
||||||
|
}
|
||||||
|
|
||||||
|
// This action is triggered by AppleDiskTab.selectNode (String), which calls
|
||||||
|
// AbstractTab.showNode (DefaultMutableTreeNode). That will trigger this listener
|
||||||
|
// ONLY if the value is different, so it is set to null first to force the event.
|
||||||
|
private void setSelectionListener (JTree tree)
|
||||||
|
{
|
||||||
|
tree.addTreeSelectionListener (new TreeSelectionListener ()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void valueChanged (TreeSelectionEvent e)
|
||||||
|
{
|
||||||
|
// A null happens when there is a click in the DiskLayoutPanel, in order
|
||||||
|
// to turn off the currently selected file
|
||||||
|
AppleFileSource afs = (AppleFileSource) getSelectedObject ();
|
||||||
|
if (afs != null)
|
||||||
|
eventHandler.fireFileSelectionEvent (afs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
448
src/com/bytezone/diskbrowser/gui/CatalogPanel.java
Executable file
448
src/com/bytezone/diskbrowser/gui/CatalogPanel.java
Executable file
|
@ -0,0 +1,448 @@
|
||||||
|
package com.bytezone.diskbrowser.gui;
|
||||||
|
|
||||||
|
/***********************************************************************************************
|
||||||
|
* Contains a single instance of FileSystemTab, and any number of AppleDiskTab instances.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
***********************************************************************************************/
|
||||||
|
|
||||||
|
import java.awt.Dimension;
|
||||||
|
import java.awt.Font;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.EventObject;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
|
import javax.swing.JTabbedPane;
|
||||||
|
import javax.swing.JTree;
|
||||||
|
import javax.swing.SwingConstants;
|
||||||
|
import javax.swing.event.ChangeEvent;
|
||||||
|
import javax.swing.event.ChangeListener;
|
||||||
|
import javax.swing.tree.DefaultMutableTreeNode;
|
||||||
|
import javax.swing.tree.TreePath;
|
||||||
|
|
||||||
|
import com.bytezone.common.FontAction.FontChangeEvent;
|
||||||
|
import com.bytezone.common.FontAction.FontChangeListener;
|
||||||
|
import com.bytezone.common.QuitAction.QuitListener;
|
||||||
|
import com.bytezone.diskbrowser.applefile.AppleFileSource;
|
||||||
|
import com.bytezone.diskbrowser.catalog.DocumentCreatorFactory;
|
||||||
|
import com.bytezone.diskbrowser.disk.DualDosDisk;
|
||||||
|
import com.bytezone.diskbrowser.disk.FormattedDisk;
|
||||||
|
import com.bytezone.diskbrowser.gui.RedoHandler.RedoEvent;
|
||||||
|
import com.bytezone.diskbrowser.gui.RedoHandler.RedoListener;
|
||||||
|
import com.bytezone.diskbrowser.gui.TreeBuilder.FileNode;
|
||||||
|
|
||||||
|
class CatalogPanel extends JTabbedPane implements RedoListener, SectorSelectionListener,
|
||||||
|
QuitListener, FontChangeListener
|
||||||
|
// PreferenceChangeListener
|
||||||
|
{
|
||||||
|
private static final String prefsLastDiskUsed = "Last disk used";
|
||||||
|
private static final String prefsLastDosUsed = "Last dos used";
|
||||||
|
private static final String prefsLastFileUsed = "Last file used";
|
||||||
|
private static final String prefsLastSectorsUsed = "Last sectors used";
|
||||||
|
private static final String prefsRootDirectory = "Root directory";
|
||||||
|
|
||||||
|
private Font font;
|
||||||
|
private FileSystemTab fileTab;
|
||||||
|
private final List<AppleDiskTab> diskTabs = new ArrayList<AppleDiskTab> ();
|
||||||
|
private final DocumentCreatorFactory lister;
|
||||||
|
private final DiskAndFileSelector selector = new DiskAndFileSelector ();
|
||||||
|
private final RedoHandler navMan;
|
||||||
|
private DuplicateAction duplicateAction; // this sux
|
||||||
|
private CloseTabAction closeTabAction;
|
||||||
|
|
||||||
|
public CatalogPanel (MenuHandler mh, RedoHandler navMan, Preferences prefs)
|
||||||
|
{
|
||||||
|
// String catalogFontName =
|
||||||
|
// prefs.get (PreferencesDialog.prefsCatalogFont, PreferencesDialog.defaultFontName);
|
||||||
|
// int catalogFontSize =
|
||||||
|
// prefs.getInt (PreferencesDialog.prefsCatalogFontSize,
|
||||||
|
// PreferencesDialog.defaultFontSize);
|
||||||
|
// this.font = new Font (catalogFontName, Font.PLAIN, catalogFontSize);
|
||||||
|
this.lister = new DocumentCreatorFactory (mh);
|
||||||
|
this.navMan = navMan;
|
||||||
|
|
||||||
|
selector.addDiskSelectionListener (lister.diskLister);
|
||||||
|
|
||||||
|
setTabPlacement (SwingConstants.BOTTOM);
|
||||||
|
setPreferredSize (new Dimension (360, 802)); // width, height
|
||||||
|
// setPreferredSize (new Dimension (360, 523)); // width, height
|
||||||
|
|
||||||
|
createTabs (prefs);
|
||||||
|
addChangeListener (new TabChangeListener ());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createTabs (Preferences prefs)
|
||||||
|
{
|
||||||
|
String rootDirectory = prefs.get (prefsRootDirectory, "");
|
||||||
|
|
||||||
|
File rootDirectoryFile = new File (rootDirectory);
|
||||||
|
if (!rootDirectoryFile.exists () || !rootDirectoryFile.isDirectory ())
|
||||||
|
{
|
||||||
|
System.out.println ("No root directory");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String lastDiskUsed = prefs.get (prefsLastDiskUsed, "");
|
||||||
|
int lastDosUsed = prefs.getInt (prefsLastDosUsed, -1);
|
||||||
|
String lastFileUsed = prefs.get (prefsLastFileUsed, "");
|
||||||
|
String lastSectorsUsed = prefs.get (prefsLastSectorsUsed, "");
|
||||||
|
|
||||||
|
if (false)
|
||||||
|
{
|
||||||
|
System.out.println ("Last disk : " + lastDiskUsed);
|
||||||
|
System.out.println ("Last dos : " + lastDosUsed);
|
||||||
|
System.out.println ("Last file : " + lastFileUsed);
|
||||||
|
System.out.println ("Last sectors : " + lastSectorsUsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
DiskSelectedEvent diskEvent = null;
|
||||||
|
if (!lastDiskUsed.isEmpty ())
|
||||||
|
{
|
||||||
|
diskEvent = DiskSelectedEvent.create (this, lastDiskUsed);
|
||||||
|
if (diskEvent != null)
|
||||||
|
{
|
||||||
|
FormattedDisk fd = diskEvent.getFormattedDisk ();
|
||||||
|
if (lastDosUsed >= 0 && fd instanceof DualDosDisk)
|
||||||
|
((DualDosDisk) fd).setCurrentDiskNo (lastDosUsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
System.out.println ("no disk selected");
|
||||||
|
|
||||||
|
fileTab = new FileSystemTab (rootDirectoryFile, selector, navMan, font, diskEvent);
|
||||||
|
fileTab.addTreeMouseListener (new MouseListener ()); // listen for disk selection
|
||||||
|
lister.catalogLister.setNode (fileTab.getRootNode ());
|
||||||
|
insertTab ("Disk Tree", null, fileTab, "Display Apple disks", 0);
|
||||||
|
|
||||||
|
if (diskEvent != null)
|
||||||
|
{
|
||||||
|
AppleDiskTab tab = null;
|
||||||
|
FormattedDisk fd = diskEvent.getFormattedDisk ();
|
||||||
|
|
||||||
|
if (!lastFileUsed.isEmpty ())
|
||||||
|
{
|
||||||
|
AppleFileSource afs = fd.getFile (lastFileUsed);
|
||||||
|
if (afs != null)
|
||||||
|
{
|
||||||
|
FileSelectedEvent fileEvent = FileSelectedEvent.create (this, afs);
|
||||||
|
tab = new AppleDiskTab (fd, selector, navMan, font, fileEvent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
tab = new AppleDiskTab (fd, selector, navMan, font, lastFileUsed);
|
||||||
|
}
|
||||||
|
else if (!lastSectorsUsed.isEmpty ())
|
||||||
|
{
|
||||||
|
SectorSelectedEvent sectorEvent =
|
||||||
|
SectorSelectedEvent.create (this, fd, lastSectorsUsed);
|
||||||
|
tab = new AppleDiskTab (fd, selector, navMan, font, sectorEvent);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
tab = new AppleDiskTab (fd, selector, navMan, font);
|
||||||
|
|
||||||
|
if (tab != null)
|
||||||
|
{
|
||||||
|
diskTabs.add (tab);
|
||||||
|
add (tab, "D" + diskTabs.size ());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
System.out.println ("No disk tab created");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void activate ()
|
||||||
|
{
|
||||||
|
if (fileTab == null)
|
||||||
|
{
|
||||||
|
System.out.println ("No file tab");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diskTabs.size () > 0)
|
||||||
|
setSelectedIndex (1);
|
||||||
|
else if (fileTab != null)
|
||||||
|
setSelectedIndex (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDuplicateAction (DuplicateAction action)
|
||||||
|
{
|
||||||
|
this.duplicateAction = action;
|
||||||
|
if (fileTab != null && fileTab.rootFolder != null)
|
||||||
|
action.setDuplicates (fileTab.rootFolder, fileTab.duplicateDisks);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setCloseTabAction (CloseTabAction action)
|
||||||
|
{
|
||||||
|
this.closeTabAction = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
// called by RootDirectoryAction
|
||||||
|
public void changeRootPanel (File root)
|
||||||
|
{
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// This might throw a NoDisksFoundException
|
||||||
|
FileSystemTab newFileTab = new FileSystemTab (root, selector, navMan, font);
|
||||||
|
|
||||||
|
// is the user replacing an existing root folder?
|
||||||
|
if (fileTab != null)
|
||||||
|
removeTabAt (0);
|
||||||
|
|
||||||
|
fileTab = newFileTab;
|
||||||
|
fileTab.addTreeMouseListener (new MouseListener ()); // listen for disk selection
|
||||||
|
lister.catalogLister.setNode (fileTab.getRootNode ());
|
||||||
|
|
||||||
|
insertTab ("Disk Tree", null, fileTab, null, 0);
|
||||||
|
setSelectedIndex (0);
|
||||||
|
duplicateAction.setDuplicates (fileTab.rootFolder, fileTab.duplicateDisks);
|
||||||
|
// }
|
||||||
|
// catch (NoDisksFoundException e)
|
||||||
|
// {
|
||||||
|
// JOptionPane.showMessageDialog (null, "Folder " + root.getAbsolutePath ()
|
||||||
|
// + " has no valid disk images.", "Bad folder", JOptionPane.ERROR_MESSAGE);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// called after a double-click in the fileTab
|
||||||
|
public void addDiskPanel (FormattedDisk disk, String lastFileUsed, boolean activate)
|
||||||
|
{
|
||||||
|
int tabNo = 1;
|
||||||
|
for (AppleDiskTab tab : diskTabs)
|
||||||
|
{
|
||||||
|
if (tab.contains (disk))
|
||||||
|
{
|
||||||
|
setSelectedIndex (tabNo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tabNo++;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppleDiskTab tab = new AppleDiskTab (disk, selector, navMan, font);
|
||||||
|
diskTabs.add (tab);
|
||||||
|
add (tab, "D" + diskTabs.size ());
|
||||||
|
if (activate)
|
||||||
|
setSelectedIndex (diskTabs.size ());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from RefreshTreeAction
|
||||||
|
public void refreshTree ()
|
||||||
|
{
|
||||||
|
Tab tab = (Tab) getSelectedComponent ();
|
||||||
|
tab.refresh ();
|
||||||
|
|
||||||
|
// Any newly created disk needs to appear in the FileSystemTab's tree
|
||||||
|
if (tab instanceof AppleDiskTab)
|
||||||
|
fileTab.replaceDisk (((AppleDiskTab) tab).disk);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called from CloseTabAction
|
||||||
|
public void closeCurrentTab ()
|
||||||
|
{
|
||||||
|
Tab tab = (Tab) getSelectedComponent ();
|
||||||
|
if (!(tab instanceof AppleDiskTab) || diskTabs.size () < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int index = getSelectedIndex ();
|
||||||
|
remove (index);
|
||||||
|
diskTabs.remove (tab);
|
||||||
|
|
||||||
|
for (int i = 1; i <= diskTabs.size (); i++)
|
||||||
|
setTitleAt (i, "D" + i);
|
||||||
|
|
||||||
|
checkCloseTabAction ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkCloseTabAction ()
|
||||||
|
{
|
||||||
|
Tab tab = (Tab) getSelectedComponent ();
|
||||||
|
if (diskTabs.size () > 1 && tab instanceof AppleDiskTab)
|
||||||
|
closeTabAction.setEnabled (true);
|
||||||
|
else
|
||||||
|
closeTabAction.setEnabled (false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void quit (Preferences prefs)
|
||||||
|
{
|
||||||
|
if (fileTab == null)
|
||||||
|
{
|
||||||
|
prefs.put (prefsRootDirectory, "");
|
||||||
|
prefs.put (prefsLastDiskUsed, "");
|
||||||
|
prefs.putInt (prefsLastDosUsed, -1);
|
||||||
|
prefs.put (prefsLastFileUsed, "");
|
||||||
|
prefs.put (prefsLastSectorsUsed, "");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
prefs.put (prefsRootDirectory, fileTab.rootFolder.getAbsolutePath ());
|
||||||
|
|
||||||
|
if (diskTabs.size () == 0)
|
||||||
|
{
|
||||||
|
RedoEvent redoEvent = fileTab.redoData.getCurrentEvent ();
|
||||||
|
if (redoEvent != null)
|
||||||
|
{
|
||||||
|
DiskSelectedEvent event = (DiskSelectedEvent) redoEvent.value;
|
||||||
|
prefs.put (prefsLastDiskUsed, event.getFormattedDisk ().getAbsolutePath ());
|
||||||
|
}
|
||||||
|
prefs.put (prefsLastFileUsed, "");
|
||||||
|
prefs.put (prefsLastSectorsUsed, "");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AbstractTab selectedTab = (AbstractTab) getSelectedComponent ();
|
||||||
|
if (selectedTab instanceof FileSystemTab)
|
||||||
|
selectedTab = diskTabs.get (diskTabs.size () - 1);
|
||||||
|
|
||||||
|
FormattedDisk fd = ((AppleDiskTab) selectedTab).disk;
|
||||||
|
prefs.put (prefsLastDiskUsed, fd.getAbsolutePath ());
|
||||||
|
if (fd instanceof DualDosDisk)
|
||||||
|
prefs.putInt (prefsLastDosUsed, ((DualDosDisk) fd).getCurrentDiskNo ());
|
||||||
|
else
|
||||||
|
prefs.putInt (prefsLastDosUsed, -1);
|
||||||
|
|
||||||
|
RedoEvent redoEvent = selectedTab.redoData.getCurrentEvent ();
|
||||||
|
if (redoEvent != null)
|
||||||
|
{
|
||||||
|
EventObject event = redoEvent.value;
|
||||||
|
|
||||||
|
if (event instanceof FileSelectedEvent)
|
||||||
|
{
|
||||||
|
AppleFileSource afs = ((FileSelectedEvent) event).file;
|
||||||
|
prefs.put (prefsLastFileUsed, afs == null ? "" : afs.getUniqueName ());
|
||||||
|
prefs.put (prefsLastSectorsUsed, "");
|
||||||
|
}
|
||||||
|
else if (event instanceof SectorSelectedEvent)
|
||||||
|
{
|
||||||
|
prefs.put (prefsLastFileUsed, "");
|
||||||
|
prefs.put (prefsLastSectorsUsed, ((SectorSelectedEvent) event).toText ());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass through to DiskSelector
|
||||||
|
public void addDiskSelectionListener (DiskSelectionListener listener)
|
||||||
|
{
|
||||||
|
selector.addDiskSelectionListener (listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass through to DiskSelector
|
||||||
|
public void addFileSelectionListener (FileSelectionListener listener)
|
||||||
|
{
|
||||||
|
selector.addFileSelectionListener (listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass through to DiskSelector
|
||||||
|
public void addFileNodeSelectionListener (FileNodeSelectionListener listener)
|
||||||
|
{
|
||||||
|
selector.addFileNodeSelectionListener (listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TabChangeListener implements ChangeListener
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void stateChanged (ChangeEvent e)
|
||||||
|
{
|
||||||
|
Tab tab = (Tab) getSelectedComponent ();
|
||||||
|
if (tab != null)
|
||||||
|
{
|
||||||
|
tab.activate ();
|
||||||
|
checkCloseTabAction ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MouseListener extends MouseAdapter
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void mousePressed (MouseEvent e)
|
||||||
|
{
|
||||||
|
JTree tree = (JTree) e.getSource ();
|
||||||
|
int selRow = tree.getRowForLocation (e.getX (), e.getY ());
|
||||||
|
if (selRow < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
TreePath tp = tree.getPathForLocation (e.getX (), e.getY ());
|
||||||
|
DefaultMutableTreeNode selectedNode =
|
||||||
|
(DefaultMutableTreeNode) tp.getLastPathComponent ();
|
||||||
|
FileNode node = (FileNode) selectedNode.getUserObject ();
|
||||||
|
if (node.file.isDirectory ())
|
||||||
|
lister.catalogLister.setNode (selectedNode);
|
||||||
|
else if (e.getClickCount () == 2)
|
||||||
|
addDiskPanel (node.getFormattedDisk (), null, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void redo (RedoEvent event)
|
||||||
|
{
|
||||||
|
Tab tab = (Tab) getSelectedComponent ();
|
||||||
|
selector.redo = true;
|
||||||
|
|
||||||
|
if (event.type.equals ("DiskEvent"))
|
||||||
|
{
|
||||||
|
if (tab instanceof FileSystemTab)
|
||||||
|
((FileSystemTab) tab).redoEvent (event);
|
||||||
|
}
|
||||||
|
else if (event.type.equals ("FileEvent"))
|
||||||
|
{
|
||||||
|
if (tab instanceof AppleDiskTab)
|
||||||
|
((AppleDiskTab) tab).redoEvent (event);
|
||||||
|
}
|
||||||
|
else if (event.type.equals ("FileNodeEvent"))
|
||||||
|
{
|
||||||
|
if (tab instanceof FileSystemTab)
|
||||||
|
((FileSystemTab) tab).redoEvent (event);
|
||||||
|
}
|
||||||
|
else if (event.type.equals ("SectorEvent"))
|
||||||
|
{
|
||||||
|
// don't care
|
||||||
|
}
|
||||||
|
else
|
||||||
|
System.out.println ("Unknown event type : " + event.type);
|
||||||
|
|
||||||
|
selector.redo = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sectorSelected (SectorSelectedEvent event)
|
||||||
|
{
|
||||||
|
// user has clicked in the DiskLayoutPanel, so turn off any current file selection
|
||||||
|
Tab tab = (Tab) getSelectedComponent ();
|
||||||
|
if (tab instanceof AppleDiskTab)
|
||||||
|
((AppleDiskTab) tab).tree.setSelectionPath (null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Override
|
||||||
|
// public void preferenceChange (PreferenceChangeEvent evt)
|
||||||
|
// {
|
||||||
|
// if (evt.getKey ().equals (PreferencesDialog.prefsCatalogFont))
|
||||||
|
// font = new Font (evt.getNewValue (), Font.PLAIN, font.getSize ());
|
||||||
|
// if (evt.getKey ().equals (PreferencesDialog.prefsCatalogFontSize))
|
||||||
|
// font = new Font (font.getFontName (),
|
||||||
|
// Font.PLAIN, Integer.parseInt (evt.getNewValue ()));
|
||||||
|
// if (fileTab != null)
|
||||||
|
// fileTab.setTreeFont (font);
|
||||||
|
// for (AppleDiskTab tab : diskTabs)
|
||||||
|
// tab.setTreeFont (font);
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void restore (Preferences preferences)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changeFont (FontChangeEvent fontChangeEvent)
|
||||||
|
{
|
||||||
|
font = fontChangeEvent.font;
|
||||||
|
if (fileTab != null)
|
||||||
|
fileTab.setTreeFont (font);
|
||||||
|
for (AppleDiskTab tab : diskTabs)
|
||||||
|
tab.setTreeFont (font);
|
||||||
|
}
|
||||||
|
}
|
31
src/com/bytezone/diskbrowser/gui/CloseTabAction.java
Normal file
31
src/com/bytezone/diskbrowser/gui/CloseTabAction.java
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package com.bytezone.diskbrowser.gui;
|
||||||
|
|
||||||
|
import java.awt.Toolkit;
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
import java.awt.event.KeyEvent;
|
||||||
|
|
||||||
|
import javax.swing.AbstractAction;
|
||||||
|
import javax.swing.Action;
|
||||||
|
import javax.swing.KeyStroke;
|
||||||
|
|
||||||
|
public class CloseTabAction extends AbstractAction
|
||||||
|
{
|
||||||
|
CatalogPanel catalogPanel;
|
||||||
|
|
||||||
|
public CloseTabAction (CatalogPanel catalogPanel)
|
||||||
|
{
|
||||||
|
super ("Close Tab");
|
||||||
|
putValue (Action.SHORT_DESCRIPTION, "Close the current disk tab");
|
||||||
|
// putValue (Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke ("ctrl W"));
|
||||||
|
int mask = Toolkit.getDefaultToolkit ().getMenuShortcutKeyMask ();
|
||||||
|
putValue (Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke (KeyEvent.VK_W, mask));
|
||||||
|
// putValue (Action.MNEMONIC_KEY, KeyEvent.VK_W);
|
||||||
|
this.catalogPanel = catalogPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed (ActionEvent e)
|
||||||
|
{
|
||||||
|
catalogPanel.closeCurrentTab ();
|
||||||
|
}
|
||||||
|
}
|
23
src/com/bytezone/diskbrowser/gui/CreateDatabaseAction.java
Normal file
23
src/com/bytezone/diskbrowser/gui/CreateDatabaseAction.java
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package com.bytezone.diskbrowser.gui;
|
||||||
|
|
||||||
|
import java.awt.event.ActionEvent;
|
||||||
|
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
|
||||||
|
import com.bytezone.common.DefaultAction;
|
||||||
|
|
||||||
|
class CreateDatabaseAction extends DefaultAction
|
||||||
|
{
|
||||||
|
public CreateDatabaseAction ()
|
||||||
|
{
|
||||||
|
super ("Create Database", "Not working yet", null);
|
||||||
|
// putValue (Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke ("alt A"));
|
||||||
|
// putValue (Action.MNEMONIC_KEY, KeyEvent.VK_A);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void actionPerformed (ActionEvent e)
|
||||||
|
{
|
||||||
|
JOptionPane.showMessageDialog (null, "Coming soon...", "Database",
|
||||||
|
JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
}
|
||||||
|
}
|
335
src/com/bytezone/diskbrowser/gui/DataPanel.java
Executable file
335
src/com/bytezone/diskbrowser/gui/DataPanel.java
Executable file
|
@ -0,0 +1,335 @@
|
||||||
|
package com.bytezone.diskbrowser.gui;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import javax.swing.event.ChangeEvent;
|
||||||
|
import javax.swing.event.ChangeListener;
|
||||||
|
|
||||||
|
import com.bytezone.common.FontAction.FontChangeEvent;
|
||||||
|
import com.bytezone.common.FontAction.FontChangeListener;
|
||||||
|
import com.bytezone.diskbrowser.disk.DiskAddress;
|
||||||
|
import com.bytezone.diskbrowser.disk.SectorList;
|
||||||
|
|
||||||
|
class DataPanel extends JTabbedPane implements DiskSelectionListener, FileSelectionListener,
|
||||||
|
SectorSelectionListener,
|
||||||
|
// PreferenceChangeListener,
|
||||||
|
FileNodeSelectionListener, FontChangeListener
|
||||||
|
{
|
||||||
|
private static final int TEXT_WIDTH = 65;
|
||||||
|
|
||||||
|
JTextArea hexText;
|
||||||
|
JTextArea disassemblyText;
|
||||||
|
|
||||||
|
// these two panes are interchangeable
|
||||||
|
JScrollPane formattedPane;
|
||||||
|
JScrollPane imagePane;
|
||||||
|
|
||||||
|
JTextArea formattedText;
|
||||||
|
ImagePanel imagePanel; // internal class
|
||||||
|
|
||||||
|
boolean imageVisible = false;
|
||||||
|
|
||||||
|
// used to determine whether the text has been set
|
||||||
|
boolean formattedTextValid;
|
||||||
|
boolean hexTextValid;
|
||||||
|
boolean assemblerTextValid;
|
||||||
|
DataSource currentDataSource;
|
||||||
|
|
||||||
|
// private Font font;
|
||||||
|
final MenuHandler menuHandler;
|
||||||
|
|
||||||
|
public DataPanel (MenuHandler mh, Preferences prefs)
|
||||||
|
{
|
||||||
|
// String dataFontName =
|
||||||
|
// prefs.get (PreferencesDialog.prefsDataFont, PreferencesDialog.defaultFontName);
|
||||||
|
// System.out.println (dataFontName);
|
||||||
|
// int dataFontSize =
|
||||||
|
// prefs.getInt (PreferencesDialog.prefsDataFontSize, PreferencesDialog.defaultFontSize);
|
||||||
|
// font = new Font (dataFontName, Font.PLAIN, dataFontSize);
|
||||||
|
|
||||||
|
this.menuHandler = mh;
|
||||||
|
setTabPlacement (SwingConstants.BOTTOM);
|
||||||
|
|
||||||
|
formattedText = new JTextArea (10, TEXT_WIDTH);
|
||||||
|
formattedPane = setPanel (formattedText, "Formatted");
|
||||||
|
formattedText.setLineWrap (mh.lineWrapItem.isSelected ());
|
||||||
|
formattedText
|
||||||
|
.setText ("Please use the 'File->Set root folder' command to "
|
||||||
|
+ "\ntell DiskBrowser where your Apple disks are located."
|
||||||
|
+ "\n\nTo see the contents of a disk in more detail, double-click"
|
||||||
|
+ "\nthe disk. You will then be able to select individual files to view completely.");
|
||||||
|
|
||||||
|
hexText = new JTextArea (10, TEXT_WIDTH);
|
||||||
|
setPanel (hexText, "Hex dump");
|
||||||
|
|
||||||
|
disassemblyText = new JTextArea (10, TEXT_WIDTH);
|
||||||
|
setPanel (disassemblyText, "Disassembly");
|
||||||
|
|
||||||
|
imagePanel = new ImagePanel ();
|
||||||
|
imagePane =
|
||||||
|
new JScrollPane (imagePanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
|
||||||
|
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||||
|
// imagePane.getVerticalScrollBar ().setUnitIncrement (font.getSize ());
|
||||||
|
|
||||||
|
// setTabsFont (font);
|
||||||
|
// this.setMinimumSize (new Dimension (800, 200));
|
||||||
|
|
||||||
|
addChangeListener (new ChangeListener ()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void stateChanged (ChangeEvent e)
|
||||||
|
{
|
||||||
|
switch (getSelectedIndex ())
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
if (!formattedTextValid)
|
||||||
|
{
|
||||||
|
if (currentDataSource == null)
|
||||||
|
formattedText.setText ("");
|
||||||
|
else
|
||||||
|
setText (formattedText, currentDataSource.getText ());
|
||||||
|
formattedTextValid = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (!hexTextValid)
|
||||||
|
{
|
||||||
|
if (currentDataSource == null)
|
||||||
|
hexText.setText ("");
|
||||||
|
else
|
||||||
|
setText (hexText, currentDataSource.getHexDump ());
|
||||||
|
hexTextValid = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (!assemblerTextValid)
|
||||||
|
{
|
||||||
|
if (currentDataSource == null)
|
||||||
|
disassemblyText.setText ("");
|
||||||
|
else
|
||||||
|
setText (disassemblyText, currentDataSource.getAssembler ());
|
||||||
|
assemblerTextValid = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
System.out.println ("Invalid index selected in DataPanel");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mh.lineWrapItem.setAction (new LineWrapAction (formattedText));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setTabsFont (Font font)
|
||||||
|
{
|
||||||
|
formattedText.setFont (font);
|
||||||
|
hexText.setFont (font);
|
||||||
|
disassemblyText.setFont (font);
|
||||||
|
imagePane.getVerticalScrollBar ().setUnitIncrement (font.getSize ());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCurrentText ()
|
||||||
|
{
|
||||||
|
int index = getSelectedIndex ();
|
||||||
|
return index == 0 ? formattedText.getText () : index == 1 ? hexText.getText ()
|
||||||
|
: disassemblyText.getText ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private JScrollPane setPanel (JTextArea outputPanel, String tabName)
|
||||||
|
{
|
||||||
|
outputPanel.setEditable (false);
|
||||||
|
outputPanel.setMargin (new Insets (5, 5, 5, 5));
|
||||||
|
|
||||||
|
JScrollPane outputScrollPane =
|
||||||
|
new JScrollPane (outputPanel, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
|
||||||
|
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
||||||
|
outputScrollPane.setBorder (null); // remove the ugly default border
|
||||||
|
add (outputScrollPane, tabName);
|
||||||
|
return outputScrollPane;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setDataSource (DataSource dataSource)
|
||||||
|
{
|
||||||
|
currentDataSource = dataSource;
|
||||||
|
if (dataSource == null)
|
||||||
|
{
|
||||||
|
formattedText.setText ("");
|
||||||
|
hexText.setText ("");
|
||||||
|
disassemblyText.setText ("");
|
||||||
|
checkImage ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (getSelectedIndex ())
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
try
|
||||||
|
{
|
||||||
|
setText (formattedText, dataSource.getText ());
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
setText (formattedText, e.toString ());
|
||||||
|
e.printStackTrace ();
|
||||||
|
}
|
||||||
|
hexTextValid = false;
|
||||||
|
assemblerTextValid = false;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
setText (hexText, dataSource.getHexDump ());
|
||||||
|
formattedTextValid = false;
|
||||||
|
assemblerTextValid = false;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
setText (disassemblyText, dataSource.getAssembler ());
|
||||||
|
hexTextValid = false;
|
||||||
|
formattedTextValid = false;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
System.out.println ("Invalid index selected in DataPanel");
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferedImage image = dataSource.getImage ();
|
||||||
|
if (image == null)
|
||||||
|
{
|
||||||
|
checkImage ();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
imagePanel.setImage (image);
|
||||||
|
imagePane.setViewportView (imagePanel);
|
||||||
|
if (!imageVisible)
|
||||||
|
{
|
||||||
|
int selected = getSelectedIndex ();
|
||||||
|
remove (formattedPane);
|
||||||
|
add (imagePane, "Formatted", 0);
|
||||||
|
setSelectedIndex (selected);
|
||||||
|
imageVisible = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkImage ()
|
||||||
|
{
|
||||||
|
if (imageVisible)
|
||||||
|
{
|
||||||
|
int selected = getSelectedIndex ();
|
||||||
|
remove (imagePane);
|
||||||
|
add (formattedPane, "Formatted", 0);
|
||||||
|
setSelectedIndex (selected);
|
||||||
|
imageVisible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setText (JTextArea textArea, String text)
|
||||||
|
{
|
||||||
|
textArea.setText (text);
|
||||||
|
textArea.setCaretPosition (0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ImagePanel extends JPanel
|
||||||
|
{
|
||||||
|
private BufferedImage image;
|
||||||
|
private int scale = 1;
|
||||||
|
|
||||||
|
public ImagePanel ()
|
||||||
|
{
|
||||||
|
this.setBackground (Color.gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setImage (BufferedImage image)
|
||||||
|
{
|
||||||
|
this.image = image;
|
||||||
|
|
||||||
|
if (image != null)
|
||||||
|
{
|
||||||
|
Graphics2D g2 = image.createGraphics ();
|
||||||
|
g2.setRenderingHint (RenderingHints.KEY_ANTIALIASING,
|
||||||
|
RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
}
|
||||||
|
|
||||||
|
int width = image.getWidth ();
|
||||||
|
int height = image.getHeight ();
|
||||||
|
|
||||||
|
if (width < 400)
|
||||||
|
scale = (400 - 1) / width + 1;
|
||||||
|
if (scale > 4)
|
||||||
|
scale = 4;
|
||||||
|
|
||||||
|
setPreferredSize (new Dimension (width * scale, height * scale));
|
||||||
|
repaint ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void paintComponent (Graphics g)
|
||||||
|
{
|
||||||
|
super.paintComponent (g);
|
||||||
|
|
||||||
|
if (image != null)
|
||||||
|
{
|
||||||
|
Graphics2D g2 = ((Graphics2D) g);
|
||||||
|
g2.transform (AffineTransform.getScaleInstance (scale, scale));
|
||||||
|
g2.drawImage (image, (getWidth () - image.getWidth () * scale) / 2 / scale, 4, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void diskSelected (DiskSelectedEvent event)
|
||||||
|
{
|
||||||
|
setSelectedIndex (0);
|
||||||
|
setDataSource (null);
|
||||||
|
if (event.getFormattedDisk () != null)
|
||||||
|
setDataSource (event.getFormattedDisk ().getCatalog ().getDataSource ());
|
||||||
|
else
|
||||||
|
System.out.println ("bollocks in diskSelected()");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fileSelected (FileSelectedEvent event)
|
||||||
|
{
|
||||||
|
setDataSource (event.file.getDataSource ());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sectorSelected (SectorSelectedEvent event)
|
||||||
|
{
|
||||||
|
List<DiskAddress> sectors = event.getSectors ();
|
||||||
|
if (sectors == null || sectors.size () == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (sectors.size () == 1)
|
||||||
|
setDataSource (event.getFormattedDisk ().getFormattedSector (sectors.get (0)));
|
||||||
|
else
|
||||||
|
setDataSource (new SectorList (event.getFormattedDisk (), sectors));
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Override
|
||||||
|
// public void preferenceChange (PreferenceChangeEvent evt)
|
||||||
|
// {
|
||||||
|
// if (evt.getKey ().equals (PreferencesDialog.prefsDataFont))
|
||||||
|
// font = new Font (evt.getNewValue (), Font.PLAIN, font.getSize ());
|
||||||
|
// if (evt.getKey ().equals (PreferencesDialog.prefsDataFontSize))
|
||||||
|
// font = new Font (font.getFontName (), Font.PLAIN, Integer.parseInt (evt.getNewValue ()));
|
||||||
|
// setTabsFont (font);
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fileNodeSelected (FileNodeSelectedEvent event)
|
||||||
|
{
|
||||||
|
setSelectedIndex (0);
|
||||||
|
setDataSource (event.getFileNode ());
|
||||||
|
// FileNode node = event.getFileNode ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void changeFont (FontChangeEvent fontChangeEvent)
|
||||||
|
{
|
||||||
|
setTabsFont (fontChangeEvent.font);
|
||||||
|
}
|
||||||
|
}
|
18
src/com/bytezone/diskbrowser/gui/DataSource.java
Executable file
18
src/com/bytezone/diskbrowser/gui/DataSource.java
Executable file
|
@ -0,0 +1,18 @@
|
||||||
|
package com.bytezone.diskbrowser.gui;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
|
||||||
|
public interface DataSource
|
||||||
|
{
|
||||||
|
public String getText ();
|
||||||
|
|
||||||
|
public String getAssembler ();
|
||||||
|
|
||||||
|
public String getHexDump ();
|
||||||
|
|
||||||
|
public BufferedImage getImage ();
|
||||||
|
|
||||||
|
public JComponent getComponent ();
|
||||||
|
}
|
138
src/com/bytezone/diskbrowser/gui/DiskAndFileSelector.java
Executable file
138
src/com/bytezone/diskbrowser/gui/DiskAndFileSelector.java
Executable file
|
@ -0,0 +1,138 @@
|
||||||
|
package com.bytezone.diskbrowser.gui;
|
||||||
|
|
||||||
|
import javax.swing.JOptionPane;
|
||||||
|
import javax.swing.event.EventListenerList;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.applefile.AppleFileSource;
|
||||||
|
import com.bytezone.diskbrowser.disk.FormattedDisk;
|
||||||
|
import com.bytezone.diskbrowser.gui.TreeBuilder.FileNode;
|
||||||
|
|
||||||
|
class DiskAndFileSelector
|
||||||
|
{
|
||||||
|
EventListenerList listenerList = new EventListenerList ();
|
||||||
|
FormattedDisk currentDisk;
|
||||||
|
boolean redo;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Apple DiskSelection routines
|
||||||
|
*/
|
||||||
|
public void addDiskSelectionListener (DiskSelectionListener listener)
|
||||||
|
{
|
||||||
|
listenerList.add (DiskSelectionListener.class, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeDiskSelectionListener (DiskSelectionListener listener)
|
||||||
|
{
|
||||||
|
listenerList.remove (DiskSelectionListener.class, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addFileNodeSelectionListener (FileNodeSelectionListener listener)
|
||||||
|
{
|
||||||
|
listenerList.add (FileNodeSelectionListener.class, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeFileNodeSelectionListener (FileNodeSelectionListener listener)
|
||||||
|
{
|
||||||
|
listenerList.remove (FileNodeSelectionListener.class, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
// public void fireDiskSelectionEvent (File file)
|
||||||
|
// {
|
||||||
|
// if (file.isDirectory ())
|
||||||
|
// {
|
||||||
|
// System.out.println ("Directory received : " + file.getAbsolutePath ());
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (currentDisk != null) // will this screw up the refresh command?
|
||||||
|
// {
|
||||||
|
// System.out.println (currentDisk.getDisk ().getFile ().getAbsolutePath ());
|
||||||
|
// System.out.println (file.getAbsolutePath ());
|
||||||
|
// }
|
||||||
|
// if (currentDisk != null
|
||||||
|
// && currentDisk.getDisk ().getFile ().getAbsolutePath ().equals (file.getAbsolutePath ()))
|
||||||
|
// fireDiskSelectionEvent (currentDisk);
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
// System.out.println (" creating disk from a file");
|
||||||
|
// fireDiskSelectionEvent (DiskFactory.createDisk (file.getAbsolutePath ()));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
public void fireDiskSelectionEvent (FileNode node)
|
||||||
|
{
|
||||||
|
if (node.file.isDirectory ())
|
||||||
|
{
|
||||||
|
fireFileNodeSelectionEvent (node);
|
||||||
|
currentDisk = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FormattedDisk fd = node.getFormattedDisk ();
|
||||||
|
if (fd == null)
|
||||||
|
JOptionPane.showMessageDialog (null, "Incorrect file format", "Format error",
|
||||||
|
JOptionPane.ERROR_MESSAGE);
|
||||||
|
else
|
||||||
|
fireDiskSelectionEvent (fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fireFileNodeSelectionEvent (FileNode node)
|
||||||
|
{
|
||||||
|
FileNodeSelectedEvent e = new FileNodeSelectedEvent (this, node);
|
||||||
|
e.redo = redo;
|
||||||
|
FileNodeSelectionListener[] listeners =
|
||||||
|
(listenerList.getListeners (FileNodeSelectionListener.class));
|
||||||
|
for (FileNodeSelectionListener listener : listeners)
|
||||||
|
listener.fileNodeSelected (e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fireDiskSelectionEvent (FormattedDisk disk)
|
||||||
|
{
|
||||||
|
if (disk == currentDisk)
|
||||||
|
{
|
||||||
|
System.out.println ("Disk event duplicated");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disk == null)
|
||||||
|
{
|
||||||
|
System.out.println ("Null disk in fireDiskSelectionEvent()");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DiskSelectedEvent e = new DiskSelectedEvent (this, disk);
|
||||||
|
e.redo = redo;
|
||||||
|
DiskSelectionListener[] listeners =
|
||||||
|
(listenerList.getListeners (DiskSelectionListener.class));
|
||||||
|
for (DiskSelectionListener listener : listeners)
|
||||||
|
listener.diskSelected (e);
|
||||||
|
currentDisk = disk;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Apple FileSelection routines
|
||||||
|
*/
|
||||||
|
|
||||||
|
public void addFileSelectionListener (FileSelectionListener listener)
|
||||||
|
{
|
||||||
|
listenerList.add (FileSelectionListener.class, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeFileSelectionListener (FileSelectionListener listener)
|
||||||
|
{
|
||||||
|
listenerList.remove (FileSelectionListener.class, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void fireFileSelectionEvent (AppleFileSource file)
|
||||||
|
{
|
||||||
|
assert file != null;
|
||||||
|
currentDisk = null;
|
||||||
|
FileSelectedEvent e = new FileSelectedEvent (this, file);
|
||||||
|
e.redo = redo;
|
||||||
|
FileSelectionListener[] listeners =
|
||||||
|
(listenerList.getListeners (FileSelectionListener.class));
|
||||||
|
for (FileSelectionListener listener : listeners)
|
||||||
|
listener.fileSelected (e);
|
||||||
|
}
|
||||||
|
}
|
196
src/com/bytezone/diskbrowser/gui/DiskBrowser.java
Executable file
196
src/com/bytezone/diskbrowser/gui/DiskBrowser.java
Executable file
|
@ -0,0 +1,196 @@
|
||||||
|
package com.bytezone.diskbrowser.gui;
|
||||||
|
|
||||||
|
import java.awt.BorderLayout;
|
||||||
|
import java.awt.Color;
|
||||||
|
import java.awt.EventQueue;
|
||||||
|
import java.util.prefs.Preferences;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
|
||||||
|
import com.bytezone.common.Platform;
|
||||||
|
import com.bytezone.common.QuitAction;
|
||||||
|
import com.bytezone.common.QuitAction.QuitListener;
|
||||||
|
import com.bytezone.common.State;
|
||||||
|
|
||||||
|
public class DiskBrowser extends JFrame implements DiskSelectionListener, QuitListener
|
||||||
|
{
|
||||||
|
private static final String windowTitle = "Apple ][ Disk Browser";
|
||||||
|
private static final String PREFS_FULL_SCREEN = "full screen";
|
||||||
|
Preferences prefs = Preferences.userNodeForPackage (this.getClass ());
|
||||||
|
|
||||||
|
public DiskBrowser ()
|
||||||
|
{
|
||||||
|
super (windowTitle);
|
||||||
|
|
||||||
|
State state = new State (prefs);
|
||||||
|
|
||||||
|
if (false)
|
||||||
|
state.clear ();
|
||||||
|
|
||||||
|
JToolBar toolBar = new JToolBar ("Toolbar", JToolBar.HORIZONTAL);
|
||||||
|
MenuHandler menuHandler = new MenuHandler (prefs);
|
||||||
|
|
||||||
|
setJMenuBar (menuHandler.menuBar);
|
||||||
|
setLayout (new BorderLayout ());
|
||||||
|
add (toolBar, BorderLayout.NORTH);
|
||||||
|
|
||||||
|
RedoHandler redoHandler = new RedoHandler (getRootPane (), toolBar); // add nav buttons
|
||||||
|
toolBar.addSeparator ();
|
||||||
|
|
||||||
|
// create and add the left-hand catalog panel
|
||||||
|
CatalogPanel catalogPanel = new CatalogPanel (menuHandler, redoHandler, prefs);
|
||||||
|
JPanel catalogBorderPanel = addPanel (catalogPanel, "Catalog", BorderLayout.WEST);
|
||||||
|
|
||||||
|
// create and add the centre output panel
|
||||||
|
DataPanel dataPanel = new DataPanel (menuHandler, prefs);
|
||||||
|
addPanel (dataPanel, "Output", BorderLayout.CENTER);
|
||||||
|
|
||||||
|
// create and add the right-hand disk layout panel
|
||||||
|
DiskLayoutPanel diskLayoutPanel = new DiskLayoutPanel ();
|
||||||
|
JPanel layoutBorderPanel = addPanel (diskLayoutPanel, "Disk layout", BorderLayout.EAST);
|
||||||
|
|
||||||
|
// create actions
|
||||||
|
RootDirectoryAction rootDirectoryAction = new RootDirectoryAction (null, catalogPanel);
|
||||||
|
RefreshTreeAction refreshTreeAction = new RefreshTreeAction (catalogPanel);
|
||||||
|
// PreferencesAction preferencesAction = new PreferencesAction (this, prefs);
|
||||||
|
AbstractAction print = new PrintAction (dataPanel);
|
||||||
|
AboutAction aboutAction = new AboutAction ();
|
||||||
|
HideCatalogAction hideCatalogAction = new HideCatalogAction (this, catalogBorderPanel);
|
||||||
|
HideLayoutAction hideLayoutAction = new HideLayoutAction (this, layoutBorderPanel);
|
||||||
|
ShowFreeSectorsAction showFreeAction =
|
||||||
|
new ShowFreeSectorsAction (menuHandler, diskLayoutPanel);
|
||||||
|
DuplicateAction duplicateAction = new DuplicateAction ();
|
||||||
|
CloseTabAction closeTabAction = new CloseTabAction (catalogPanel);
|
||||||
|
|
||||||
|
// add action buttons to toolbar
|
||||||
|
toolBar.add (rootDirectoryAction);
|
||||||
|
toolBar.add (refreshTreeAction);
|
||||||
|
// toolBar.add (preferencesAction);
|
||||||
|
toolBar.add (duplicateAction);
|
||||||
|
toolBar.add (print);
|
||||||
|
toolBar.add (aboutAction);
|
||||||
|
|
||||||
|
// set the listeners
|
||||||
|
catalogPanel.addDiskSelectionListener (this);
|
||||||
|
catalogPanel.addDiskSelectionListener (dataPanel);
|
||||||
|
catalogPanel.addDiskSelectionListener (diskLayoutPanel);
|
||||||
|
catalogPanel.addDiskSelectionListener (redoHandler);
|
||||||
|
catalogPanel.addDiskSelectionListener (menuHandler);
|
||||||
|
|
||||||
|
catalogPanel.addFileSelectionListener (dataPanel);
|
||||||
|
catalogPanel.addFileSelectionListener (diskLayoutPanel);
|
||||||
|
catalogPanel.addFileSelectionListener (redoHandler);
|
||||||
|
catalogPanel.addFileSelectionListener (menuHandler);
|
||||||
|
|
||||||
|
catalogPanel.addFileNodeSelectionListener (dataPanel);
|
||||||
|
catalogPanel.addFileNodeSelectionListener (redoHandler);
|
||||||
|
|
||||||
|
diskLayoutPanel.addSectorSelectionListener (dataPanel);
|
||||||
|
diskLayoutPanel.addSectorSelectionListener (redoHandler);
|
||||||
|
diskLayoutPanel.addSectorSelectionListener (catalogPanel);
|
||||||
|
|
||||||
|
redoHandler.addRedoListener (catalogPanel);
|
||||||
|
redoHandler.addRedoListener (diskLayoutPanel);
|
||||||
|
|
||||||
|
menuHandler.fontAction.addFontChangeListener (dataPanel);
|
||||||
|
menuHandler.fontAction.addFontChangeListener (catalogPanel);
|
||||||
|
menuHandler.fontAction.addFontChangeListener (diskLayoutPanel);
|
||||||
|
|
||||||
|
// set the MenuItem Actions
|
||||||
|
menuHandler.printItem.setAction (print);
|
||||||
|
// menuHandler.addHelpMenuAction (preferencesAction, "prefs");
|
||||||
|
menuHandler.addHelpMenuAction (aboutAction, "about");
|
||||||
|
menuHandler.refreshTreeItem.setAction (refreshTreeAction);
|
||||||
|
menuHandler.rootItem.setAction (rootDirectoryAction);
|
||||||
|
menuHandler.showCatalogItem.setAction (hideCatalogAction);
|
||||||
|
menuHandler.showLayoutItem.setAction (hideLayoutAction);
|
||||||
|
menuHandler.showFreeSectorsItem.setAction (showFreeAction);
|
||||||
|
menuHandler.duplicateItem.setAction (duplicateAction);
|
||||||
|
menuHandler.closeTabItem.setAction (closeTabAction);
|
||||||
|
|
||||||
|
final QuitAction quitAction = Platform.setQuit (this, prefs, menuHandler.fileMenu);
|
||||||
|
|
||||||
|
quitAction.addQuitListener (menuHandler);
|
||||||
|
quitAction.addQuitListener (menuHandler.fontAction);
|
||||||
|
quitAction.addQuitListener (catalogPanel);
|
||||||
|
quitAction.addQuitListener (this);
|
||||||
|
|
||||||
|
catalogPanel.setDuplicateAction (duplicateAction);
|
||||||
|
catalogPanel.setCloseTabAction (closeTabAction);
|
||||||
|
|
||||||
|
pack ();
|
||||||
|
|
||||||
|
// prefs.addPreferenceChangeListener (catalogPanel);
|
||||||
|
// prefs.addPreferenceChangeListener (dataPanel);
|
||||||
|
|
||||||
|
// Remove the two optional panels if they were previously hidden
|
||||||
|
if (!menuHandler.showLayoutItem.isSelected ())
|
||||||
|
hideLayoutAction.set (false);
|
||||||
|
if (!menuHandler.showCatalogItem.isSelected ())
|
||||||
|
hideCatalogAction.set (false);
|
||||||
|
|
||||||
|
// activate the highest panel now that the listeners are ready
|
||||||
|
catalogPanel.activate ();
|
||||||
|
|
||||||
|
quitAction.restore ();
|
||||||
|
}
|
||||||
|
|
||||||
|
private JPanel addPanel (JComponent pane, String title, String location)
|
||||||
|
{
|
||||||
|
JPanel panel = new JPanel (new BorderLayout ());
|
||||||
|
panel.setBackground (Color.WHITE);
|
||||||
|
// panel.setOpaque (true);
|
||||||
|
panel.setBorder (BorderFactory.createTitledBorder (title));
|
||||||
|
panel.add (pane);
|
||||||
|
add (panel, location);
|
||||||
|
return panel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void diskSelected (DiskSelectedEvent e)
|
||||||
|
{
|
||||||
|
setTitle (windowTitle + e.getFormattedDisk () == null ? "" : e.getFormattedDisk ()
|
||||||
|
.getName ());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main (String[] args)
|
||||||
|
{
|
||||||
|
EventQueue.invokeLater (new Runnable ()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void run ()
|
||||||
|
{
|
||||||
|
Platform.setLookAndFeel ();
|
||||||
|
new DiskBrowser ().setVisible (true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void quit (Preferences preferences)
|
||||||
|
{
|
||||||
|
prefs.putBoolean (PREFS_FULL_SCREEN, getExtendedState () == MAXIMIZED_BOTH);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void restore (Preferences preferences)
|
||||||
|
{
|
||||||
|
if (true)
|
||||||
|
{
|
||||||
|
setLocationRelativeTo (null); // centre
|
||||||
|
|
||||||
|
// if we are on a smallish screen, just go fullscreen width
|
||||||
|
if (Platform.toolkit.getScreenSize ().width <= 1280)
|
||||||
|
setExtendedState (MAXIMIZED_HORIZ);
|
||||||
|
|
||||||
|
// restore window if it was previously at full screen
|
||||||
|
if (prefs.getBoolean (PREFS_FULL_SCREEN, false))
|
||||||
|
setExtendedState (MAXIMIZED_BOTH);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setLocation (10, 10);
|
||||||
|
setSize (1200, 812);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
src/com/bytezone/diskbrowser/gui/DiskDetails.java
Normal file
41
src/com/bytezone/diskbrowser/gui/DiskDetails.java
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package com.bytezone.diskbrowser.gui;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import com.bytezone.common.ComputeCRC32;
|
||||||
|
|
||||||
|
class DiskDetails
|
||||||
|
{
|
||||||
|
private final File file;
|
||||||
|
private long checksum = -1;
|
||||||
|
boolean duplicate;
|
||||||
|
|
||||||
|
public DiskDetails (File file)
|
||||||
|
{
|
||||||
|
this.file = file;
|
||||||
|
duplicate = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAbsolutePath ()
|
||||||
|
{
|
||||||
|
return file.getAbsolutePath ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getChecksum ()
|
||||||
|
{
|
||||||
|
if (checksum < 0)
|
||||||
|
checksum = ComputeCRC32.getChecksumValue (file);
|
||||||
|
return checksum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean delete ()
|
||||||
|
{
|
||||||
|
return file.delete ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString ()
|
||||||
|
{
|
||||||
|
return String.format ("%s (%s)", file.getAbsolutePath (), duplicate ? "duplicate" : "OK");
|
||||||
|
}
|
||||||
|
}
|
273
src/com/bytezone/diskbrowser/gui/DiskLayoutImage.java
Normal file
273
src/com/bytezone/diskbrowser/gui/DiskLayoutImage.java
Normal file
|
@ -0,0 +1,273 @@
|
||||||
|
package com.bytezone.diskbrowser.gui;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.event.InputEvent;
|
||||||
|
import java.awt.event.MouseAdapter;
|
||||||
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
import javax.swing.Scrollable;
|
||||||
|
import javax.swing.SwingConstants;
|
||||||
|
|
||||||
|
import com.bytezone.diskbrowser.disk.Disk;
|
||||||
|
import com.bytezone.diskbrowser.disk.DiskAddress;
|
||||||
|
import com.bytezone.diskbrowser.disk.FormattedDisk;
|
||||||
|
import com.bytezone.diskbrowser.disk.SectorType;
|
||||||
|
import com.bytezone.diskbrowser.gui.DiskLayoutPanel.LayoutDetails;
|
||||||
|
import com.bytezone.diskbrowser.gui.RedoHandler.RedoEvent;
|
||||||
|
|
||||||
|
class DiskLayoutImage extends JComponent implements Scrollable
|
||||||
|
{
|
||||||
|
static final Cursor crosshairCursor = new Cursor (Cursor.CROSSHAIR_CURSOR);
|
||||||
|
FormattedDisk disk;
|
||||||
|
LayoutDetails layoutDetails;
|
||||||
|
private boolean showFreeSectors;
|
||||||
|
DiskLayoutSelection selectionHandler = new DiskLayoutSelection ();
|
||||||
|
boolean redo;
|
||||||
|
|
||||||
|
// set defaults (used until a real disk is set)
|
||||||
|
int bw = 30;
|
||||||
|
int bh = 15;
|
||||||
|
int gw = 8;
|
||||||
|
int gh = 35;
|
||||||
|
|
||||||
|
public DiskLayoutImage ()
|
||||||
|
{
|
||||||
|
setPreferredSize (new Dimension (240 + 1, 525 + 1));
|
||||||
|
addMouseListener (new MyMouseListener ());
|
||||||
|
setBackground (Color.WHITE);
|
||||||
|
setOpaque (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDisk (FormattedDisk disk, LayoutDetails details)
|
||||||
|
{
|
||||||
|
this.disk = disk;
|
||||||
|
layoutDetails = details;
|
||||||
|
|
||||||
|
// System.out.println (details);
|
||||||
|
// new Exception ().printStackTrace ();
|
||||||
|
|
||||||
|
bw = layoutDetails.block.width;
|
||||||
|
bh = layoutDetails.block.height;
|
||||||
|
gw = layoutDetails.grid.width;
|
||||||
|
gh = layoutDetails.grid.height;
|
||||||
|
|
||||||
|
setPreferredSize (new Dimension (gw * bw + 1, gh * bh + 1));
|
||||||
|
selectionHandler.setSelection (null);
|
||||||
|
|
||||||
|
repaint ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setShowFreeSectors (boolean showFree)
|
||||||
|
{
|
||||||
|
showFreeSectors = showFree;
|
||||||
|
repaint ();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSelection (List<DiskAddress> sectors)
|
||||||
|
{
|
||||||
|
selectionHandler.setSelection (sectors);
|
||||||
|
if (sectors != null && sectors.size () > 0)
|
||||||
|
{
|
||||||
|
DiskAddress da = sectors.size () == 1 ? sectors.get (0) : sectors.get (1);
|
||||||
|
scrollRectToVisible (layoutDetails.getLocation (da));
|
||||||
|
}
|
||||||
|
repaint ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void paintComponent (Graphics g)
|
||||||
|
{
|
||||||
|
super.paintComponent (g);
|
||||||
|
|
||||||
|
// why doesn't linux do this?
|
||||||
|
g.setColor (Color.WHITE);
|
||||||
|
g.fillRect (0, 0, getWidth (), getHeight ());
|
||||||
|
|
||||||
|
if (disk == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Rectangle clipRect = g.getClipBounds ();
|
||||||
|
|
||||||
|
Point p1 = new Point (clipRect.x / bw * bw, clipRect.y / bh * bh);
|
||||||
|
Point p2 =
|
||||||
|
new Point ((clipRect.x + clipRect.width - 1) / bw * bw, (clipRect.y
|
||||||
|
+ clipRect.height - 1)
|
||||||
|
/ bh * bh);
|
||||||
|
|
||||||
|
// System.out.printf ("gw=%d, gh=%d, bw=%d, bh=%d%n", gw, gh, bw, bh);
|
||||||
|
// int totalBlocks = 0;
|
||||||
|
int maxBlock = gw * gh;
|
||||||
|
// System.out.printf ("Max blocks: %d%n", maxBlock);
|
||||||
|
Disk d = disk.getDisk ();
|
||||||
|
List<DiskAddress> selectedBlocks = selectionHandler.getHighlights ();
|
||||||
|
|
||||||
|
// this stops an index error when using alt-5 to switch to 512-byte blocks
|
||||||
|
if (maxBlock > d.getTotalBlocks ())
|
||||||
|
maxBlock = d.getTotalBlocks ();
|
||||||
|
|
||||||
|
for (int y = p1.y; y <= p2.y; y += bh)
|
||||||
|
for (int x = p1.x; x <= p2.x; x += bw)
|
||||||
|
{
|
||||||
|
int blockNo = y / bh * gw + x / bw;
|
||||||
|
if (blockNo < maxBlock)
|
||||||
|
{
|
||||||
|
DiskAddress da = d.getDiskAddress (blockNo);
|
||||||
|
boolean flag = showFreeSectors && disk.isSectorFree (da);
|
||||||
|
boolean selected = selectedBlocks.contains (da);
|
||||||
|
drawBlock ((Graphics2D) g, blockNo, x, y, flag, selected);
|
||||||
|
// totalBlocks++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void drawBlock (Graphics2D g, int blockNo, int x, int y, boolean flagFree,
|
||||||
|
boolean selected)
|
||||||
|
{
|
||||||
|
SectorType type = disk.getSectorType (blockNo);
|
||||||
|
int offset = (bw - 4) / 2 + 1;
|
||||||
|
|
||||||
|
// Rectangle rect = new Rectangle (x, y, bw, bh);
|
||||||
|
Rectangle rect = new Rectangle (x, y, bw, bh);
|
||||||
|
// System.out.printf ("Rect: %4d %4d %4d %4d%n", x, y, bw, bh);
|
||||||
|
|
||||||
|
// draw frame
|
||||||
|
if (true) // this needs to draw the outside rectangle, and show less white space
|
||||||
|
// between blocks
|
||||||
|
{
|
||||||
|
g.setColor (Color.GRAY);
|
||||||
|
g.drawRect (rect.x, rect.y, rect.width, rect.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw coloured block
|
||||||
|
if (type.colour != Color.WHITE)
|
||||||
|
{
|
||||||
|
g.setColor (type.colour);
|
||||||
|
g.fillRect (rect.x + 2, rect.y + 2, rect.width - 3, rect.height - 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw an indicator in free blocks
|
||||||
|
if (flagFree)
|
||||||
|
{
|
||||||
|
g.setColor (getContrastColor (type));
|
||||||
|
g.drawOval (rect.x + offset - 2, rect.y + 4, 7, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
// draw an indicator in selected blocks
|
||||||
|
if (selected)
|
||||||
|
{
|
||||||
|
g.setColor (getContrastColor (type));
|
||||||
|
g.fillOval (rect.x + offset, rect.y + 6, 3, 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color getContrastColor (SectorType type)
|
||||||
|
{
|
||||||
|
if (type.colour == Color.WHITE || type.colour == Color.YELLOW || type.colour == Color.PINK
|
||||||
|
|| type.colour == Color.CYAN || type.colour == Color.ORANGE)
|
||||||
|
return Color.BLACK;
|
||||||
|
return Color.WHITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dimension getPreferredScrollableViewportSize ()
|
||||||
|
{
|
||||||
|
return new Dimension (240 + 1, 525 + 1); // floppy disk size
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int
|
||||||
|
getScrollableUnitIncrement (Rectangle visibleRect, int orientation, int direction)
|
||||||
|
{
|
||||||
|
return orientation == SwingConstants.HORIZONTAL ? bw : bh;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int
|
||||||
|
getScrollableBlockIncrement (Rectangle visibleRect, int orientation, int direction)
|
||||||
|
{
|
||||||
|
return orientation == SwingConstants.HORIZONTAL ? bw * 4 : bh * 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getScrollableTracksViewportHeight ()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getScrollableTracksViewportWidth ()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void redoEvent (RedoEvent redoEvent)
|
||||||
|
{
|
||||||
|
redo = true;
|
||||||
|
SectorSelectedEvent event = (SectorSelectedEvent) redoEvent.value;
|
||||||
|
setSelection (event.getSectors ());
|
||||||
|
fireSectorSelectionEvent (event);
|
||||||
|
redo = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fireSectorSelectionEvent ()
|
||||||
|
{
|
||||||
|
SectorSelectedEvent event =
|
||||||
|
new SectorSelectedEvent (this, selectionHandler.getHighlights (), disk);
|
||||||
|
fireSectorSelectionEvent (event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fireSectorSelectionEvent (SectorSelectedEvent event)
|
||||||
|
{
|
||||||
|
event.redo = redo;
|
||||||
|
SectorSelectionListener[] listeners =
|
||||||
|
(listenerList.getListeners (SectorSelectionListener.class));
|
||||||
|
for (SectorSelectionListener listener : listeners)
|
||||||
|
listener.sectorSelected (event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addSectorSelectionListener (SectorSelectionListener listener)
|
||||||
|
{
|
||||||
|
listenerList.add (SectorSelectionListener.class, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeSectorSelectionListener (SectorSelectionListener listener)
|
||||||
|
{
|
||||||
|
listenerList.remove (SectorSelectionListener.class, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyMouseListener extends MouseAdapter
|
||||||
|
{
|
||||||
|
private Cursor currentCursor;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseClicked (MouseEvent e)
|
||||||
|
{
|
||||||
|
int x = e.getX () / bw;
|
||||||
|
int y = e.getY () / bh;
|
||||||
|
int blockNo = y * gw + x;
|
||||||
|
DiskAddress da = disk.getDisk ().getDiskAddress (blockNo);
|
||||||
|
|
||||||
|
boolean extend = ((e.getModifiersEx () & InputEvent.SHIFT_DOWN_MASK) > 0);
|
||||||
|
boolean append = ((e.getModifiersEx () & InputEvent.CTRL_DOWN_MASK) > 0);
|
||||||
|
|
||||||
|
selectionHandler.doClick (disk.getDisk (), da, extend, append);
|
||||||
|
fireSectorSelectionEvent ();
|
||||||
|
repaint ();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseEntered (MouseEvent e)
|
||||||
|
{
|
||||||
|
currentCursor = getCursor ();
|
||||||
|
setCursor (crosshairCursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mouseExited (MouseEvent e)
|
||||||
|
{
|
||||||
|
setCursor (currentCursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user