Initial commit

This commit is contained in:
Denis Molony 2015-06-01 19:35:51 +10:00
commit c953204c60
199 changed files with 24747 additions and 0 deletions

3
resources/buildNumber Executable file
View File

@ -0,0 +1,3 @@
#Build Number for ANT. Do not edit!
#Mon Jun 01 19:29:14 AEST 2015
build.number=627

View 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 + "]";
}
}

View 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;
}
}

View 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);
}
}

View 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);
}
}

View 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 ();
}
}

View 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 ();
}
}

View 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 ();
}
}
}

View 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 ();
}
}
}

View 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;
}
}
}

View 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 ();
}

View 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 };
}

View 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, };
}

View 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 ();
}
}
}

View 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);
}
}

View 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;
}
}
}

View 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 ();
}
}

View 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 ();
}
}

View 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;
}
}

View 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 ();
}
}

View 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;
}
}

View 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");
}
}

View 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);
}
}
}

View 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 ();
}
}

View 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;
}
}

View 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;
}
}

View 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 ();
}
}

View 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);
}
}
}

View 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" };
}

View 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";
}
}

View 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 ();
}
}

View 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 ();
}
}

View 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 ();
}
}

View 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;
}
}

View 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;
}
}

View 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 ();
}
}

View 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 ();
}
}

View 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 ();
}
}

View 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 ();
}
}

View 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;
}
}

View 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;
}
}
}

View 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;
}
}

View 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

View 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);
}
}

View 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 ();
}
}
}

View 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 ();
}
}
}

View 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);
}
}

View 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);
}
}

View 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 ();
}
}
}

View 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;
}
}

View 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 ();
}
}

View 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 += "+";
}
}

View 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);
}
}
}

View 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";
}
}

View 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 ();
}
}

View 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 ();
}
}

View 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 ();
}
}

View 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;
}
}

View 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 ());
}
}

View 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 ();
}

View 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 ();
}

View 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 ();
}
});
}
}

View 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";
}
}

View 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";
}
}

View 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;
}
}

View 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));
}
}

View 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);
}
}

View 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 ();
}
}

View 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;
}
}

View 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 ();
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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);
}

View 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 ();
}

View 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;
}
}

View 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 ();
}
}

View 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)

View 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 ();
}
}

View 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) + ";");
}
}

View 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);
}
}

View 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;
}
}

View 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 ());
}
}

View 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");
}
}

View 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)");
}
}

View 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);
}
}

View 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 ();
}
}

View 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 ();
}
}

View 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);
}
}

View 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);
}
}
}

View 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);
}
});
}
}

View 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);
}
}

View 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 ();
}
}

View 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);
}
}

View 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);
}
}

View 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 ();
}

View 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);
}
}

View 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);
}
}
}

View 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");
}
}

View 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