mirror of
https://github.com/AppleCommander/AppleCommander.git
synced 2026-04-25 20:18:05 +00:00
Changing the TrackSectorToBlockAdapter to (a) rely on the given TrackSectorDevice to handle sector skew (via the SkewedTrackSectorDevice) and (b) handle varying block sizes (RDOS, ProDOS/Pascal, CP/M).
This commit is contained in:
+15
-3
@@ -22,7 +22,10 @@ package com.webcodepro.applecommander.storage.os.pascal;
|
||||
import com.webcodepro.applecommander.storage.DiskConstants;
|
||||
import com.webcodepro.applecommander.storage.DiskFactory;
|
||||
import org.applecommander.device.BlockDevice;
|
||||
import org.applecommander.device.SkewedTrackSectorDevice;
|
||||
import org.applecommander.device.TrackSectorDevice;
|
||||
import org.applecommander.device.TrackSectorToBlockAdapter;
|
||||
import org.applecommander.hint.Hint;
|
||||
import org.applecommander.util.DataBuffer;
|
||||
|
||||
/**
|
||||
@@ -37,9 +40,18 @@ public class PascalDiskFactory implements DiskFactory {
|
||||
}
|
||||
}
|
||||
if (ctx.sectorDevice != null && ctx.sectorDevice.getGeometry().sectorsPerDisk() <= 1600) {
|
||||
BlockDevice device = new TrackSectorToBlockAdapter(ctx.sectorDevice);
|
||||
if (check(device)) {
|
||||
ctx.disks.add(new PascalFormatDisk(ctx.source.getName(), device));
|
||||
TrackSectorDevice skewed = null;
|
||||
if (ctx.sectorDevice.is(Hint.NIBBLE_SECTOR_ORDER)) {
|
||||
skewed = SkewedTrackSectorDevice.physicalToPascalSkew(ctx.sectorDevice);
|
||||
}
|
||||
else if (ctx.sectorDevice.is(Hint.DOS_SECTOR_ORDER)) {
|
||||
skewed = SkewedTrackSectorDevice.dosToPascalSkew(ctx.sectorDevice);
|
||||
}
|
||||
if (skewed != null) {
|
||||
BlockDevice device = new TrackSectorToBlockAdapter(skewed, TrackSectorToBlockAdapter.BlockStyle.PASCAL);
|
||||
if (check(device)) {
|
||||
ctx.disks.add(new PascalFormatDisk(ctx.source.getName(), device));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-2
@@ -21,7 +21,10 @@ package com.webcodepro.applecommander.storage.os.prodos;
|
||||
|
||||
import com.webcodepro.applecommander.storage.DiskFactory;
|
||||
import org.applecommander.device.BlockDevice;
|
||||
import org.applecommander.device.SkewedTrackSectorDevice;
|
||||
import org.applecommander.device.TrackSectorDevice;
|
||||
import org.applecommander.device.TrackSectorToBlockAdapter;
|
||||
import org.applecommander.hint.Hint;
|
||||
import org.applecommander.util.DataBuffer;
|
||||
import static com.webcodepro.applecommander.storage.DiskConstants.*;
|
||||
|
||||
@@ -37,8 +40,17 @@ public class ProdosDiskFactory implements DiskFactory {
|
||||
tests.add(new ProdosFormatDisk(ctx.source.getName(), ctx.blockDevice));
|
||||
}
|
||||
if (ctx.sectorDevice != null && ctx.sectorDevice.getGeometry().sectorsPerDisk() <= 1600) {
|
||||
BlockDevice device = new TrackSectorToBlockAdapter(ctx.sectorDevice);
|
||||
tests.add(new ProdosFormatDisk(ctx.source.getName(), device));
|
||||
TrackSectorDevice skewed = null;
|
||||
if (ctx.sectorDevice.is(Hint.NIBBLE_SECTOR_ORDER)) {
|
||||
skewed = SkewedTrackSectorDevice.physicalToPascalSkew(ctx.sectorDevice);
|
||||
}
|
||||
else if (ctx.sectorDevice.is(Hint.DOS_SECTOR_ORDER)) {
|
||||
skewed = SkewedTrackSectorDevice.dosToPascalSkew(ctx.sectorDevice);
|
||||
}
|
||||
if (skewed != null) {
|
||||
BlockDevice device = new TrackSectorToBlockAdapter(skewed, TrackSectorToBlockAdapter.BlockStyle.PRODOS);
|
||||
tests.add(new ProdosFormatDisk(ctx.source.getName(), device));
|
||||
}
|
||||
}
|
||||
// ... and then test for ProDOS details:
|
||||
for (ProdosFormatDisk fdisk : tests) {
|
||||
|
||||
@@ -45,5 +45,9 @@ public interface BlockDevice extends Device {
|
||||
}
|
||||
}
|
||||
|
||||
record Geometry(int blockSize, int blocksOnDevice) {}
|
||||
record Geometry(int blockSize, int blocksOnDevice) {
|
||||
public int deviceSize() {
|
||||
return blocksOnDevice * blockSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,12 +26,62 @@ import org.applecommander.util.DataBuffer;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* This is an overlay on a TrackSectorDevice to give the proper sector skew to the device.
|
||||
* <p/>
|
||||
* <pre>
|
||||
* From Beneath Apple DOS (2020).
|
||||
* -------- SECTOR ORDERING --------
|
||||
* Physical* DOS 3.3 Pascal* CP/M
|
||||
* 0 0 0 0
|
||||
* 1 D 2 3
|
||||
* 2 B 4 6
|
||||
* 3 9 6 9
|
||||
* 4 7 8 C
|
||||
* 5 5 A F
|
||||
* 6 3 C 2
|
||||
* 7 1 E 5
|
||||
* 8 E 1 8
|
||||
* 9 C 3 B
|
||||
* A A 5 E
|
||||
* B 8 7 1
|
||||
* C 6 9 4
|
||||
* D 4 B 7
|
||||
* E 2 D A
|
||||
* F F F D
|
||||
* </pre>
|
||||
* <ul>Notes:
|
||||
* <li>Physical = RDOS and DOS 3.2 sector ordering</li>
|
||||
* <li>Pascal = ProDOS sector ordering</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class SkewedTrackSectorDevice implements TrackSectorDevice {
|
||||
public static TrackSectorDevice dosToPhysicalSkew(TrackSectorDevice device) {
|
||||
public static TrackSectorDevice physicalToDosSkew(TrackSectorDevice device) {
|
||||
return new SkewedTrackSectorDevice(device,
|
||||
0x0, 0xd, 0xb, 0x9, 0x7, 0x5, 0x3, 0x1,
|
||||
0xe, 0xc, 0xa, 0x8, 0x6, 0x4, 0x2, 0xf);
|
||||
}
|
||||
public static TrackSectorDevice physicalToPascalSkew(TrackSectorDevice device) {
|
||||
return new SkewedTrackSectorDevice(device,
|
||||
0x0, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0xe,
|
||||
0x1, 0x3, 0x5, 0x7, 0x9, 0xb, 0xd, 0xf);
|
||||
}
|
||||
public static TrackSectorDevice dosToPhysicalSkew(TrackSectorDevice device) {
|
||||
return new SkewedTrackSectorDevice(device,
|
||||
0x0, 0x7, 0xe, 0x6, 0xd, 0x5, 0xc, 0x4,
|
||||
0xb, 0x3, 0xa, 0x2, 0x9, 0x1, 0x8, 0xf);
|
||||
}
|
||||
public static TrackSectorDevice dosToPascalSkew(TrackSectorDevice device) {
|
||||
return new SkewedTrackSectorDevice(device,
|
||||
0x0, 0xe, 0xd, 0xc, 0xb, 0xa, 0x9, 0x8,
|
||||
0x7, 0x6, 0x5, 0x4, 0x3, 0x2, 0x1, 0xf);
|
||||
}
|
||||
// CP/M skews are from 'cpmtools'
|
||||
public static TrackSectorDevice dosToCpmSkew(TrackSectorDevice device) {
|
||||
return new SkewedTrackSectorDevice(device,
|
||||
0x0, 0x6, 0xc, 0x3, 0x9, 0xf, 0xe, 0x5,
|
||||
0xb, 0x2, 0x8, 0x7, 0xd, 0x4, 0xa, 0x1);
|
||||
}
|
||||
|
||||
private final TrackSectorDevice device;
|
||||
private final int[] sectorSkew;
|
||||
|
||||
@@ -45,5 +45,8 @@ public interface TrackSectorDevice extends Device {
|
||||
public int sectorsPerDisk() {
|
||||
return tracksOnDisk*sectorsPerTrack;
|
||||
}
|
||||
public int deviceSize() {
|
||||
return sectorsPerDisk() * SECTOR_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,15 +25,22 @@ import org.applecommander.util.Container;
|
||||
import org.applecommander.util.DataBuffer;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* Provides a _very simple_ mapping from Track/Sectors to blocks. All sector skewing is expected
|
||||
* to be handled in the TrackSectorDevice. As such, this device expects to map sectors in the given
|
||||
* ("natural") order. (That is, ProDOS blocks are sectors 0,1 and 2,3 and 3,4 etc. RDOS is in physical
|
||||
* order. CP/M is in CP/M's expected order.)
|
||||
*/
|
||||
public class TrackSectorToBlockAdapter implements BlockDevice {
|
||||
private final TrackSectorDevice device;
|
||||
private final BlockStyle style;
|
||||
private final Geometry geometry;
|
||||
|
||||
public TrackSectorToBlockAdapter(TrackSectorDevice device) {
|
||||
public TrackSectorToBlockAdapter(TrackSectorDevice device, BlockStyle style) {
|
||||
this.device = device;
|
||||
this.geometry = new Geometry(STANDARD_BLOCK_SIZE, device.getGeometry().sectorsPerDisk() / 2);
|
||||
this.style = style;
|
||||
this.geometry = new Geometry(style.blockSize, device.getGeometry().deviceSize() / style.blockSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,7 +50,7 @@ public class TrackSectorToBlockAdapter implements BlockDevice {
|
||||
|
||||
@Override
|
||||
public boolean is(Hint hint) {
|
||||
return device.is(hint);
|
||||
return hint == Hint.PRODOS_BLOCK_ORDER;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,33 +65,52 @@ public class TrackSectorToBlockAdapter implements BlockDevice {
|
||||
|
||||
@Override
|
||||
public DataBuffer readBlock(int block) {
|
||||
DataBuffer data = DataBuffer.create(STANDARD_BLOCK_SIZE);
|
||||
operate(block,
|
||||
(t,s) -> data.put(0, device.readSector(t,s)),
|
||||
(t,s) -> data.put(256, device.readSector(t,s)));
|
||||
DataBuffer data = DataBuffer.create(style.blockSize);
|
||||
operate(block, (t,s,o) -> data.put(o, device.readSector(t,s)));
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBlock(int block, DataBuffer blockData) {
|
||||
operate(block,
|
||||
(t,s) -> device.writeSector(t,s,blockData.slice(0,256)),
|
||||
(t,s) -> device.writeSector(t,s,blockData.slice(256,256)));
|
||||
assert blockData.limit() == style.blockSize;
|
||||
operate(block, (t,s,o) -> device.writeSector(t, s, blockData.slice(o, TrackSectorDevice.SECTOR_SIZE)));
|
||||
}
|
||||
|
||||
public void operate(int block, BiConsumer<Integer,Integer> operation1, BiConsumer<Integer,Integer> operation2) {
|
||||
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];
|
||||
operation1.accept(track, sector1);
|
||||
operation2.accept(track, sector2);
|
||||
private void operate(int block, Operation operation) {
|
||||
assert block < geometry.blocksOnDevice();
|
||||
int offset = 0;
|
||||
int physicalSector = block * style.sectorsPerBlock;
|
||||
int track = physicalSector / device.getGeometry().sectorsPerTrack();
|
||||
int sector = physicalSector % device.getGeometry().sectorsPerTrack();
|
||||
while (offset < style.blockSize) {
|
||||
assert sector < device.getGeometry().sectorsPerTrack();
|
||||
operation.perform(track, sector, offset);
|
||||
sector++; // note that we assume we never wrap to next track
|
||||
offset += TrackSectorDevice.SECTOR_SIZE;
|
||||
}
|
||||
}
|
||||
private interface Operation {
|
||||
void perform(int track, int sector, int offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void format() {
|
||||
device.format();
|
||||
}
|
||||
|
||||
public enum BlockStyle {
|
||||
RDOS(256),
|
||||
PASCAL(512),
|
||||
PRODOS(512),
|
||||
CPM(1024);
|
||||
|
||||
final int blockSize;
|
||||
final int sectorsPerBlock;
|
||||
|
||||
BlockStyle(int blockSize) {
|
||||
assert blockSize % TrackSectorDevice.SECTOR_SIZE == 0;
|
||||
this.blockSize = blockSize;
|
||||
this.sectorsPerBlock = blockSize / TrackSectorDevice.SECTOR_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,8 @@ public class DiskWriterTest {
|
||||
public void testWriteToProdos140kDisk() throws IOException, DiskException {
|
||||
Source source = DataBufferSource.create(DiskConstants.APPLE_140KB_DISK, "new-disk").get();
|
||||
TrackSectorDevice trackSectorDevice = new DosOrderedTrackSectorDevice(source);
|
||||
BlockDevice blockDevice = new TrackSectorToBlockAdapter(trackSectorDevice);
|
||||
TrackSectorDevice skewedDevice = SkewedTrackSectorDevice.dosToPascalSkew(trackSectorDevice);
|
||||
BlockDevice blockDevice = new TrackSectorToBlockAdapter(skewedDevice, TrackSectorToBlockAdapter.BlockStyle.PRODOS);
|
||||
FormattedDisk[] disks = ProdosFormatDisk.create(
|
||||
"write-test-prodos-140k.dsk", "TEST", blockDevice); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
writeFiles(disks, "BIN", "TXT", true); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
@@ -218,7 +219,8 @@ public class DiskWriterTest {
|
||||
public void testCreateAndDeleteProdos140kDisk() throws IOException, DiskException {
|
||||
Source source = DataBufferSource.create(DiskConstants.APPLE_140KB_DISK, "new-disk").get();
|
||||
TrackSectorDevice trackSectorDevice = new DosOrderedTrackSectorDevice(source);
|
||||
BlockDevice blockDevice = new TrackSectorToBlockAdapter(trackSectorDevice);
|
||||
TrackSectorDevice skewedDevice = SkewedTrackSectorDevice.dosToPascalSkew(trackSectorDevice);
|
||||
BlockDevice blockDevice = new TrackSectorToBlockAdapter(skewedDevice, TrackSectorToBlockAdapter.BlockStyle.PRODOS);
|
||||
FormattedDisk[] disks = ProdosFormatDisk.create(
|
||||
"createanddelete-test-prodos-140k.dsk", "TEST", //$NON-NLS-1$ //$NON-NLS-2$
|
||||
blockDevice);
|
||||
|
||||
@@ -112,11 +112,12 @@ public class AppleUtilTest {
|
||||
fileEntry.setFileData("This is a test file.".getBytes()); //$NON-NLS-1$
|
||||
// A duplicate - then we change it to a NIB disk image...
|
||||
Source source2 = DataBufferSource.create(DiskConstants.APPLE_140KB_NIBBLE_DISK, "new-disk").get();
|
||||
TrackSectorDevice tsDevice = new TrackSectorNibbleDevice(new NibbleImage(source2),
|
||||
TrackSectorDevice nibbleDevice = new TrackSectorNibbleDevice(new NibbleImage(source2),
|
||||
DiskMarker.disk525sector16(), new Nibble62Disk525Codec(), 16);
|
||||
TrackSectorDevice skewedDevice = SkewedTrackSectorDevice.physicalToPascalSkew(nibbleDevice);
|
||||
ProdosFormatDisk prodosDiskNibbleOrder = ProdosFormatDisk.create("prodostemp2.nib", //$NON-NLS-1$
|
||||
"prodostemp2", //$NON-NLS-1$
|
||||
new TrackSectorToBlockAdapter(tsDevice))[0];
|
||||
new TrackSectorToBlockAdapter(skewedDevice, TrackSectorToBlockAdapter.BlockStyle.PRODOS))[0];
|
||||
AppleUtil.changeOrderByBlock(prodosDiskDosOrder.get(BlockDevice.class).orElseThrow(),
|
||||
prodosDiskNibbleOrder.get(BlockDevice.class).orElseThrow());
|
||||
// Confirm that these disks are identical:
|
||||
|
||||
@@ -83,8 +83,9 @@ public class NewDeviceTest {
|
||||
public void readBlockGalatt() {
|
||||
final String filename = "galatt.dsk";
|
||||
Source source = sourceDisk(filename);
|
||||
DosOrderedTrackSectorDevice tsDevice = new DosOrderedTrackSectorDevice(source);
|
||||
TrackSectorToBlockAdapter blockDevice = new TrackSectorToBlockAdapter(tsDevice);
|
||||
DosOrderedTrackSectorDevice doDevice = new DosOrderedTrackSectorDevice(source);
|
||||
TrackSectorDevice skewedDevice = SkewedTrackSectorDevice.dosToPascalSkew(doDevice);
|
||||
TrackSectorToBlockAdapter blockDevice = new TrackSectorToBlockAdapter(skewedDevice, TrackSectorToBlockAdapter.BlockStyle.PRODOS);
|
||||
DataBuffer blockData = blockDevice.readBlock(2);
|
||||
dumpAsHex(blockData, filename);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user