Merge pull request #149 from AppleCommander/feature/woz-image

Adding WOZ image 5.25" support; adding 13-sector 5&3 decoding (WOZ/Nibble images)
This commit is contained in:
A2 Geek authored and GitHub committed 2025-07-27 13:18:15 -05:00
commit 32c1a30ebe
14 files changed
+962 -311

No files matched your search

@@ -19,36 +19,25 @@
*/
package com.webcodepro.applecommander.storage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import com.webcodepro.applecommander.storage.os.cpm.CpmFileEntry;
import com.webcodepro.applecommander.storage.os.cpm.CpmFormatDisk;
import com.webcodepro.applecommander.storage.os.dos33.DosFormatDisk;
import com.webcodepro.applecommander.storage.os.dos33.OzDosFormatDisk;
import com.webcodepro.applecommander.storage.os.dos33.UniDosFormatDisk;
import com.webcodepro.applecommander.storage.os.nakedos.NakedosFormatDisk;
import com.webcodepro.applecommander.storage.os.gutenberg.GutenbergFormatDisk;
import com.webcodepro.applecommander.storage.os.nakedos.NakedosFormatDisk;
import com.webcodepro.applecommander.storage.os.pascal.PascalFormatDisk;
import com.webcodepro.applecommander.storage.os.prodos.ProdosFormatDisk;
import com.webcodepro.applecommander.storage.os.rdos.RdosFormatDisk;
import com.webcodepro.applecommander.storage.physical.ByteArrayImageLayout;
import com.webcodepro.applecommander.storage.physical.DosOrder;
import com.webcodepro.applecommander.storage.physical.ImageOrder;
import com.webcodepro.applecommander.storage.physical.NibbleOrder;
import com.webcodepro.applecommander.storage.physical.ProdosOrder;
import com.webcodepro.applecommander.storage.physical.UniversalDiskImageLayout;
import com.webcodepro.applecommander.storage.physical.*;
import com.webcodepro.applecommander.util.AppleUtil;
import com.webcodepro.applecommander.util.StreamUtil;
import com.webcodepro.applecommander.util.TextBundle;
import java.io.*;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
* Abstract representation of an Apple2 disk (floppy, 800k, hard disk).
* <p>
@@ -130,7 +119,7 @@ public class Disk {
private Disk() {
filenameFilters = new FilenameFilter[] {
new FilenameFilter(textBundle.get("Disk.AllImages"), //$NON-NLS-1$
"*.do", "*.dsk", "*.po", "*.nib", "*.2mg", "*.2img", "*.hdv", "*.do.gz", "*.dsk.gz", "*.po.gz", "*.nib.gz", "*.2mg.gz", "*.2img.gz"), //$NON-NLS-1$
"*.do", "*.dsk", "*.po", "*.nib", "*.2mg", "*.2img", "*.hdv", "*.do.gz", "*.dsk.gz", "*.po.gz", "*.nib.gz", "*.2mg.gz", "*.2img.gz", "*.woz"), //$NON-NLS-1$
new FilenameFilter(textBundle.get("Disk.140kDosImages"), //$NON-NLS-1$
"*.do", "*.dsk", "*.do.gz", "*.dsk.gz"), //$NON-NLS-1$
new FilenameFilter(textBundle.get("Disk.140kNibbleImages"), //$NON-NLS-1$
@@ -143,6 +132,8 @@ public class Disk {
"*.hdv"), //$NON-NLS-1$
new FilenameFilter(textBundle.get("Disk.CompressedImages"), //$NON-NLS-1$
"*.sdk", "*.shk", "*.do.gz", "*.dsk.gz", "*.po.gz", "*.2mg.gz", "*.2img.gz"), //$NON-NLS-1$
new FilenameFilter(textBundle.get("Disk.WozImages"),
"*.woz"),
new FilenameFilter(textBundle.get("Disk.AllFiles"), //$NON-NLS-1$
"*.*") //$NON-NLS-1$
};
@@ -229,10 +220,16 @@ public class Disk {
diskImage = diskImageByteArray.toByteArray();
}
boolean is2img = false;
boolean isWoz = false;
/* Does it have the 2IMG header? */
if ((diskImage[0] == 0x32) && (diskImage[1] == 0x49) && (diskImage[2] == 0x4D) && (diskImage[3]) == 0x47) {
is2img = true;
}
/* Does it have the WOZ1 or WOZ2 header? */
else if ((diskImage[0] == 0x57) && (diskImage[1] == 0x4f) && (diskImage[2] == 0x5a)
&& ((diskImage[3] == 0x31) || (diskImage[3] == 0x32))) {
isWoz = true;
}
/* Does it have the DiskCopy 4.2 header? */
else if (Disk.isDC42(diskImage)) {
isDC42 = true;
@@ -265,6 +262,8 @@ public class Disk {
if (isSDK()) {
imageOrder = proDosOrder; // SDKs are always in ProDOS order
} else if (isWoz) {
imageOrder = new WozOrder(diskImageManager);
} else {
/*
* First step: test physical disk orders for viable file systems.
@@ -519,6 +518,10 @@ public class Disk {
* Identify the size of this disk.
*/
public int getPhysicalSize() {
if (getImageOrder() instanceof WozOrder) {
// Total hack since WOZ is currently a special case.
return getImageOrder().getPhysicalSize();
}
if (getDiskImageManager() != null) {
return getDiskImageManager().getPhysicalSize();
}
@@ -605,7 +608,7 @@ public class Disk {
// can vary && vtoc[0x02] == 15 // expect catalog to start on sector 15 (140KB disk only!)
&& vtoc[0x27] == 122 // expect 122 track/sector pairs per sector
&& (vtoc[0x34] == 35 || vtoc[0x34] == 40) // expect 35 or 40 tracks per disk (140KB disk only!)
&& vtoc[0x35] == 16 // expect 16 sectors per disk (140KB disk only!)
&& (vtoc[0x35] == 16 || vtoc[0x35] == 13) // expect 13 or 16 sectors per disk (140KB disk only!)
;
if (good) {
int catTrack = vtoc[0x01]; // Pull out the first catalog track/sector
@@ -789,6 +792,7 @@ public class Disk {
*/
public boolean isRdosFormat() {
if (!is140KbDisk()) return false;
if (getImageOrder().getSectorsPerTrack() != 16) return false;
byte[] block = readSector(0, 0x0d);
String id = AppleUtil.getString(block, 0xe0, 4);
return "RDOS".equals(id) || isRdos33Format(); //$NON-NLS-1$
@@ -130,11 +130,13 @@ public class DosFormatDisk extends FormattedDisk {
}
/**
* Identify the operating system format of this disk as DOS 3.3.
* Identify the operating system format of this disk as DOS 3.x.
* @see com.webcodepro.applecommander.storage.FormattedDisk#getFormat()
*/
public String getFormat() {
return textBundle.get("Dos33"); //$NON-NLS-1$
byte[] vtoc = readVtoc();
int version = Byte.toUnsignedInt(vtoc[3]);
return String.format("DOS 3.%d", version);
}
/**
@@ -3,29 +3,30 @@
* Copyright (C) 2003-2022 by Robert Greene
* robgreene at users.sourceforge.net
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.webcodepro.applecommander.storage.physical;
import com.webcodepro.applecommander.storage.Disk;
import com.webcodepro.applecommander.storage.StorageBundle;
import com.webcodepro.applecommander.storage.os.dos33.DosSectorAddress;
import com.webcodepro.applecommander.util.TextBundle;
/**
* Supports disk images stored in DOS physical order.
* <p>
* <p>
* @author Rob Greene (RobGreene@users.sourceforge.net)
*/
public class DosOrder extends ImageOrder {
@@ -43,7 +44,7 @@ public class DosOrder extends ImageOrder {
public boolean isBlockDevice() {
return false;
}
/**
* Indicates that this device is track and sector ordered.
*/
@@ -57,11 +58,11 @@ public class DosOrder extends ImageOrder {
public byte[] readSector(int track, int sector) throws IllegalArgumentException {
return readBytes(getOffset(track, sector), Disk.SECTOR_SIZE);
}
/**
* Write the specified sector.
*/
public void writeSector(int track, int sector, byte[] bytes)
public void writeSector(int track, int sector, byte[] bytes)
throws IllegalArgumentException {
writeBytes(getOffset(track, sector), bytes);
}
@@ -72,18 +73,18 @@ public class DosOrder extends ImageOrder {
* with.
*/
protected int getOffset(int track, int sector) throws IllegalArgumentException {
if (!isSizeApprox(Disk.APPLE_140KB_DISK)
&& !isSizeApprox(Disk.APPLE_800KB_DISK)
&& !isSizeApprox(Disk.APPLE_800KB_2IMG_DISK)
&& track != 0 && sector != 0) { // HACK: Allows boot sector writing
if (!isSizeApprox(Disk.APPLE_140KB_DISK)
&& !isSizeApprox(Disk.APPLE_800KB_DISK)
&& !isSizeApprox(Disk.APPLE_800KB_2IMG_DISK)
&& track != 0 && sector != 0) { // HACK: Allows boot sector writing
throw new IllegalArgumentException(
textBundle.get("DosOrder.UnrecognizedFormatError")); //$NON-NLS-1$
}
int offset = (track * getSectorsPerTrack() + sector) * Disk.SECTOR_SIZE;
if (offset > getPhysicalSize()) {
throw new IllegalArgumentException(
textBundle.format("DosOrder.InvalidSizeError", //$NON-NLS-1$
track, sector));
textBundle.format("DosOrder.InvalidSizeError", //$NON-NLS-1$
track, sector));
}
return offset;
}
@@ -93,40 +94,43 @@ public class DosOrder extends ImageOrder {
* Note: Defined in terms of reading sectors.
*/
public byte[] readBlock(int block) {
int track = block / 8;
int sectorIndex = block % 8;
int[] sectorMapping1 = { 0, 13, 11, 9, 7, 5, 3, 1 };
int[] sectorMapping2 = { 14, 12, 10, 8, 6, 4, 2, 15 };
int sector1 = sectorMapping1[sectorIndex];
int sector2 = sectorMapping2[sectorIndex];
DosSectorAddress[] sectors = blockToSectors525(block);
byte[] blockData = new byte[Disk.BLOCK_SIZE];
System.arraycopy(readSector(track, sector1), 0, blockData, 0, Disk.SECTOR_SIZE);
System.arraycopy(readSector(track, sector2), 0, blockData, Disk.SECTOR_SIZE, Disk.SECTOR_SIZE);
System.arraycopy(readSector(sectors[0].track, sectors[0].sector), 0, blockData, 0, Disk.SECTOR_SIZE);
System.arraycopy(readSector(sectors[1].track, sectors[1].sector), 0, blockData, Disk.SECTOR_SIZE, Disk.SECTOR_SIZE);
return blockData;
}
/**
* Write the block to the disk image.
* Note: Defined in terms of reading sectors.
*/
public void writeBlock(int block, byte[] data) {
int track = block / 8;
int sectorIndex = block % 8;
int[] sectorMapping1 = { 0, 13, 11, 9, 7, 5, 3, 1 };
int[] sectorMapping2 = { 14, 12, 10, 8, 6, 4, 2, 15 };
int sector1 = sectorMapping1[sectorIndex];
int sector2 = sectorMapping2[sectorIndex];
DosSectorAddress[] sectors = blockToSectors525(block);
byte[] sectorData = new byte[Disk.SECTOR_SIZE];
System.arraycopy(data, 0, sectorData, 0, Disk.SECTOR_SIZE);
writeSector(track, sector1, sectorData);
writeSector(sectors[0].track, sectors[0].sector, sectorData);
System.arraycopy(data, Disk.SECTOR_SIZE, sectorData, 0, Disk.SECTOR_SIZE);
writeSector(track, sector2, sectorData);
writeSector(sectors[1].track, sectors[1].sector, sectorData);
}
/**
* Return the name of this image order.
*/
public String getName() {
return textBundle.get("DosOrder.OrderName"); //$NON-NLS-1$
return textBundle.get("DosOrder.OrderName"); //$NON-NLS-1$
}
public static DosSectorAddress[] blockToSectors525(int block) {
int track = block / 8;
int sectorIndex = block % 8;
int[] sectorMapping1 = { 0, 13, 11, 9, 7, 5, 3, 1 };
int[] sectorMapping2 = { 14, 12, 10, 8, 6, 4, 2, 15 };
int sector1 = sectorMapping1[sectorIndex];
int sector2 = sectorMapping2[sectorIndex];
return new DosSectorAddress[] {
new DosSectorAddress(track, sector1),
new DosSectorAddress(track, sector2)
};
}
}
@@ -0,0 +1,345 @@
package com.webcodepro.applecommander.storage.physical;
import com.webcodepro.applecommander.storage.StorageBundle;
import com.webcodepro.applecommander.util.AppleUtil;
import com.webcodepro.applecommander.util.TextBundle;
public class NibbleCodec {
private static final TextBundle textBundle = StorageBundle.getInstance();
/**
* This is the 6 and 3 write translate table, as given in Beneath
* Apple DOS and Beneath Apple ProDOS 2020, pg 25.
*/
private static final int[] writeTranslateTable53 = {
//$0 $1 $2 $3 $4 $5 $6 $7
0xab, 0xad, 0xae, 0xaf, 0xb5, 0xb6, 0xb7, 0xba, // +$00
0xbb, 0xbd, 0xbe, 0xbf, 0xd6, 0xd7, 0xda, 0xdb, // +$08
0xdd, 0xde, 0xdf, 0xea, 0xeb, 0xed, 0xee, 0xef, // +$10
0xf5, 0xf6, 0xf7, 0xfa, 0xfb, 0xfd, 0xfe, 0xff // +$18
};
/**
* The 5&3 read translation table. Constructed from the 5&3
* write translate table. Used to decode a disk byte into a
* value from 0x00 to 0x1f which is further decoded...
*/
private static final int[] readTranslateTable53;
static {
// Construct the read translation table:
readTranslateTable53 = new int[256];
for (int i = 0; i< writeTranslateTable53.length; i++) {
readTranslateTable53[writeTranslateTable53[i]] = i;
}
}
/**
* This is the 6 and 2 write translate table, as given in Beneath
* Apple DOS, pg 3-21.
*/
private static final int[] writeTranslateTable62 = {
//$0 $1 $2 $3 $4 $5 $6 $7
0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6, // +$00
0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3, // +$08
0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc, // +$10
0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3, // +$18
0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, // +$20
0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec, // +$28
0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, // +$30
0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff // +$38
};
/**
* The 6&2 read translation table. Constructed from the 6&2
* write translate table. Used to decode a disk byte into a
* value from 0x00 to 0x3f which is further decoded...
*/
private static final int[] readTranslateTable62;
static {
// Construct the read translation table:
readTranslateTable62 = new int[256];
for (int i = 0; i< writeTranslateTable62.length; i++) {
readTranslateTable62[writeTranslateTable62[i]] = i;
}
}
public static final int[] DOS32_ADDRESS_PROLOGUE = { 0xd5, 0xaa, 0xb5 };
public static final int[] DOS32_DATA_PROLOGUE = { 0xd5, 0xaa, 0xad };
public static final int[] DOS33_ADDRESS_PROLOGUE = { 0xd5, 0xaa, 0x96 };
public static final int[] DOS33_DATA_PROLOGUE = { 0xd5, 0xaa, 0xad };
/**
* Decode odd-even bytes as stored on disk. The format will be
* in two bytes. They are stored as such:<pre>
* XX = 1d1d1d1d (odd data bits)
* YY = 1d1d1d1d (even data bits)
* </pre>
* XX is then shifted by a bit and ANDed with YY to get the data byte.
* See page 3-12 in Beneath Apple DOS for more information.
*/
public static int decodeOddEven(byte[] buffer, int offset) {
int b1 = AppleUtil.getUnsignedByte(buffer[offset]);
int b2 = AppleUtil.getUnsignedByte(buffer[offset+1]);
return (b1 << 1 | 0x01) & b2;
}
/**
* Encode odd-even bytes to be stored on disk. See decodeOddEven
* for the format.
* @see #decodeOddEven
*/
public static void encodeOddEven(byte[] buffer, int offset, int value) {
buffer[offset] = (byte) ((value >> 1) | 0xaa);
buffer[offset+1] = (byte) (value | 0xaa);
}
/**
* Identify if this is a 13-sector or 16-sector disk. Note that it is "guaranteed"
* to return 13, 16, or an exception.
*/
public static int identifySectorsPerTrack(byte[] trackData) {
int sectorsPerTrack = 0;
try {
byte[] data = readSectorFromTrack62(trackData, 0, 0, 16);
if (data != null) sectorsPerTrack = 16;
} catch (IllegalArgumentException ex1) {
// ignored
}
if (sectorsPerTrack == 0) {
try {
byte[] data = readSectorFromTrack53(trackData, 0, 0, 13);
if (data != null) sectorsPerTrack = 13;
} catch (IllegalArgumentException ex2) {
// ignored
}
}
if (sectorsPerTrack == 0) {
throw new RuntimeException("unable to locate T0,S0 on disk");
}
return sectorsPerTrack;
}
/**
* Locate physical sector in the given track data. Decodes 6+2 encoding.
*/
public static byte[] readSectorFromTrack62(byte[] trackData, int track, int physicalSector, int sectorsPerTrack) {
// 2. locate address field for this track and sector
int offset = 0;
byte[] addressField = new byte[14];
boolean found = false;
int attempts = sectorsPerTrack;
while (!found && attempts >= 0) {
int nextOffset = locateField(DOS33_ADDRESS_PROLOGUE, trackData, addressField, offset);
attempts--;
offset = nextOffset;
int t = decodeOddEven(addressField, 5);
int s = decodeOddEven(addressField, 7);
found = (t == track && s == physicalSector);
}
if (!found) {
throw new IllegalArgumentException(textBundle
.format("NibbleOrder.InvalidPhysicalSectorError", physicalSector, track, 1)); //$NON-NLS-1$
}
// 3. read data field that immediately follows the address field
byte[] dataField = new byte[349];
locateField(DOS33_DATA_PROLOGUE, trackData, dataField, offset);
// 4. translate data field
byte[] buffer = new byte[342];
int checksum = 0;
for (int i=0; i<buffer.length; i++) {
int b = AppleUtil.getUnsignedByte(dataField[i+3]);
checksum ^= readTranslateTable62[b]; // XOR
if (i < 86) {
buffer[buffer.length - i - 1] = (byte) checksum;
} else {
buffer[i - 86] = (byte) checksum;
}
}
checksum ^= readTranslateTable62[AppleUtil.getUnsignedByte(dataField[345])];
if (checksum != 0) return null; // BAD DATA
// 5. decode data field
byte[] sectorData = new byte[256];
for (int i=0; i<sectorData.length; i++) {
int b1 = AppleUtil.getUnsignedByte(buffer[i]);
int lowerBits = buffer.length - (i % 86) - 1;
int b2 = AppleUtil.getUnsignedByte(buffer[lowerBits]);
int shiftPairs = (i / 86) * 2;
// shift b1 up by 2 bytes (contains bits 7-2)
// align 2 bits in b2 appropriately, mask off anything but
// bits 0 and 1 and then REVERSE THEM...
int[] reverseValues = { 0x0, 0x2, 0x1, 0x3 };
int b = (b1 << 2) | reverseValues[(b2 >> shiftPairs) & 0x03];
sectorData[i] = (byte) b;
}
return sectorData;
}
/**
* Locate physical sector in the given track data. Decodes 5+3 encoding.
*/
public static byte[] readSectorFromTrack53(byte[] trackData, int track, int physicalSector, int sectorsPerTrack) {
// 2. locate address field for this track and sector
int offset = 0;
byte[] addressField = new byte[14];
boolean found = false;
int attempts = sectorsPerTrack;
while (!found && attempts >= 0) {
int nextOffset = locateField(DOS32_ADDRESS_PROLOGUE, trackData, addressField, offset);
attempts--;
offset = nextOffset;
int t = decodeOddEven(addressField, 5);
int s = decodeOddEven(addressField, 7);
found = (t == track && s == physicalSector);
}
if (!found) {
throw new IllegalArgumentException(textBundle
.format("NibbleOrder.InvalidPhysicalSectorError", physicalSector, track, 1)); //$NON-NLS-1$
}
// 3. read data field that immediately follows the address field (D5 AA AD ...410 data bytes... CKSUM DE AA EB
byte[] dataField = new byte[417];
locateField(DOS33_DATA_PROLOGUE, trackData, dataField, offset);
// 4. translate data field
byte[] buffer = new byte[410];
int checksum = 0;
for (int i=0; i<buffer.length; i++) {
int b = AppleUtil.getUnsignedByte(dataField[i+3]);
checksum ^= readTranslateTable53[b]; // XOR
if (i < 154) {
buffer[buffer.length - i - 1] = (byte) checksum;
} else {
buffer[i - 154] = (byte) checksum;
}
}
checksum ^= readTranslateTable53[AppleUtil.getUnsignedByte(dataField[413])];
if (checksum != 0) return null; // BAD DATA
// 5. decode data field
// Note: This reads from these ranges and leaves two bytes that we use at end:
// 0x00-0x33, 0x33-0x65, 0x66-0x98, 0x99-0xcb, 0xcc-0xfe, 0xff - unused
// 0x100-0x132, 0x133-0x165, 0x166-0x198, 0x199 - unused
byte[] sectorData = new byte[256];
offset = 0;
for (int i=0x33-1; i>= 0; i--) {
int a76543 = Byte.toUnsignedInt(buffer[i]);
int b76543 = Byte.toUnsignedInt(buffer[0x33 + i]);
int c76543 = Byte.toUnsignedInt(buffer[0x66 + i]);
int d76543 = Byte.toUnsignedInt(buffer[0x99 + i]);
int e76543 = Byte.toUnsignedInt(buffer[0xcc + i]);
int a210d2e2 = Byte.toUnsignedInt(buffer[0x100 + i]);
int b210d1e1 = Byte.toUnsignedInt(buffer[0x133 + i]);
int c210d0e0 = Byte.toUnsignedInt(buffer[0x166 + i]);
sectorData[offset++] = (byte) (a76543 << 3 | (a210d2e2 >> 2) & 0x7);
sectorData[offset++] = (byte) (b76543 << 3 | (b210d1e1 >> 2) & 0x7);
sectorData[offset++] = (byte) (c76543 << 3 | (c210d0e0 >> 2) & 0x7);
sectorData[offset++] = (byte) (d76543 << 3 | (a210d2e2 << 1) & 0x4 | b210d1e1 & 0x2 | (c210d0e0 >> 1) & 0x1);
sectorData[offset++] = (byte) (e76543 << 3 | (a210d2e2 << 2) & 0x4 | (b210d1e1 << 1) & 0x2 | c210d0e0 & 0x1);
}
sectorData[255] = (byte)(buffer[0xff] << 3 | buffer[0x199] & 0x07);
return sectorData;
}
/**
* Locate a field on the track. These are identified by a 3 byte unique
* signature. Because of the way in which disk bytes are captured, we need
* to wrap around the track to ensure all sequences of bytes are accounted for.
* <p>
* This method fills fieldData as well as returning the last position referenced
* in the track buffer.
*/
public static int locateField(int[] prologue, byte[] trackData, byte[] fieldData, int startingOffset) {
int i = startingOffset; // logical position in track buffer (can wrap)
int position = 0; // physical position in field buffer
while (i < trackData.length + fieldData.length) {
int offset = i % trackData.length; // physical position in track buffer
int b = AppleUtil.getUnsignedByte(trackData[offset]);
if (position == 0 && b == prologue[0]) {
fieldData[position++] = (byte) b;
} else if (position == 1 && b == prologue[1]) {
fieldData[position++] = (byte) b;
} else if (position == 2 && b == prologue[2]) {
fieldData[position++] = (byte) b;
} else if (position >= 3 && position <= fieldData.length) {
if (position < fieldData.length) fieldData[position++] = (byte) b;
if (position == fieldData.length) break; // done!
} else {
position = 0;
}
i++;
}
return i % trackData.length;
}
public static void writeSectorToTrack(byte[] trackData, byte[] sectorData, int track, int physicalSector, int sectorsPerTrack) {
// 2. locate address field for this track and sector
int offset = 0;
byte[] addressField = new byte[14];
boolean found = false;
while (!found && offset < trackData.length) {
int nextOffset = locateField(DOS33_ADDRESS_PROLOGUE, trackData, addressField, offset);
if (nextOffset < offset) { // we wrapped!
throw new IllegalArgumentException(textBundle
.format("NibbleOrder.InvalidPhysicalSectorError", physicalSector, track, 2)); //$NON-NLS-1$
}
offset = nextOffset;
int t = decodeOddEven(addressField, 5);
int s = decodeOddEven(addressField, 7);
found = (t == track && s == physicalSector);
}
if (!found) {
throw new IllegalArgumentException(textBundle
.format("NibbleOrder.InvalidPhysicalSectorError", physicalSector, track, 2)); //$NON-NLS-1$
}
// 3. PRENIBBLE: This is Java translated from assembly @ $B800
// The Java routine was not working... :o(
int[] bb00 = new int[0x100];
int[] bc00 = new int[0x56];
int x = 0;
int y = 2;
while (true) {
y--;
if (y < 0) {
y+= 256;
}
int a = AppleUtil.getUnsignedByte(sectorData[y]);
bc00[x]<<= 1;
bc00[x]|= a & 1;
a>>= 1;
bc00[x]<<= 1;
bc00[x]|= a & 1;
a>>= 1;
bb00[y] = a;
x++;
if (x >= 0x56) {
x = 0;
if (y == 0) break; // done
}
}
for (x=0; x<0x56; x++) {
bc00[x]&= 0x3f;
}
// 4. Translated from portions of WRITE at $B82A:
byte[] diskData = new byte[343];
int pos = 0;
for (y=0x56; y>0; y--) {
if (y == 0x56) {
diskData[pos++] = (byte) writeTranslateTable62[bc00[y-1]];
} else {
diskData[pos++] = (byte) writeTranslateTable62[bc00[y] ^ bc00[y-1]];
}
}
diskData[pos++] = (byte) writeTranslateTable62[bc00[0] ^ bb00[y]];
for (y=1; y<256; y++) {
diskData[pos++] = (byte) writeTranslateTable62[bb00[y] ^ bb00[y-1]];
}
diskData[pos++] = (byte) writeTranslateTable62[bb00[255]];
// 5. write to disk (data may wrap - hence the manual copy)
byte[] dataFieldPrologue = new byte[3];
offset= locateField(DOS33_DATA_PROLOGUE, trackData, dataFieldPrologue, offset);
for (int i=0; i<diskData.length; i++) {
pos = (offset + i) % trackData.length;
trackData[pos] = diskData[i];
}
}
}
@@ -3,27 +3,25 @@
* Copyright (C) 2003-2022 by Robert Greene
* robgreene at users.sourceforge.net
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.webcodepro.applecommander.storage.physical;
import java.util.Arrays;
import com.webcodepro.applecommander.storage.StorageBundle;
import com.webcodepro.applecommander.util.AppleUtil;
import com.webcodepro.applecommander.util.TextBundle;
import static com.webcodepro.applecommander.storage.physical.NibbleCodec.*;
/**
* Supports disk images stored in nibbilized DOS physical order.
@@ -31,51 +29,28 @@ import com.webcodepro.applecommander.util.TextBundle;
* @author Rob Greene (RobGreene@users.sourceforge.net)
*/
public class NibbleOrder extends DosOrder {
private TextBundle textBundle = StorageBundle.getInstance();
/**
* This is the 6 and 2 write translate table, as given in Beneath
* Apple DOS, pg 3-21.
*/
private static int[] writeTranslateTable = {
//$0 $1 $2 $3 $4 $5 $6 $7
0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6, // +$00
0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3, // +$08
0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc, // +$10
0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3, // +$18
0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, // +$20
0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec, // +$28
0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, // +$30
0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff // +$38
};
/**
* This maps a DOS 3.3 sector to a physical sector.
* (readSector and writeSector work off of the DOS 3.3
* sector numbering.)
*/
private static int[] sectorInterleave = {
0x0, 0xd, 0xb, 0x9, 0x7, 0x5, 0x3, 0x1,
0xe, 0xc, 0xa, 0x8, 0x6, 0x4, 0x2, 0xf
};
/**
* The read translation table. Constructed from the write
* translate table. Used to decode a disk byte into a value
* from 0x00 to 0x3f which is further decoded...
*/
public int[] readTranslateTable;
public static final int[] DOS_SECTOR_SKEW = {
0x0, 0xd, 0xb, 0x9, 0x7, 0x5, 0x3, 0x1,
0xe, 0xc, 0xa, 0x8, 0x6, 0x4, 0x2, 0xf
};
private int sectorsPerTrack = 16;
/**
* Construct a NibbleOrder.
*/
public NibbleOrder(ByteArrayImageLayout diskImageManager) {
super(diskImageManager);
// Construct the read translation table:
readTranslateTable = new int[256];
for (int i=0; i<writeTranslateTable.length; i++) {
readTranslateTable[writeTranslateTable[i]] = i;
}
// Identify 13-sector vs 16-sector
byte[] trackData = readTrackData(0);
sectorsPerTrack = identifySectorsPerTrack(trackData);
}
/**
* Read nibbilized track data.
*/
@@ -83,7 +58,7 @@ public class NibbleOrder extends DosOrder {
int trackSize = getPhysicalSize() / getTracksPerDisk();
return readBytes(track * trackSize, trackSize);
}
/**
* Write nibbilized track data.
*/
@@ -97,210 +72,41 @@ public class NibbleOrder extends DosOrder {
* for this process is directly from Beneath Apple DOS, chapter 3.
*/
public byte[] readSector(int track, int dosSector) throws IllegalArgumentException {
int sector = sectorInterleave[dosSector];
// 1. read track
byte[] trackData = readTrackData(track);
// 2. locate address field for this track and sector
int offset = 0;
byte[] addressField = new byte[14];
boolean found = false;
int attempts = getSectorsPerTrack();
while (!found && attempts >= 0) {
int nextOffset = locateField(0xd5, 0xaa, 0x96, trackData, addressField, offset);
attempts--;
offset = nextOffset;
int t = decodeOddEven(addressField, 5);
int s = decodeOddEven(addressField, 7);
found = (t == track && s == sector);
if (sectorsPerTrack == 16) {
int sector = DOS_SECTOR_SKEW[dosSector];
byte[] trackData = readTrackData(track);
return readSectorFromTrack62(trackData, track, sector, getSectorsPerTrack());
} else {
byte[] trackData = readTrackData(track);
return readSectorFromTrack53(trackData, track, dosSector, getSectorsPerTrack());
}
if (!found) {
throw new IllegalArgumentException(textBundle
.format("NibbleOrder.InvalidPhysicalSectorError", sector, track, 1)); //$NON-NLS-1$
}
// 3. read data field that immediately follows the address field
byte[] dataField = new byte[349];
locateField(0xd5, 0xaa, 0xad, trackData, dataField, offset);
// 4. translate data field
byte[] buffer = new byte[342];
int checksum = 0;
for (int i=0; i<buffer.length; i++) {
int b = AppleUtil.getUnsignedByte(dataField[i+3]);
checksum ^= readTranslateTable[b]; // XOR
if (i < 86) {
buffer[buffer.length - i - 1] = (byte) checksum;
} else {
buffer[i - 86] = (byte) checksum;
}
}
checksum ^= readTranslateTable[AppleUtil.getUnsignedByte(dataField[345])];
if (checksum != 0) return null; // BAD DATA
// 5. decode data field
byte[] sectorData = new byte[256];
for (int i=0; i<sectorData.length; i++) {
int b1 = AppleUtil.getUnsignedByte(buffer[i]);
int lowerBits = buffer.length - (i % 86) - 1;
int b2 = AppleUtil.getUnsignedByte(buffer[lowerBits]);
int shiftPairs = (i / 86) * 2;
// shift b1 up by 2 bytes (contains bits 7-2)
// align 2 bits in b2 appropriately, mask off anything but
// bits 0 and 1 and then REVERSE THEM...
int[] reverseValues = { 0x0, 0x2, 0x1, 0x3 };
int b = (b1 << 2) | reverseValues[(b2 >> shiftPairs) & 0x03];
sectorData[i] = (byte) b;
}
return sectorData;
}
/**
* Locate a field on the track. These are identified by a 3 byte unique
* signature. Because of the way in which disk bytes are captured, we need
* to wrap around the track to ensure all sequences of bytes are accounted for.
* <p>
* This method fills fieldData as well as returning the last position referenced
* in the track buffer.
*/
protected int locateField(int byte1, int byte2, int byte3, byte[] trackData, byte[] fieldData, int startingOffset) {
int i = startingOffset; // logical position in track buffer (can wrap)
int position = 0; // physical position in field buffer
while (i < trackData.length + fieldData.length) {
int offset = i % trackData.length; // physical position in track buffer
int b = AppleUtil.getUnsignedByte(trackData[offset]);
if (position == 0 && b == byte1) {
fieldData[position++] = (byte) b;
} else if (position == 1 && b == byte2) {
fieldData[position++] = (byte) b;
} else if (position == 2 && b == byte3) {
fieldData[position++] = (byte) b;
} else if (position >= 3 && position <= fieldData.length) {
if (position < fieldData.length) fieldData[position++] = (byte) b;
if (position == fieldData.length) break; // done!
} else {
position = 0;
}
i++;
}
return i % trackData.length;
}
/**
* Decode odd-even bytes as stored on disk. The format will be
* in two bytes. They are stored as such:<pre>
* XX = 1d1d1d1d (odd data bits)
* YY = 1d1d1d1d (even data bits)
* </pre>
* XX is then shifted by a bit and ANDed with YY to get the data byte.
* See page 3-12 in Beneath Apple DOS for more information.
*/
protected int decodeOddEven(byte[] buffer, int offset) {
int b1 = AppleUtil.getUnsignedByte(buffer[offset]);
int b2 = AppleUtil.getUnsignedByte(buffer[offset+1]);
return (b1 << 1 | 0x01) & b2;
}
/**
* Encode odd-even bytes to be stored on disk. See decodeOddEven
* for the format.
* @see #decodeOddEven
*/
protected void encodeOddEven(byte[] buffer, int offset, int value) {
buffer[offset] = (byte) ((value >> 1) | 0xaa);
buffer[offset+1] = (byte) (value | 0xaa);
}
/**
* Write the specified sector.
*/
public void writeSector(int track, int dosSector, byte[] sectorData) throws IllegalArgumentException {
int sector = sectorInterleave[dosSector];
// 1. read track
if (sectorsPerTrack == 13) {
throw new RuntimeException("writing to 13-sector disks not supported");
}
int sector = DOS_SECTOR_SKEW[dosSector];
byte[] trackData = readTrackData(track);
// 2. locate address field for this track and sector
int offset = 0;
byte[] addressField = new byte[14];
boolean found = false;
while (!found && offset < trackData.length) {
int nextOffset = locateField(0xd5, 0xaa, 0x96, trackData, addressField, offset);
if (nextOffset < offset) { // we wrapped!
throw new IllegalArgumentException(textBundle
.format("NibbleOrder.InvalidPhysicalSectorError", sector, track, 2)); //$NON-NLS-1$
}
offset = nextOffset;
int t = decodeOddEven(addressField, 5);
int s = decodeOddEven(addressField, 7);
found = (t == track && s == sector);
}
if (!found) {
throw new IllegalArgumentException(textBundle
.format("NibbleOrder.InvalidPhysicalSectorError", sector, track, 2)); //$NON-NLS-1$
}
// 3. PRENIBBLE: This is Java translated from assembly @ $B800
// The Java routine was not working... :o(
int[] bb00 = new int[0x100];
int[] bc00 = new int[0x56];
int x = 0;
int y = 2;
while (true) {
y--;
if (y < 0) {
y+= 256;
}
int a = AppleUtil.getUnsignedByte(sectorData[y]);
bc00[x]<<= 1;
bc00[x]|= a & 1;
a>>= 1;
bc00[x]<<= 1;
bc00[x]|= a & 1;
a>>= 1;
bb00[y] = a;
x++;
if (x >= 0x56) {
x = 0;
if (y == 0) break; // done
}
}
for (x=0; x<0x56; x++) {
bc00[x]&= 0x3f;
}
// 4. Translated from portions of WRITE at $B82A:
byte[] diskData = new byte[343];
int pos = 0;
for (y=0x56; y>0; y--) {
if (y == 0x56) {
diskData[pos++] = (byte) writeTranslateTable[bc00[y-1]];
} else {
diskData[pos++] = (byte) writeTranslateTable[bc00[y] ^ bc00[y-1]];
}
}
diskData[pos++] = (byte) writeTranslateTable[bc00[0] ^ bb00[y]];
for (y=1; y<256; y++) {
diskData[pos++] = (byte) writeTranslateTable[bb00[y] ^ bb00[y-1]];
}
diskData[pos++] = (byte) writeTranslateTable[bb00[255]];
// 5. write to disk (data may wrap - hence the manual copy)
byte[] dataFieldPrologue = new byte[3];
offset= locateField(0xd5, 0xaa, 0xad, trackData, dataFieldPrologue, offset);
for (int i=0; i<diskData.length; i++) {
pos = (offset + i) % trackData.length;
trackData[pos] = diskData[i];
}
writeSectorToTrack(trackData, sectorData, track, sector, getSectorsPerTrack());
writeTrackData(track, trackData);
}
/**
* Answer with the number of tracks on this device.
*/
public int getTracksPerDisk() {
return 35;
}
/**
* Answer with the number of sectors per track on this device.
*/
public int getSectorsPerTrack() {
return 16;
return sectorsPerTrack;
}
/**
@@ -309,7 +115,7 @@ public class NibbleOrder extends DosOrder {
* bytes (6+2 encoded) instead of a full 8-bit byte.
*/
public int getBlocksOnDevice() {
return 280;
return 280; // Note: Only relevant to DOS 3.3 disks; irrelevant for DOS 3.2. (Right?)
}
/**
@@ -320,6 +126,9 @@ public class NibbleOrder extends DosOrder {
* sector markers.
*/
public void format() {
if (sectorsPerTrack == 13) {
throw new RuntimeException("formatting 13-sector disks not supported");
}
// pre-fill entire disk with 0xff
byte[] diskImage = new byte[232960]; // 6656 bytes per track
Arrays.fill(diskImage, (byte)0xff);
@@ -33,14 +33,14 @@ public class ProdosOrder extends ImageOrder {
/**
* This table contains the block offset for a particular DOS sector.
*/
private static final int[] blockInterleave = {
public static final int[] blockInterleave = {
0, 7, 6, 6, 5, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0, 7
};
/**
* Defines the location within a block in which the DOS sector resides.
* (0 = 0-255 and 1 = 256-511.)
*/
private static final int[] blockOffsets = {
public static final int[] blockOffsets = {
0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1
};
@@ -0,0 +1,453 @@
package com.webcodepro.applecommander.storage.physical;
import com.webcodepro.applecommander.storage.Disk;
import com.webcodepro.applecommander.storage.os.dos33.DosSectorAddress;
import com.webcodepro.applecommander.util.AppleUtil;
import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import static com.webcodepro.applecommander.storage.physical.NibbleCodec.*;
import static com.webcodepro.applecommander.storage.physical.NibbleOrder.DOS_SECTOR_SKEW;
import static com.webcodepro.applecommander.storage.physical.ProdosOrder.blockInterleave;
import static com.webcodepro.applecommander.storage.physical.ProdosOrder.blockOffsets;
public class WozOrder extends ImageOrder {
// The magic bytes are read in little-endian order, so they do appear reversed here.
public static final int WOZ1_MAGIC = 0x315a4f57;
public static final int WOZ2_MAGIC = 0x325a4f57;
public static final int INFO_CHUNK_ID = 0x4f464e49;
public static final int TMAP_CHUNK_ID = 0x50414d54;
public static final int TRKS_CHUNK_ID = 0x534b5254;
public static final int FLUX_CHUNK_ID = 0x54495257;
public static final int META_CHUNK_ID = 0x4154454d;
// WOZ image representation
private InfoChunk info;
private Map<String,String> meta = new HashMap<>();
private List<TmapChunk> tmap = new ArrayList<>();
private List<TrkInfo> trks = new ArrayList<>();
// Internal configuration settings
private boolean blockDevice;
private boolean trackAndSectorDevice;
private int blocksOnDevice;
private int sectorsPerTrack;
private Function<Integer,byte[]> trackReader = null;
private Function<Integer,byte[]> blockReader = this::readBlock525;
private BiFunction<Integer,Integer,byte[]> sectorReader = this::readSector525;
public WozOrder(ByteArrayImageLayout layout) {
super(layout);
ByteBuffer bb = ByteBuffer.wrap(layout.getDiskImage());
bb.order(ByteOrder.LITTLE_ENDIAN);
int sig = bb.getInt();
int test = bb.getInt();
final int testExpected = 0xa0d0aff;
if (sig == WOZ1_MAGIC && test == testExpected) {
this.trackReader = this::readTrackDataWOZ1;
}
else if (sig == WOZ2_MAGIC && test == testExpected) {
this.trackReader = this::readTrackDataWOZ2;
}
else {
throw new RuntimeException("Not a WOZ1 or WOZ2 format file.");
}
bb.getInt(); // ignoring CRC
Consumer<byte[]> tmapReader = this::readTmapChunk525;
while (bb.hasRemaining()) {
int chunkId = bb.getInt();
int chunkSize = bb.getInt();
byte[] data = new byte[chunkSize];
bb.get(data);
switch (chunkId) {
case INFO_CHUNK_ID:
this.info = new InfoChunk(data);
if (this.info.getDiskType() == 1) { // DISK II - 5.25"
this.blockDevice = false;
this.trackAndSectorDevice = true;
this.blocksOnDevice = 280;
tmapReader = this::readTmapChunk525;
this.blockReader = this::readBlock525;
this.sectorReader = this::readSector525;
}
else { // 400K or 800K 3.5" disk
this.blockDevice = true;
this.trackAndSectorDevice = false;
this.blocksOnDevice = this.info.getDiskSides() * 800;
tmapReader = this::readTmapChunk35;
//this.blockReader = this::readBlock35;
this.sectorReader = this::readSector35;
}
break;
case TMAP_CHUNK_ID:
tmapReader.accept(data);
break;
case TRKS_CHUNK_ID:
readTrksChunk(data);
case META_CHUNK_ID:
readMetaChunk(data);
break;
}
}
// Identify 13-sector vs 16-sector
byte[] trackData = readTrackData(0);
sectorsPerTrack = identifySectorsPerTrack(trackData);
}
@Override
public String getName() {
return "WOZ Disk Image";
}
@Override
public int getPhysicalSize() {
// We fake it. "Size" is sometimes used to determine OS type, so we give the decoded disk size.
return this.blocksOnDevice * 512;
}
@Override
public int getSectorsPerTrack() {
return sectorsPerTrack;
}
@Override
public byte[] readBlock(int block) {
return this.blockReader.apply(block);
}
public byte[] readBlock525(int block) {
if (sectorsPerTrack == 13) {
// 13 sector disks don't map to blocks, but the interrogation routines don't know this; faking it.
return new byte[512];
}
byte[] sector1;
byte[] sector2;
DosSectorAddress[] sectors;
sectors = DosOrder.blockToSectors525(block);
sector1 = readSector(sectors[0].track, sectors[0].sector);
sector2 = readSector(sectors[1].track, sectors[1].sector);
byte[] blockData = new byte[Disk.BLOCK_SIZE];
System.arraycopy(sector1, 0, blockData, 0, Disk.SECTOR_SIZE);
System.arraycopy(sector2, 0, blockData, Disk.SECTOR_SIZE, Disk.SECTOR_SIZE);
return blockData;
}
// public byte[] readBlock35(int block) {
// DosSectorAddress addr = blockToSector35(block);
// byte[] trackData = readTrackData(addr.track);
// return readSectorFromTrack35(trackData, addr.track, addr.sector, getSectorsPerTrack());
// }
public DosSectorAddress blockToSector35(int block) {
// 12x8, 11x8, 10x8, 9x8, 8x8 = 96+88+80+72+64 = 400 sectors per side
int sector = block;
int maxSectorsOnTrack = 0;
int track = 0;
locateBlockLoop:
for (int i=0; i<info.getDiskSides(); i++) {
for (int s=12; s>=8; s--) {
maxSectorsOnTrack = s;
for (int c=0; c<8; c++) {
if (sector < maxSectorsOnTrack) {
break locateBlockLoop;
}
sector -= s;
track++;
}
}
}
return new DosSectorAddress(track, sector);
}
@Override
public void writeBlock(int block, byte[] data) {
throw new RuntimeException("WOZ Disk Image does not support writing at this time");
}
@Override
public boolean isBlockDevice() {
return blockDevice;
}
@Override
public boolean isTrackAndSectorDevice() {
return trackAndSectorDevice;
}
@Override
public int getBlocksOnDevice() {
return blocksOnDevice;
}
@Override
public byte[] readSector(int track, int sector) throws IllegalArgumentException {
return this.sectorReader.apply(track, sector);
}
public byte[] readSector525(int track, int sector) throws IllegalArgumentException {
if (sectorsPerTrack == 16) {
sector = DOS_SECTOR_SKEW[sector];
byte[] trackData = readTrackData(track);
return readSectorFromTrack62(trackData, track, sector, getSectorsPerTrack());
} else {
byte[] trackData = readTrackData(track);
return readSectorFromTrack53(trackData, track, sector, getSectorsPerTrack());
}
}
public byte[] readSector35(int track, int sector) throws IllegalArgumentException {
int block = track * 8 + blockInterleave[sector];
byte[] blockData = readBlock(block);
int offset = blockOffsets[sector];
byte[] sectorData = new byte[Disk.SECTOR_SIZE];
System.arraycopy(blockData, offset * Disk.SECTOR_SIZE,
sectorData, 0, Disk.SECTOR_SIZE);
return sectorData;
}
@Override
public void writeSector(int track, int sector, byte[] bytes) throws IllegalArgumentException {
throw new RuntimeException("WOZ Disk Image does not support writing at this time");
}
private void readMetaChunk(byte[] data) {
if (data.length > 0) {
String meta = new String(data);
for (String line : meta.split("\n")) {
String[] parts = line.split("\t");
if (parts.length == 2) {
this.meta.put(parts[0], parts[1]);
}
}
}
}
private void readTmapChunk525(byte[] data) {
ByteBuffer bb = ByteBuffer.wrap(data);
while (bb.hasRemaining()) {
byte[] chunk = new byte[4];
bb.get(chunk);
tmap.add(new TmapChunk(chunk));
}
}
private void readTmapChunk35(byte[] data) {
ByteBuffer bb = ByteBuffer.wrap(data);
while (bb.hasRemaining()) {
byte chunk = bb.get();
tmap.add(new TmapChunk(chunk));
}
}
private void readTrksChunk(byte[] data) {
// Note: WOZ2 only
ByteBuffer bb = ByteBuffer.wrap(data);
bb.order(ByteOrder.LITTLE_ENDIAN);
for (int i=0; i<160; i++) {
trks.add(new TrkInfo(bb));
}
}
protected byte[] readTrackData(int track) {
return this.trackReader.apply(track);
}
protected byte[] readTrackDataWOZ2(int track) {
TmapChunk map = tmap.get(track);
int trkInfo = map.getOffset00();
if (trkInfo == 255) trkInfo = map.getOffset25();
if (trkInfo == 255) trkInfo = map.getOffset50();
if (trkInfo == 255) trkInfo = map.getOffset75();
TrkInfo trk = trks.get(trkInfo);
byte[] rawData = readBytes(trk.getStartingBlock()*512, trk.getBlockCount()*512);
return transformBitstream(rawData, trk.getBitCount());
}
protected byte[] readTrackDataWOZ1(int track) {
final int trackLength = 6656;
int start = 256 + (track * trackLength);
byte[] details = readBytes(start + 6646, 10);
int bytesUsed = AppleUtil.getWordValue(details, 0);
int bitCount = AppleUtil.getWordValue(details, 2);
int end = start + bytesUsed;
byte[] rawData = readBytes(start, end);
return transformBitstream(rawData, bitCount);
}
protected byte[] transformBitstream(byte[] rawData, int bitCount) {
// NOTE: Uncertain if we need to track 0's (only 2 allowed by hardware) or not.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bitNo = 7;
int byteNo = 0;
int byteVal = 0;
while (bitCount > 0) {
bitCount--;
byteVal <<= 1;
if (AppleUtil.isBitSet(rawData[byteNo], bitNo)) {
byteVal |= 1;
}
bitNo--;
if (bitNo < 0) {
bitNo = 7;
byteNo++;
}
if (byteVal >= 128) {
baos.write(byteVal);
byteVal = 0;
}
}
return baos.toByteArray();
}
public static class InfoChunk {
private int version;
private int diskType;
private int writeProtected;
private int synchrinized;
private int cleaned;
private String creator;
private int diskSides;
private int bootSectorFormat;
private int optimalBitTiming;
private int compatibleHardware;
private int requiredRAM;
private int largestTrack;
private int fluxBlock;
private int largestFluxTrack;
public InfoChunk(byte[] data) {
ByteBuffer bb = ByteBuffer.wrap(data);
bb.order(ByteOrder.LITTLE_ENDIAN);
this.version = bb.get();
this.diskType = bb.get();
this.writeProtected = bb.get();
this.synchrinized = bb.get();
this.cleaned = bb.get();
byte[] creator = new byte[32];
bb.get(creator);
this.creator = new String(creator);
if (this.version == 2) {
this.diskSides = bb.get();
this.bootSectorFormat = bb.get();
this.optimalBitTiming = bb.get();
this.compatibleHardware = bb.getShort();
this.requiredRAM = bb.getShort();
this.largestTrack = bb.getShort();
}
if (this.version == 3) {
this.fluxBlock = bb.getShort();
this.largestFluxTrack = bb.getShort();
}
}
public int getVersion() {
return version;
}
public int getDiskType() {
return diskType;
}
public int getWriteProtected() {
return writeProtected;
}
public int getSynchrinized() {
return synchrinized;
}
public int getCleaned() {
return cleaned;
}
public String getCreator() {
return creator;
}
public int getDiskSides() {
return diskSides;
}
public int getBootSectorFormat() {
return bootSectorFormat;
}
public int getOptimalBitTiming() {
return optimalBitTiming;
}
public int getCompatibleHardware() {
return compatibleHardware;
}
public int getRequiredRAM() {
return requiredRAM;
}
public int getLargestTrack() {
return largestTrack;
}
public int getFluxBlock() {
return fluxBlock;
}
public int getLargestFluxTrack() {
return largestFluxTrack;
}
}
public static class TmapChunk {
private byte[] tmap;
/** Create a 5.25" TMAP entry; one for every quarter track. */
public TmapChunk(byte[] data) {
if (data.length != 4) {
throw new RuntimeException("Unexpected TMAP chunk size of " + data.length);
}
tmap = data;
}
/** Create a 3.5" TMAP entry; just one for the track. */
public TmapChunk(byte data) {
tmap = new byte[4];
tmap[0] = data;
tmap[1] = (byte)255;
tmap[2] = (byte)255;
tmap[3] = (byte)255;
}
public int getOffset00() {
return Byte.toUnsignedInt(tmap[0]);
}
public int getOffset25() {
return Byte.toUnsignedInt(tmap[1]);
}
public int getOffset50() {
return Byte.toUnsignedInt(tmap[2]);
}
public int getOffset75() {
return Byte.toUnsignedInt(tmap[3]);
}
}
public static class TrkInfo {
private int startingBlock;
private int blockCount;
private int bitCount;
public TrkInfo(ByteBuffer bb) {
this.startingBlock = bb.getShort();
this.blockCount = bb.getShort();
this.bitCount = bb.getInt();
}
public int getStartingBlock() {
return startingBlock;
}
public int getBlockCount() {
return blockCount;
}
public int getBitCount() {
return bitCount;
}
}
}
@@ -21,7 +21,6 @@ Filetype=Filetype
Modified=Modified
SizeInBytes=Size (bytes)
DiskNameN={0} (Disk {1})
Dos33=DOS 3.3
NakedOS=NakedOS
LockedQ=Locked?
DirectoryCreationNotSupported=Unable to create directories.
@@ -36,6 +35,7 @@ Disk.140kDosImages=140K DOS Ordered Images (*.do, *.dsk)
Disk.140kNibbleImages=140K Nibbilized Images (*.nib)
Disk.140kProdosImages=140K ProDOS Ordered Images (*.po)
Disk.800kProdosImages=800K ProDOS Ordered Images (*.2mg, *.2img)
Disk.WozImages=WOZ 2.x Images (*.woz)
Disk.ApplePcImages=ApplePC Hard Disk Images (*.hdv)
Disk.CompressedImages=All Compressed Images
Disk.AllFiles=All Files
@@ -19,22 +19,15 @@
*/
package com.webcodepro.applecommander.storage;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import com.webcodepro.applecommander.storage.FormattedDisk.DiskUsage;
import com.webcodepro.applecommander.storage.filters.*;
import com.webcodepro.applecommander.testconfig.TestConfig;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
import org.junit.Test;
import com.webcodepro.applecommander.storage.FormattedDisk.DiskUsage;
import com.webcodepro.applecommander.storage.filters.ApplesoftFileFilter;
import com.webcodepro.applecommander.storage.filters.BinaryFileFilter;
import com.webcodepro.applecommander.storage.filters.DisassemblyFileFilter;
import com.webcodepro.applecommander.storage.filters.GraphicsFileFilter;
import com.webcodepro.applecommander.storage.filters.IntegerBasicFileFilter;
import com.webcodepro.applecommander.storage.filters.TextFileFilter;
import com.webcodepro.applecommander.testconfig.TestConfig;
import static org.junit.Assert.*;
/**
* Test Disk and FormattedDisk for read.
@@ -43,6 +36,9 @@ import com.webcodepro.applecommander.testconfig.TestConfig;
* @author Rob Greene
*/
public class DiskHelperTest {
private static final String DOS33_FORMAT = "DOS 3.3";
private static final String DOS32_FORMAT = "DOS 3.2";
private TestConfig config = TestConfig.getInstance();
@Test
@@ -53,6 +49,7 @@ public class DiskHelperTest {
assertIntegerFile(disks[0], "ANIMALS"); //$NON-NLS-1$
assertTextFile(disks[0], "APPLE PROMS"); //$NON-NLS-1$
assertBinaryFile(disks[0], "BOOT13"); //$NON-NLS-1$
assertEquals(DOS33_FORMAT, disks[0].getFormat());
}
@Test
@@ -147,7 +144,46 @@ public class DiskHelperTest {
showDirectory(config.getDiskDir()
+ "/D3151.dsk"); //$NON-NLS-1$
}
@Test
public void testLoadDos33SystemMasterWoz1() throws IOException, DiskException {
FormattedDisk[] disks = showDirectory(config.getDiskDir() +
"/DOS 3.3 System Master.woz1");
assertApplesoftFile(disks[0], "HELLO"); //$NON-NLS-1$
assertIntegerFile(disks[0], "COPY"); //$NON-NLS-1$
assertBinaryFile(disks[0], "BOOT13"); //$NON-NLS-1$
assertEquals(DOS33_FORMAT, disks[0].getFormat());
}
@Test
public void testLoadDos33SystemMasterWoz2() throws IOException, DiskException {
FormattedDisk[] disks = showDirectory(config.getDiskDir() +
"/DOS 3.3 System Master.woz2");
assertApplesoftFile(disks[0], "HELLO"); //$NON-NLS-1$
assertIntegerFile(disks[0], "COPY"); //$NON-NLS-1$
assertBinaryFile(disks[0], "BOOT13"); //$NON-NLS-1$
assertEquals(DOS33_FORMAT, disks[0].getFormat());
}
@Test
public void testLoadDos32SystemMasterWoz() throws IOException, DiskException {
FormattedDisk[] disks = showDirectory(config.getDiskDir() +
"/DOS 3.2 System Master.woz");
assertIntegerFile(disks[0], "HELLO"); //$NON-NLS-1$
assertBinaryFile(disks[0], "UPDATE 3.2"); //$NON-NLS-1$
assertEquals(DOS32_FORMAT, disks[0].getFormat());
}
@Test
public void testLoadDos32SystemMasterNib() throws IOException, DiskException {
FormattedDisk[] disks = showDirectory(config.getDiskDir() +
"/original321sysmaspls.nib");
assertApplesoftFile(disks[0], "HELLO");
assertBinaryFile(disks[0], "UPDATE 3.2.1");
assertTextFile(disks[0], "APPLE PROMS");
assertEquals(DOS32_FORMAT, disks[0].getFormat());
}
protected FormattedDisk[] showDirectory(String imageName) throws IOException, DiskException {
Disk disk = new Disk(imageName);
FormattedDisk[] formattedDisks = disk.getFormattedDisks();
@@ -19,12 +19,6 @@
*/
package com.webcodepro.applecommander.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Test;
import com.webcodepro.applecommander.storage.Disk;
import com.webcodepro.applecommander.storage.DiskFullException;
import com.webcodepro.applecommander.storage.FileEntry;
@@ -36,6 +30,9 @@ import com.webcodepro.applecommander.storage.physical.ByteArrayImageLayout;
import com.webcodepro.applecommander.storage.physical.DosOrder;
import com.webcodepro.applecommander.storage.physical.NibbleOrder;
import com.webcodepro.applecommander.storage.physical.ProdosOrder;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Test AppleUtil.
+1
View File
@@ -0,0 +1 @@
������������������������������������������������摘��������������摘�狠踔换��祷蹼惦��莼踔���蛀葺趸莴�诀诸��觖揸趸莴�恢蹼��臌��蝶�猾腚��趸值祷齄第肢铑邴�髦莼�蹼踔�踔�趸氲肢��蓦蒗�换莴浑贿��之���碉��哧��巨蓦揠拗鲚貂貊醌�祸伙唼��坏只腙陬���膪�罪咧鲚嵇�竞脒唠镲��鲲邛碇警�鬓谵夯���篆范宏鬓邡谆���篆粉钻鬓邹��哧鬓钻��蹶鬓�氙愤猾��犄�疥�������������������鲻�������������������������睫����������������������������摘�������蕺�������摘����莼郛觖懋铛痪���岛喵痪究胼椒�最�决吨划寒淡�葵浸警锘�湖捉葵懋筋�趾旷缔�诤醵�黼到谯缔�恩龆�份击眭�埤鳆汾巨锦��亏蘅谯�钪鲭磔胼��董�����鬓但�靠淼��觑埘铽�旷锏晋霏��犄铽蹶�鳙镛�龇泛�划�矾觑��虢葺麝凯�宏积逢贳�嵇钲�禁凤祷�肢�埝氆铽詈劢�汉�觋谨�铽作铽鬃��诏唔档谨铽铸氟��贽�刀铽呓�吊贩�貂鳟瞢蒋鲲汁鼢�忽�符椒镏�铽菌�贩恩志霁觐��蹲圯腚锆呓郢镗醌�甑镞�锦��昃������������������������������摘�������������摘��麟�葳走�作胼麝畀��蹙殿揪睇��碹钓眵�疃莸膂眄���葳志魂��醴薜觜挚醵��谵揄�悍巾助钻哆圯��腚牖葺谮窘俄薜磉喏��撖伙众捱�揩��葑戤褐��瞢�揶蕲�瞰����蕻���韩����胡鄂恨��鬃�庄谳���陉挚扣��骄奉糊警贻捣�烤囤�淀钰麟�铍�铥����陔�君�只��揄踝哧俄揄氕��隹腩桔奉咧鞫咿��咦��陔�递����贻蒗鬓�递腼�磙锦��斤谮牒揠�弹�锆氙町�降壕���净揎局�蹀但��睇帝��诮眦蹀詈葑黠锥疆谳��恨蕺����������������������������摘������蕺�������摘�捱咻诙侩氆腙咿�画��颠咻经卷额哧钶葺惦�啐刖醉精讽�坏汾�淀懋趸蝴葺揪鼋��汾�箕环服叻纷��醐递�敕诛�呖经哌捱赀�胼葺叨���精��蝴�鬏蘧�铪��锟陔貅函眍�锝��镲觜�邡�符��眄谮谥诛�口钸井符��趸宗�郗秭蒋��寒魁呖�慨�第�揪揶瞰�饿�觜羁�昊��骱�钭�碲谮砗哼���鲒谮�������钸��局沮�唢钭��谮鲚氕稻咀���貊诋悼孔助蒋�貂���搛诋悼哭函锥口�骱����株磉泛慨������觖莴�降悼�������������������������������摘��������������摘�弹�纷揞龊蓓�邝帝���埚眄邝�蓣精虢汁谆��鲫蹙�黝葺诰但���斗葑帝睫呲诜捉诘攵字�唤��晋尔胱款晋�薅斛酆霎牾址�竭薤����杲樊�貂����鲎符镲�憝��瞢�胶�靠���韩�函邹钝贳莺晋钲�侄��埘�缀鄱���氆涵�织但���帆��葺��炕弘氆���懂诳动��觋�慝涵�阶东��追�矾犏锆鳋骄莓摞磔��诋��碉镗鳙掭��涵��翻��泛黝牖淀葑锘甓铪�蓥捱�郛邴吆忽霁莸瞰警郢�唤���汉豢劬黪劭�戬�昕霁�髦碜呔鼢��蕺����������������������������摘��������������摘���钝贻��岛碉蹲����蛔���鲱睑�鹤��谵���但揄瑚黑攉���蛔�薜猾�铤�莸肢�撅樊�囤烬�����鳋但霁�脎腩�矾��攵蒇郢�弹龇�����氆�种种肢������������������������������������������������������������������������������������������������������������������������������������������������������������摘�������������摘�蒉�莪�唠蕉�筋樊锖��鬃�撅糊蓐�蔺���������蛀逗贽�醉睚醴咻�町划�岛�攉�鲚莼趸酆���捱憾�碟���鄯�莪�递恨�诰陧化化画�拷聚但�����������分�慝������������������������������������������������翻桩锂���������������������符�分�������������������������分�铽���������������蕺����������������������������摘�������������摘�揞���黼腩�莸肿圳到猾�洱东�篆�刀�����踅攴颠����喵哳藓�肢档胶肢觉惠犏�谥��鲻萁将殿�鳟黪铛虢蹩弋眄瞽旰���鲰���主扶胼��莘谨�锃莼���撅�陬�莴哼�铑�积�鞫攉铽臌�郗秕埙锟����鲚氟拗葳憾�枯做服镪呔逗��莴蓐蓊膈搋具醯糠��趾�诰纵��貂�挚鞯揄垌�侩戬黜�淼犄铒呖禁碹肪鄯����镞埤韩函但�牿铸扣追�镜�铛纷鞯镏�摭�纸贩靠莼�宽�镱朕碉君醉薜揩�悍貂��缔哆帆刖秭邺��胶痕�铒��忿����������������������������摘�������������摘���龊觏�锦睇环孵州猴愤�坏份攉氘甑畀���烤��碟�淡掭��胼�董贻髭坊�郫��肢但究��谳�铽�黼���贽��降�刀淡�据圩揎�镙机鲭蹩�呲贻蓊��慝净镲铥�郛亏牒�谥沮档�瞰慝篆觚警�窥扣到碉唼搡鲫画蛾欢掇莴慝�祷乎镗诤�旰莜�悍节氆疃翻亢扶�鬃�砭����仅��蹼蕉碇隹岛��董��忒叠蔸盹藓镯弋埙葸淀�黼��瞢龆���胱慝貊洱痕�椒抖将方圹局伙�疃亏髯份��邴�抵葺膈州柞��蹼抵诘�但�捣镲悼诜髹决�����铸�奉蕺����������������������������摘������蕺�������摘���烬���昊蹴斫具骺嵇旰醌町胼��髭�嵇脎�鲒鳊侄州��攉�莸蒿井铐�缔铽�惦�斫�悍机眍帆锆腚主�篆亢���亢��额�菥醍劬��换��鲭铛醌蘅岛�撖炕戤斫碉莪�搋揶�讷�乎诜膻昕����搡况�氕��膂蕲肢�揶��瑚�镱���镛�葵葑醴�值主掰�麝���谮邡���邗钷�究瞩蛀蒗圳�铑�虢�貊碜��蒉�蝴纵氆��醐郢�邗��刖�纷侩讷�浸诜鲱�圯诨藁亢痉吨鲛���鼾����鳟净�蓰踮捋���株埏觖���鲚�锞戢���种��蕺����������������������������摘�������������摘���精�詈�戢昕�罘哆锆疆��乎瞰镯觏砘凯镱腩�助�铟搋攵霏鳙���撰瞢节���莘�份����降之�遁蹼町�攮淀锆咻泛�鳅��坏脍膑�作涵��服鼢��啕�铒呓龊羁邝�邗锞氆鲛动�街峻霁诘�董���钲匡谶肿�氆莴�貊竭粉旷��攵�揩慷贩房�����划��攵睫��莪拗��蹲莜�莘����呲谵纷��镞瞢额�Line truncated