Fixed subdirectory bugs, added support for A2GameServer data files suffix (#TTAAAA where TT is the hex code and AAAA is the aux data/load address). This should be very servicable now.

This commit is contained in:
Brendan Robert 2015-12-31 21:48:48 -06:00
parent 5f9352abb3
commit 732f4768a6
8 changed files with 269 additions and 180 deletions

View File

@ -80,7 +80,7 @@ public abstract class ProdosDriver {
MOS65C02 cpu = (MOS65C02) computer.getCpu(); MOS65C02 cpu = (MOS65C02) computer.getCpu();
cpu.A = returnCode; cpu.A = returnCode;
// Clear carry flag if no error, otherwise set carry flag // Clear carry flag if no error, otherwise set carry flag
cpu.C = (returnCode == 0x00) ? 00 : 01; cpu.C = (returnCode == 0x00) ? 00 : 01;
} }
private MLI_RETURN prodosMLI() { private MLI_RETURN prodosMLI() {

View File

@ -18,14 +18,17 @@
*/ */
package jace.hardware.massStorage; package jace.hardware.massStorage;
import static jace.hardware.massStorage.IDisk.BLOCK_SIZE;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -35,10 +38,14 @@ import java.util.logging.Logger;
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com * @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/ */
public class DirectoryNode extends DiskNode implements FileFilter { public class DirectoryNode extends DiskNode implements FileFilter {
// public static int FILE_ENTRY_SIZE = 38;
public static int FILE_ENTRY_SIZE = 0x027; public static final byte STANDARD_PERMISSIONS = (byte) 0x0c3;
public static final int PRODOS_VERSION = 0x023;
public static final int FILE_ENTRY_SIZE = 0x027;
public static final int ENTRIES_PER_BLOCK = (ProdosVirtualDisk.BLOCK_SIZE - 4) / FILE_ENTRY_SIZE;
private boolean isRoot; private boolean isRoot;
private List<DiskNode> directoryEntries;
public DirectoryNode(ProdosVirtualDisk ownerFilesystem, File physicalDir, int baseBlock, boolean root) throws IOException { public DirectoryNode(ProdosVirtualDisk ownerFilesystem, File physicalDir, int baseBlock, boolean root) throws IOException {
super(ownerFilesystem, baseBlock); super(ownerFilesystem, baseBlock);
@ -52,9 +59,11 @@ public class DirectoryNode extends DiskNode implements FileFilter {
private void init(ProdosVirtualDisk ownerFilesystem, File physicalFile, boolean root) throws IOException { private void init(ProdosVirtualDisk ownerFilesystem, File physicalFile, boolean root) throws IOException {
isRoot = root; isRoot = root;
directoryEntries = new ArrayList<>();
setPhysicalFile(physicalFile); setPhysicalFile(physicalFile);
setType(EntryType.SUBDIRECTORY); setType(EntryType.SUBDIRECTORY);
setName(physicalFile.getName()); setName(physicalFile.getName());
allocate();
} }
@Override @Override
@ -63,15 +72,15 @@ public class DirectoryNode extends DiskNode implements FileFilter {
@Override @Override
public void doAllocate() throws IOException { public void doAllocate() throws IOException {
File[] files = physicalFile.listFiles(this); for (int i = 1; i < getBlockCount(); i++) {
int numEntries = files.length; if (isRoot) {
// First block has 12 entries, subsequent blocks have 13 entries new SubNode(i, this, getOwnerFilesystem().getNextFreeBlock(3));
int numBlocks = 1 + numEntries / 13; } else {
for (int i=1; i < numBlocks; i++) { new SubNode(i, this);
new SubNode(i, this, getOwnerFilesystem().getNextFreeBlock()); }
} }
for (File f : files) { for (File f : physicalFile.listFiles()) {
addFile(f); addFile(f);
} }
Collections.sort(children, (DiskNode o1, DiskNode o2) -> o1.getName().compareTo(o2.getName())); Collections.sort(children, (DiskNode o1, DiskNode o2) -> o1.getName().compareTo(o2.getName()));
@ -88,58 +97,66 @@ public class DirectoryNode extends DiskNode implements FileFilter {
*/ */
public boolean checkFile() throws IOException { public boolean checkFile() throws IOException {
boolean success = true; boolean success = true;
if (!super.checkFile()) { if (!allocated) {
return false; allocate();
} } else {
HashSet<String> realFiles = new HashSet<>(); try {
File[] realFileList = physicalFile.listFiles(this); if (!super.checkFile()) {
for (File f : realFileList) { return false;
realFiles.add(f.getName());
}
for (Iterator<DiskNode> i = getChildren().iterator(); i.hasNext();) {
DiskNode node = i.next();
if (realFiles.contains(node.getPhysicalFile().getName())) {
realFiles.remove(node.getPhysicalFile().getName());
} else {
i.remove();
success = false;
}
if (node.isAllocated()) {
if (!(node instanceof DirectoryNode) && !node.checkFile()) {
success = false;
} }
HashSet<String> realFiles = new HashSet<>();
File[] realFileList = physicalFile.listFiles(this);
for (File f : realFileList) {
realFiles.add(f.getName());
}
for (Iterator<DiskNode> i = directoryEntries.iterator(); i.hasNext();) {
DiskNode node = i.next();
if (realFiles.contains(node.getPhysicalFile().getName())) {
realFiles.remove(node.getPhysicalFile().getName());
} else {
i.remove();
success = false;
}
if (node.isAllocated()) {
if (!(node instanceof DirectoryNode) && !node.checkFile()) {
success = false;
}
}
}
if (!realFiles.isEmpty()) {
success = false;
// New files showed up -- deal with them!
realFiles.stream().forEach((fileName) -> {
addFile(new File(physicalFile, fileName));
});
}
} catch (IOException ex) {
return false;
} }
} }
if (!realFiles.isEmpty()) {
success = false;
// New files showed up -- deal with them!
realFiles.stream().forEach((fileName) -> {
addFile(new File(physicalFile, fileName));
});
}
return success; return success;
} }
@Override @Override
public void readBlock(int block, byte[] buffer) throws IOException { public void readBlock(int block, byte[] buffer) throws IOException {
Arrays.fill(buffer, (byte) 0);
checkFile(); checkFile();
int start = 0;
int end = 0;
int offset = 4;
generatePointers(buffer, block);
// System.out.println("Directory "+getName()+" sequence "+block+"; physical block "+getNodeSequence(block).getBaseBlock());
if (block == 0) { if (block == 0) {
generateHeader(buffer); generateHeader(buffer);
for (int i = 0; i < 12 && i < children.size(); i++) { offset += FILE_ENTRY_SIZE;
generateFileEntry(buffer, 4 + (i + 1) * FILE_ENTRY_SIZE, i); end = ENTRIES_PER_BLOCK - 1;
}
} else { } else {
generatePointers(buffer, getNodeSequence(block-1).getBaseBlock(), getNodeSequence(block+1) != null ? getNodeSequence(block+1).getBaseBlock() : 0); start = (block * ENTRIES_PER_BLOCK) - 1;
int start = (block * 13) - 1; end = start + ENTRIES_PER_BLOCK;
int end = start + 13; }
int offset = 4; for (int i = start; i < end && i < directoryEntries.size(); i++, offset += FILE_ENTRY_SIZE) {
// TODO: Add any parts that are not file entries.
for (int i = start; i < end && i < children.size(); i++) { // System.out.println("Entry "+i+": "+children.get(i).getName()+"; offset "+offset);
// TODO: Add any parts that are not file entries. generateFileEntry(buffer, offset, i);
generateFileEntry(buffer, offset, i);
offset += FILE_ENTRY_SIZE;
}
} }
} }
@ -155,13 +172,16 @@ public class DirectoryNode extends DiskNode implements FileFilter {
return !file.isHidden(); return !file.isHidden();
} }
private void generatePointers(byte[] buffer, int prevBlock, int nextBlock) { private void generatePointers(byte[] buffer, int sequence) {
// Previous block = 0 DiskNode prev = getNodeSequence(sequence - 1);
generateWord(buffer, 0, prevBlock); DiskNode next = getNodeSequence(sequence + 1);
// Next block // System.out.println("Sequence "+sequence+" prev="+(prev != null ? prev.getBaseBlock() : 0)+"; next="+(next != null ? next.getBaseBlock() : 0));
generateWord(buffer, 0x02, nextBlock); // Previous block (or 0)
generateWord(buffer, 0, prev != null ? prev.getBaseBlock() : 0);
// Next block (or 0)
generateWord(buffer, 0x02, next != null ? next.getBaseBlock() : 0);
} }
/** /**
* Generate the directory header found in the base block of a directory * Generate the directory header found in the base block of a directory
* *
@ -169,40 +189,43 @@ public class DirectoryNode extends DiskNode implements FileFilter {
*/ */
@SuppressWarnings("static-access") @SuppressWarnings("static-access")
private void generateHeader(byte[] buffer) { private void generateHeader(byte[] buffer) {
generatePointers(buffer, 0, getNodeSequence(1) == null ? 0 : getNodeSequence(1).getBaseBlock());
// Directory header + name length // Directory header + name length
// Volumme header = 0x0f0; Subdirectory header = 0x0e0 // Volumme header = 0x0f0; Subdirectory header = 0x0e0
buffer[4] = (byte) ((isRoot ? 0x0F0 : 0x0E0) | getName().length()); buffer[4] = (byte) ((isRoot ? 0x0F0 : 0x0E0) | getName().length());
generateName(buffer, 5, this); generateName(buffer, 5, this);
for (int i = 0x014; i <= 0x01b; i++) {
buffer[i] = 0;
}
if (!isRoot) { if (!isRoot) {
buffer[0x014] = 0x075; buffer[0x014] = 0x075;
buffer[0x015] = PRODOS_VERSION;
buffer[0x017] = STANDARD_PERMISSIONS;
buffer[0x018] = FILE_ENTRY_SIZE;
buffer[0x019] = ENTRIES_PER_BLOCK;
} }
generateTimestamp(buffer, 0x01c, getPhysicalFile().lastModified()); generateTimestamp(buffer, 0x01c, getPhysicalFile().lastModified());
// Prodos 1.9 // Prodos 1.0 = 0
buffer[0x020] = 0x019; buffer[0x020] = PRODOS_VERSION;
// Minimum version = 0 (no min) // Minimum version = 0 (no min)
buffer[0x021] = 0x000; buffer[0x021] = 0x000;
// Directory may be read/written to, may not be destroyed or renamed // Directory may be read/written to, may not be destroyed or renamed
buffer[0x022] = 0x03; buffer[0x022] = STANDARD_PERMISSIONS;
// Entry size // Entry size
buffer[0x023] = (byte) FILE_ENTRY_SIZE; buffer[0x023] = (byte) FILE_ENTRY_SIZE;
// Entries per block // Entries per block
buffer[0x024] = (byte) 0x0d; buffer[0x024] = (byte) ENTRIES_PER_BLOCK;
// Directory items count // Directory items count
generateWord(buffer, 0x025, children.size()); generateWord(buffer, 0x025, directoryEntries.size()+1);
if (isRoot) { if (isRoot) {
// Volume bitmap pointer // Volume bitmap pointer
generateWord(buffer, 0x027, ownerFilesystem.freespaceBitmap.getBaseBlock()); generateWord(buffer, 0x027, ownerFilesystem.freespaceBitmap.getBaseBlock());
// Total number of blocks // Total number of blocks
generateWord(buffer, 0x029, ownerFilesystem.MAX_BLOCK); generateWord(buffer, 0x029, ownerFilesystem.MAX_BLOCK);
} else { } else {
// According to the Beneath Apple Prodos supplement
int indexInParent = getParent().getChildren().indexOf(this) + 2;
int parentBlock = getParent().getNodeSequence(indexInParent / ENTRIES_PER_BLOCK).getBaseBlock();
// Parent pointer // Parent pointer
generateWord(buffer, 0x027, getParent().getBaseBlock()); generateWord(buffer, 0x027, parentBlock);
buffer[0x029] = (byte) (getParent().getChildren().indexOf(this) + 1); buffer[0x029] = (byte) (indexInParent % ENTRIES_PER_BLOCK);
buffer[0x02a] = 0x027; buffer[0x02a] = (byte) FILE_ENTRY_SIZE;
} }
} }
@ -214,9 +237,9 @@ public class DirectoryNode extends DiskNode implements FileFilter {
* @param fileNumber number of file (indexed in Children array) to write * @param fileNumber number of file (indexed in Children array) to write
*/ */
private void generateFileEntry(byte[] buffer, int offset, int fileNumber) throws IOException { private void generateFileEntry(byte[] buffer, int offset, int fileNumber) throws IOException {
DiskNode child = children.get(fileNumber); DiskNode child = directoryEntries.get(fileNumber);
// Entry Type and length // Entry Type and length
buffer[offset] = (byte) ((child.getType().code << 4) + child.getName().length()); buffer[offset] = (byte) ((child.getType().code << 4) | child.getName().length());
// Name // Name
generateName(buffer, offset + 1, child); generateName(buffer, offset + 1, child);
// File type // File type
@ -224,20 +247,20 @@ public class DirectoryNode extends DiskNode implements FileFilter {
// Key pointer // Key pointer
generateWord(buffer, offset + 0x011, child.getBaseBlock()); generateWord(buffer, offset + 0x011, child.getBaseBlock());
// Blocks used -- will report only one unless file is actually allocated // Blocks used -- will report only one unless file is actually allocated
generateWord(buffer, offset + 0x013, child.additionalNodes.size()); generateWord(buffer, offset + 0x013, child.additionalNodes.size() + 1);
// EOF // EOF (file size or directory structure size
// TODO: Verify this is the right thing to do -- is EOF total length or a modulo? int length = child.getLength();
int length = ((int) child.physicalFile.length()) & 0x0ffffff; length &= 0x0ffffff;
generateWord(buffer, offset + 0x015, length & 0x0ffff); generateWord(buffer, offset + 0x015, length & 0x0ffff);
buffer[offset + 0x017] = (byte) ((length >> 16) & 0x0ff); buffer[offset + 0x017] = (byte) ((length >> 16) & 0x0ff);
// Creation date // Creation date
generateTimestamp(buffer, offset + 0x018, child.physicalFile.lastModified()); generateTimestamp(buffer, offset + 0x018, child.physicalFile.lastModified());
// Version = 1.9 // Version = 1.0
buffer[offset + 0x01c] = 0x19; buffer[offset + 0x01c] = PRODOS_VERSION;
// Minimum version = 0 // Minimum version = 0
buffer[offset + 0x01d] = 0; buffer[offset + 0x01d] = 0;
// Access = Read-only // Access = Read-only
buffer[offset + 0x01e] = (byte) 0x001; buffer[offset + 0x01e] = STANDARD_PERMISSIONS;
// AUX type // AUX type
if (child instanceof FileNode) { if (child instanceof FileNode) {
generateWord(buffer, offset + 0x01f, ((FileNode) child).loadAddress); generateWord(buffer, offset + 0x01f, ((FileNode) child).loadAddress);
@ -267,20 +290,40 @@ public class DirectoryNode extends DiskNode implements FileFilter {
} }
private void generateName(byte[] buffer, int offset, DiskNode node) { private void generateName(byte[] buffer, int offset, DiskNode node) {
for (int i = 0; i < node.getName().length(); i++) { for (int i = 0; i < node.getName().length() && i < 15; i++) {
buffer[offset + i] = (byte) node.getName().charAt(i); buffer[offset + i] = (byte) node.getName().charAt(i);
} }
} }
private Optional<DiskNode> findChildByFilename(String name) {
return directoryEntries.stream().filter((child) -> child.getPhysicalFile().getName().equals(name)).findFirst();
}
private void addFile(File file) { private void addFile(File file) {
try { if (!hasChildNamed(file.getName())) {
if (file.isDirectory()) { try {
addChild(new DirectoryNode(getOwnerFilesystem(), file, false)); if (file.isDirectory()) {
} else { addFileEntry(new DirectoryNode(getOwnerFilesystem(), file, false));
addChild(new FileNode(getOwnerFilesystem(), file)); } else {
addFileEntry(new FileNode(getOwnerFilesystem(), file));
}
} catch (IOException ex) {
Logger.getLogger(DirectoryNode.class.getName()).log(Level.SEVERE, null, ex);
} }
} catch (IOException ex) {
Logger.getLogger(DirectoryNode.class.getName()).log(Level.SEVERE, null, ex);
} }
} }
private void addFileEntry(DiskNode entry) {
directoryEntries.add(entry);
entry.setParent(this);
}
@Override
public int getLength() {
return getBlockCount() * BLOCK_SIZE;
}
private int getBlockCount() {
return isRoot ? 4 : 1 + (physicalFile.listFiles().length / ENTRIES_PER_BLOCK);
}
} }

View File

@ -22,6 +22,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional;
/** /**
* Prodos file/directory node abstraction. This provides a lot of the glue for * Prodos file/directory node abstraction. This provides a lot of the glue for
@ -60,12 +61,12 @@ public abstract class DiskNode {
public DiskNode(ProdosVirtualDisk fs) throws IOException { public DiskNode(ProdosVirtualDisk fs) throws IOException {
init(fs); init(fs);
setBaseBlock(fs.getNextFreeBlock()); fs.allocateEntry(this);
} }
public DiskNode(ProdosVirtualDisk fs, int blockNumber) throws IOException { public DiskNode(ProdosVirtualDisk fs, int blockNumber) throws IOException {
init(fs); init(fs);
setBaseBlock(blockNumber); fs.allocateEntryNear(this, blockNumber);
} }
private void init(ProdosVirtualDisk fs) throws IOException { private void init(ProdosVirtualDisk fs) throws IOException {
@ -89,7 +90,6 @@ public abstract class DiskNode {
public void allocate() throws IOException { public void allocate() throws IOException {
if (!allocated) { if (!allocated) {
doAllocate(); doAllocate();
getOwnerFilesystem().physicalMap.put(baseBlock, this);
allocationTime = System.currentTimeMillis(); allocationTime = System.currentTimeMillis();
allocated = true; allocated = true;
} }
@ -118,7 +118,7 @@ public abstract class DiskNode {
public DiskNode getNodeSequence(int num) { public DiskNode getNodeSequence(int num) {
if (num == 0) { if (num == 0) {
return this; return this;
} else if (num <= additionalNodes.size()) { } else if (num > 0 && num <= additionalNodes.size()) {
return additionalNodes.get(num-1); return additionalNodes.get(num-1);
} else { } else {
return null; return null;
@ -156,9 +156,8 @@ public abstract class DiskNode {
/** /**
* @param baseBlock the baseBlock to set * @param baseBlock the baseBlock to set
*/ */
private void setBaseBlock(int baseBlock) { public void setBaseBlock(int baseBlock) {
this.baseBlock = baseBlock; this.baseBlock = baseBlock;
ownerFilesystem.physicalMap.put(baseBlock, this);
} }
/** /**
@ -189,6 +188,7 @@ public abstract class DiskNode {
public void setPhysicalFile(File physicalFile) { public void setPhysicalFile(File physicalFile) {
this.physicalFile = physicalFile; this.physicalFile = physicalFile;
setName(physicalFile.getName()); setName(physicalFile.getName());
lastCheckTime = physicalFile.lastModified();
} }
/** /**
@ -227,6 +227,14 @@ public abstract class DiskNode {
public void removeChild(DiskNode child) { public void removeChild(DiskNode child) {
children.remove(child); children.remove(child);
} }
public boolean hasChildNamed(String name) {
return findChildByFilename(name).isPresent();
}
private Optional<DiskNode> findChildByFilename(String name) {
return getChildren().stream().filter((child) -> child.getPhysicalFile().getName().equals(name)).findFirst();
}
/** /**
* @return the type * @return the type
@ -253,10 +261,7 @@ public abstract class DiskNode {
* @param name the name to set * @param name the name to set
*/ */
public void setName(String name) { public void setName(String name) {
if (name.length() > 15) { this.name = (name.length() > 15 ? name.substring(0, 15) : name).toUpperCase();
name = name.substring(0, 15);
}
this.name = name.toUpperCase();
} }
public abstract void doDeallocate(); public abstract void doDeallocate();
@ -266,6 +271,8 @@ public abstract class DiskNode {
public abstract void doRefresh(); public abstract void doRefresh();
public abstract void readBlock(int sequence, byte[] buffer) throws IOException; public abstract void readBlock(int sequence, byte[] buffer) throws IOException;
public abstract int getLength();
public void readBlock(byte[] buffer) throws IOException { public void readBlock(byte[] buffer) throws IOException {
checkFile(); checkFile();

View File

@ -21,7 +21,6 @@ package jace.hardware.massStorage;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
/** /**
* Representation of a prodos file with a known file type and having a known * Representation of a prodos file with a known file type and having a known
@ -31,6 +30,11 @@ import java.util.Arrays;
*/ */
public class FileNode extends DiskNode { public class FileNode extends DiskNode {
@Override
public int getLength() {
return (int) getPhysicalFile().length();
}
public enum FileType { public enum FileType {
UNKNOWN(0x00, 0x0000), UNKNOWN(0x00, 0x0000),
@ -67,6 +71,15 @@ public class FileNode extends DiskNode {
this.code = code; this.code = code;
this.defaultLoadAddress = addr; this.defaultLoadAddress = addr;
} }
public static FileType findByCode(int code) {
for (FileType t : FileType.values()) {
if (t.code == code) {
return t;
}
}
return UNKNOWN;
}
} }
public int fileType = 0x00; public int fileType = 0x00;
public int loadAddress = 0x00; public int loadAddress = 0x00;
@ -90,28 +103,35 @@ public class FileNode extends DiskNode {
@Override @Override
public void setName(String name) { public void setName(String name) {
String[] parts = name.replaceAll("[^A-Za-z0-9#]", ".").split("\\.");
FileType t = FileType.UNKNOWN; FileType t = FileType.UNKNOWN;
int offset = 0; int offset = 0;
String prodosName = name; String prodosName = name;
if (parts.length > 1) { if (name.matches("^.*?#[0-9A-Fa-f]{6}$")) {
String extension = parts[parts.length - 1].toUpperCase(); int type = Integer.parseInt(name.substring(name.length() - 6, name.length() - 4), 16);
String[] extParts = extension.split("\\#"); offset = Integer.parseInt(name.substring(name.length() - 4), 16);
if (extParts.length == 2) { t = FileType.findByCode(type);
offset = Integer.parseInt(extParts[1], 16); prodosName = name.substring(0, name.length()-7).replaceAll("[^A-Za-z0-9#]", ".").toUpperCase();
extension = extParts[0]; } else {
} String[] parts = name.replaceAll("[^A-Za-z0-9#]", ".").split("\\.");
try { if (parts.length > 1) {
t = FileType.valueOf(extension); String extension = parts[parts.length - 1].toUpperCase();
} catch (IllegalArgumentException ex) { String[] extParts = extension.split("\\#");
System.out.println("Not sure what extension " + extension + " is!"); if (extParts.length == 2) {
} offset = Integer.parseInt(extParts[1], 16);
prodosName = ""; extension = extParts[0];
for (int i = 0; i < parts.length - 1; i++) { }
prodosName += (i > 0 ? "." + parts[i] : parts[i]); try {
} t = FileType.valueOf(extension);
if (extParts[extParts.length - 1].equals("SYSTEM")) { } catch (IllegalArgumentException ex) {
prodosName += ".SYSTEM"; System.out.println("Not sure what extension " + extension + " is!");
}
prodosName = "";
for (int i = 0; i < parts.length - 1; i++) {
prodosName += (i > 0 ? "." + parts[i] : parts[i]);
}
if (extParts[extParts.length - 1].equals("SYSTEM")) {
prodosName += ".SYSTEM";
}
} }
} }
if (offset == 0) { if (offset == 0) {
@ -128,6 +148,7 @@ public class FileNode extends DiskNode {
super(ownerFilesystem); super(ownerFilesystem);
setPhysicalFile(file); setPhysicalFile(file);
setName(file.getName()); setName(file.getName());
allocate();
} }
@Override @Override
@ -167,7 +188,7 @@ public class FileNode extends DiskNode {
readFile(buffer, (block - 1)); readFile(buffer, (block - 1));
} else { } else {
// Generate seedling index block // Generate seedling index block
generateIndex(buffer, 1, dataBlocks+1); generateIndex(buffer, 1, dataBlocks + 1);
} }
break; break;
case TREE: case TREE:
@ -192,7 +213,6 @@ public class FileNode extends DiskNode {
} }
private void generateIndex(byte[] buffer, int indexStart, int indexLimit) { private void generateIndex(byte[] buffer, int indexStart, int indexLimit) {
Arrays.fill(buffer, (byte) 0);
for (int i = indexStart, count = 0; count < 256 && i < indexLimit && i <= additionalNodes.size(); i++, count++) { for (int i = indexStart, count = 0; count < 256 && i < indexLimit && i <= additionalNodes.size(); i++, count++) {
int base = getNodeSequence(i).getBaseBlock(); int base = getNodeSequence(i).getBaseBlock();
buffer[count] = (byte) (base & 0x0ff); buffer[count] = (byte) (base & 0x0ff);

View File

@ -22,25 +22,28 @@ import java.io.IOException;
/** /**
* Maintain freespace and node allocation * Maintain freespace and node allocation
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com *
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/ */
public class FreespaceBitmap extends DiskNode { public class FreespaceBitmap extends DiskNode {
int size = (ProdosVirtualDisk.MAX_BLOCK + 1) / 8 / ProdosVirtualDisk.BLOCK_SIZE; int size = (ProdosVirtualDisk.MAX_BLOCK + 1) / 8 / ProdosVirtualDisk.BLOCK_SIZE;
public FreespaceBitmap(ProdosVirtualDisk fs, int start) throws IOException { public FreespaceBitmap(ProdosVirtualDisk fs, int start) throws IOException {
super(fs, start); super(fs, start);
allocate();
for (int i=1; i < size; i++) {
SubNode subNode = new SubNode(i, this, start+i);
}
} }
@Override @Override
public void doDeallocate() { public void doDeallocate() {
// //
} }
@Override @Override
public void doAllocate() { public void doAllocate() throws IOException {
/// for (int i = 1; i < size; i++) {
SubNode subNode = new SubNode(i, this, getBaseBlock());
}
} }
@Override @Override
@ -51,22 +54,18 @@ public class FreespaceBitmap extends DiskNode {
@Override @Override
public void readBlock(int sequence, byte[] buffer) throws IOException { public void readBlock(int sequence, byte[] buffer) throws IOException {
int startBlock = sequence * ProdosVirtualDisk.BLOCK_SIZE * 8; int startBlock = sequence * ProdosVirtualDisk.BLOCK_SIZE * 8;
int endBlock = (sequence+1)* ProdosVirtualDisk.BLOCK_SIZE * 8; int endBlock = (sequence + 1) * ProdosVirtualDisk.BLOCK_SIZE * 8;
int bitCounter=0; for (int i = startBlock; i < endBlock; i++) {
int pos=0; if (!getOwnerFilesystem().isBlockAllocated(i)) {
int value=0; int pos = (i - startBlock) / 8;
for (int i=startBlock; i < endBlock; i++) { int bit = 1 << (i % 8);
if (!getOwnerFilesystem().isAllocated(i)) { buffer[pos] |= bit;
value++;
}
bitCounter++;
if (bitCounter < 8) {
value *= 2;
} else {
bitCounter = 0;
buffer[pos++]=(byte) value;
value = 0;
} }
} }
} }
}
@Override
public int getLength() {
return (1 + getChildren().size()) * IDisk.BLOCK_SIZE;
}
}

View File

@ -28,7 +28,7 @@ import java.io.IOException;
*/ */
public interface IDisk { public interface IDisk {
public static int BLOCK_SIZE = 512; public static int BLOCK_SIZE = 512;
public static int MAX_BLOCK = 65535; public static int MAX_BLOCK = 0x07fff;
public void mliFormat() throws IOException; public void mliFormat() throws IOException;
public void mliRead(int block, int bufferAddress, RAM memory) throws IOException; public void mliRead(int block, int bufferAddress, RAM memory) throws IOException;

View File

@ -18,6 +18,7 @@
*/ */
package jace.hardware.massStorage; package jace.hardware.massStorage;
import jace.Emulator;
import jace.EmulatorUILogic; import jace.EmulatorUILogic;
import jace.apple2e.MOS65C02; import jace.apple2e.MOS65C02;
import jace.core.Computer; import jace.core.Computer;
@ -27,8 +28,6 @@ import jace.hardware.ProdosDriver.MLI_COMMAND_TYPE;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -42,16 +41,17 @@ import java.util.logging.Logger;
*/ */
public class ProdosVirtualDisk implements IDisk { public class ProdosVirtualDisk implements IDisk {
public static int VOLUME_START = 2; public static final int VOLUME_START = 2;
public static int FREESPACE_BITMAP_START = 6; public static final int FREESPACE_BITMAP_START = 6;
byte[] ioBuffer; byte[] ioBuffer;
File physicalRoot; File physicalRoot;
Map<Integer, DiskNode> physicalMap; private final DiskNode[] physicalMap;
DirectoryNode rootDirectory; DirectoryNode rootDirectory;
FreespaceBitmap freespaceBitmap; FreespaceBitmap freespaceBitmap;
public ProdosVirtualDisk(File rootPath) throws IOException { public ProdosVirtualDisk(File rootPath) throws IOException {
ioBuffer = new byte[BLOCK_SIZE]; ioBuffer = new byte[BLOCK_SIZE];
physicalMap = new DiskNode[MAX_BLOCK];
initDiskStructure(); initDiskStructure();
setPhysicalPath(rootPath); setPhysicalPath(rootPath);
} }
@ -59,29 +59,28 @@ public class ProdosVirtualDisk implements IDisk {
@Override @Override
public void mliRead(int block, int bufferAddress, RAM memory) throws IOException { public void mliRead(int block, int bufferAddress, RAM memory) throws IOException {
// System.out.println("Read block " + block + " to " + Integer.toHexString(bufferAddress)); // System.out.println("Read block " + block + " to " + Integer.toHexString(bufferAddress));
DiskNode node = physicalMap.get(block); DiskNode node = physicalMap[block];
Arrays.fill(ioBuffer, (byte) (block & 0x0ff)); Arrays.fill(ioBuffer, (byte) 0);
if (node == null) { if (node == null) {
System.out.println("Reading unknown block " + Integer.toHexString(block)); System.out.println("Unknown block " + Integer.toHexString(block));
for (int i = 0; i < BLOCK_SIZE; i++) {
memory.write(bufferAddress + i, (byte) 0, false, false);
}
} else { } else {
// if (node.getPhysicalFile() == null) {
// System.out.println("reading block "+block+ " from directory structure to "+Integer.toHexString(bufferAddress));
// } else {
// System.out.println("reading block "+block+ " from "+node.getPhysicalFile().getName()+" to "+Integer.toHexString(bufferAddress));
// }
node.readBlock(ioBuffer); node.readBlock(ioBuffer);
for (int i = 0; i < BLOCK_SIZE; i++) {
memory.write(bufferAddress + i, ioBuffer[i], false, false);
}
} }
// for (int i=0; i < 512; i++) { for (int i = 0; i < ioBuffer.length; i++) {
// if (i % 32 == 0 && i > 0) System.out.println(); memory.write(bufferAddress + i, ioBuffer[i], false, false);
// System.out.print(((ioBuffer[i]&0x0ff)<16 ? "0" : "") + Integer.toHexString(ioBuffer[i] & 0x0ff) + " "); }
// System.out.println("Block " + Integer.toHexString(block));
// for (int i = 0; i < 32; i++) {
// String hex = "";
// String text = "";
// for (int j = 0; j < 16; j++) {
// int val = 0x0ff & memory.readRaw(bufferAddress + i * 16 + j);
// char show = (char) (((val & 0x7f) < ' ') ? '.' : val & 0x7f);
// hex += (val < 16 ? "0" : "") + Integer.toString(val, 16) + " ";
// text += show;
// }
// System.out.println(hex + " " + text);
// } // }
// System.out.println();
} }
@Override @Override
@ -121,33 +120,51 @@ public class ProdosVirtualDisk implements IDisk {
return mostLikelyMatch; return mostLikelyMatch;
} }
public int getNextFreeBlock() throws IOException { public int getNextFreeBlock(int start) throws IOException {
// Don't allocate Zero block for anything! // Don't allocate Zero block for anything!
// for (int i = 0; i < MAX_BLOCK; i++) { // for (int i = 0; i < MAX_BLOCK; i++) {
for (int i = 2; i < MAX_BLOCK; i++) { for (int i = start; i < MAX_BLOCK; i++) {
if (!physicalMap.containsKey(i)) { if (physicalMap[i] == null) {
return i; return i;
} }
} }
throw new IOException("Virtual Disk Full!"); throw new IOException("Virtual Disk Full!");
} }
public int allocateEntry(DiskNode node) throws IOException {
return allocateEntryNear(node, FREESPACE_BITMAP_START);
}
public int allocateEntryNear(DiskNode node, int start) throws IOException {
if (isNodeAllocated(node)) {
return node.getBaseBlock();
}
int block = getNextFreeBlock(start);
node.setBaseBlock(block);
physicalMap[block] = node;
return block;
}
public boolean isNodeAllocated(DiskNode node) {
return node.getBaseBlock() >= 0 && physicalMap[node.getBaseBlock()] == node;
}
// Mark space occupied by nodes as free (remove allocation mapping) // Mark space occupied by nodes as free (remove allocation mapping)
public void deallocateEntry(DiskNode node) { public void deallocateEntry(DiskNode node) {
// Only de-map nodes if the allocation table is actually pointing to the nodes! // Only de-map nodes if the allocation table is actually pointing to the nodes!
if (physicalMap.get(node.getBaseBlock()) != null && physicalMap.get(node.getBaseBlock()).equals(node)) { if (physicalMap[node.getBaseBlock()] != null && physicalMap[node.getBaseBlock()].equals(node)) {
physicalMap.remove(node.getBaseBlock()); physicalMap[node.getBaseBlock()] = null;
} }
node.additionalNodes.stream().filter((sub) node.additionalNodes.stream().filter((sub)
-> (physicalMap.get(sub.getBaseBlock()) != null && physicalMap.get(sub.getBaseBlock()).equals(sub))). -> (physicalMap[sub.getBaseBlock()] != null && physicalMap[sub.getBaseBlock()].equals(sub))).
forEach((sub) -> { forEach((sub) -> {
physicalMap.remove(sub.getBaseBlock()); physicalMap[sub.getBaseBlock()] = null;
}); });
} }
// Is the specified block in use? // Is the specified block in use?
public boolean isAllocated(int i) { public boolean isBlockAllocated(int i) {
return (physicalMap.containsKey(i)); return (i >= physicalMap.length || physicalMap[i] != null);
} }
@Override @Override
@ -174,10 +191,9 @@ public class ProdosVirtualDisk implements IDisk {
} }
private void initDiskStructure() throws IOException { private void initDiskStructure() throws IOException {
physicalMap = new HashMap<>();
freespaceBitmap = new FreespaceBitmap(this, FREESPACE_BITMAP_START); freespaceBitmap = new FreespaceBitmap(this, FREESPACE_BITMAP_START);
} }
private void setPhysicalPath(File f) throws IOException { private void setPhysicalPath(File f) throws IOException {
if (physicalRoot != null && physicalRoot.equals(f)) { if (physicalRoot != null && physicalRoot.equals(f)) {
return; return;
@ -193,7 +209,6 @@ public class ProdosVirtualDisk implements IDisk {
// Root directory ALWAYS starts on block 2! // Root directory ALWAYS starts on block 2!
rootDirectory = new DirectoryNode(this, physicalRoot, VOLUME_START, true); rootDirectory = new DirectoryNode(this, physicalRoot, VOLUME_START, true);
rootDirectory.setName("VIRTUAL"); rootDirectory.setName("VIRTUAL");
rootDirectory.allocate();
} }
@Override @Override
@ -208,6 +223,6 @@ public class ProdosVirtualDisk implements IDisk {
@Override @Override
public int getSize() { public int getSize() {
return 0x0ffff; return MAX_BLOCK;
} }
} }

View File

@ -69,4 +69,9 @@ public class SubNode extends DiskNode {
public void readBlock(int sequence, byte[] buffer) throws IOException { public void readBlock(int sequence, byte[] buffer) throws IOException {
parent.readBlock(sequenceNumber, buffer); parent.readBlock(sequenceNumber, buffer);
} }
@Override
public int getLength() {
return IDisk.BLOCK_SIZE;
}
} }