mirror of
https://github.com/AppleCommander/AppleCommander.git
synced 2026-04-20 03:16:37 +00:00
Switching CP/M over to use a BlockDevice.
This commit is contained in:
@@ -429,12 +429,12 @@ public class ScanCommand extends ReusableCommandOptions {
|
||||
|
||||
private void readAllCPMBlocks(CpmFormatDisk cpm) {
|
||||
dataType = "CPM blocks";
|
||||
// This adjusts for the start. The CPM filesystem ignores the first 3 tracks on disk.
|
||||
int blocksToRead = cpm.getBitmapLength() -
|
||||
(CpmFormatDisk.PHYSICAL_BLOCK_TRACK_START * CpmFormatDisk.CPM_BLOCKS_PER_TRACK);
|
||||
for (int b=0; b<blocksToRead && errors.size() < MAX_ERRORS; b++) {
|
||||
// Note that the "raw" device can read the entire CP/M disk and that the CP/M filesystem handles the
|
||||
// "logical" block 0 starting on track 3.
|
||||
BlockDevice device = cpm.get(BlockDevice.class).orElseThrow();
|
||||
for (int b = 0; b < device.getGeometry().blocksOnDevice(); b++) {
|
||||
try {
|
||||
cpm.readCpmBlock(b);
|
||||
device.readBlock(b);
|
||||
dataRead++;
|
||||
} catch (Throwable t) {
|
||||
errors.add(String.format("Unable to read CPM block #%d for disk #%d", b, cpm.getLogicalDiskNumber()));
|
||||
|
||||
+37
-4
@@ -21,8 +21,13 @@ package com.webcodepro.applecommander.storage.os.cpm;
|
||||
|
||||
import com.webcodepro.applecommander.storage.DiskConstants;
|
||||
import com.webcodepro.applecommander.storage.DiskFactory;
|
||||
import org.applecommander.device.*;
|
||||
import org.applecommander.hint.Hint;
|
||||
import org.applecommander.util.DataBuffer;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Test this disk for a likely CP/M filesystem.
|
||||
* @see <a href="https://www.seasip.info/Cpm/format22.html">CP/M 2.2</a>
|
||||
@@ -31,9 +36,37 @@ import org.applecommander.util.DataBuffer;
|
||||
public class CpmDiskFactory implements DiskFactory {
|
||||
@Override
|
||||
public void inspect(Context ctx) {
|
||||
ctx.orders.forEach(order -> {
|
||||
if (order.isSizeApprox(DiskConstants.APPLE_140KB_DISK) || order.isSizeApprox(DiskConstants.APPLE_140KB_NIBBLE_DISK)) {
|
||||
CpmFormatDisk disk = new CpmFormatDisk(ctx.source.getName(), order);
|
||||
List<TrackSectorDevice> devices = new ArrayList<>();
|
||||
if (ctx.sectorDevice != null) {
|
||||
if (ctx.sectorDevice.is(Hint.NIBBLE_SECTOR_ORDER)) {
|
||||
// cheating so I don't need to figure out physical to CP/M skew!
|
||||
TrackSectorDevice dosSkew = SkewedTrackSectorDevice.physicalToDosSkew(ctx.sectorDevice);
|
||||
devices.add(SkewedTrackSectorDevice.dosToCpmSkew(dosSkew));
|
||||
}
|
||||
else if (ctx.sectorDevice.is(Hint.DOS_SECTOR_ORDER)) {
|
||||
devices.add(SkewedTrackSectorDevice.dosToCpmSkew(ctx.sectorDevice));
|
||||
}
|
||||
else if (ctx.sectorDevice.is(Hint.PRODOS_BLOCK_ORDER)) {
|
||||
devices.add(SkewedTrackSectorDevice.pascalToCpmSkew(ctx.sectorDevice));
|
||||
}
|
||||
else {
|
||||
// Presumably a DSK image, so DO and PO are possibilities
|
||||
devices.add(SkewedTrackSectorDevice.dosToCpmSkew(ctx.sectorDevice));
|
||||
devices.add(SkewedTrackSectorDevice.pascalToCpmSkew(ctx.sectorDevice));
|
||||
}
|
||||
}
|
||||
else if (ctx.blockDevice != null) {
|
||||
if (ctx.blockDevice.getGeometry().blocksOnDevice() == 280) {
|
||||
TrackSectorDevice device = new BlockToTrackSectorAdapter(ctx.blockDevice,
|
||||
new ProdosBlockToTrackSectorAdapterStrategy());
|
||||
devices.add(SkewedTrackSectorDevice.pascalToCpmSkew(device));
|
||||
}
|
||||
}
|
||||
// Any devices in the list are expected to be in CP/M block order
|
||||
devices.forEach(device -> {
|
||||
if (device.getGeometry().sectorsPerDisk() == 560) {
|
||||
BlockDevice blockDevice = new TrackSectorToBlockAdapter(device, TrackSectorToBlockAdapter.BlockStyle.CPM);
|
||||
CpmFormatDisk disk = new CpmFormatDisk(ctx.source.getName(), blockDevice);
|
||||
if (check(disk)) {
|
||||
ctx.disks.add(disk);
|
||||
}
|
||||
@@ -53,7 +86,7 @@ public class CpmDiskFactory implements DiskFactory {
|
||||
if (e5count != CpmFileEntry.ENTRY_LENGTH) { // Not all bytes were 0xE5
|
||||
// Check user number. Should be 0-15 or 0xE5
|
||||
int userNumber = entries.getUnsignedByte(offset);
|
||||
if (userNumber > 15 && userNumber != 0xe5) return false;
|
||||
if (userNumber > 0x1f && userNumber != 0xe5) return false;
|
||||
// Validate filename has highbit off and is a character
|
||||
for (int i=0; i<8; i++) {
|
||||
int ch = entries.getUnsignedByte(offset+1+i);
|
||||
|
||||
+8
-2
@@ -502,6 +502,10 @@ public class CpmFileEntry implements FileEntry {
|
||||
* Answer with a list of blocks allocated to this file.
|
||||
*/
|
||||
public int[] getAllocations() {
|
||||
// It appears that a user number of 0x1f ("cp/m.sys", "DOS 3.3.") marks system tracks!
|
||||
// Presumably an unmatched user number (0x00 likely being the default) hides the "file".
|
||||
boolean adjust = getUserNumber(0) == 0x1f;
|
||||
|
||||
int blocks = getBlocksUsed();
|
||||
int[] allocations = new int[blocks];
|
||||
int block = 0;
|
||||
@@ -509,9 +513,11 @@ public class CpmFileEntry implements FileEntry {
|
||||
byte[] data = readFileEntry(i);
|
||||
int offset = ALLOCATION_OFFSET;
|
||||
while (block < blocks && offset < ENTRY_LENGTH) {
|
||||
allocations[block++] = AppleUtil.getUnsignedByte(data[offset++]);
|
||||
int allocation = AppleUtil.getUnsignedByte(data[offset++]);
|
||||
if (adjust && allocation >= 0x80 && allocation <= 0x8b) allocation &= 0x7f;
|
||||
allocations[block++] = allocation;
|
||||
}
|
||||
}
|
||||
return allocations;
|
||||
return allocations;
|
||||
}
|
||||
}
|
||||
|
||||
+40
-95
@@ -20,10 +20,11 @@
|
||||
package com.webcodepro.applecommander.storage.os.cpm;
|
||||
|
||||
import com.webcodepro.applecommander.storage.*;
|
||||
import com.webcodepro.applecommander.storage.physical.ImageOrder;
|
||||
import com.webcodepro.applecommander.util.AppleUtil;
|
||||
import com.webcodepro.applecommander.util.TextBundle;
|
||||
import static com.webcodepro.applecommander.storage.DiskConstants.*;
|
||||
import org.applecommander.device.BlockDevice;
|
||||
import org.applecommander.source.Source;
|
||||
import org.applecommander.util.Container;
|
||||
import org.applecommander.util.DataBuffer;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@@ -32,7 +33,7 @@ import java.util.*;
|
||||
* <p>
|
||||
* @author Rob Greene
|
||||
*/
|
||||
public class CpmFormatDisk extends FormattedDiskX {
|
||||
public class CpmFormatDisk extends FormattedDisk implements Container {
|
||||
private TextBundle textBundle = StorageBundle.getInstance();
|
||||
/**
|
||||
* The size of the CP/M sector. Assumed to be 128.
|
||||
@@ -60,12 +61,10 @@ public class CpmFormatDisk extends FormattedDiskX {
|
||||
* (The other tracks are boot-related and not available.)
|
||||
*/
|
||||
public static final int PHYSICAL_BLOCK_TRACK_START = 3;
|
||||
/**
|
||||
* The sector skew of the CP/M disk image.
|
||||
*/
|
||||
public static final int[] sectorSkew = {
|
||||
0x0, 0x6, 0xc, 0x3, 0x9, 0xf, 0xe, 0x5,
|
||||
0xb, 0x2, 0x8, 0x7, 0xd, 0x4, 0xa, 0x1 };
|
||||
/**
|
||||
* The underlying block number which CP/M data block #0 resides.
|
||||
*/
|
||||
public static final int FIRST_DATA_BLOCK = PHYSICAL_BLOCK_TRACK_START * PHYSICAL_SECTORS_PER_BLOCK;
|
||||
|
||||
/**
|
||||
* Manage CP/M disk usage.
|
||||
@@ -90,24 +89,32 @@ public class CpmFormatDisk extends FormattedDiskX {
|
||||
}
|
||||
}
|
||||
|
||||
private BlockDevice device;
|
||||
|
||||
/**
|
||||
* Construct a CP/M formatted disk.
|
||||
*/
|
||||
public CpmFormatDisk(String filename, ImageOrder imageOrder) {
|
||||
super(filename, imageOrder);
|
||||
public CpmFormatDisk(String filename, BlockDevice device) {
|
||||
super(filename, device.get(Source.class).orElseThrow());
|
||||
this.device = device;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a CpmFormatDisk. All CP/M disk images are expected to
|
||||
* be 140K in size.
|
||||
*/
|
||||
public static CpmFormatDisk[] create(String filename, ImageOrder imageOrder) {
|
||||
CpmFormatDisk disk = new CpmFormatDisk(filename, imageOrder);
|
||||
public static CpmFormatDisk[] create(String filename, BlockDevice device) {
|
||||
CpmFormatDisk disk = new CpmFormatDisk(filename, device);
|
||||
disk.format();
|
||||
return new CpmFormatDisk[] { disk };
|
||||
}
|
||||
|
||||
/**
|
||||
@Override
|
||||
public <T> Optional<T> get(Class<T> iface) {
|
||||
return Container.get(iface, device);
|
||||
}
|
||||
|
||||
/**
|
||||
* There apparently is no corresponding CP/M disk name.
|
||||
* @see com.webcodepro.applecommander.storage.FormattedDisk#getDiskName()
|
||||
*/
|
||||
@@ -128,7 +135,7 @@ public class CpmFormatDisk extends FormattedDiskX {
|
||||
* @see com.webcodepro.applecommander.storage.FormattedDisk#getFreeSpace()
|
||||
*/
|
||||
public int getFreeSpace() {
|
||||
return getPhysicalSize() - getUsedSpace();
|
||||
return device.getGeometry().deviceSize() - getUsedSpace();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,7 +177,7 @@ public class CpmFormatDisk extends FormattedDiskX {
|
||||
* @see com.webcodepro.applecommander.storage.FormattedDisk#getBitmapLength()
|
||||
*/
|
||||
public int getBitmapLength() {
|
||||
return getPhysicalSize() / CPM_BLOCKSIZE;
|
||||
return device.getGeometry().deviceSize() / CPM_BLOCKSIZE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -258,7 +265,7 @@ public class CpmFormatDisk extends FormattedDiskX {
|
||||
for (int i=0; i<allocations.length; i++) {
|
||||
int blockNumber = allocations[i];
|
||||
if (blockNumber > 0) {
|
||||
byte[] block = readCpmBlock(blockNumber);
|
||||
byte[] block = device.readBlock(FIRST_DATA_BLOCK+blockNumber).asBytes();
|
||||
System.arraycopy(block, 0,
|
||||
data, i * CPM_BLOCKSIZE, CPM_BLOCKSIZE);
|
||||
}
|
||||
@@ -275,16 +282,12 @@ public class CpmFormatDisk extends FormattedDiskX {
|
||||
* @see com.webcodepro.applecommander.storage.FormattedDisk#format()
|
||||
*/
|
||||
public void format() {
|
||||
getImageOrder().format();
|
||||
byte[] sectorData = new byte[SECTOR_SIZE];
|
||||
for (int i=0; i<SECTOR_SIZE; i++) {
|
||||
sectorData[i] = (byte) 0xe5;
|
||||
}
|
||||
for (int track=0; track<35; track++) {
|
||||
for (int sector=0; sector<16; sector++) {
|
||||
writeSector(track, sector, sectorData);
|
||||
}
|
||||
}
|
||||
device.format();
|
||||
DataBuffer blockData = DataBuffer.create(CPM_BLOCKSIZE);
|
||||
blockData.fill(0xe5);
|
||||
for (int block=0; block<device.getGeometry().blocksOnDevice(); block++) {
|
||||
device.writeBlock(block, blockData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -421,65 +424,18 @@ public class CpmFormatDisk extends FormattedDiskX {
|
||||
public boolean canCreateFile() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a CP/M block (1K in size).
|
||||
*/
|
||||
public byte[] readCpmBlock(int block) {
|
||||
byte[] data = new byte[CPM_BLOCKSIZE];
|
||||
int track = computeTrack(block);
|
||||
int sector = computeSector(block);
|
||||
for (int i=0; i<PHYSICAL_SECTORS_PER_BLOCK; i++) {
|
||||
System.arraycopy(readSector(track, sectorSkew[sector+i]),
|
||||
0, data, i*SECTOR_SIZE, SECTOR_SIZE);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
public byte[] readCpmFileEntries() {
|
||||
byte[] data = new byte[2 * CpmFormatDisk.CPM_BLOCKSIZE];
|
||||
System.arraycopy(readCpmBlock(0), 0, data,
|
||||
0, CpmFormatDisk.CPM_BLOCKSIZE);
|
||||
System.arraycopy(readCpmBlock(1), 0, data,
|
||||
CpmFormatDisk.CPM_BLOCKSIZE, CpmFormatDisk.CPM_BLOCKSIZE);
|
||||
return data;
|
||||
DataBuffer directory = DataBuffer.create(2 * CPM_BLOCKSIZE);
|
||||
directory.put(0, device.readBlock(FIRST_DATA_BLOCK));
|
||||
directory.put(CPM_BLOCKSIZE, device.readBlock(FIRST_DATA_BLOCK+1));
|
||||
return directory.asBytes();
|
||||
}
|
||||
public void writeCpmFileEntries(byte[] data) {
|
||||
byte[] block = new byte[CPM_BLOCKSIZE];
|
||||
System.arraycopy(data, 0, block,
|
||||
0, CpmFormatDisk.CPM_BLOCKSIZE);
|
||||
writeCpmBlock(0, block);
|
||||
System.arraycopy(data, CpmFormatDisk.CPM_BLOCKSIZE, block,
|
||||
0, CpmFormatDisk.CPM_BLOCKSIZE);
|
||||
writeCpmBlock(1, block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the physical track number.
|
||||
*/
|
||||
protected int computeTrack(int block) {
|
||||
return PHYSICAL_BLOCK_TRACK_START + (block / CPM_BLOCKS_PER_TRACK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the physical sector number. The rest of the block
|
||||
* follows in sequential order.
|
||||
*/
|
||||
protected int computeSector(int block) {
|
||||
return (block % CPM_BLOCKS_PER_TRACK) * PHYSICAL_SECTORS_PER_BLOCK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a CP/M block.
|
||||
*/
|
||||
public void writeCpmBlock(int block, byte[] data) {
|
||||
int track = computeTrack(block);
|
||||
int sector = computeSector(block);
|
||||
byte[] sectorData = new byte[SECTOR_SIZE];
|
||||
for (int i=0; i<PHYSICAL_SECTORS_PER_BLOCK; i++) {
|
||||
System.arraycopy(data, i*SECTOR_SIZE, sectorData, 0, SECTOR_SIZE);
|
||||
writeSector(track, sectorSkew[sector+i], sectorData);
|
||||
}
|
||||
assert data.length == 2*CPM_BLOCKSIZE;
|
||||
DataBuffer directory = DataBuffer.wrap(data);
|
||||
device.writeBlock(FIRST_DATA_BLOCK, directory.slice(0, CPM_BLOCKSIZE));
|
||||
device.writeBlock(FIRST_DATA_BLOCK+1, directory.slice(CPM_BLOCKSIZE, CPM_BLOCKSIZE));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -523,17 +479,6 @@ public class CpmFormatDisk extends FormattedDiskX {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Change to a different ImageOrder. Remains in CP/M format but
|
||||
* the underlying order can change.
|
||||
* @see ImageOrder
|
||||
*/
|
||||
public void changeImageOrder(ImageOrder imageOrder) {
|
||||
AppleUtil.changeImageOrderByTrackAndSector(getImageOrder(), imageOrder);
|
||||
setImageOrder(imageOrder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the raw bytes into the file. This bypasses any special formatting
|
||||
* of the data (such as prepending the data with a length and/or an address).
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.applecommander.hint.Hint;
|
||||
import org.applecommander.util.Container;
|
||||
import org.applecommander.util.DataBuffer;
|
||||
|
||||
import javax.sound.midi.Track;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -83,9 +84,10 @@ public class SkewedTrackSectorDevice implements TrackSectorDevice {
|
||||
}
|
||||
// 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);
|
||||
return new SkewedTrackSectorDevice(device, 0,6,12,3,9,15,14,5,11,2,8,7,13,4,10,1);
|
||||
}
|
||||
public static TrackSectorDevice pascalToCpmSkew(TrackSectorDevice device) {
|
||||
return new SkewedTrackSectorDevice(device, 0,9,3,12,6,15,1,10,4,13,7,8,2,11,5,14);
|
||||
}
|
||||
// Special RDOS "skew" for truncation (from 16 sector to 13 sector)
|
||||
public static TrackSectorDevice truncate16sectorTo13(TrackSectorDevice device) {
|
||||
|
||||
@@ -227,6 +227,20 @@ public class DiskHelperTest {
|
||||
assertCanReadFiles(disks);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCPMV233Disk() throws DiskException, IOException {
|
||||
FormattedDisk[] disks = showDirectory(config.getDiskDir() +
|
||||
"/CPMV233.DSK");
|
||||
assertCanReadFiles(disks);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCPAM51BDisk() throws DiskException, IOException {
|
||||
FormattedDisk[] disks = showDirectory(config.getDiskDir() +
|
||||
"/CPAM51B.dsk");
|
||||
assertCanReadFiles(disks);
|
||||
}
|
||||
|
||||
protected FormattedDisk[] showDirectory(String imageName) throws IOException, DiskException {
|
||||
Source source = Sources.create(imageName).orElseThrow();
|
||||
DiskFactory.Context ctx = Disks.inspect(source);
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+14
-1
@@ -19,6 +19,7 @@
|
||||
*/
|
||||
package com.webcodepro.applecommander.ui.swt.wizard.diskimage;
|
||||
|
||||
import com.webcodepro.applecommander.ui.swt.util.SwtUtil;
|
||||
import org.applecommander.codec.Nibble62Disk525Codec;
|
||||
import org.applecommander.codec.NibbleDiskCodec;
|
||||
import org.applecommander.device.*;
|
||||
@@ -123,6 +124,9 @@ public class DiskImageWizard extends Wizard {
|
||||
blockDevice = new ProdosOrderedBlockDevice(source, BlockDevice.STANDARD_BLOCK_SIZE);
|
||||
sectorDevice = new BlockToTrackSectorAdapter(blockDevice, new ProdosBlockToTrackSectorAdapterStrategy());
|
||||
break;
|
||||
default:
|
||||
SwtUtil.showErrorDialog(getDialog(), "Bug!", "Unexpected order value: " + getOrder());
|
||||
return null;
|
||||
}
|
||||
switch (format) {
|
||||
case FORMAT_DOS33:
|
||||
@@ -139,7 +143,16 @@ public class DiskImageWizard extends Wizard {
|
||||
case FORMAT_UNIDOS:
|
||||
return UniDosFormatDisk.create(name.toString(), imageOrder);
|
||||
case FORMAT_CPM:
|
||||
return CpmFormatDisk.create(name.toString(), imageOrder);
|
||||
TrackSectorDevice cpmDevice = switch (getOrder()) {
|
||||
case ORDER_DOS -> SkewedTrackSectorDevice.dosToCpmSkew(sectorDevice);
|
||||
case ORDER_NIBBLE -> SkewedTrackSectorDevice.dosToCpmSkew(SkewedTrackSectorDevice.physicalToDosSkew(sectorDevice));
|
||||
case ORDER_PRODOS -> SkewedTrackSectorDevice.pascalToCpmSkew(sectorDevice);
|
||||
default -> null;
|
||||
};
|
||||
if (cpmDevice != null) {
|
||||
BlockDevice cpmBlock = new TrackSectorToBlockAdapter(cpmDevice, TrackSectorToBlockAdapter.BlockStyle.CPM);
|
||||
return CpmFormatDisk.create(name.toString(), cpmBlock);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user