mirror of
https://github.com/AppleCommander/AppleCommander.git
synced 2026-04-24 13:16:40 +00:00
Merge pull request #173 from AppleCommander/feature/add-disk-factories
Feature/add disk factories
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
+56
@@ -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;
|
||||
}
|
||||
}
|
||||
+1
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
+157
@@ -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;
|
||||
}
|
||||
}
|
||||
+10
@@ -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_
|
||||
|
||||
|
||||
+2
@@ -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 };
|
||||
}
|
||||
/**
|
||||
|
||||
+20
-3
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+63
@@ -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;
|
||||
}
|
||||
}
|
||||
+1
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
+47
@@ -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;
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -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();
|
||||
|
||||
+64
@@ -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;
|
||||
}
|
||||
}
|
||||
+1
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
+66
@@ -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;
|
||||
}
|
||||
}
|
||||
+1
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
+117
@@ -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);
|
||||
}
|
||||
}
|
||||
+7
-9
@@ -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);
|
||||
|
||||
+7
@@ -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
|
||||
+52
-21
@@ -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$
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user