Merge pull request #173 from AppleCommander/feature/add-disk-factories

Feature/add disk factories
This commit is contained in:
A2 Geek
2025-08-21 11:31:15 -05:00
committed by GitHub
32 changed files with 947 additions and 507 deletions
+9
View File
@@ -15,6 +15,15 @@ dependencies {
implementation project(':app:cli-ac')
implementation project(':lib:ac-api')
compileOnly "org.apache.ant:ant:$antVersion"
testImplementation platform("org.junit:junit-bom:$junitVersion")
testImplementation 'org.junit.jupiter:junit-jupiter'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
testImplementation "org.apache.ant:ant:$antVersion"
}
test {
useJUnitPlatform()
}
jar {
@@ -46,12 +46,17 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import com.webcodepro.applecommander.storage.*;
import com.webcodepro.applecommander.storage.os.cpm.CpmDiskFactory;
import com.webcodepro.applecommander.storage.os.dos33.DosDiskFactory;
import com.webcodepro.applecommander.storage.os.gutenberg.GutenbergDiskFactory;
import com.webcodepro.applecommander.storage.os.nakedos.NakedosDiskFactory;
import com.webcodepro.applecommander.storage.os.pascal.PascalDiskFactory;
import com.webcodepro.applecommander.storage.os.prodos.ProdosDiskFactory;
import com.webcodepro.applecommander.storage.os.rdos.RdosDiskFactory;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import com.webcodepro.applecommander.storage.Disk;
import com.webcodepro.applecommander.storage.DiskException;
import com.webcodepro.applecommander.storage.FormattedDisk;
import org.applecommander.image.DiskCopyImage;
import org.applecommander.image.UniversalDiskImage;
import org.applecommander.source.FileSource;
@@ -62,7 +67,10 @@ public class AntTask extends Task
static {
// This is a hack to deal with -whatever- is going on with the Ant classpath.
// Issue was verified by creating a simple (and separate) Main class and then doing a source identification.
Sources.setFactories(new FileSource.Factory(), new UniversalDiskImage.Factory(), new DiskCopyImage.Factory());
Sources.setFactories(new FileSource.Factory(), new UniversalDiskImage.Factory(), new DiskCopyImage.Factory(),
new FileEntrySource.Factory(), new ShrinkitSourceFactory());
Disks.setFactories(new CpmDiskFactory(), new DosDiskFactory(), new GutenbergDiskFactory(),
new NakedosDiskFactory(), new PascalDiskFactory(), new ProdosDiskFactory(), new RdosDiskFactory());
}
public void execute() throws BuildException
@@ -0,0 +1,42 @@
package com.webcodepro.applecommand.ui;
import com.webcodepro.applecommander.ui.AntTask;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* This unit test is used to "mock" Ant itself. Current IDE does not allow debugging, and
* because "ant" is alphabetized first, it runs first and fails first. Without debugging,
* it gets annoying. This allows a JUnit based approach to decipher what is going on. Ugh.
*/
public class AntTaskTest {
/**
* <property name="tmpdir" value="build/tmp" />
* <property name="dos140image" value="${tmpdir}/test-ant-dos140.do"/>
* <property name="appantdir" value="app/ant-ac/src/test/resources" />
* <appleCommander command="dos140" imagename="${dos140image}" />
* <appleCommander command="p" input="${appantdir}/manifest.mf"
* imagename="${dos140image}" filename="MANIFEST" type="T" />
*/
@Test
public void testPutFileOnDOS140kImage() {
final String dos140image = "build/tmp/test-ant-dos140.do";
final String appantdir = "src/test/resources";
assertDoesNotThrow(() -> {
AntTask t = new AntTask();
t.setCommand("dos140");
t.setImageName(dos140image);
t.execute();
}, "Creating DOS 140K image");
assertDoesNotThrow(() -> {
AntTask t = new AntTask();
t.setCommand("p");
t.setInput(appantdir + "/manifest.mf");
t.setImageName(dos140image);
t.setFileName("MANIFEST");
t.setType("T");
t.execute();
}, "'ac' put file onto DOS 140K image");
}
}
@@ -63,6 +63,7 @@ import io.github.applecommander.bastools.api.Visitors;
import io.github.applecommander.bastools.api.model.Program;
import io.github.applecommander.bastools.api.model.Token;
import org.applecommander.hint.Hint;
import org.applecommander.image.DiskCopyImage;
import org.applecommander.source.DataBufferSource;
import org.applecommander.source.Source;
import org.applecommander.util.Information;
@@ -240,7 +241,7 @@ public class ac {
}
/**
* Put fileName from the local filesytem into the file named fileOnImageName on the disk named imageName;
* Put fileName from the local filesystem into the file named fileOnImageName on the disk named imageName;
* Note: only volume level supported; input size unlimited.
*/
public static void putFile(String fileName, String imageName, String fileOnImageName, String fileType, String address) throws IOException, DiskException {
@@ -315,7 +316,8 @@ public class ac {
if (formattedDisks == null)
System.out.println("Dude, formattedDisks is null!");
FormattedDisk formattedDisk = formattedDisks[0];
if (!disk.isSDK() && !disk.isDC42()) {
boolean isDC42 = disk.getDiskImageManager() instanceof DiskCopyImage;
if (!disk.isSDK() && !isDC42) {
FileEntry entry = name.createEntry(formattedDisk);
if (entry != null) {
entry.setFiletype(fileType);
@@ -410,7 +412,8 @@ public class ac {
throws IOException, DiskException {
Disk disk = new Disk(imageName);
Name name = new Name(fileName);
if (!disk.isSDK() && !disk.isDC42()) {
boolean isDC42 = disk.getDiskImageManager() instanceof DiskCopyImage;
if (!disk.isSDK() && !isDC42) {
FormattedDisk[] formattedDisks = disk.getFormattedDisks();
for (int i = 0; i < formattedDisks.length; i++) {
FormattedDisk formattedDisk = formattedDisks[i];
@@ -579,7 +582,8 @@ public class ac {
static void setFileLocked(String imageName, Name name,
boolean lockState) throws IOException, DiskException {
Disk disk = new Disk(imageName);
if (!disk.isSDK() && !disk.isDC42()) {
boolean isDC42 = disk.getDiskImageManager() instanceof DiskCopyImage;
if (!disk.isSDK() && !isDC42) {
FormattedDisk[] formattedDisks = disk.getFormattedDisks();
for (int i = 0; i < formattedDisks.length; i++) {
FormattedDisk formattedDisk = formattedDisks[i];
@@ -604,7 +608,8 @@ public class ac {
public static void setDiskName(String imageName, String volName)
throws IOException, DiskException {
Disk disk = new Disk(imageName);
if (!disk.isSDK() && !disk.isDC42()) {
boolean isDC42 = disk.getDiskImageManager() instanceof DiskCopyImage;
if (!disk.isSDK() && !isDC42) {
FormattedDisk[] formattedDisks = disk.getFormattedDisks();
FormattedDisk formattedDisk = formattedDisks[0];
formattedDisk.setDiskName(volName);
@@ -58,7 +58,7 @@ public class ScanCommand extends ReusableCommandOptions {
for (Path dir : directories) {
Files.walkFileTree(dir, visitor);
}
showReport(visitor.reportData);
showReportData(visitor.reportData);
System.out.printf("Scanned %d disk images.\n", visitor.getCounter());
}
return 0;
@@ -70,22 +70,70 @@ public class ScanCommand extends ReusableCommandOptions {
JsonStreamParser parser = new JsonStreamParser(reader);
ReportData oldData = new ReportData("Old");
ReportData newData = new ReportData("New");
int degradationCount = 0;
int improvementCount = 0;
while (parser.hasNext()) {
Report oldReport = gson.fromJson(parser.next(), Report.class);
Report newReport = visitor.scanFile(Path.of(oldReport.imageName));
oldData.tallyData(oldReport);
newData.tallyData(newReport);
if (oldReport.success && !newReport.success) {
System.out.printf("Degradation with: %s\n", oldReport.imageName);
degradationCount++;
List<String> diffs = diffReport(oldReport, newReport);
if (!diffs.isEmpty()) {
System.out.printf("Degradation with: %s (%s)\n", oldReport.imageName, String.join(",", diffs));
}
}
else if (!oldReport.success && newReport.success) {
improvementCount++;
}
}
showReport(oldData, newData);
System.out.println();
System.out.printf("Recognition degraded by %d disks and improved by %d disks.\n", degradationCount, improvementCount);
System.out.println();
showReportData(oldData, newData);
} catch (IOException e) {
LOG.severe(e.getMessage());
}
}
public void showReport(ReportData... data) {
public List<String> diffReport(Report oldReport, Report newReport) {
List<String> diffs = new ArrayList<>();
diffBoolean(diffs, "success", r->r.success, oldReport, newReport);
diffString(diffs, "type", r->r.imageType, oldReport, newReport);
diffInt(diffs, "disks", r->r.logicalDisks, oldReport, newReport);
diffInt(diffs, "deleted", r->r.deletedFiles, oldReport, newReport);
diffInt(diffs, "dirs", r->r.directoriesVisited, oldReport, newReport);
diffInt(diffs, "visited", r->r.filesVisited, oldReport, newReport);
diffInt(diffs, "read", r->r.filesRead, oldReport, newReport);
diffString(diffs, "geometry", r->r.dataType, oldReport, newReport);
diffInt(diffs, "georead", r->r.dataRead, oldReport, newReport);
diffInt(diffs, "errors", r->r.errors.size(), oldReport, newReport);
return diffs;
}
public void diffBoolean(List<String> diffs, String title, Function<Report,Boolean> boolFn, Report oldReport, Report newReport) {
boolean oldValue = boolFn.apply(oldReport);
boolean newValue = boolFn.apply(newReport);
if (oldValue != newValue) {
diffs.add(String.format("%s %s<>%s", title, oldValue, newValue));
}
}
public void diffString(List<String> diffs, String title, Function<Report,String> strFn, Report oldReport, Report newReport) {
String oldValue = strFn.apply(oldReport);
String newValue = strFn.apply(newReport);
if (!Objects.equals(oldValue, newValue)) {
diffs.add(String.format("%s '%s'<>'%s'", title, oldValue, newValue));
}
}
public void diffInt(List<String> diffs, String title, Function<Report,Integer> intFn, Report oldReport, Report newReport) {
int oldValue = intFn.apply(oldReport);
int newValue = intFn.apply(newReport);
if (oldValue != newValue) {
diffs.add(String.format("%s %d<>%d", title, oldValue, newValue));
}
}
public void showReportData(ReportData... data) {
System.out.println();
showString("Title", ReportData::getTitle, data);
showInteger("Total Images", ReportData::getReportCount, data);
@@ -141,7 +189,13 @@ public class ScanCommand extends ReusableCommandOptions {
for (String ext : Disk.getAllExtensions()) {
if (!first) globs.append(",");
ext = ext.substring(1); // skip the "." - lots of assumptions here!
globs.append(ext);
// Unix is case-sensitive, so we need to make the pattern case-insensitive (yuck)
for (char ch : ext.toCharArray()) {
globs.append("[");
globs.append(Character.toLowerCase(ch));
globs.append(Character.toUpperCase(ch));
globs.append("]");
}
first = false;
}
globs.append("}");
@@ -19,22 +19,9 @@
*/
package com.webcodepro.applecommander.storage;
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.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.*;
import com.webcodepro.applecommander.util.AppleUtil;
import com.webcodepro.applecommander.util.TextBundle;
import org.applecommander.hint.Hint;
import org.applecommander.image.UniversalDiskImage;
import org.applecommander.image.WozImage;
import org.applecommander.source.Source;
import org.applecommander.source.Sources;
import org.applecommander.util.DataBuffer;
@@ -80,6 +67,7 @@ public class Disk {
public static final int DOS33_SECTORS_ON_140KB_DISK = 560;
public static final int APPLE_140KB_DISK = 143360;
public static final int APPLE_140KB_NIBBLE_DISK = 232960;
public static final int APPLE_400KB_DISK = 409600;
public static final int APPLE_800KB_DISK = 819200;
public static final int APPLE_800KB_2IMG_DISK =
APPLE_800KB_DISK + UniversalDiskImage.HEADER_SIZE;
@@ -129,9 +117,9 @@ public class Disk {
private String filename;
private boolean newImage = false;
private boolean isDC42 = false;
private Source diskImageManager;
private ImageOrder imageOrder = null;
private FormattedDisk[] formattedDisks;
/**
* Get the supported file filters supported by the Disk interface.
@@ -194,95 +182,16 @@ public class Disk {
public Disk(String filename, Source source, int startBlocks, boolean knownProDOSOrder) throws IOException {
this.filename = filename;
this.diskImageManager = source;
int diskSize = source.getSize();
knownProDOSOrder |= source.is(Hint.PRODOS_BLOCK_ORDER);
/* Does it have the WOZ1 or WOZ2 header? */
int signature = diskImageManager.readBytes(0, 4).readInt();
boolean isWoz = WozImage.WOZ1_MAGIC == signature || WozImage.WOZ2_MAGIC == signature;
ImageOrder dosOrder = new DosOrder(diskImageManager);
ImageOrder proDosOrder = new ProdosOrder(diskImageManager);
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.
*/
int rc = -1;
if (diskSize == APPLE_140KB_DISK) {
// First, test the really-really likely orders/formats for
// 5-1/4" disks.
imageOrder = dosOrder;
if ((isProdosFormat() || isDosFormat()) && !knownProDOSOrder) {
rc = 0;
} else {
imageOrder = proDosOrder;
if (knownProDOSOrder || isProdosFormat() || isDosFormat() || isRdos33Format()) {
rc = 0;
}
}
if (rc == -1) {
/*
* Check filenames for something deterministic.
*/
if (isProdosOrder() || is2ImgOrder()) {
imageOrder = proDosOrder;
rc = 0;
} else if (isDosOrder()) {
imageOrder = dosOrder;
rc = 0;
} else if (isNibbleOrder()) {
imageOrder = new NibbleOrder(diskImageManager);
rc = 0;
}
}
if (rc == -1) {
/*
* Ok, it's not one of those. Now, let's go back to DOS
* order, and see if we recognize other things. If not,
* we'll fall through to other processing later.
*/
imageOrder = dosOrder;
rc = testImageOrder();
}
}
if (rc == -1) {
imageOrder = proDosOrder;
rc = testImageOrder();
if (rc == -1) {
/*
* Couldn't find anything recognizable. Final step:
* just punt and start testing filenames.
*/
if (isProdosOrder() || is2ImgOrder()) {
imageOrder = proDosOrder;
} else if (isDosOrder()) {
imageOrder = dosOrder;
} else if (isNibbleOrder()) {
imageOrder = new NibbleOrder(diskImageManager);
} else {
imageOrder = proDosOrder;
}
}
}
List<FormattedDisk> foundDisks = Disks.inspect(source);
if (!foundDisks.isEmpty()) {
formattedDisks = foundDisks.toArray(new FormattedDisk[0]);
imageOrder = foundDisks.getFirst().getImageOrder();
}
else {
DiskFactory.Context ctx = new DiskFactory.Context(source);
imageOrder = ctx.orders.getFirst();
}
}
/**
* Test the image order to see if we can recognize a file system. Returns: 0
* on recognition; -1 on failure.
*/
public int testImageOrder()
{
int rc = (isProdosFormat() ? 1 : 0) + (isDosFormat() ? 2 : 0) + (isCpmFormat() ? 4 : 0) + (isUniDosFormat() ? 8 : 0) + (isPascalFormat() ? 16 : 0) + (isOzDosFormat() ? 32 : 0);
if (rc == 0)
rc = -1;
return rc;
}
/**
@@ -320,43 +229,21 @@ public class Disk {
* @throws DiskUnrecognizedException
*/
public FormattedDisk[] getFormattedDisks() throws DiskUnrecognizedException {
if (isProdosFormat()) {
return new FormattedDisk[]
{ new ProdosFormatDisk(filename, imageOrder) };
} else if (isUniDosFormat()) {
return new FormattedDisk[] {
new UniDosFormatDisk(filename, imageOrder,
UniDosFormatDisk.UNIDOS_DISK_1),
new UniDosFormatDisk(filename, imageOrder,
UniDosFormatDisk.UNIDOS_DISK_2) };
} else if (isOzDosFormat()) {
return new FormattedDisk[] {
new OzDosFormatDisk(filename, imageOrder,
OzDosFormatDisk.OZDOS_DISK_1),
new OzDosFormatDisk(filename, imageOrder,
OzDosFormatDisk.OZDOS_DISK_2) };
} else if (isDosFormat()) {
return new FormattedDisk[]
{ new DosFormatDisk(filename, imageOrder) };
} else if (isNakedosFormat()) {
return new FormattedDisk[]
{ new NakedosFormatDisk(filename, imageOrder) };
} else if (isPascalFormat()) {
return new FormattedDisk[]
{ new PascalFormatDisk(filename, imageOrder) };
} else if (isRdosFormat()) {
return new FormattedDisk[]
{ new RdosFormatDisk(filename, imageOrder) };
} else if (isCpmFormat()) {
return new FormattedDisk[]
{ new CpmFormatDisk(filename, imageOrder) };
} else if (isWPFormat()) {
return new FormattedDisk[]
{ new GutenbergFormatDisk(filename, imageOrder) };
if (formattedDisks != null && formattedDisks.length > 0) {
return formattedDisks;
}
throw new DiskUnrecognizedException(filename);
}
/**
* Allows super-classes to pass in the specific FormattedDisk to support new discovery mechanism.
* (Discovery occurs at class construction, not every time a formatted disk is pulled.)
*/
protected void setFormattedDisks(FormattedDisk... formattedDisks) {
assert(formattedDisks != null);
this.formattedDisks = formattedDisks;
}
/**
* Returns the diskImageManager.
* @return Source diskImageManager The disk Image Manager of this disk
@@ -406,22 +293,6 @@ public class Disk {
return filename.toLowerCase().endsWith(".sdk"); //$NON-NLS-1$
}
/**
* Indicate if this disk is a ShrinkIt-compressed package.
*/
public boolean isSHK()
{
return filename.toLowerCase().endsWith(".shk"); //$NON-NLS-1$
}
/**
* Indicate if this disk is a ShrinkIt-compressed binary II archive.
*/
public boolean isBXY()
{
return filename.toLowerCase().endsWith(".bxy"); //$NON-NLS-1$
}
/**
* Indicate if this disk is ProDOS ordered (beginning with block 0).
*/
@@ -520,199 +391,7 @@ public class Disk {
throws IllegalArgumentException {
imageOrder.writeSector(track, sector, bytes);
}
/**
* Test the disk format to see if this is a ProDOS formatted
* disk.
*/
public boolean isProdosFormat() {
byte[] prodosVolumeDirectory = readBlock(2);
int volDirEntryLength = prodosVolumeDirectory[0x23];
int volDirEntriesPerBlock = prodosVolumeDirectory[0x24];
return prodosVolumeDirectory[0] == 0 &&
prodosVolumeDirectory[1] == 0 &&
(prodosVolumeDirectory[4]&0xf0) == 0xf0 &&
(volDirEntryLength * volDirEntriesPerBlock <= BLOCK_SIZE);
}
/**
* Test the disk format to see if this is a DOS 3.3 formatted
* disk. This is a little nasty - since 800KB and 140KB images have
* different characteristics. This just tests 140KB images.
*/
public boolean isDosFormat() {
boolean good = false;
if (!is140KbDisk()) {
return false;
}
try {
byte[] vtoc = readSector(17, 0);
good = (imageOrder.isSizeApprox(APPLE_140KB_DISK)
|| imageOrder.isSizeApprox(APPLE_140KB_NIBBLE_DISK))
&& vtoc[0x01] == 17 // expect catalog to start on track 17
// 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 || 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
int catSect = vtoc[0x02];
byte[] cat = readSector(catTrack, catSect);
if (catTrack == cat[1] && catSect == cat[2] + 1) {
// Still good... let's follow one more
catTrack = cat[1];
catSect = cat[2];
cat = readSector(catTrack, catSect);
if (catTrack == cat[1] && catSect == cat[2] + 1) {
good = true;
} else {
good = false;
}
}
}
} catch (Exception ex) {
/*
* If we get various exceptions from reading tracks and sectors, then we
* definitely don't have a valid DOS image.
*/
good = false;
}
return good;
}
/**
* Test the disk format to see if this is a UniDOS formatted
* disk. UniDOS creates two logical disks on an 800KB physical disk.
* The first logical disk takes up the first 400KB and the second
* logical disk takes up the second 400KB.
*/
public boolean isUniDosFormat() {
if (!is800KbDisk()) return false;
byte[] vtoc1 = readSector(17, 0); // logical disk #1
byte[] vtoc2 = readSector(67, 0); // logical disk #2
return
// LOGICAL DISK #1
vtoc1[0x01] == 17 // expect catalog to start on track 17
&& vtoc1[0x02] == 31 // expect catalog to start on sector 31
&& vtoc1[0x27] == 122 // expect 122 tract/sector pairs per sector
&& vtoc1[0x34] == 50 // expect 50 tracks per disk
&& vtoc1[0x35] == 32 // expect 32 sectors per disk
&& vtoc1[0x36] == 0 // bytes per sector (low byte)
&& vtoc1[0x37] == 1 // bytes per sector (high byte)
// LOGICAL DISK #2
&& vtoc2[0x01] == 17 // expect catalog to start on track 17
&& vtoc2[0x02] == 31 // expect catalog to start on sector 31
&& vtoc2[0x27] == 122 // expect 122 tract/sector pairs per sector
&& vtoc2[0x34] == 50 // expect 50 tracks per disk
&& vtoc2[0x35] == 32 // expect 32 sectors per disk
&& vtoc2[0x36] == 0 // bytes per sector (low byte)
&& vtoc2[0x37] == 1; // bytes per sector (high byte)
}
/**
* Test the disk format to see if this is a OzDOS formatted
* disk. OzDOS creates two logical disks on an 800KB physical disk.
* The first logical disk takes the first half of each block and
* the second logical disk takes the second half of each block.
*/
public boolean isOzDosFormat() {
if (!is800KbDisk()) return false;
byte[] vtoc = readBlock(544); // contains BOTH VTOCs!
return
// LOGICAL DISK #1
vtoc[0x001] == 17 // expect catalog to start on track 17
&& vtoc[0x002] == 31 // expect catalog to start on sector 31
&& vtoc[0x027] == 122 // expect 122 tract/sector pairs per sector
&& vtoc[0x034] == 50 // expect 50 tracks per disk
&& vtoc[0x035] == 32 // expect 32 sectors per disk
&& vtoc[0x036] == 0 // bytes per sector (low byte)
&& vtoc[0x037] == 1 // bytes per sector (high byte)
// LOGICAL DISK #2
&& vtoc[0x137] == 1 // bytes per sector (high byte)
&& vtoc[0x101] == 17 // expect catalog to start on track 17
&& vtoc[0x102] == 31 // expect catalog to start on sector 31
&& vtoc[0x127] == 122 // expect 122 tract/sector pairs per sector
&& vtoc[0x134] == 50 // expect 50 tracks per disk
&& vtoc[0x135] == 32 // expect 32 sectors per disk
&& vtoc[0x136] == 0 // bytes per sector (low byte)
&& vtoc[0x137] == 1; // bytes per sector (high byte)
}
/**
* Test the disk format to see if this is a NakedOS formatted
* disk.
*/
public boolean isNakedosFormat() {
if (!is140KbDisk()) return false;
byte[] vtoc = readSector(0, 3); // VTOC starts on sector 9 (mapped to 3)
return (imageOrder.isSizeApprox(APPLE_140KB_DISK)
|| imageOrder.isSizeApprox(APPLE_140KB_NIBBLE_DISK))
&& vtoc[0xd0] == -2 // expect DOS as reserved
&& vtoc[0xd1] == -2 // expect DOS as reserved
&& vtoc[0xd2] == -2 // expect DOS as reserved
&& vtoc[0xd3] == -2 // expect DOS as reserved
&& vtoc[0xd4] == -2 // expect DOS as reserved
&& vtoc[0xd5] == -2 // expect DOS as reserved
&& vtoc[0xd6] == -2 // expect DOS as reserved
&& vtoc[0xd7] == -2 // expect DOS as reserved
&& vtoc[0xd8] == -2 // expect DOS as reserved
&& vtoc[0xd9] == -2 // expect DOS as reserved
&& vtoc[0xda] == -2 // expect DOS as reserved
&& vtoc[0xdb] == -2 // expect DOS as reserved
&& vtoc[0xdc] != -2 // expect something besides DOS next
;
}
/**
* Test the disk format to see if this is a Pascal formatted
* disk. Pascal disks may be either 140K or 800K.
*/
public boolean isPascalFormat() {
if (!(is140KbDisk() || is800KbDisk())) return false;
byte[] directory = readBlock(2);
return directory[0] == 0 && directory[1] == 0
&& directory[2] == 6 && directory[3] == 0
&& directory[4] == 0 && directory[5] == 0;
}
/**
* Test the disk format to see if this is a CP/M formatted disk.
* Check the first 256 bytes of the CP/M directory for validity.
*/
public boolean isCpmFormat() {
if (!is140KbDisk()) return false;
byte[] directory = readSector(3, 0);
int bytes[] = new int[256];
for (int i=0; i<directory.length; i++) {
bytes[i] = AppleUtil.getUnsignedByte(directory[i]);
}
int offset = 0;
while (offset < directory.length) {
// Check if this is an empty directory entry (and ignore it)
int e5count = 0;
for (int i=0; i<CpmFileEntry.ENTRY_LENGTH; i++) {
e5count+= bytes[offset+i] == 0xe5 ? 1 : 0;
}
if (e5count != CpmFileEntry.ENTRY_LENGTH) { // Not all bytes were 0xE5
// Check user number. Should be 0-15 or 0xE5
if (bytes[offset] > 15 && bytes[offset] != 0xe5) return false;
// Validate filename has highbit off
for (int i=0; i<8; i++) {
if (bytes[offset+1+i] > 127) return false;
}
// Extent should be 0-31 (low = 0-31 and high = 0)
if (bytes[offset+0xc] > 31 || bytes[offset+0xe] > 0) return false;
// Number of used records cannot exceed 0x80
if (bytes[offset+0xf] > 0x80) return false;
}
// Next entry
offset+= CpmFileEntry.ENTRY_LENGTH;
}
return true;
}
/**
* Answers true if this disk image is within the expected 140K
* disk size. Can vary if a header has been applied or if this is
@@ -723,64 +402,6 @@ public class Disk {
&& getPhysicalSize() <= APPLE_140KB_NIBBLE_DISK;
}
/**
* Answers true if this disk image is within the expected 800K
* disk size. Can vary if a 2IMG header has been applied.
*/
protected boolean is800KbDisk() {
return getPhysicalSize() >= APPLE_800KB_DISK
&& getPhysicalSize() <= APPLE_800KB_2IMG_DISK;
}
/**
* Test the disk format to see if this is a RDOS formatted
* 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$
}
/**
* Test the disk format to see if this is a RDOS 33 formatted
* disk.
*/
public boolean isRdos33Format() {
if (!is140KbDisk()) return false;
byte[] block = readSector(1, 0x0);
String id = AppleUtil.getString(block, 0x0, 6);
return "RDOS 3".equals(id); //$NON-NLS-1$
}
/**
* Test the disk format to see if this is a WP formatted
* disk.
*/
public boolean isWPFormat() {
if (!is140KbDisk()) return false;
byte[] vtoc = readSector(17, 7);
return (imageOrder.isSizeApprox(APPLE_140KB_DISK)
|| imageOrder.isSizeApprox(APPLE_140KB_NIBBLE_DISK))
&& vtoc[0x00] == 17 // expect catalog to start on track 17
&& vtoc[0x01] == 7 // expect catalog to start on sector 7
&& vtoc[0x0f] == -115; // expect 0x8d's every 16 bytes
}
/**
* Indicates if a given byte sequence is likely to be a DiskCopy 42 stream.
*
* @return boolean liklihood it is a DC42 stream
*/
private static boolean isDC42(byte[] buffer) {
return (((buffer[0x52] == 0x01) && (buffer[0x53] == 0x00)) &&
((buffer[0x51] == 0x02) || (buffer[0x51] == 0x22) || (buffer[0x51] == 0x24)));
}
public boolean isDC42() {
return isDC42;
}
/**
* Indicates if the disk has changed. Triggered when data is
* written and cleared when data is saved.
@@ -831,53 +452,4 @@ public class Disk {
}
return -1;
}
/**
* Change ImageOrder from source order to target order by copying sector by
* sector.
*/
private void changeImageOrderByTrackAndSector(ImageOrder sourceOrder, ImageOrder targetOrder)
{
if (!sameSectorsPerDisk(sourceOrder, targetOrder)) {
throw new IllegalArgumentException(textBundle.get("Disk.ResizeDiskError"));
}
for (int track = 0; track < sourceOrder.getTracksPerDisk(); track++) {
for (int sector = 0; sector < sourceOrder.getSectorsPerTrack(); sector++) {
byte[] data = sourceOrder.readSector(track, sector);
targetOrder.writeSector(track, sector, data);
}
}
}
/**
* Change ImageOrder from source order to target order by copying block by
* block.
*/
private void changeImageOrderByBlock(ImageOrder sourceOrder, ImageOrder targetOrder)
{
if (!sameBlocksPerDisk(sourceOrder, targetOrder)) {
throw new IllegalArgumentException(textBundle.get("Disk.ResizeDiskError"));
}
for (int block = 0; block < sourceOrder.getBlocksOnDevice(); block++) {
byte[] blockData = sourceOrder.readBlock(block);
targetOrder.writeBlock(block, blockData);
}
}
/**
* Answers true if the two disks have the same number of blocks per disk.
*/
private static boolean sameBlocksPerDisk(ImageOrder sourceOrder, ImageOrder targetOrder)
{
return sourceOrder.getBlocksOnDevice() == targetOrder.getBlocksOnDevice();
}
/**
* Answers true if the two disks have the same number of sectors per disk.
*/
private static boolean sameSectorsPerDisk(ImageOrder sourceOrder, ImageOrder targetOrder)
{
return sourceOrder.getSectorsPerDisk() == targetOrder.getSectorsPerDisk();
}
}
@@ -0,0 +1,39 @@
package com.webcodepro.applecommander.storage;
import com.webcodepro.applecommander.storage.physical.*;
import org.applecommander.hint.Hint;
import org.applecommander.image.WozImage;
import org.applecommander.source.Source;
import java.util.ArrayList;
import java.util.List;
public interface DiskFactory {
void inspect(Context ctx);
class Context {
public final Source source;
public final List<ImageOrder> orders = new ArrayList<>();
public final List<FormattedDisk> disks = new ArrayList<>();
public Context(Source source) {
this.source = source;
/* Does it have the WOZ1 or WOZ2 header? */
int signature = source.readBytes(0, 4).readInt();
if (WozImage.WOZ1_MAGIC == signature || WozImage.WOZ2_MAGIC == signature) {
orders.add(new WozOrder(source));
} else if (source.is(Hint.NIBBLE_SECTOR_ORDER) || source.isApproxEQ(Disk.APPLE_140KB_NIBBLE_DISK)) {
orders.add(new NibbleOrder(source));
} else if (source.is(Hint.PRODOS_BLOCK_ORDER) || source.getSize() > Disk.APPLE_400KB_DISK || source.extensionLike("po")) {
orders.add(new ProdosOrder(source));
} else if (source.is(Hint.DOS_SECTOR_ORDER) || source.extensionLike("do")) {
orders.add(new DosOrder(source));
} else {
// Could be either - most likely the nebulous "dsk" extension
orders.add(new DosOrder(source));
orders.add(new ProdosOrder(source));
}
}
}
}
@@ -0,0 +1,42 @@
package com.webcodepro.applecommander.storage;
import org.applecommander.source.Source;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
public class Disks {
private static final List<DiskFactory> FACTORIES;
static {
FACTORIES = new ArrayList<>();
for (DiskFactory factory : ServiceLoader.load(DiskFactory.class)) {
FACTORIES.add(factory);
}
}
/**
* This call is a shim for the AntTask to populate its known factories since ServiceLoader
* doesn't appear to work within the Ant classpath.
*/
public static void setFactories(DiskFactory... factories) {
FACTORIES.clear();
FACTORIES.addAll(List.of(factories));
}
/**
* Standardized FormattedDisk creation. Uses the ServiceLoader mechanism to identify
* all potential FormattedDisk factories.
*/
static List<FormattedDisk> inspect(Source source) {
DiskFactory.Context ctx = new DiskFactory.Context(source);
FACTORIES.forEach(factory -> {
try {
factory.inspect(ctx);
} catch (Throwable t) {
// ignore it
}
});
return ctx.disks;
}
}
@@ -0,0 +1,56 @@
package com.webcodepro.applecommander.storage.os.cpm;
import com.webcodepro.applecommander.storage.Disk;
import com.webcodepro.applecommander.storage.DiskFactory;
import org.applecommander.util.DataBuffer;
/**
* Test this disk for a likely CP/M filesystem.
* @see <a href="https://www.seasip.info/Cpm/format22.html">CP/M 2.2</a>
* @see <a href="https://www.seasip.info/Cpm/format31.html">CP/M 3.1</a>
*/
public class CpmDiskFactory implements DiskFactory {
@Override
public void inspect(Context ctx) {
ctx.orders.forEach(order -> {
if (order.isSizeApprox(Disk.APPLE_140KB_DISK) || order.isSizeApprox(Disk.APPLE_140KB_NIBBLE_DISK)) {
CpmFormatDisk disk = new CpmFormatDisk(ctx.source.getName(), order);
if (check(disk)) {
ctx.disks.add(disk);
}
}
});
}
public boolean check(CpmFormatDisk disk) {
DataBuffer entries = DataBuffer.wrap(disk.readCpmFileEntries());
int offset = 0;
while (offset < entries.limit()) {
// Check if this is an empty directory entry (and ignore it)
int e5count = 0;
for (int i=0; i<CpmFileEntry.ENTRY_LENGTH; i++) {
e5count+= entries.getUnsignedByte(offset+i) == 0xe5 ? 1 : 0;
}
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;
// Validate filename has highbit off and is a character
for (int i=0; i<8; i++) {
int ch = entries.getUnsignedByte(offset+1+i);
if (ch < 0x20 || ch > 127) return false;
}
// Extent should be 0-31 (low = 0-31 and high = 0)
int exLow = entries.getUnsignedByte(offset+0xc);
int exHighS2 = entries.getUnsignedByte(offset+0xe);
if (exLow > 31 || exHighS2 > 0) return false;
// Number of used records cannot exceed 0x80
int numberOfRecords = entries.getUnsignedByte(offset+0xf);
if (numberOfRecords > 0x80) return false;
}
// Next entry
offset+= CpmFileEntry.ENTRY_LENGTH;
}
return true;
}
}
@@ -103,6 +103,7 @@ public class CpmFormatDisk extends FormattedDisk {
public static CpmFormatDisk[] create(String filename, ImageOrder imageOrder) {
CpmFormatDisk disk = new CpmFormatDisk(filename, imageOrder);
disk.format();
disk.setFormattedDisks(disk);
return new CpmFormatDisk[] { disk };
}
@@ -0,0 +1,157 @@
package com.webcodepro.applecommander.storage.os.dos33;
import com.webcodepro.applecommander.storage.DiskFactory;
import com.webcodepro.applecommander.storage.FormattedDisk;
import com.webcodepro.applecommander.storage.physical.ImageOrder;
import org.applecommander.util.DataBuffer;
import java.util.*;
public class DosDiskFactory implements DiskFactory {
@Override
public void inspect(Context ctx) {
// It seems easiest to gather all possibilities first...
List<FormattedDisk> tests = new ArrayList<>();
if (ctx.orders.size() == 1) {
ImageOrder order = ctx.orders.getFirst();
if (order.isSizeApprox(FormattedDisk.APPLE_800KB_DISK)) {
tests.add(new UniDosFormatDisk(ctx.source.getName(), order, UniDosFormatDisk.UNIDOS_DISK_1));
tests.add(new UniDosFormatDisk(ctx.source.getName(), order, UniDosFormatDisk.UNIDOS_DISK_2));
tests.add(new OzDosFormatDisk(ctx.source.getName(), order, OzDosFormatDisk.OZDOS_DISK_1));
tests.add(new OzDosFormatDisk(ctx.source.getName(), order, OzDosFormatDisk.OZDOS_DISK_2));
}
else {
tests.add(new DosFormatDisk(ctx.source.getName(), ctx.orders.getFirst()));
}
}
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));
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
if (count1 >= count2) tests.add(fdisk1);
else tests.add(fdisk2);
}
// ... and then test for DOS VTOC etc. Passing track number along to hopefully handle it later!
for (FormattedDisk fdisk : tests) {
try {
if (check(fdisk, 17)) {
ctx.disks.add(fdisk);
}
} catch (Throwable t) {
// obviously wrong configuration
}
}
}
/**
* Test this image order by looking for a likely DOS VTOC and set of catalog sectors.
*/
public boolean check(FormattedDisk disk, final int vtocTrack) {
DataBuffer vtoc = DataBuffer.wrap(disk.readSector(vtocTrack, 0));
int nextTrack = vtoc.getUnsignedByte(0x01);
int nextSector = vtoc.getUnsignedByte(0x02);
int tracksPerDisk = vtoc.getUnsignedByte(0x34);
int sectorsPerTrack = vtoc.getUnsignedByte(0x35);
if (nextSector == 0 && nextTrack != 0) {
// Some folks "hid" the catalog by setting the pointer to T17,S0 - try and adjust
nextSector = sectorsPerTrack-1;
}
if (nextSector != 0 && nextTrack == 0) {
// Some folks zeroed out the next track field, so try the same as VTOC (T17)
nextTrack = vtocTrack;
}
// Start with VTOC test
boolean good = nextTrack <= tracksPerDisk // expect catalog to be sensible
&& nextSector > 0 // expect catalog to be...
&& nextSector < sectorsPerTrack // ... a legitimate sector
&& tracksPerDisk >= vtocTrack // expect sensible...
&& tracksPerDisk <= 50 // ... tracks per disk
&& sectorsPerTrack > 10 // expect sensible...
&& sectorsPerTrack <= 32; // ... sectors per disk
// Check that the free sectors are sensible (really only valid for 13 or 16 sector disks)
if (sectorsPerTrack == 13 || sectorsPerTrack == 16) {
// We only check that which is in common (bytes 3+4 of 1-4).
// Some DOS 3.2 cracks are on DOS 3.3 disks but report as 13 sector.
int unexpectedValue = 0;
for (int i=0; i<tracksPerDisk; i++) {
int offset = 0x38 + (i * 4);
int t3 = vtoc.getUnsignedByte(offset+2);
int t4 = vtoc.getUnsignedByte(offset+3);
// Found a free sector that should not exist
if (t3 != 0 || t4 != 0) unexpectedValue++;
}
// Totally arbitrary. Allow some errors but not a lot.
if (unexpectedValue > 3) {
return false;
}
}
// Now chase the directory links (note we assume catalog is all on same track).
Set<Integer> visited = new HashSet<>();
while (good) {
int mark = nextTrack * 100 + nextSector;
if (visited.contains(mark)) break;
visited.add(mark);
DataBuffer cat = DataBuffer.wrap(disk.readSector(nextTrack,nextSector));
nextTrack = cat.getUnsignedByte(0x01);
nextSector = cat.getUnsignedByte(0x02);
if (nextTrack == 0) break; // at end
good = checkCatalogValidity(cat, tracksPerDisk, sectorsPerTrack);
}
return good;
}
public int count(FormattedDisk disk, final int vtocTrack) {
DataBuffer vtoc = DataBuffer.wrap(disk.readSector(vtocTrack, 0));
int nextTrack = vtoc.getUnsignedByte(0x01);
int nextSector = vtoc.getUnsignedByte(0x02);
int tracksPerDisk = vtoc.getUnsignedByte(0x34);
int sectorsPerTrack = vtoc.getUnsignedByte(0x35);
if (tracksPerDisk > 50 || sectorsPerTrack > 32) {
return 0;
}
if (nextSector == 0 && nextTrack != 0) {
// Some folks "hid" the catalog by setting the pointer to T17,S0 - try and adjust
nextSector = sectorsPerTrack-1;
}
int count = 0;
Set<Integer> visited = new HashSet<>();
while (nextTrack > 0 && nextTrack <= tracksPerDisk && nextSector > 0 && nextSector < sectorsPerTrack) {
int mark = nextTrack * 100 + nextSector;
if (visited.contains(mark)) break;
visited.add(mark);
count++;
DataBuffer data = DataBuffer.wrap(disk.readSector(nextTrack, nextSector));
if (!checkCatalogValidity(data, tracksPerDisk, sectorsPerTrack)) break;
nextTrack = data.getUnsignedByte(0x01);
nextSector = data.getUnsignedByte(0x02);
}
return count;
}
// Notes (all of this makes it more difficult to test!):
// 1. File type isn't always as designated by Apple DOS.
// 2. Sector size is frequently bunk (as in > 560).
// 3. T/S pair can be bunk - trying to do the test but exclude "bad" components
public boolean checkCatalogValidity(DataBuffer data, int tracksPerDisk, int sectorsPerTrack) {
int nextTrack = data.getUnsignedByte(0x01);
int nextSector = data.getUnsignedByte(0x02);
if (nextTrack > tracksPerDisk || nextSector > sectorsPerTrack) return false;
for (int offset=0x0b; offset<0xff; offset+=0x23) {
int track = data.getUnsignedByte(offset);
if (track == 0) break;
if (track == 0xff) continue; // just skip deleted files
int sector = data.getUnsignedByte(offset+0x01);
int sectorSize = data.getUnsignedShort(offset+0x21);
// Allow potentially bad T/S if the file size is 0.
if (sectorSize == 0) continue;
// Otherwise expect things to be legit.
if (track > tracksPerDisk || sector > sectorsPerTrack) {
return false;
}
}
return true;
}
}
@@ -126,6 +126,7 @@ public class DosFormatDisk extends FormattedDisk {
public static DosFormatDisk[] create(String filename, ImageOrder imageOrder) {
DosFormatDisk disk = new DosFormatDisk(filename, imageOrder);
disk.format();
disk.setFormattedDisks(disk);
return new DosFormatDisk[] { disk };
}
@@ -149,6 +150,15 @@ public class DosFormatDisk extends FormattedDisk {
byte[] vtoc = readVtoc();
int track = AppleUtil.getUnsignedByte(vtoc[1]);
int sector = AppleUtil.getUnsignedByte(vtoc[2]);
int sectorsPerTrack = AppleUtil.getUnsignedByte(vtoc[0x35]);
if (sector == 0 && track != 0) {
// Some folks "hid" the catalog by setting the pointer to T17,S0 - try and adjust
sector = sectorsPerTrack-1;
}
if (sector != 0 && track == 0) {
// Some folks zeroed out the next track field, so try the same as VTOC (T17)
track = CATALOG_TRACK;
}
final Set<DosSectorAddress> visits = new HashSet<>();
while (sector != 0) { // bug fix: iterate through all catalog _sectors_
@@ -65,6 +65,8 @@ public class OzDosFormatDisk extends DosFormatDisk {
OzDosFormatDisk disk2 = new OzDosFormatDisk(filename, imageOrder, OZDOS_DISK_2);
disk1.format();
disk2.format();
disk1.setFormattedDisks(disk1, disk2);
disk2.setFormattedDisks(disk1, disk2);
return new OzDosFormatDisk[] { disk1, disk2 };
}
/**
@@ -63,6 +63,8 @@ public class UniDosFormatDisk extends DosFormatDisk {
UniDosFormatDisk disk2 = new UniDosFormatDisk(filename, imageOrder, UNIDOS_DISK_2);
disk1.format();
disk2.format();
disk1.setFormattedDisks(disk1, disk2);
disk2.setFormattedDisks(disk1, disk2);
return new UniDosFormatDisk[] { disk1, disk2 };
}
/**
@@ -116,14 +118,29 @@ public class UniDosFormatDisk extends DosFormatDisk {
* Retrieve the specified sector.
*/
public byte[] readSector(int track, int sector) throws IllegalArgumentException {
return getImageOrder().readSector(track+logicalOffset, sector);
byte[] blockData = readBlock(getBlockNumber(track, sector));
int offset = getBlockOffset(sector);
byte[] sectorData = new byte[SECTOR_SIZE];
System.arraycopy(blockData, offset, sectorData, 0, sectorData.length);
return sectorData;
}
/**
* Write the specified sector.
*/
public void writeSector(int track, int sector, byte[] bytes)
public void writeSector(int track, int sector, byte[] sectorData)
throws IllegalArgumentException {
getImageOrder().writeSector(track+logicalOffset, sector, bytes);
int block = getBlockNumber(track, sector);
byte[] blockData = readBlock(block);
int offset = getBlockOffset(sector);
System.arraycopy(sectorData, 0, blockData, offset, sectorData.length);
writeBlock(block, blockData);
}
public int getBlockNumber(int track, int sector) {
return ((track+logicalOffset)*32 + sector)/2;
}
public int getBlockOffset(int sector) {
return (sector & 0x1) * 0x100;
}
}
@@ -0,0 +1,63 @@
package com.webcodepro.applecommander.storage.os.gutenberg;
import com.webcodepro.applecommander.storage.Disk;
import com.webcodepro.applecommander.storage.DiskFactory;
import com.webcodepro.applecommander.storage.physical.ImageOrder;
import org.applecommander.util.DataBuffer;
import static com.webcodepro.applecommander.storage.os.gutenberg.GutenbergFormatDisk.*;
public class GutenbergDiskFactory implements DiskFactory {
@Override
public void inspect(Context ctx) {
ctx.orders.forEach(order -> {
if (check(order)) {
ctx.disks.add(new GutenbergFormatDisk(ctx.source.getName(), order));
}
});
}
public boolean check(ImageOrder order) {
boolean good = false;
if (order.isSizeApprox(Disk.APPLE_140KB_DISK) || order.isSizeApprox(Disk.APPLE_140KB_NIBBLE_DISK)) {
final int tracksPerDisk = 35;
final int sectorsPerTrack = 16;
// Everything starts at T17,S7
DataBuffer data = DataBuffer.wrap(order.readSector(CATALOG_TRACK, VTOC_SECTOR));
for (int i=0x0f; i<data.limit(); i+= 0x10) {
// Check for the CR at every 16th byte.
if (data.getUnsignedByte(i) != 0x8d) return false;
}
// Verify T/S links:
int priorTrack = data.getUnsignedByte(0x00);
int priorSector = data.getUnsignedByte(0x01);
int currentTrack = data.getUnsignedByte(0x02);
int currentSector = data.getUnsignedByte(0x03); // high bit set if first DIR sector
int nextTrack = data.getUnsignedByte(0x04); // high bit set if last DIR sector
int nextSector = data.getUnsignedByte(0x05);
good = priorTrack < tracksPerDisk && priorSector < sectorsPerTrack
&& currentTrack < tracksPerDisk && (currentSector & 0x7f) < sectorsPerTrack
&& (nextTrack & 0x7f) < tracksPerDisk && nextSector < sectorsPerTrack
&& isHighAscii(data, 6, 9);
if (!good) return false;
// Check that the file entries are as expected
for (int i=0x10; i<data.limit(); i+= 0x10) {
int firstTrack = data.getUnsignedByte(i+0xc);
int firstSector = data.getUnsignedByte(i+0xd); // if == 0x40, file is deleted
good = isHighAscii(data, i, 12)
&& firstTrack < tracksPerDisk
&& (firstSector < sectorsPerTrack || firstSector == 0x40)
&& isHighAscii(data, i+0xe, 1);
if (!good) return false;
}
}
return good;
}
public boolean isHighAscii(DataBuffer data, int start, int length) {
for (int i=start; i<start+length; i++) {
if (data.getUnsignedByte(i) < 0xa0) return false;
}
return true;
}
}
@@ -114,6 +114,7 @@ public class GutenbergFormatDisk extends FormattedDisk {
public static GutenbergFormatDisk[] create(String filename, ImageOrder imageOrder) {
GutenbergFormatDisk disk = new GutenbergFormatDisk(filename, imageOrder);
disk.format();
disk.setFormattedDisks(disk);
return new GutenbergFormatDisk[] { disk };
}
@@ -0,0 +1,47 @@
package com.webcodepro.applecommander.storage.os.nakedos;
import com.webcodepro.applecommander.storage.Disk;
import com.webcodepro.applecommander.storage.DiskFactory;
import com.webcodepro.applecommander.storage.physical.ImageOrder;
import org.applecommander.util.DataBuffer;
public class NakedosDiskFactory implements DiskFactory {
@Override
public void inspect(Context ctx) {
ctx.orders.forEach(order -> {
if (check(order)) {
ctx.disks.add(new NakedosFormatDisk(ctx.source.getName(), order));
}
});
}
public boolean check(ImageOrder image) {
boolean good = false;
if (image.isSizeApprox(Disk.APPLE_140KB_DISK) || image.isSizeApprox(Disk.APPLE_140KB_NIBBLE_DISK)) {
final int catalogSize = 560; // 35 tracks, 16 bytes per track
// Capture entire catalog
DataBuffer cat = DataBuffer.create(catalogSize);
for (int i=0; i<3; i++) {
// NOTE: Documented as sectors A..C but is really 9..B (https://bitbucket.org/martin.haye/super-mon/src/master/)
int sector = NakedosFormatDisk.sectorTranslate[9+i];
DataBuffer data = DataBuffer.wrap(image.readSector(0, sector));
if (i == 0) {
cat.put(0, data.slice(0xd0, 0x30));
}
else {
cat.put(0x30+(i-1)*256, data);
}
}
// Scan for validity
for (int i=0; i<catalogSize; i++) {
int fileId = cat.getUnsignedByte(i);
boolean validNakedosReserved = i <= 0x0b && fileId == 0xfe;
boolean othersNotReserved = i > 0x0b && fileId != 0xfe;
boolean hex00invalid = fileId != 0x00;
good = (validNakedosReserved || othersNotReserved) && hex00invalid;
if (!good) break;
}
}
return good;
}
}
@@ -66,12 +66,12 @@ public class NakedosFormatDisk extends FormattedDisk {
*/
private int usedSectors = 0;
private static final int[] sectorTranslate = {0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15};
public static final int[] sectorTranslate = {0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15};
/**
* Use this inner interface for managing the disk usage data.
* This off-loads format-specific implementation to the implementing class.
*/
private class WPDiskUsage implements DiskUsage {
private class NakedosDiskUsage implements DiskUsage {
private int[] location = null;
public boolean hasNext() {
return location == null
@@ -119,6 +119,7 @@ public class NakedosFormatDisk extends FormattedDisk {
public static NakedosFormatDisk[] create(String filename, ImageOrder imageOrder) {
NakedosFormatDisk disk = new NakedosFormatDisk(filename, imageOrder);
disk.format();
disk.setFormattedDisks(disk);
return new NakedosFormatDisk[] { disk };
}
@@ -261,7 +262,7 @@ public class NakedosFormatDisk extends FormattedDisk {
* Get the disk usage iterator.
*/
public DiskUsage getDiskUsage() {
return new WPDiskUsage();
return new NakedosDiskUsage();
}
/**
@@ -304,7 +305,7 @@ public class NakedosFormatDisk extends FormattedDisk {
}
/**
* Get WP-specific disk information.
* Get NakedOS-specific disk information.
*/
public List<DiskInformation> getDiskInformation() {
getFiles();
@@ -0,0 +1,64 @@
package com.webcodepro.applecommander.storage.os.pascal;
import com.webcodepro.applecommander.storage.Disk;
import com.webcodepro.applecommander.storage.DiskFactory;
import com.webcodepro.applecommander.storage.physical.ImageOrder;
import org.applecommander.util.DataBuffer;
/**
* Automatic discovery of Pascal volumes.
*/
public class PascalDiskFactory implements DiskFactory {
@Override
public void inspect(Context ctx) {
ctx.orders.forEach(order -> {
if (check(order)) {
ctx.disks.add(new PascalFormatDisk(ctx.source.getName(), order));
}
});
}
/** Check for a likely directory structure. Note that we scan all sizes, even though that is overkill. */
public boolean check(ImageOrder order) {
boolean good = false;
if (order.getPhysicalSize() >= Disk.APPLE_140KB_DISK) {
// Read entire directory for analysis
DataBuffer dir = DataBuffer.create(2048);
for (int block=2; block<6; block++) {
byte[] data = order.readBlock(block);
dir.put((block-2)*Disk.BLOCK_SIZE, DataBuffer.wrap(data));
}
// Check volume entry
int dFirstBlock = dir.getUnsignedShort(0);
int dLastBlock = dir.getUnsignedShort(2);
int dEntryType = dir.getUnsignedShort(4);
int dNameLength = dir.getUnsignedByte(6);
int dBlocksOnDisk = dir.getUnsignedShort(14);
int dFilesOnDisk = dir.getUnsignedShort(16);
int dZeroBlock = dir.getUnsignedShort(18);
if (dBlocksOnDisk == 0) dBlocksOnDisk = 280; // patch for some Pascal disks found
good = dFirstBlock == 0 && dLastBlock == 6 && dEntryType == 0
&& dNameLength < 8
&& dFilesOnDisk < 78
&& dBlocksOnDisk >= 280 && dZeroBlock == 0;
// Check (any) existing file entries
int offset = 26;
while (good && dFilesOnDisk > 0 && offset < dir.limit()) {
int fFirstBlock = dir.getUnsignedShort(offset);
int fLastBlock = dir.getUnsignedShort(offset+2);
int fEntryType = dir.getUnsignedShort(offset+4);
int fNameLength = dir.getUnsignedByte(offset+6);
int fBytesLastBlock = dir.getUnsignedShort(offset+22);
if (fNameLength == 0) break; // last entry?
good = fFirstBlock < fLastBlock
&& fLastBlock <= dBlocksOnDisk
&& (fEntryType & 0x7fff) < 9
&& fNameLength < 16
&& fBytesLastBlock <= 512;
offset += 26;
dFilesOnDisk--;
}
}
return good;
}
}
@@ -127,6 +127,7 @@ public class PascalFormatDisk extends FormattedDisk {
PascalFormatDisk disk = new PascalFormatDisk(filename, imageOrder);
disk.format();
disk.setDiskName(volumeName);
disk.setFormattedDisks(disk);
return new PascalFormatDisk[] { disk };
}
@@ -0,0 +1,66 @@
package com.webcodepro.applecommander.storage.os.prodos;
import com.webcodepro.applecommander.storage.DiskFactory;
import com.webcodepro.applecommander.storage.FormattedDisk;
import org.applecommander.util.DataBuffer;
import java.util.ArrayList;
import java.util.List;
public class ProdosDiskFactory implements DiskFactory {
@Override
public void inspect(Context ctx) {
// It seems easiest to gather all possibilities first...
List<FormattedDisk> 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) {
if (check(fdisk)) {
ctx.disks.add(fdisk);
}
}
}
public boolean check(FormattedDisk fdisk) {
int nextBlock = 2;
DataBuffer volumeDirectory = DataBuffer.wrap(fdisk.readBlock(nextBlock));
int priorBlock = volumeDirectory.getUnsignedShort(0x00);
int storageType = volumeDirectory.getUnsignedByte(0x04) >> 4;
int entryLength = volumeDirectory.getUnsignedByte(0x23);
int bitmapPointer = volumeDirectory.getUnsignedShort(0x27);
int totalBlocks = volumeDirectory.getUnsignedShort(0x29);
// Note entriesPerBlock is documented as $D, but other values exist as well ($C, for instance)
int entriesPerBlock = volumeDirectory.getUnsignedByte(0x24);
// Check primary block for values
boolean good = priorBlock == 0
&& storageType == 0xf
&& entryLength == 0x27
&& (entryLength * entriesPerBlock) < FormattedDisk.BLOCK_SIZE
&& bitmapPointer < totalBlocks;
// Now follow the directory blocks -- but only forward; it seems some images have "bad" backward links!
while (good) {
// Verify the entries a bit
for (int i=0x04; i<256; i+=0x27) {
// skip the volume directory header
if (nextBlock == 2 && i == 0x04) continue;
// Skip deleted files
storageType = volumeDirectory.getUnsignedByte(i) >> 4;
if (storageType == 0) continue;
int keyPointer = volumeDirectory.getUnsignedShort(i+0x11);
int blocksUsed = volumeDirectory.getUnsignedShort(i+0x13);
int headerPointer = volumeDirectory.getUnsignedShort(i+0x25);
good = keyPointer != 0
&& keyPointer < totalBlocks
&& blocksUsed < totalBlocks
&& headerPointer < totalBlocks;
if (!good) return false;
}
nextBlock = volumeDirectory.getUnsignedShort(0x02);
if (nextBlock == 0) break;
if (nextBlock >= totalBlocks) return false;
volumeDirectory = DataBuffer.wrap(fdisk.readBlock(nextBlock));
}
return good;
}
}
@@ -165,6 +165,7 @@ public class ProdosFormatDisk extends FormattedDisk {
ProdosFormatDisk disk = new ProdosFormatDisk(filename, imageOrder);
disk.format();
disk.setDiskName(diskName);
disk.setFormattedDisks(disk);
return new ProdosFormatDisk[] { disk };
}
@@ -0,0 +1,117 @@
package com.webcodepro.applecommander.storage.os.rdos;
import com.webcodepro.applecommander.storage.Disk;
import com.webcodepro.applecommander.storage.DiskFactory;
import com.webcodepro.applecommander.storage.physical.ImageOrder;
import org.applecommander.util.DataBuffer;
import static com.webcodepro.applecommander.storage.os.rdos.RdosFormatDisk.*;
import java.util.Set;
public class RdosDiskFactory implements DiskFactory {
private final static Set<Integer> validFileTypes = Set.of(0xc1 /*A*/, 0xc2 /*B*/, 0xd4 /*T*/, 0xd3 /*S*/);
@Override
public void inspect(Context ctx) {
ctx.orders.forEach(order -> {
int sectorsPerTrack = check(order);
if (sectorsPerTrack > 0) {
ctx.disks.add(new RdosFormatDisk(ctx.source.getName(), order, sectorsPerTrack));
}
});
}
/**
* Check for RDOS catalog. Note that it might be DOS ordered -or- physical sector (13-sector disks).
* Returns sectors per track (13 or 16).
*/
public int check(ImageOrder order) {
boolean good = false;
if (order.isSizeApprox(Disk.APPLE_140KB_DISK) || order.isSizeApprox(Disk.APPLE_140KB_NIBBLE_DISK)) {
// 16-sector disks are DOS ordered...
good = true;
for (int s=0; s<CATALOG_SECTORS; s++) {
DataBuffer data = DataBuffer.wrap(order.readSector(1, s));
good = testCatalogSector(data, 560);
if (!good) break;
}
if (good && testForCatalogCode(order, 0, 1)) { //testForStartupFile(order, 0x60, 15)) {
return 16;
}
else {
// 13-sector disks are "physical" ordered...
for (int s=0; s<CATALOG_SECTORS; s++) {
DataBuffer data = DataBuffer.wrap(order.readSector(1,sectorSkew[s]));
good = testCatalogSector(data, 455);
if (!good) break;
}
if (good && testForCatalogCode(order, 1, 9)) { // testForStartupFile(order, 0xfa, 4, 5)) {
return 13;
}
}
}
return -1;
}
/*
* Test a single catalog sector.
* <br/>
* Notes from RdosFileEntry...
* $00-$17 File name; space-filled. If the first byte is $00, that is the end of the<br>
* directory. If the first byte is $80, the file is deleted.<br>
* $18 File type. Appears to be actual letter ('A'=Applesoft, etc)<br>
* $19 File length in blocks (block = sector = 256 bytes)<br>
* $1A-$1B Address of application. For Applesoft and binary; others may vary.<br>
* $1C-$1D Length in bytes of file.<br>
* $1E-$1F Starting block of application.<br>
*/
public boolean testCatalogSector(DataBuffer data, final int maxBlocks) {
boolean good = true;
boolean atEnd = false;
for (int i=0; i<Disk.SECTOR_SIZE; i+= 0x20) {
int check = data.getUnsignedByte(i);
// Once we reach the last file, ensure the rest of the directory is all 0's
if (atEnd && check != 0) return false;
atEnd = check == 0; // at end of directory
if (atEnd) continue;
if (check == 0x80) continue; // deleted
// Check this is all valid high-bit ASCII
for (int j=0; j<0x18; j++) {
good = data.getUnsignedByte(i) >= 0xA0;
if (!good) return good;
}
int fileType = data.getUnsignedByte(i+0x18);
int lengthInBlocks = data.getUnsignedByte(i+0x19);
int lengthInBytes = data.getUnsignedShort(i+0x1c);
int firstBlock = data.getUnsignedShort(i+0x1e);
good = validFileTypes.contains(fileType)
&& lengthInBytes <= lengthInBlocks*Disk.SECTOR_SIZE
&& firstBlock < maxBlocks;
if (!good) return good;
}
return good;
}
/**
* This is (hopefully) the determinant of correct sector ordering. Sector 1 (16-sector image) and
* 9 (13-sector image) get mapped differently between DO and PO disks.
*/
public boolean testForCatalogCode(ImageOrder order, int track, int sector) {
DataBuffer data = DataBuffer.wrap(order.readSector(track,sector));
final String header = " LEN -<NAME>- LENGTH BLK";
final String notInUse = "<NOT IN USE>";
return locate(data,header) && locate(data,notInUse);
}
public boolean locate(DataBuffer buffer, String search) {
// Convert to a string type thing
StringBuilder sb = new StringBuilder();
buffer.position(0);
while (buffer.hasRemaining()) {
int ch = buffer.readUnsignedByte() & 0x7f;
if (ch < 0x20) ch = 0x2e; // "."
sb.appendCodePoint(ch);
}
// And just use a String function
return sb.toString().contains(search);
}
}
@@ -21,7 +21,6 @@ package com.webcodepro.applecommander.storage.os.rdos;
import com.webcodepro.applecommander.storage.*;
import com.webcodepro.applecommander.storage.physical.ImageOrder;
import com.webcodepro.applecommander.storage.physical.ProdosOrder;
import com.webcodepro.applecommander.util.AppleUtil;
import com.webcodepro.applecommander.util.TextBundle;
@@ -54,7 +53,7 @@ public class RdosFormatDisk extends FormattedDisk {
* ordering. It appears that RDOS may use the physical sector number
* instead of the logical sector.
*/
private static final int sectorSkew[] = {
public static final int[] sectorSkew = {
0, 7, 0x0e, 6, 0x0d, 5, 0x0c, 4,
0x0b, 3, 0x0a, 2, 9, 1, 8, 0x0f
};
@@ -82,15 +81,13 @@ public class RdosFormatDisk extends FormattedDisk {
"B", "BIN"
);
private final int sectorsPerTrack;
/**
* 13 sectors for RDOS 2.1/3.2, native sectoring (16) for RDOS 3.3
*/
private int SectorsPerTrack() {
if (getImageOrder() instanceof ProdosOrder) {
return getImageOrder().getSectorsPerTrack();
} else {
return 13;
}
return sectorsPerTrack;
}
/**
@@ -152,15 +149,16 @@ public class RdosFormatDisk extends FormattedDisk {
/**
* Constructor for RdosFormatDisk.
*/
public RdosFormatDisk(String filename, ImageOrder imageOrder) {
public RdosFormatDisk(String filename, ImageOrder imageOrder, int sectorsPerTrack) {
super(filename, imageOrder);
this.sectorsPerTrack = sectorsPerTrack;
}
/**
* Create a RdosFormatDisk.
*/
public static RdosFormatDisk[] create(String filename, ImageOrder imageOrder) {
RdosFormatDisk disk = new RdosFormatDisk(filename, imageOrder);
RdosFormatDisk disk = new RdosFormatDisk(filename, imageOrder, 16);
disk.format();
return new RdosFormatDisk[] { disk };
}
@@ -26,6 +26,7 @@ public class FileSource implements Source {
public FileSource(Path path) {
try {
this.path = path;
this.filename = path.toString();
byte[] rawData = Files.readAllBytes(path);
this.buffer = DataBuffer.wrap(rawData);
if (this.buffer.getUnsignedShort(0) == GZIPInputStream.GZIP_MAGIC) {
@@ -43,11 +44,6 @@ public class FileSource implements Source {
throw new UncheckedIOException(ex);
}
}
public FileSource(Path path, DataBuffer buffer) {
this.path = path;
this.filename = path.getFileName().toString();
this.buffer = buffer;
}
@Override
public boolean can(Capability capability) {
@@ -33,6 +33,35 @@ public interface Source extends CapabilityProvider, HintProvider, Container {
void clearChanges();
List<Information> information();
/**
* Indicates if the source image is approximately equal to this size
* (less any image over-read).
* Currently, hardcoded to allow up to 10 extra bytes at the end of a
* disk image. Must be at least the requested size!
*/
default boolean isApproxEQ(int value) {
return getSize() >= value && getSize() <= value + 10;
}
/**
* Indicates if the source image is approximately less than this
* size (less any image over-read).
* Currently, hardcoded to allow up to 10 extra bytes at the end of a
* disk image. Must be at least the requested size!
*/
default boolean isApproxLE(int value) {
return getSize() <= value + 10;
}
/**
* Test if this name has a given file extension.
*/
default boolean extensionLike(String extension) {
String name = getName().toLowerCase();
String ext1 = String.format(".%s", extension.toLowerCase());
String ext2 = String.format(".%s.gz", extension.toLowerCase());
return name.endsWith(ext1) || name.endsWith(ext2);
}
interface Factory {
Optional<Source> fromObject(Object object);
Optional<Source> fromSource(Source source);
@@ -67,6 +67,10 @@ public class DataBuffer {
public int getUnsignedShort(int index) {
return Short.toUnsignedInt(this.buffer.getShort(index));
}
/** Retrieve a 3 byte number (as in ProDOS EOF values). */
public int getUnsignedShort3(int index) {
return getUnsignedByte(index)<<16 | getUnsignedShort(index+1);
}
public int getUnsignedShortBE(int index) {
this.buffer.order(ByteOrder.BIG_ENDIAN);
int value = getUnsignedShort(index);
@@ -0,0 +1,7 @@
com.webcodepro.applecommander.storage.os.cpm.CpmDiskFactory
com.webcodepro.applecommander.storage.os.dos33.DosDiskFactory
com.webcodepro.applecommander.storage.os.gutenberg.GutenbergDiskFactory
com.webcodepro.applecommander.storage.os.nakedos.NakedosDiskFactory
com.webcodepro.applecommander.storage.os.pascal.PascalDiskFactory
com.webcodepro.applecommander.storage.os.prodos.ProdosDiskFactory
com.webcodepro.applecommander.storage.os.rdos.RdosDiskFactory
@@ -50,17 +50,19 @@ public class DiskHelperTest {
assertTextFile(disks[0], "APPLE PROMS"); //$NON-NLS-1$
assertBinaryFile(disks[0], "BOOT13"); //$NON-NLS-1$
assertEquals(DOS33_FORMAT, disks[0].getFormat());
assertCanReadFiles(disks[0]);
assertCanReadFiles(disks);
}
@Test
public void testLoadMaster() throws IOException, DiskException {
showDirectory(config.getDiskDir() + "/MASTER.DSK"); //$NON-NLS-1$
FormattedDisk[] disks = showDirectory(config.getDiskDir() + "/MASTER.DSK"); //$NON-NLS-1$
assertCanReadFiles(disks);
}
@Test
public void testLoadGalacticAttack1() throws IOException, DiskException {
showDirectory(config.getDiskDir() + "/galatt.dsk"); //$NON-NLS-1$
FormattedDisk[] disks = showDirectory(config.getDiskDir() + "/galatt.dsk"); //$NON-NLS-1$
assertCanReadFiles(disks);
}
@Test
@@ -69,23 +71,26 @@ public class DiskHelperTest {
assertApplesoftFile(disks[0], "COPY.ME"); //$NON-NLS-1$
assertBinaryFile(disks[0], "SETTINGS"); //$NON-NLS-1$
assertDisassemblyFile(disks[0], "PRODOS"); //$NON-NLS-1$
assertCanReadFiles(disks[0]);
assertCanReadFiles(disks);
}
@Test
public void testLoadMarbleMadness() throws IOException, DiskException {
showDirectory(config.getDiskDir()
FormattedDisk[] disks = showDirectory(config.getDiskDir()
+ "/Marble Madness (1985)(Electronic Arts).2mg"); //$NON-NLS-1$
assertCanReadFiles(disks);
}
@Test
public void testRdosBoot() throws IOException, DiskException {
showDirectory(config.getDiskDir() + "/RDOSboot.dsk"); //$NON-NLS-1$
FormattedDisk[] disks = showDirectory(config.getDiskDir() + "/RDOSboot.dsk"); //$NON-NLS-1$
assertCanReadFiles(disks);
}
@Test
public void testSsiSave() throws IOException, DiskException {
showDirectory(config.getDiskDir() + "/SSIsave.dsk"); //$NON-NLS-1$
FormattedDisk[] disks = showDirectory(config.getDiskDir() + "/SSIsave.dsk"); //$NON-NLS-1$
assertCanReadFiles(disks);
}
@Test
@@ -96,12 +101,13 @@ public class DiskHelperTest {
assertBinaryFile(disks[0], "TWN31"); //$NON-NLS-1$
assertTextFile(disks[0], "ITEM"); //$NON-NLS-1$
assertGraphicsFile(disks[0], "ICE DRAGON"); //$NON-NLS-1$
assertCanReadFiles(disks[0]);
assertCanReadFiles(disks);
}
@Test
public void testPhanta32() throws IOException, DiskException {
showDirectory(config.getDiskDir() + "/PHANTA32.DSK"); //$NON-NLS-1$
FormattedDisk[] disks = showDirectory(config.getDiskDir() + "/PHANTA32.DSK"); //$NON-NLS-1$
assertCanReadFiles(disks);
}
@Test
@@ -112,22 +118,25 @@ public class DiskHelperTest {
assertBinaryFile(disks[0], "TWN21"); //$NON-NLS-1$
assertTextFile(disks[0], "ITEM"); //$NON-NLS-1$
assertGraphicsFile(disks[0], "ICE DRAGON"); //$NON-NLS-1$
assertCanReadFiles(disks[0]);
assertCanReadFiles(disks);
}
@Test
public void testPhan2d2() throws IOException, DiskException {
showDirectory(config.getDiskDir() + "/phan2d2.dsk"); //$NON-NLS-1$
FormattedDisk[] disks = showDirectory(config.getDiskDir() + "/phan2d2.dsk"); //$NON-NLS-1$
assertCanReadFiles(disks);
}
@Test
public void testPhantasie1() throws IOException, DiskException {
showDirectory(config.getDiskDir() + "/Phantasie1.dsk"); //$NON-NLS-1$
FormattedDisk[] disks = showDirectory(config.getDiskDir() + "/Phantasie1.dsk"); //$NON-NLS-1$
assertCanReadFiles(disks);
}
@Test
public void testPhantasie2() throws IOException, DiskException {
showDirectory(config.getDiskDir() + "/Phantasie2.dsk"); //$NON-NLS-1$
FormattedDisk[] disks = showDirectory(config.getDiskDir() + "/Phantasie2.dsk"); //$NON-NLS-1$
assertCanReadFiles(disks);
}
@Test
@@ -135,19 +144,21 @@ public class DiskHelperTest {
FormattedDisk[] disks = showDirectory(config.getDiskDir()
+ "/CavernsOfFreitag.dsk"); //$NON-NLS-1$
assertGraphicsFile(disks[0], "TITLE.PIC"); //$NON-NLS-1$
assertCanReadFiles(disks[0]);
assertCanReadFiles(disks);
}
@Test
public void testUniDosD3110() throws IOException, DiskException {
showDirectory(config.getDiskDir()
FormattedDisk[] disks = showDirectory(config.getDiskDir()
+ "/D3110.dsk"); //$NON-NLS-1$
assertCanReadFiles(disks);
}
@Test
public void testUniDosD3151() throws IOException, DiskException {
showDirectory(config.getDiskDir()
FormattedDisk[] disks = showDirectory(config.getDiskDir()
+ "/D3151.dsk"); //$NON-NLS-1$
assertCanReadFiles(disks);
}
@Test
@@ -158,7 +169,7 @@ public class DiskHelperTest {
assertIntegerFile(disks[0], "COPY"); //$NON-NLS-1$
assertBinaryFile(disks[0], "BOOT13"); //$NON-NLS-1$
assertEquals(DOS33_FORMAT, disks[0].getFormat());
assertCanReadFiles(disks[0]);
assertCanReadFiles(disks);
}
@Test
@@ -169,7 +180,7 @@ public class DiskHelperTest {
assertIntegerFile(disks[0], "COPY"); //$NON-NLS-1$
assertBinaryFile(disks[0], "BOOT13"); //$NON-NLS-1$
assertEquals(DOS33_FORMAT, disks[0].getFormat());
assertCanReadFiles(disks[0]);
assertCanReadFiles(disks);
}
@Test
@@ -179,7 +190,7 @@ public class DiskHelperTest {
assertIntegerFile(disks[0], "HELLO"); //$NON-NLS-1$
assertBinaryFile(disks[0], "UPDATE 3.2"); //$NON-NLS-1$
assertEquals(DOS32_FORMAT, disks[0].getFormat());
assertCanReadFiles(disks[0]);
assertCanReadFiles(disks);
}
@Test
@@ -190,9 +201,23 @@ public class DiskHelperTest {
assertBinaryFile(disks[0], "UPDATE 3.2.1");
assertTextFile(disks[0], "APPLE PROMS");
assertEquals(DOS32_FORMAT, disks[0].getFormat());
assertCanReadFiles(disks[0]);
assertCanReadFiles(disks);
}
@Test
public void testLoadNakedosSuperMonDisk() throws DiskException, IOException {
FormattedDisk[] disks = showDirectory(config.getDiskDir() +
"/Super-Mon-dev.dsk");
assertCanReadFiles(disks);
}
@Test
public void testLoadGutenbergDisk() throws DiskException, IOException {
FormattedDisk[] disks = showDirectory(config.getDiskDir() +
"/Gutenberg_side1.DSK");
assertCanReadFiles(disks);
}
protected FormattedDisk[] showDirectory(String imageName) throws IOException, DiskException {
Disk disk = new Disk(imageName);
FormattedDisk[] formattedDisks = disk.getFormattedDisks();
@@ -304,12 +329,18 @@ public class DiskHelperTest {
assertInstanceOf(GraphicsFileFilter.class, fileEntry.getSuggestedFilter(), "GraphicsFileFilter was not chosen");
}
protected void assertCanReadFiles(FormattedDisk... disks) throws DiskException {
for (FormattedDisk disk : disks) assertCanReadFiles((DirectoryEntry) disk);
}
protected void assertCanReadFiles(DirectoryEntry dir) throws DiskException {
for (FileEntry file : dir.getFiles()) {
if (file instanceof DirectoryEntry) {
assertCanReadFiles((DirectoryEntry) file);
}
else {
else if (file.isDeleted()) {
System.out.printf("Skipping deleted file: %s\n", file.getFilename());
} else {
try {
byte[] data = file.getFileData();
assertNotNull(data);
@@ -174,7 +174,7 @@ public class DiskWriterTest {
@Test
public void testCreateAndDeleteUniDos() throws IOException, DiskException {
Source source = DataBufferSource.create(Disk.APPLE_800KB_DISK, "new-disk").get();
ImageOrder imageOrder = new DosOrder(source);
ImageOrder imageOrder = new ProdosOrder(source);
FormattedDisk[] disks = UniDosFormatDisk.create(
"createanddelete-test-unidos.dsk", imageOrder); //$NON-NLS-1$
createAndDeleteFiles(disks, "B"); //$NON-NLS-1$
@@ -272,7 +272,7 @@ public class DiskWriterTest {
@Test
public void testCreateDeleteCreateUnidosDisk() throws IOException, DiskException {
Source source = DataBufferSource.create(Disk.APPLE_800KB_DISK, "new-disk").get();
ImageOrder imageOrder = new DosOrder(source);
ImageOrder imageOrder = new ProdosOrder(source);
FormattedDisk[] disks = UniDosFormatDisk.create(
"createdeletecreate-test-unidos-800k.dsk", imageOrder); //$NON-NLS-1$
createDeleteCreate(disks, "B"); //$NON-NLS-1$