Adding interface layer in preparation of implementing BlockDevice and TrackSectorDevice.

This commit is contained in:
Rob Greene
2025-08-27 17:39:37 -05:00
parent 66084d273a
commit 9312663e9d
33 changed files with 630 additions and 249 deletions
@@ -24,13 +24,12 @@ import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.logging.Logger;
import com.webcodepro.applecommander.storage.DiskConstants;
import com.webcodepro.applecommander.storage.DiskException;
import com.webcodepro.applecommander.storage.FileEntry;
import com.webcodepro.applecommander.storage.FormattedDisk;
import com.webcodepro.applecommander.storage.*;
import com.webcodepro.applecommander.storage.os.dos33.DosFormatDisk;
import io.github.applecommander.acx.fileutil.FileUtils;
import org.applecommander.device.BlockDevice;
import org.applecommander.device.TrackSectorDevice;
public enum SystemType {
DOS(OrderType.DOS, SystemType::enforce140KbDisk,
@@ -97,23 +96,27 @@ public enum SystemType {
}
static void copyDosSystemTracks(FormattedDisk targetDisk, FormattedDisk source) {
TrackSectorDevice sourceDevice = TrackSectorDeviceAdapter.from(source);
DosFormatDisk target = (DosFormatDisk)targetDisk;
byte[] vtoc = target.readVtoc();
int sectorsPerTrack = vtoc[0x35];
// Note that this also patches T0 S0 for BOOT0
for (int t=0; t<3; t++) {
for (int s=0; s<sectorsPerTrack; s++) {
target.writeSector(t, s, source.readSector(t, s));
byte[] data = sourceDevice.readSector(t, s).asBytes();
target.writeSector(t, s, data);
target.setSectorUsed(t, s, vtoc);
}
}
target.writeVtoc(vtoc);
}
static void copyProdosSystemFiles(FormattedDisk target, FormattedDisk source) {
BlockDevice targetDevice = BlockDeviceAdapter.from(target);
BlockDevice sourceDevice = BlockDeviceAdapter.from(source);
// We need to explicitly fix the boot block
target.writeBlock(0, source.readBlock(0));
target.writeBlock(1, source.readBlock(1));
targetDevice.writeBlock(0, sourceDevice.readBlock(0));
targetDevice.writeBlock(1, sourceDevice.readBlock(1));
try {
FileUtils copier = new FileUtils(false);
for (String filename : Arrays.asList("PRODOS", "BASIC.SYSTEM")) {
@@ -125,10 +128,12 @@ public enum SystemType {
}
}
static void copyPascalSystemFiles(FormattedDisk target, FormattedDisk source) {
BlockDevice targetDevice = BlockDeviceAdapter.from(target);
BlockDevice sourceDevice = BlockDeviceAdapter.from(source);
// We need to explicitly fix the boot block
target.writeBlock(0, source.readBlock(0));
target.writeBlock(1, source.readBlock(1));
targetDevice.writeBlock(0, sourceDevice.readBlock(0));
targetDevice.writeBlock(1, sourceDevice.readBlock(1));
// TODO; uncertain what files Pascal disks require for booting
}
}
}
@@ -19,9 +19,14 @@
*/
package io.github.applecommander.acx.arggroup;
import com.webcodepro.applecommander.storage.BlockDeviceAdapter;
import com.webcodepro.applecommander.storage.FormattedDisk;
import com.webcodepro.applecommander.storage.TrackSectorDeviceAdapter;
import io.github.applecommander.acx.converter.IntegerTypeConverter;
import org.applecommander.device.BlockDevice;
import org.applecommander.device.TrackSectorDevice;
import org.applecommander.util.DataBuffer;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Option;
@@ -38,7 +43,8 @@ public class CoordinateSelection {
else if (blockCoordinate != null) {
return blockCoordinate.read(disk);
}
return disk.readSector(0, 0);
TrackSectorDevice device = TrackSectorDeviceAdapter.from(disk);
return device.readSector(0, 0).asBytes();
}
public void write(FormattedDisk disk, byte[] data) {
@@ -48,7 +54,8 @@ public class CoordinateSelection {
else if (blockCoordinate != null) {
blockCoordinate.write(disk, data);
}
disk.writeSector(0, 0, data);
TrackSectorDevice device = TrackSectorDeviceAdapter.from(disk);
device.writeSector(0, 0, DataBuffer.wrap(data));
}
public static class SectorCoordinateSelection {
@@ -59,9 +66,9 @@ public class CoordinateSelection {
converter = IntegerTypeConverter.class)
private Integer sector;
public void validateTrackAndSector(FormattedDisk disk) throws IllegalArgumentException {
final int tracksPerDisk = disk.getImageOrder().getTracksPerDisk();
final int sectorsPerTrack = disk.getImageOrder().getSectorsPerTrack();
public void validateTrackAndSector(TrackSectorDevice device) throws IllegalArgumentException {
final int tracksPerDisk = device.getGeometry().tracksOnDisk();
final int sectorsPerTrack = device.getGeometry().sectorsPerTrack();
if (track < 0 || track >= tracksPerDisk) {
String errormsg = String.format("The track number(%d) is out of range(0-%d) on this image.", track, tracksPerDisk-1);
@@ -75,21 +82,23 @@ public class CoordinateSelection {
}
public byte[] read(FormattedDisk disk) {
validateTrackAndSector(disk);
return disk.readSector(track, sector);
TrackSectorDevice device = TrackSectorDeviceAdapter.from(disk);
validateTrackAndSector(device);
return device.readSector(track, sector).asBytes();
}
public void write(FormattedDisk disk, byte[] data) {
validateTrackAndSector(disk);
disk.writeSector(track, sector, data);
TrackSectorDevice device = TrackSectorDeviceAdapter.from(disk);
validateTrackAndSector(device);
device.writeSector(track, sector, DataBuffer.wrap(data));
}
}
public static class BlockCoordinateSelection {
@Option(names = { "-b", "--block" }, description = "Block number.", converter = IntegerTypeConverter.class)
private Integer block;
public void validateBlockNum(FormattedDisk disk) throws IllegalArgumentException {
final int blocksOnDevice = disk.getImageOrder().getBlocksOnDevice();
public void validateBlockNum(BlockDevice device) throws IllegalArgumentException {
final int blocksOnDevice = device.getGeometry().blocksOnDevice();
if (block < 0 || block >= blocksOnDevice) {
String errormsg = String.format("The block number(%d) is out of range(0-%d) on this image.", block, blocksOnDevice-1);
@@ -98,13 +107,15 @@ public class CoordinateSelection {
}
public byte[] read(FormattedDisk disk) {
validateBlockNum(disk);
return disk.readBlock(block);
BlockDevice device = BlockDeviceAdapter.from(disk);
validateBlockNum(device);
return device.readBlock(block).asBytes();
}
public void write(FormattedDisk disk, byte[] data) {
validateBlockNum(disk);
disk.writeBlock(block, data);
BlockDevice device = BlockDeviceAdapter.from(disk);
validateBlockNum(device);
device.writeBlock(block, DataBuffer.wrap(data));
}
}
}
@@ -25,7 +25,9 @@ import java.util.Optional;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import com.webcodepro.applecommander.storage.BlockDeviceAdapter;
import com.webcodepro.applecommander.storage.FormattedDisk;
import com.webcodepro.applecommander.storage.TrackSectorDeviceAdapter;
import com.webcodepro.applecommander.util.AppleUtil;
import com.webcodepro.applecommander.util.Range;
@@ -38,6 +40,8 @@ import io.github.applecommander.disassembler.api.InstructionSet;
import io.github.applecommander.disassembler.api.mos6502.InstructionSet6502;
import io.github.applecommander.disassembler.api.sweet16.InstructionSetSWEET16;
import io.github.applecommander.disassembler.api.switching6502.InstructionSet6502Switching;
import org.applecommander.device.BlockDevice;
import org.applecommander.device.TrackSectorDevice;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
@@ -55,10 +59,11 @@ public class DumpCommand extends ReadOnlyDiskImageCommandOptions {
public int handleCommand() throws Exception {
FormattedDisk disk = disks.getFirst();
if (options.coordinate.blockRangeSelection != null) {
BlockDevice device = BlockDeviceAdapter.from(disk);
options.coordinate.blockRangeSelection.blocks.stream().forEach(block -> {
validateBlockNum(disk, block);
validateBlockNum(device, block);
options.includesBootSector = block == 0;
byte[] data = disk.readBlock(block);
byte[] data = device.readBlock(block).asBytes();
System.out.printf("Block #%d:\n", block);
System.out.println(output.format(options, data));
});
@@ -66,10 +71,11 @@ public class DumpCommand extends ReadOnlyDiskImageCommandOptions {
}
else if (options.coordinate.trackSectorRangeSelection != null) {
options.coordinate.trackSectorRangeSelection.tracks.stream().forEach(track -> {
TrackSectorDevice device = TrackSectorDeviceAdapter.from(disk);
options.coordinate.trackSectorRangeSelection.sectors.stream().forEach(sector -> {
validateTrackAndSector(disk, track, sector);
validateTrackAndSector(device, track, sector);
options.includesBootSector = track == 0 && sector == 0;
byte[] data = disk.readSector(track, sector);
byte[] data = device.readSector(track, sector).asBytes();
System.out.printf("Track %02d, Sector %02d:\n", track, sector);
System.out.println(output.format(options, data));
});
@@ -80,8 +86,8 @@ public class DumpCommand extends ReadOnlyDiskImageCommandOptions {
return 1;
}
public void validateBlockNum(FormattedDisk disk, int block) throws IllegalArgumentException {
final int blocksOnDevice = disk.getImageOrder().getBlocksOnDevice();
public void validateBlockNum(BlockDevice device, int block) throws IllegalArgumentException {
final int blocksOnDevice = device.getGeometry().blocksOnDevice();
if (block < 0 || block >= blocksOnDevice) {
String errormsg = String.format("The block number(%d) is out of range(0-%d) on this image.", block, blocksOnDevice-1);
@@ -89,9 +95,9 @@ public class DumpCommand extends ReadOnlyDiskImageCommandOptions {
}
}
public void validateTrackAndSector(FormattedDisk disk, int track, int sector) throws IllegalArgumentException {
final int tracksPerDisk = disk.getImageOrder().getTracksPerDisk();
final int sectorsPerTrack = disk.getImageOrder().getSectorsPerTrack();
public void validateTrackAndSector(TrackSectorDevice device, int track, int sector) throws IllegalArgumentException {
final int tracksPerDisk = device.getGeometry().tracksOnDisk();
final int sectorsPerTrack = device.getGeometry().sectorsPerTrack();
if (track < 0 || track >= tracksPerDisk) {
String errormsg = String.format("The track number(%d) is out of range(0-%d) on this image.", track, tracksPerDisk-1);
@@ -20,7 +20,6 @@
package io.github.applecommander.acx.command;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
@@ -73,7 +72,7 @@ public class ListCommand extends ReadOnlyDiskImageCommandOptions {
int display = fileDisplay.format();
ListingStrategy listingStrategy = outputType.create(display);
listingStrategy.first(disks.getFirst().getImageOrder());
listingStrategy.first(disks.getFirst());
FileStreamer.forDisks(disks)
.ignoreErrors(true)
@@ -32,6 +32,8 @@ 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 io.github.applecommander.acx.base.ReusableCommandOptions;
import org.applecommander.device.BlockDevice;
import org.applecommander.device.TrackSectorDevice;
import org.applecommander.source.Source;
import org.applecommander.source.Sources;
@@ -423,10 +425,11 @@ public class ScanCommand extends ReusableCommandOptions {
}
private void readAllBlocks(FormattedDisk fdisk) {
BlockDevice device = BlockDeviceAdapter.from(fdisk);
dataType = "blocks";
for (int b = 0; b < fdisk.getBitmapLength() && errors.size() < MAX_ERRORS; b++) {
try {
fdisk.readBlock(b);
device.readBlock(b);
dataRead++;
} catch (Throwable t) {
errors.add(String.format("Unable to read block #%d for disk #%d", b, fdisk.getLogicalDiskNumber()));
@@ -447,12 +450,13 @@ public class ScanCommand extends ReusableCommandOptions {
}
private void readAllSectors(FormattedDisk fdisk) {
TrackSectorDevice device = TrackSectorDeviceAdapter.from(fdisk);
dataType = "sectors";
int[] dims = fdisk.getBitmapDimensions();
for (int track = 0; track < dims[0] && errors.size() < MAX_ERRORS; track++) {
for (int sector = 0; sector < dims[1] && errors.size() < MAX_ERRORS; sector++) {
try {
fdisk.readSector(track, sector);
device.readSector(track, sector);
dataRead++;
} catch (Throwable t) {
errors.add(String.format("Unable to read sector T%d,S%s for disk #%d",
@@ -0,0 +1,77 @@
/*
* AppleCommander - An Apple ][ image utility.
* Copyright (C) 2025 by Robert Greene and others
* 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
* 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
* 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.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.webcodepro.applecommander.storage;
import com.webcodepro.applecommander.storage.physical.ImageOrder;
import org.applecommander.capability.Capability;
import org.applecommander.device.BlockDevice;
import org.applecommander.hint.Hint;
import org.applecommander.util.Container;
import org.applecommander.util.DataBuffer;
import java.util.Optional;
/**
* Temporary transition layer between ImageOrder and BlockDevice.
*/
public class BlockDeviceAdapter implements BlockDevice {
public static BlockDevice from(FormattedDisk disk) {
if (disk instanceof Container c) {
Optional<BlockDevice> opt = c.get(BlockDevice.class);
if (opt.isPresent()) return opt.get();
}
if (disk instanceof FormattedDiskX x) {
return new BlockDeviceAdapter(x.getImageOrder());
}
throw new RuntimeException("No BlockDevice present: " + disk.getClass().getName());
}
private ImageOrder order;
private BlockDeviceAdapter(ImageOrder order) {
this.order = order;
}
@Override
public boolean is(Hint hint) {
return order.is(hint);
}
@Override
public boolean can(Capability capability) {
// TODO
return false;
}
@Override
public Geometry getGeometry() {
return new Geometry(STANDARD_BLOCK_SIZE, order.getBlocksOnDevice());
}
@Override
public DataBuffer readBlock(int block) {
return DataBuffer.wrap(order.readBlock(block));
}
@Override
public void writeBlock(int block, DataBuffer blockData) {
order.writeBlock(block, blockData.asBytes());
}
}
@@ -0,0 +1,48 @@
/*
* AppleCommander - An Apple ][ image utility.
* Copyright (C) 2025 by Robert Greene and others
* 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
* 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
* 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.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.webcodepro.applecommander.storage;
import com.webcodepro.applecommander.storage.os.pascal.PascalFormatDisk;
import com.webcodepro.applecommander.storage.os.prodos.ProdosFormatDisk;
import org.applecommander.device.Device;
import org.applecommander.util.Container;
import java.util.Optional;
public class DeviceAdapter {
public static Device from(FormattedDisk disk) {
if (disk instanceof FormattedDiskX) {
// old school, translate...
if (disk instanceof ProdosFormatDisk || disk instanceof PascalFormatDisk) {
return BlockDeviceAdapter.from(disk);
}
else {
return TrackSectorDeviceAdapter.from(disk);
}
}
if (disk instanceof Container c) {
Optional<Device> opt = c.get(Device.class);
if (opt.isPresent()) {
return opt.get();
}
}
throw new RuntimeException("Unable to locate device for " + disk.getClass().getName());
}
}
@@ -19,8 +19,6 @@
*/
package com.webcodepro.applecommander.storage;
import com.webcodepro.applecommander.storage.physical.ImageOrder;
import com.webcodepro.applecommander.storage.physical.WozOrder;
import com.webcodepro.applecommander.util.TextBundle;
import org.applecommander.source.Source;
import org.applecommander.util.DataBuffer;
@@ -133,15 +131,13 @@ public abstract class FormattedDisk implements DirectoryEntry {
private String filename;
private boolean newImage = false;
private Source source;
private ImageOrder imageOrder = null;
private final Source source;
/**
* Constructor for FormattedDisk.
*/
public FormattedDisk(String filename, ImageOrder imageOrder) {
this.imageOrder = imageOrder;
this.source = imageOrder.getSource();
public FormattedDisk(String filename, Source source) {
this.source = source;
this.filename = filename;
this.newImage = true;
}
@@ -213,13 +209,10 @@ public abstract class FormattedDisk implements DirectoryEntry {
List<DiskInformation> list = new ArrayList<>();
list.add(new DiskInformation(textBundle.get("FormattedDisk.FileName"), getFilename())); //$NON-NLS-1$
list.add(new DiskInformation(textBundle.get("FormattedDisk.DiskName"), getDiskName())); //$NON-NLS-1$
list.add(new DiskInformation(textBundle.get("FormattedDisk.PhysicalSizeInBytes"), getPhysicalSize())); //$NON-NLS-1$
list.add(new DiskInformation(textBundle.get("FormattedDisk.FreeSpaceInBytes"), getFreeSpace())); //$NON-NLS-1$
list.add(new DiskInformation(textBundle.get("FormattedDisk.UsedSpaceInBytes"), getUsedSpace())); //$NON-NLS-1$
list.add(new DiskInformation(textBundle.get("FormattedDisk.PhysicalSizeInKb"), getPhysicalSize() / 1024)); //$NON-NLS-1$
list.add(new DiskInformation(textBundle.get("FormattedDisk.FreeSpaceInKb"), getFreeSpace() / 1024)); //$NON-NLS-1$
list.add(new DiskInformation(textBundle.get("FormattedDisk.UsedSpaceInKb"), getUsedSpace() / 1024)); //$NON-NLS-1$
list.add(new DiskInformation(textBundle.get("FormattedDisk.ArchiveOrder"), getOrderName())); //$NON-NLS-1$
list.add(new DiskInformation(textBundle.get("FormattedDisk.DiskFormat"), getFormat())); //$NON-NLS-1$
return list;
}
@@ -326,26 +319,7 @@ public abstract class FormattedDisk implements DirectoryEntry {
* there is no backing out!
*/
public abstract void format();
/**
* Write the AppleCommander boot code to track 0 sector 0 of
* the disk. This will work for a floppy, but may cause problems
* for other devices.
*/
protected void writeBootCode() {
InputStream inputStream = getClass().
getResourceAsStream("/com/webcodepro/applecommander/storage/AppleCommander-boot.dump"); //$NON-NLS-1$
if (inputStream != null) {
byte[] bootCode = new byte[DiskConstants.SECTOR_SIZE];
try {
inputStream.read(bootCode, 0, bootCode.length);
writeSector(0, 0, bootCode);
} catch (IOException ignored) {
// Ignored
}
}
}
/**
* Returns the logical disk number. This can be used to identify
* between disks when a format supports multiple logical volumes.
@@ -388,14 +362,6 @@ public abstract class FormattedDisk implements DirectoryEntry {
public FormattedDisk getFormattedDisk() {
return this;
}
/**
* Resize the disk image to be its full size. Only invoke this
* method if a size does not match exception is thrown.
*/
public void resizeDiskImage() {
resizeDiskImage(getFreeSpace() + getUsedSpace());
}
/**
* Indicates if this FormattedDisk supports a disk map.
@@ -437,9 +403,6 @@ public abstract class FormattedDisk implements DirectoryEntry {
* Returns the source.
*/
public Source getSource() {
if (imageOrder != null) {
return imageOrder.getSource();
}
return source;
}
@@ -458,73 +421,6 @@ public abstract class FormattedDisk implements DirectoryEntry {
this.filename = filename;
}
/**
* Returns the name of the underlying image order.
* @return String
*/
public String getOrderName() {
return (imageOrder == null) ? textBundle.get("FormattedDisk.Unknown") : imageOrder.getName();
}
/**
* 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 (getSource() != null) {
return getSource().getSize();
}
return getImageOrder().getPhysicalSize();
}
/**
* Resize a disk image up to a larger size. The primary intention is to
* "fix" disk images that have been created too small. The primary culprit
* is ApplePC HDV images which dynamically grow. Since AppleCommander
* works with a byte array, the image must grow to its full size.
* @param newSize
*/
protected void resizeDiskImage(int newSize) {
if (newSize < getPhysicalSize()) {
throw new IllegalArgumentException(
textBundle.get("Disk.ResizeDiskError")); //$NON-NLS-1$
}
DataBuffer backingBuffer = imageOrder.getSource().get(DataBuffer.class).orElseThrow();
backingBuffer.limit(newSize);
}
/**
* Read the block from the disk image.
*/
public byte[] readBlock(int block) {
return imageOrder.readBlock(block);
}
/**
* Write the block to the disk image.
*/
public void writeBlock(int block, byte[] data) {
imageOrder.writeBlock(block, data);
}
/**
* Retrieve the specified sector.
*/
public byte[] readSector(int track, int sector) throws IllegalArgumentException {
return imageOrder.readSector(track, sector);
}
/**
* Write the specified sector.
*/
public void writeSector(int track, int sector, byte[] bytes)
throws IllegalArgumentException {
imageOrder.writeSector(track, sector, bytes);
}
/**
* Indicates if the disk has changed. Triggered when data is
* written and cleared when data is saved.
@@ -541,28 +437,6 @@ public abstract class FormattedDisk implements DirectoryEntry {
return newImage;
}
/**
* Answer with the physical ordering of the disk.
*/
public ImageOrder getImageOrder() {
return imageOrder;
}
/**
* Set the physical ordering of the disk.
*/
protected void setImageOrder(ImageOrder imageOrder) {
this.imageOrder = imageOrder;
}
/**
* Change the physical ordering of the disk. This must be implemented by all
* subclasses. See AppleUtil for common utility methods. (It is assumed that a
* disk needs to be copied in the appropriate order - i.e., by track and sector for
* a DOS type disk or by blocks in a ProDOS type disk.)
*/
public abstract void changeImageOrder(ImageOrder 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).
@@ -0,0 +1,174 @@
/*
* AppleCommander - An Apple ][ image utility.
* Copyright (C) 2025 by Robert Greene and others
* 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
* 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
* 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.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.webcodepro.applecommander.storage;
import com.webcodepro.applecommander.storage.physical.ImageOrder;
import com.webcodepro.applecommander.storage.physical.WozOrder;
import com.webcodepro.applecommander.util.TextBundle;
import org.applecommander.util.DataBuffer;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* This is a transitional component while the various file systems get refitted with devices.
* It contains the ImageOrder related functions that will be replaced.
*/
public abstract class FormattedDiskX extends FormattedDisk {
private final TextBundle textBundle = StorageBundle.getInstance();
private ImageOrder imageOrder = null;
public FormattedDiskX(String filename, ImageOrder imageOrder) {
super(filename, imageOrder.getSource());
this.imageOrder = imageOrder;
}
/**
* Get disk information. This is intended to be pretty generic -
* each disk format can build this as appropriate. Each subclass should
* override this method and add its own detail.
*/
public List<DiskInformation> getDiskInformation() {
List<DiskInformation> list = new ArrayList<>(super.getDiskInformation());
list.add(new DiskInformation(textBundle.get("FormattedDisk.PhysicalSizeInBytes"), getPhysicalSize()));
list.add(new DiskInformation(textBundle.get("FormattedDisk.PhysicalSizeInKb"), getPhysicalSize() / 1024));
list.add(new DiskInformation(textBundle.get("FormattedDisk.ArchiveOrder"), getOrderName()));
return list;
}
/**
* Returns the name of the underlying image order.
* @return String
*/
public String getOrderName() {
return (imageOrder == null) ? textBundle.get("FormattedDisk.Unknown") : imageOrder.getName();
}
/**
* 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 (getSource() != null) {
return getSource().getSize();
}
return getImageOrder().getPhysicalSize();
}
/**
* Resize the disk image to be its full size. Only invoke this
* method if a size does not match exception is thrown.
*/
public void resizeDiskImage() {
resizeDiskImage(getFreeSpace() + getUsedSpace());
}
/**
* Resize a disk image up to a larger size. The primary intention is to
* "fix" disk images that have been created too small. The primary culprit
* is ApplePC HDV images which dynamically grow. Since AppleCommander
* works with a byte array, the image must grow to its full size.
* @param newSize
*/
protected void resizeDiskImage(int newSize) {
if (newSize < getPhysicalSize()) {
throw new IllegalArgumentException(
textBundle.get("Disk.ResizeDiskError")); //$NON-NLS-1$
}
DataBuffer backingBuffer = imageOrder.getSource().get(DataBuffer.class).orElseThrow();
backingBuffer.limit(newSize);
}
/**
* Read the block from the disk image.
*/
public byte[] readBlock(int block) {
return imageOrder.readBlock(block);
}
/**
* Write the block to the disk image.
*/
public void writeBlock(int block, byte[] data) {
imageOrder.writeBlock(block, data);
}
/**
* Retrieve the specified sector.
*/
public byte[] readSector(int track, int sector) throws IllegalArgumentException {
return imageOrder.readSector(track, sector);
}
/**
* Write the specified sector.
*/
public void writeSector(int track, int sector, byte[] bytes)
throws IllegalArgumentException {
imageOrder.writeSector(track, sector, bytes);
}
/**
* Write the AppleCommander boot code to track 0 sector 0 of
* the disk. This will work for a floppy, but may cause problems
* for other devices.
*/
protected void writeBootCode() {
InputStream inputStream = getClass().
getResourceAsStream("/com/webcodepro/applecommander/storage/AppleCommander-boot.dump"); //$NON-NLS-1$
if (inputStream != null) {
byte[] bootCode = new byte[DiskConstants.SECTOR_SIZE];
try {
inputStream.read(bootCode, 0, bootCode.length);
writeSector(0, 0, bootCode);
} catch (IOException ignored) {
// Ignored
}
}
}
/**
* Answer with the physical ordering of the disk.
*/
public ImageOrder getImageOrder() {
return imageOrder;
}
/**
* Set the physical ordering of the disk.
*/
protected void setImageOrder(ImageOrder imageOrder) {
this.imageOrder = imageOrder;
}
/**
* Change the physical ordering of the disk. This must be implemented by all
* subclasses. See AppleUtil for common utility methods. (It is assumed that a
* disk needs to be copied in the appropriate order - i.e., by track and sector for
* a DOS type disk or by blocks in a ProDOS type disk.)
*/
public abstract void changeImageOrder(ImageOrder imageOrder);
}
@@ -0,0 +1,91 @@
/*
* AppleCommander - An Apple ][ image utility.
* Copyright (C) 2025 by Robert Greene and others
* 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
* 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
* 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.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.webcodepro.applecommander.storage;
import com.webcodepro.applecommander.storage.os.dos33.OzDosFormatDisk;
import com.webcodepro.applecommander.storage.os.dos33.UniDosFormatDisk;
import com.webcodepro.applecommander.storage.physical.ImageOrder;
import org.applecommander.capability.Capability;
import org.applecommander.device.BlockDevice;
import org.applecommander.device.BlockToTrackSectorAdapter;
import org.applecommander.device.TrackSectorDevice;
import org.applecommander.hint.Hint;
import org.applecommander.os.dos.OzdosAdapterStrategy;
import org.applecommander.os.dos.UnidosAdapterStrategy;
import org.applecommander.util.Container;
import org.applecommander.util.DataBuffer;
import java.util.Optional;
public class TrackSectorDeviceAdapter implements TrackSectorDevice {
public static TrackSectorDevice from(FormattedDisk disk) {
if (disk instanceof Container c) {
Optional<TrackSectorDevice> opt = c.get(TrackSectorDevice.class);
if (opt.isPresent()) return opt.get();
}
// 800K DOS disks cause some issues, so they are pulled out...
return switch (disk) {
case UniDosFormatDisk unidos -> {
BlockDevice device = BlockDeviceAdapter.from(unidos);
yield new BlockToTrackSectorAdapter(device, unidos.getLogicalDiskNumber() == 1
? UnidosAdapterStrategy.UNIDOS_DISK_1 : UnidosAdapterStrategy.UNIDOS_DISK_2);
}
case OzDosFormatDisk ozdos -> {
BlockDevice device = BlockDeviceAdapter.from(ozdos);
yield new BlockToTrackSectorAdapter(device, ozdos.getLogicalDiskNumber() == 1
? OzdosAdapterStrategy.OZDOS_DISK_1 : OzdosAdapterStrategy.OZDOS_DISK_2);
}
case FormattedDiskX x -> new TrackSectorDeviceAdapter(x.getImageOrder());
default -> throw new RuntimeException("No BlockDevice present: " + disk.getClass().getName());
};
}
private ImageOrder order;
private TrackSectorDeviceAdapter(ImageOrder order) {
this.order = order;
}
@Override
public boolean is(Hint hint) {
return order.is(hint);
}
@Override
public boolean can(Capability capability) {
// TODO
return false;
}
@Override
public Geometry getGeometry() {
return new Geometry(order.getTracksPerDisk(), order.getSectorsPerTrack());
}
@Override
public DataBuffer readSector(int track, int sector) {
return DataBuffer.wrap(order.readSector(track, sector));
}
@Override
public void writeSector(int track, int sector, DataBuffer sectorData) {
order.writeSector(track, sector, sectorData.asBytes());
}
}
@@ -22,24 +22,19 @@ package com.webcodepro.applecommander.storage.compare;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import com.webcodepro.applecommander.storage.DiskGeometry;
import com.webcodepro.applecommander.storage.FormattedDisk;
import com.webcodepro.applecommander.storage.physical.ImageOrder;
import com.webcodepro.applecommander.storage.*;
import com.webcodepro.applecommander.util.Range;
import com.webcodepro.applecommander.util.filestreamer.FileStreamer;
import com.webcodepro.applecommander.util.filestreamer.FileTuple;
import com.webcodepro.applecommander.util.filestreamer.TypeOfFile;
import com.webcodepro.applecommander.util.readerwriter.FileEntryReader;
import org.applecommander.device.BlockDevice;
import org.applecommander.device.TrackSectorDevice;
import org.applecommander.util.DataBuffer;
/**
* Perform a disk comparison based on selected strategy.
@@ -118,20 +113,22 @@ public class DiskDiff {
/** Compare disks by 512-byte ProDOS/Pascal blocks. */
public void compareByBlockGeometry(FormattedDisk formattedDiskA, FormattedDisk formattedDiskB) {
ImageOrder orderA = formattedDiskA.getImageOrder();
ImageOrder orderB = formattedDiskB.getImageOrder();
if (orderA.getBlocksOnDevice() != orderB.getBlocksOnDevice()) {
results.addError("Different sized disks do not equal. (Blocks: %d <> %d)",
orderA.getBlocksOnDevice(), orderB.getBlocksOnDevice());
BlockDevice deviceA = BlockDeviceAdapter.from(formattedDiskA);
BlockDevice deviceB = BlockDeviceAdapter.from(formattedDiskB);
int blocksOnDeviceA = deviceA.getGeometry().blocksOnDevice();
int blocksOnDeviceB = deviceB.getGeometry().blocksOnDevice();
if (blocksOnDeviceA != blocksOnDeviceB) {
results.addError("Different sized disks do not equal. (Blocks: %d <> %d)",
blocksOnDeviceA, blocksOnDeviceB);
return;
}
List<Integer> unequalBlocks = new ArrayList<>();
for (int block=0; block<orderA.getBlocksOnDevice(); block++) {
byte[] blockA = orderA.readBlock(block);
byte[] blockB = orderB.readBlock(block);
if (!Arrays.equals(blockA, blockB)) {
for (int block=0; block<blocksOnDeviceA; block++) {
DataBuffer blockA = deviceA.readBlock(block);
DataBuffer blockB = deviceB.readBlock(block);
if (!blockA.equals(blockB)) {
unequalBlocks.add(block);
}
}
@@ -144,24 +141,26 @@ public class DiskDiff {
}
}
}
/** Compare disks by 256-byte DOS sectors. */
public void compareByTrackSectorGeometry(FormattedDisk formattedDiskA, FormattedDisk formattedDiskB) {
ImageOrder orderA = formattedDiskA.getImageOrder();
ImageOrder orderB = formattedDiskB.getImageOrder();
TrackSectorDevice deviceA = TrackSectorDeviceAdapter.from(formattedDiskA);
TrackSectorDevice deviceB = TrackSectorDeviceAdapter.from(formattedDiskB);
if (orderA.getSectorsPerDisk() != orderB.getSectorsPerDisk()) {
int sectorsPerDiskA = deviceA.getGeometry().sectorsPerDisk();
int sectorsPerDiskB = deviceB.getGeometry().sectorsPerDisk();
if (sectorsPerDiskA != sectorsPerDiskB) {
results.addError("Different sized disks do not equal. (Sectors: %d <> %d)",
orderA.getSectorsPerDisk(), orderB.getSectorsPerDisk());
sectorsPerDiskA, sectorsPerDiskB);
return;
}
for (int track=0; track<orderA.getTracksPerDisk(); track++) {
for (int track=0; track<deviceA.getGeometry().tracksOnDisk(); track++) {
List<Integer> unequalSectors = new ArrayList<>();
for (int sector=0; sector<orderA.getSectorsPerTrack(); sector++) {
byte[] sectorA = orderA.readSector(track, sector);
byte[] sectorB = orderB.readSector(track, sector);
if (!Arrays.equals(sectorA, sectorB)) {
for (int sector=0; sector<deviceA.getGeometry().sectorsPerTrack(); sector++) {
DataBuffer sectorA = deviceA.readSector(track, sector);
DataBuffer sectorB = deviceB.readSector(track, sector);
if (!sectorA.equals(sectorB)) {
unequalSectors.add(sector);
}
}
@@ -32,7 +32,7 @@ import java.util.*;
* <p>
* @author Rob Greene
*/
public class CpmFormatDisk extends FormattedDisk {
public class CpmFormatDisk extends FormattedDiskX {
private TextBundle textBundle = StorageBundle.getInstance();
/**
* The size of the CP/M sector. Assumed to be 128.
@@ -20,7 +20,7 @@
package com.webcodepro.applecommander.storage.os.dos33;
import com.webcodepro.applecommander.storage.DiskFactory;
import com.webcodepro.applecommander.storage.FormattedDisk;
import com.webcodepro.applecommander.storage.FormattedDiskX;
import com.webcodepro.applecommander.storage.physical.ImageOrder;
import org.applecommander.util.DataBuffer;
import static com.webcodepro.applecommander.storage.DiskConstants.*;
@@ -31,7 +31,7 @@ public class DosDiskFactory implements DiskFactory {
@Override
public void inspect(Context ctx) {
// It seems easiest to gather all possibilities first...
List<FormattedDisk> tests = new ArrayList<>();
List<FormattedDiskX> tests = new ArrayList<>();
if (ctx.orders.size() == 1) {
ImageOrder order = ctx.orders.getFirst();
if (order.isSizeApprox(APPLE_800KB_DISK)) {
@@ -46,8 +46,8 @@ public class DosDiskFactory implements DiskFactory {
}
else if (ctx.orders.size() == 2) {
// Could be either, so count both (should be PO vs DO) and choose the longest catalog
FormattedDisk fdisk1 = new DosFormatDisk(ctx.source.getName(), ctx.orders.get(0));
FormattedDisk fdisk2 = new DosFormatDisk(ctx.source.getName(), ctx.orders.get(1));
FormattedDiskX fdisk1 = new DosFormatDisk(ctx.source.getName(), ctx.orders.get(0));
FormattedDiskX fdisk2 = new DosFormatDisk(ctx.source.getName(), ctx.orders.get(1));
int count1 = count(fdisk1, 17);
int count2 = count(fdisk2, 17);
// Note this assumes DO was the first ImageOrder in the list to give it an edge
@@ -55,7 +55,7 @@ public class DosDiskFactory implements DiskFactory {
else tests.add(fdisk2);
}
// ... and then test for DOS VTOC etc. Passing track number along to hopefully handle it later!
for (FormattedDisk fdisk : tests) {
for (FormattedDiskX fdisk : tests) {
try {
if (check(fdisk, 17)) {
ctx.disks.add(fdisk);
@@ -69,7 +69,7 @@ public class DosDiskFactory implements DiskFactory {
/**
* Test this image order by looking for a likely DOS VTOC and set of catalog sectors.
*/
public boolean check(FormattedDisk disk, final int vtocTrack) {
public boolean check(FormattedDiskX disk, final int vtocTrack) {
DataBuffer vtoc = DataBuffer.wrap(disk.readSector(vtocTrack, 0));
int nextTrack = vtoc.getUnsignedByte(0x01);
int nextSector = vtoc.getUnsignedByte(0x02);
@@ -123,7 +123,7 @@ public class DosDiskFactory implements DiskFactory {
return good;
}
public int count(FormattedDisk disk, final int vtocTrack) {
public int count(FormattedDiskX disk, final int vtocTrack) {
DataBuffer vtoc = DataBuffer.wrap(disk.readSector(vtocTrack, 0));
int nextTrack = vtoc.getUnsignedByte(0x01);
int nextSector = vtoc.getUnsignedByte(0x02);
@@ -36,7 +36,7 @@ import java.util.*;
* Changed at: Dec 1, 2017
* @author Lisias Toledo
*/
public class DosFormatDisk extends FormattedDisk {
public class DosFormatDisk extends FormattedDiskX {
private TextBundle textBundle = StorageBundle.getInstance();
/**
* Indicates the index of the track in the location array.
@@ -34,7 +34,7 @@ import java.util.List;
* Date created: Dec 17, 2008 04:29:23 PM
* @author David Schmidt
*/
public class GutenbergFormatDisk extends FormattedDisk {
public class GutenbergFormatDisk extends FormattedDiskX {
private TextBundle textBundle = StorageBundle.getInstance();
/**
* Indicates the index of the track in the location array.
@@ -34,7 +34,7 @@ import java.util.List;
* Date created: August 5, 2010 10:23:23 AM
* @author David Schmidt
*/
public class NakedosFormatDisk extends FormattedDisk {
public class NakedosFormatDisk extends FormattedDiskX {
private TextBundle textBundle = StorageBundle.getInstance();
/**
* Indicates the index of the track in the location array.
@@ -36,7 +36,7 @@ import java.util.*;
* @author Rob Greene
* @author John B. Matthews [getFiles(), get/putDirectory(), createFile()]
*/
public class PascalFormatDisk extends FormattedDisk {
public class PascalFormatDisk extends FormattedDiskX {
private TextBundle textBundle = StorageBundle.getInstance();
/**
* The size of the Pascal file entry.
@@ -20,7 +20,7 @@
package com.webcodepro.applecommander.storage.os.prodos;
import com.webcodepro.applecommander.storage.DiskFactory;
import com.webcodepro.applecommander.storage.FormattedDisk;
import com.webcodepro.applecommander.storage.FormattedDiskX;
import org.applecommander.util.DataBuffer;
import static com.webcodepro.applecommander.storage.DiskConstants.*;
@@ -31,17 +31,17 @@ public class ProdosDiskFactory implements DiskFactory {
@Override
public void inspect(Context ctx) {
// It seems easiest to gather all possibilities first...
List<FormattedDisk> tests = new ArrayList<>();
List<FormattedDiskX> tests = new ArrayList<>();
ctx.orders.forEach(order -> tests.add(new ProdosFormatDisk(ctx.source.getName(), order)));
// ... and then test for ProDOS details:
for (FormattedDisk fdisk : tests) {
for (FormattedDiskX fdisk : tests) {
if (check(fdisk)) {
ctx.disks.add(fdisk);
}
}
}
public boolean check(FormattedDisk fdisk) {
public boolean check(FormattedDiskX fdisk) {
int nextBlock = 2;
DataBuffer volumeDirectory = DataBuffer.wrap(fdisk.readBlock(nextBlock));
int priorBlock = volumeDirectory.getUnsignedShort(0x00);
@@ -38,7 +38,7 @@ import java.util.*;
* Changed at: Dec 1, 2017
* @author Lisias Toledo
*/
public class ProdosFormatDisk extends FormattedDisk {
public class ProdosFormatDisk extends FormattedDiskX {
private TextBundle textBundle = StorageBundle.getInstance();
/**
* The location of the "next block" pointer in a directory entry.
@@ -46,7 +46,7 @@ import java.util.Map;
* Date created: Oct 7, 2002 2:03:58 PM
* @author Rob Greene
*/
public class RdosFormatDisk extends FormattedDisk {
public class RdosFormatDisk extends FormattedDiskX {
private TextBundle textBundle = StorageBundle.getInstance();
/**
* RDOS 2.1/3.2 disks are structured in a different order than DOS 3.3.
@@ -25,7 +25,6 @@ import java.util.List;
import java.util.stream.Collectors;
import com.webcodepro.applecommander.storage.*;
import com.webcodepro.applecommander.storage.physical.ImageOrder;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
@@ -36,8 +35,11 @@ import com.webcodepro.applecommander.storage.FormattedDisk.FileColumnHeader;
import com.webcodepro.applecommander.util.TextBundle;
import com.webcodepro.applecommander.util.filestreamer.FileStreamer;
import com.webcodepro.applecommander.util.filestreamer.FileTuple;
import org.applecommander.device.BlockDevice;
import org.applecommander.device.TrackSectorDevice;
import org.applecommander.source.Source;
import org.applecommander.source.Sources;
import org.applecommander.util.Container;
public class DirectoryLister {
private static TextBundle textBundle = UiBundle.getInstance();
@@ -62,7 +64,7 @@ public class DirectoryLister {
Source source = Sources.create(filename).orElseThrow();
DiskFactory.Context ctx = Disks.inspect(source);
// Pulling ImageOrder from a FormattedDisk to ensure it's one we chose
strategy.first(ctx.disks.getFirst().getImageOrder());
strategy.first(ctx.disks.getFirst());
FileStreamer.forDisks(ctx.disks)
.recursive(true)
@@ -81,7 +83,7 @@ public class DirectoryLister {
this.display = display;
}
public void first(ImageOrder o) {};
public void first(FormattedDisk d) {};
public void beforeDisk(FormattedDisk d) {}
public void afterDisk(FormattedDisk d) {}
public void forEach(FileTuple f) {}
@@ -169,11 +171,22 @@ public class DirectoryLister {
super(display);
}
@Override
public void first(ImageOrder order) {
public void first(FormattedDisk disk) {
root = new JsonObject();
root.addProperty("filename", order.getSource().getName());
root.addProperty("order", order.getName());
root.addProperty("physicalSize", order.getPhysicalSize());
root.addProperty("filename", disk.getFilename());
if (disk instanceof FormattedDiskX x) {
root.addProperty("order", x.getOrderName());
root.addProperty("physicalSize", x.getPhysicalSize());
}
else if (disk instanceof Container c) {
root.addProperty("size", disk.getSource().getSize());
if (c.get(BlockDevice.class).isPresent()) {
root.addProperty("device", "block device");
}
else if (c.get(TrackSectorDevice.class).isPresent()) {
root.addProperty("device", "track/sector device");
}
}
this.disks = new JsonArray();
root.add("disks", disks);
}
@@ -19,11 +19,10 @@
*/
package org.applecommander.device;
import org.applecommander.capability.CapabilityProvider;
import org.applecommander.util.DataBuffer;
public interface BlockDevice extends CapabilityProvider {
int BLOCK_SIZE = 512;
public interface BlockDevice extends Device {
int STANDARD_BLOCK_SIZE = 512;
Geometry getGeometry();
DataBuffer readBlock(int block);
@@ -20,6 +20,7 @@
package org.applecommander.device;
import org.applecommander.capability.Capability;
import org.applecommander.hint.Hint;
import org.applecommander.util.DataBuffer;
/**
@@ -36,6 +37,11 @@ public class BlockToTrackSectorAdapter implements TrackSectorDevice {
this.geometry = new Geometry(strategy.getTotalTracks(), strategy.getSectorsPerTrack());
}
@Override
public boolean is(Hint hint) {
return device.is(hint);
}
@Override
public boolean can(Capability capability) {
return device.can(capability);
@@ -0,0 +1,30 @@
/*
* AppleCommander - An Apple ][ image utility.
* Copyright (C) 2025 by Robert Greene and others
* 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
* 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
* 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.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.applecommander.device;
import org.applecommander.capability.CapabilityProvider;
import org.applecommander.hint.HintProvider;
/**
* This is a marker interface for both the BlockDevice and TrackSectorDevice and should
* not be implemented directly.
*/
public interface Device extends HintProvider, CapabilityProvider {
}
@@ -20,6 +20,7 @@
package org.applecommander.device;
import org.applecommander.capability.Capability;
import org.applecommander.hint.Hint;
import org.applecommander.source.Source;
import org.applecommander.util.DataBuffer;
@@ -32,6 +33,11 @@ public class DosOrderedTrackSectorDevice implements TrackSectorDevice {
this.geometry = new Geometry(35, 16); // assumed for now?
}
@Override
public boolean is(Hint hint) {
return hint == Hint.DOS_SECTOR_ORDER;
}
@Override
public boolean can(Capability capability) {
return capability == Capability.WRITE_SECTOR;
@@ -20,6 +20,7 @@
package org.applecommander.device;
import org.applecommander.capability.Capability;
import org.applecommander.hint.Hint;
import org.applecommander.source.Source;
import org.applecommander.util.DataBuffer;
@@ -32,6 +33,11 @@ public class ProdosOrderedBlockDevice implements BlockDevice {
this.geometry = new Geometry(blockSize, blocksOnDevice);
}
@Override
public boolean is(Hint hint) {
return hint == Hint.PRODOS_BLOCK_ORDER;
}
@Override
public boolean can(Capability capability) {
return capability == Capability.WRITE_BLOCK;
@@ -20,6 +20,7 @@
package org.applecommander.device;
import org.applecommander.capability.Capability;
import org.applecommander.hint.Hint;
import org.applecommander.util.DataBuffer;
public class SkewedTrackSectorDevice implements TrackSectorDevice {
@@ -38,6 +39,11 @@ public class SkewedTrackSectorDevice implements TrackSectorDevice {
this.sectorSkew = sectorSkew;
}
@Override
public boolean is(Hint hint) {
return device.is(hint);
}
@Override
public boolean can(Capability capability) {
if (capability == Capability.WRITE_SECTOR) {
@@ -19,17 +19,16 @@
*/
package org.applecommander.device;
import org.applecommander.capability.CapabilityProvider;
import org.applecommander.util.DataBuffer;
public interface TrackSectorDevice extends CapabilityProvider {
public interface TrackSectorDevice extends Device {
int SECTOR_SIZE = 256;
Geometry getGeometry();
DataBuffer readSector(int track, int sector);
void writeSector(int track, int sector, DataBuffer data);
record Geometry(int tracksOnDisk, int sectorsPerTrack) {
int getSectorsPerDisk() {
public int sectorsPerDisk() {
return tracksOnDisk*sectorsPerTrack;
}
}
@@ -21,6 +21,7 @@ package org.applecommander.device;
import org.applecommander.capability.Capability;
import org.applecommander.codec.NibbleDiskCodec;
import org.applecommander.hint.Hint;
import org.applecommander.util.DataBuffer;
public class TrackSectorNibbleDevice implements TrackSectorDevice {
@@ -41,7 +42,12 @@ public class TrackSectorNibbleDevice implements TrackSectorDevice {
this.geometry = new Geometry(trackReaderWriter.getTracksOnDevice(), sectorsPerTrack);
}
@Override
@Override
public boolean is(Hint hint) {
return hint == Hint.NIBBLE_SECTOR_ORDER;
}
@Override
public boolean can(Capability capability) {
if (capability == Capability.WRITE_SECTOR) {
return trackReaderWriter.can(Capability.WRITE_TRACK) && dataCodec.can(Capability.ENCODE);
@@ -20,6 +20,7 @@
package org.applecommander.device;
import org.applecommander.capability.Capability;
import org.applecommander.hint.Hint;
import org.applecommander.util.DataBuffer;
import java.util.function.BiConsumer;
@@ -30,7 +31,12 @@ public class TrackSectorToBlockAdapter implements BlockDevice {
public TrackSectorToBlockAdapter(TrackSectorDevice device) {
this.device = device;
this.geometry = new Geometry(BLOCK_SIZE, device.getGeometry().getSectorsPerDisk() / 2);
this.geometry = new Geometry(STANDARD_BLOCK_SIZE, device.getGeometry().sectorsPerDisk() / 2);
}
@Override
public boolean is(Hint hint) {
return device.is(hint);
}
@Override
@@ -45,7 +51,7 @@ public class TrackSectorToBlockAdapter implements BlockDevice {
@Override
public DataBuffer readBlock(int block) {
DataBuffer data = DataBuffer.create(BLOCK_SIZE);
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)));
@@ -77,6 +77,21 @@ public class DataBuffer {
get(offset, match);
return Arrays.equals(data, match);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof DataBuffer that) {
return this.buffer.equals(that.buffer);
}
return false;
}
/**
* A utility method to pull the buffer as bytes.
*/
public byte[] asBytes() {
byte[] data = new byte[limit()];
get(0, data);
return data;
}
// GET/PUT RELATED FUNCTIONS
@@ -331,8 +331,9 @@ public class DiskWriterTest {
writeFile(disk,
"This is a test text file create from the DiskWriterTest".getBytes(), //$NON-NLS-1$
textType, testText);
if (disk.getPhysicalSize() > DiskConstants.APPLE_140KB_DISK
&& disk.getPhysicalSize() != DiskConstants.APPLE_140KB_NIBBLE_DISK) {
Source source = disk.getSource();
if (source.getSize() > DiskConstants.APPLE_140KB_DISK
&& source.getSize() != DiskConstants.APPLE_140KB_NIBBLE_DISK) {
// create a few big files
writeFile(disk, 150000, binaryType, true);
writeFile(disk, 300000, binaryType, true);
@@ -39,6 +39,7 @@ import com.webcodepro.applecommander.util.Host;
import com.webcodepro.applecommander.util.StreamUtil;
import com.webcodepro.applecommander.util.TextBundle;
import io.github.applecommander.applesingle.AppleSingle;
import org.applecommander.device.Device;
import org.applecommander.hint.Hint;
import org.applecommander.source.DataBufferSource;
import org.applecommander.source.Source;
@@ -1276,12 +1277,7 @@ public class DiskExplorerTab {
changeOrderToolItem.setImage(imageManager.get(ImageManager.ICON_CHANGE_IMAGE_ORDER));
changeOrderToolItem.setText(textBundle.get("ChangeDiskOrderToolItem")); //$NON-NLS-1$
changeOrderToolItem.setToolTipText(textBundle.get("ChangeDiskOrderHoverText")); //$NON-NLS-1$
ImageOrder imageOrder = disks[0].getImageOrder();
changeOrderToolItem.setEnabled(
(imageOrder.isBlockDevice()
&& imageOrder.getBlocksOnDevice() == DiskConstants.PRODOS_BLOCKS_ON_140KB_DISK)
|| (imageOrder.isTrackAndSectorDevice()
&& imageOrder.getSectorsPerDisk() == DiskConstants.DOS33_SECTORS_ON_140KB_DISK));
changeOrderToolItem.setEnabled(disks[0].getSource().isApproxEQ(DiskConstants.APPLE_140KB_DISK));
changeOrderToolItem.addSelectionListener(
new DropDownSelectionListener(getChangeImageOrderMenu()));
changeOrderToolItem.addSelectionListener(new SelectionAdapter () {
@@ -1807,7 +1803,8 @@ public class DiskExplorerTab {
*/
protected void changeImageOrder(String extension, ImageOrder newImageOrder) {
try {
disks[0].changeImageOrder(newImageOrder);
FormattedDiskX diskx = (FormattedDiskX) disks[0];
diskx.changeImageOrder(newImageOrder);
String filename = disks[0].getFilename();
if (filename.toLowerCase().endsWith(".gz")) {
int chop = filename.lastIndexOf(".", filename.length()-4); //$NON-NLS-1$
@@ -1840,15 +1837,15 @@ public class DiskExplorerTab {
Menu menu = new Menu(shell, SWT.NONE);
menu.addMenuListener(new MenuAdapter() {
public void menuShown(MenuEvent event) {
ImageOrder order = getDisk(0).getImageOrder();
Device device = DeviceAdapter.from(getDisk(0));
Menu theMenu = (Menu) event.getSource();
MenuItem[] subItems = theMenu.getItems();
// Nibble Order (*.nib)
subItems[0].setSelection(order.is(Hint.NIBBLE_SECTOR_ORDER));
subItems[0].setSelection(device.is(Hint.NIBBLE_SECTOR_ORDER));
// DOS Order (*.dsk)
subItems[1].setSelection(order.is(Hint.DOS_SECTOR_ORDER));
subItems[1].setSelection(device.is(Hint.DOS_SECTOR_ORDER));
// ProDOS Order (*.po)
subItems[2].setSelection(order.is(Hint.PRODOS_BLOCK_ORDER));
subItems[2].setSelection(device.is(Hint.PRODOS_BLOCK_ORDER));
}
});
@@ -1856,7 +1853,8 @@ public class DiskExplorerTab {
item.setText(textBundle.get("ChangeToNibbleOrderMenuItem")); //$NON-NLS-1$
item.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
if (!getDisk(0).getImageOrder().is(Hint.NIBBLE_SECTOR_ORDER)) {
Device device = DeviceAdapter.from(getDisk(0));
if (!device.is(Hint.NIBBLE_SECTOR_ORDER)) {
NibbleOrder nibbleOrder = new NibbleOrder(
DataBufferSource.create(DiskConstants.APPLE_140KB_NIBBLE_DISK, "new-image.nib").get());
nibbleOrder.format();
@@ -1869,7 +1867,8 @@ public class DiskExplorerTab {
item.setText(textBundle.get("ChangeToDosOrderMenuItem")); //$NON-NLS-1$
item.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
if (!getDisk(0).getImageOrder().is(Hint.DOS_SECTOR_ORDER)) {
Device device = DeviceAdapter.from(getDisk(0));
if (!device.is(Hint.DOS_SECTOR_ORDER)) {
changeImageOrder("dsk", new DosOrder( //$NON-NLS-1$
DataBufferSource.create(DiskConstants.APPLE_140KB_DISK, "new-image.dsk").get()));
}
@@ -1880,7 +1879,8 @@ public class DiskExplorerTab {
item.setText(textBundle.get("ChangeToProdosOrderMenuItem")); //$NON-NLS-1$
item.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
if (!getDisk(0).getImageOrder().is(Hint.PRODOS_BLOCK_ORDER)) {
Device device = DeviceAdapter.from(getDisk(0));
if (!device.is(Hint.PRODOS_BLOCK_ORDER)) {
changeImageOrder("po", new ProdosOrder( //$NON-NLS-1$
DataBufferSource.create(DiskConstants.APPLE_140KB_DISK, "new-image.po").get()));
}