mirror of
https://github.com/dmolony/DiskBrowser.git
synced 2025-02-20 04:29:02 +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…
x
Reference in New Issue
Block a user