diff --git a/src/main/java/jace/hardware/ProdosDriver.java b/src/main/java/jace/hardware/ProdosDriver.java index 8c9e856..b381e76 100644 --- a/src/main/java/jace/hardware/ProdosDriver.java +++ b/src/main/java/jace/hardware/ProdosDriver.java @@ -80,7 +80,7 @@ public abstract class ProdosDriver { MOS65C02 cpu = (MOS65C02) computer.getCpu(); cpu.A = returnCode; // 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() { diff --git a/src/main/java/jace/hardware/massStorage/DirectoryNode.java b/src/main/java/jace/hardware/massStorage/DirectoryNode.java index 73ab874..d2b7487 100644 --- a/src/main/java/jace/hardware/massStorage/DirectoryNode.java +++ b/src/main/java/jace/hardware/massStorage/DirectoryNode.java @@ -18,14 +18,17 @@ */ package jace.hardware.massStorage; +import static jace.hardware.massStorage.IDisk.BLOCK_SIZE; import java.io.File; import java.io.FileFilter; import java.io.IOException; -import java.util.Arrays; +import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; +import java.util.List; +import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; @@ -35,10 +38,14 @@ import java.util.logging.Logger; * @author Brendan Robert (BLuRry) brendan.robert@gmail.com */ 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 List directoryEntries; public DirectoryNode(ProdosVirtualDisk ownerFilesystem, File physicalDir, int baseBlock, boolean root) throws IOException { 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 { isRoot = root; + directoryEntries = new ArrayList<>(); setPhysicalFile(physicalFile); setType(EntryType.SUBDIRECTORY); setName(physicalFile.getName()); + allocate(); } @Override @@ -63,15 +72,15 @@ public class DirectoryNode extends DiskNode implements FileFilter { @Override public void doAllocate() throws IOException { - File[] files = physicalFile.listFiles(this); - int numEntries = files.length; - // First block has 12 entries, subsequent blocks have 13 entries - int numBlocks = 1 + numEntries / 13; - for (int i=1; i < numBlocks; i++) { - new SubNode(i, this, getOwnerFilesystem().getNextFreeBlock()); + for (int i = 1; i < getBlockCount(); i++) { + if (isRoot) { + new SubNode(i, this, getOwnerFilesystem().getNextFreeBlock(3)); + } else { + new SubNode(i, this); + } } - - for (File f : files) { + + for (File f : physicalFile.listFiles()) { addFile(f); } 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 { boolean success = true; - if (!super.checkFile()) { - return false; - } - HashSet realFiles = new HashSet<>(); - File[] realFileList = physicalFile.listFiles(this); - for (File f : realFileList) { - realFiles.add(f.getName()); - } - for (Iterator 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; + if (!allocated) { + allocate(); + } else { + try { + if (!super.checkFile()) { + return false; } + HashSet realFiles = new HashSet<>(); + File[] realFileList = physicalFile.listFiles(this); + for (File f : realFileList) { + realFiles.add(f.getName()); + } + for (Iterator 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; } @Override public void readBlock(int block, byte[] buffer) throws IOException { - Arrays.fill(buffer, (byte) 0); 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) { generateHeader(buffer); - for (int i = 0; i < 12 && i < children.size(); i++) { - generateFileEntry(buffer, 4 + (i + 1) * FILE_ENTRY_SIZE, i); - } + offset += FILE_ENTRY_SIZE; + end = ENTRIES_PER_BLOCK - 1; } else { - generatePointers(buffer, getNodeSequence(block-1).getBaseBlock(), getNodeSequence(block+1) != null ? getNodeSequence(block+1).getBaseBlock() : 0); - int start = (block * 13) - 1; - int end = start + 13; - int offset = 4; - - for (int i = start; i < end && i < children.size(); i++) { - // TODO: Add any parts that are not file entries. - generateFileEntry(buffer, offset, i); - offset += FILE_ENTRY_SIZE; - } + start = (block * ENTRIES_PER_BLOCK) - 1; + end = start + ENTRIES_PER_BLOCK; + } + for (int i = start; i < end && i < directoryEntries.size(); i++, offset += FILE_ENTRY_SIZE) { + // TODO: Add any parts that are not file entries. +// System.out.println("Entry "+i+": "+children.get(i).getName()+"; offset "+offset); + generateFileEntry(buffer, offset, i); } } @@ -155,13 +172,16 @@ public class DirectoryNode extends DiskNode implements FileFilter { return !file.isHidden(); } - private void generatePointers(byte[] buffer, int prevBlock, int nextBlock) { - // Previous block = 0 - generateWord(buffer, 0, prevBlock); - // Next block - generateWord(buffer, 0x02, nextBlock); + private void generatePointers(byte[] buffer, int sequence) { + DiskNode prev = getNodeSequence(sequence - 1); + DiskNode next = getNodeSequence(sequence + 1); +// System.out.println("Sequence "+sequence+" prev="+(prev != null ? prev.getBaseBlock() : 0)+"; next="+(next != null ? next.getBaseBlock() : 0)); + // 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 * @@ -169,40 +189,43 @@ public class DirectoryNode extends DiskNode implements FileFilter { */ @SuppressWarnings("static-access") private void generateHeader(byte[] buffer) { - generatePointers(buffer, 0, getNodeSequence(1) == null ? 0 : getNodeSequence(1).getBaseBlock()); // Directory header + name length // Volumme header = 0x0f0; Subdirectory header = 0x0e0 buffer[4] = (byte) ((isRoot ? 0x0F0 : 0x0E0) | getName().length()); generateName(buffer, 5, this); - for (int i = 0x014; i <= 0x01b; i++) { - buffer[i] = 0; - } if (!isRoot) { 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()); - // Prodos 1.9 - buffer[0x020] = 0x019; + // Prodos 1.0 = 0 + buffer[0x020] = PRODOS_VERSION; // Minimum version = 0 (no min) buffer[0x021] = 0x000; // Directory may be read/written to, may not be destroyed or renamed - buffer[0x022] = 0x03; + buffer[0x022] = STANDARD_PERMISSIONS; // Entry size buffer[0x023] = (byte) FILE_ENTRY_SIZE; // Entries per block - buffer[0x024] = (byte) 0x0d; + buffer[0x024] = (byte) ENTRIES_PER_BLOCK; // Directory items count - generateWord(buffer, 0x025, children.size()); + generateWord(buffer, 0x025, directoryEntries.size()+1); if (isRoot) { // Volume bitmap pointer generateWord(buffer, 0x027, ownerFilesystem.freespaceBitmap.getBaseBlock()); // Total number of blocks generateWord(buffer, 0x029, ownerFilesystem.MAX_BLOCK); } 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 - generateWord(buffer, 0x027, getParent().getBaseBlock()); - buffer[0x029] = (byte) (getParent().getChildren().indexOf(this) + 1); - buffer[0x02a] = 0x027; + generateWord(buffer, 0x027, parentBlock); + buffer[0x029] = (byte) (indexInParent % ENTRIES_PER_BLOCK); + 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 */ 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 - buffer[offset] = (byte) ((child.getType().code << 4) + child.getName().length()); + buffer[offset] = (byte) ((child.getType().code << 4) | child.getName().length()); // Name generateName(buffer, offset + 1, child); // File type @@ -224,20 +247,20 @@ public class DirectoryNode extends DiskNode implements FileFilter { // Key pointer generateWord(buffer, offset + 0x011, child.getBaseBlock()); // Blocks used -- will report only one unless file is actually allocated - generateWord(buffer, offset + 0x013, child.additionalNodes.size()); - // EOF - // TODO: Verify this is the right thing to do -- is EOF total length or a modulo? - int length = ((int) child.physicalFile.length()) & 0x0ffffff; + generateWord(buffer, offset + 0x013, child.additionalNodes.size() + 1); + // EOF (file size or directory structure size + int length = child.getLength(); + length &= 0x0ffffff; generateWord(buffer, offset + 0x015, length & 0x0ffff); buffer[offset + 0x017] = (byte) ((length >> 16) & 0x0ff); // Creation date generateTimestamp(buffer, offset + 0x018, child.physicalFile.lastModified()); - // Version = 1.9 - buffer[offset + 0x01c] = 0x19; + // Version = 1.0 + buffer[offset + 0x01c] = PRODOS_VERSION; // Minimum version = 0 buffer[offset + 0x01d] = 0; // Access = Read-only - buffer[offset + 0x01e] = (byte) 0x001; + buffer[offset + 0x01e] = STANDARD_PERMISSIONS; // AUX type if (child instanceof FileNode) { 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) { - 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); } } - + + private Optional findChildByFilename(String name) { + return directoryEntries.stream().filter((child) -> child.getPhysicalFile().getName().equals(name)).findFirst(); + } + private void addFile(File file) { - try { - if (file.isDirectory()) { - addChild(new DirectoryNode(getOwnerFilesystem(), file, false)); - } else { - addChild(new FileNode(getOwnerFilesystem(), file)); + if (!hasChildNamed(file.getName())) { + try { + if (file.isDirectory()) { + addFileEntry(new DirectoryNode(getOwnerFilesystem(), file, false)); + } 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); + } } diff --git a/src/main/java/jace/hardware/massStorage/DiskNode.java b/src/main/java/jace/hardware/massStorage/DiskNode.java index 6257280..b5fa20c 100644 --- a/src/main/java/jace/hardware/massStorage/DiskNode.java +++ b/src/main/java/jace/hardware/massStorage/DiskNode.java @@ -22,6 +22,7 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Optional; /** * 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 { init(fs); - setBaseBlock(fs.getNextFreeBlock()); + fs.allocateEntry(this); } public DiskNode(ProdosVirtualDisk fs, int blockNumber) throws IOException { init(fs); - setBaseBlock(blockNumber); + fs.allocateEntryNear(this, blockNumber); } private void init(ProdosVirtualDisk fs) throws IOException { @@ -89,7 +90,6 @@ public abstract class DiskNode { public void allocate() throws IOException { if (!allocated) { doAllocate(); - getOwnerFilesystem().physicalMap.put(baseBlock, this); allocationTime = System.currentTimeMillis(); allocated = true; } @@ -118,7 +118,7 @@ public abstract class DiskNode { public DiskNode getNodeSequence(int num) { if (num == 0) { return this; - } else if (num <= additionalNodes.size()) { + } else if (num > 0 && num <= additionalNodes.size()) { return additionalNodes.get(num-1); } else { return null; @@ -156,9 +156,8 @@ public abstract class DiskNode { /** * @param baseBlock the baseBlock to set */ - private void setBaseBlock(int baseBlock) { + public void setBaseBlock(int baseBlock) { this.baseBlock = baseBlock; - ownerFilesystem.physicalMap.put(baseBlock, this); } /** @@ -189,6 +188,7 @@ public abstract class DiskNode { public void setPhysicalFile(File physicalFile) { this.physicalFile = physicalFile; setName(physicalFile.getName()); + lastCheckTime = physicalFile.lastModified(); } /** @@ -227,6 +227,14 @@ public abstract class DiskNode { public void removeChild(DiskNode child) { children.remove(child); } + + public boolean hasChildNamed(String name) { + return findChildByFilename(name).isPresent(); + } + + private Optional findChildByFilename(String name) { + return getChildren().stream().filter((child) -> child.getPhysicalFile().getName().equals(name)).findFirst(); + } /** * @return the type @@ -253,10 +261,7 @@ public abstract class DiskNode { * @param name the name to set */ public void setName(String name) { - if (name.length() > 15) { - name = name.substring(0, 15); - } - this.name = name.toUpperCase(); + this.name = (name.length() > 15 ? name.substring(0, 15) : name).toUpperCase(); } public abstract void doDeallocate(); @@ -266,6 +271,8 @@ public abstract class DiskNode { public abstract void doRefresh(); public abstract void readBlock(int sequence, byte[] buffer) throws IOException; + + public abstract int getLength(); public void readBlock(byte[] buffer) throws IOException { checkFile(); diff --git a/src/main/java/jace/hardware/massStorage/FileNode.java b/src/main/java/jace/hardware/massStorage/FileNode.java index 6b36c44..ae425b6 100644 --- a/src/main/java/jace/hardware/massStorage/FileNode.java +++ b/src/main/java/jace/hardware/massStorage/FileNode.java @@ -21,7 +21,6 @@ package jace.hardware.massStorage; import java.io.File; import java.io.FileInputStream; import java.io.IOException; -import java.util.Arrays; /** * 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 { + @Override + public int getLength() { + return (int) getPhysicalFile().length(); + } + public enum FileType { UNKNOWN(0x00, 0x0000), @@ -67,6 +71,15 @@ public class FileNode extends DiskNode { this.code = code; 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 loadAddress = 0x00; @@ -90,28 +103,35 @@ public class FileNode extends DiskNode { @Override public void setName(String name) { - String[] parts = name.replaceAll("[^A-Za-z0-9#]", ".").split("\\."); FileType t = FileType.UNKNOWN; int offset = 0; String prodosName = name; - if (parts.length > 1) { - String extension = parts[parts.length - 1].toUpperCase(); - String[] extParts = extension.split("\\#"); - if (extParts.length == 2) { - offset = Integer.parseInt(extParts[1], 16); - extension = extParts[0]; - } - try { - t = FileType.valueOf(extension); - } catch (IllegalArgumentException ex) { - 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 (name.matches("^.*?#[0-9A-Fa-f]{6}$")) { + int type = Integer.parseInt(name.substring(name.length() - 6, name.length() - 4), 16); + offset = Integer.parseInt(name.substring(name.length() - 4), 16); + t = FileType.findByCode(type); + prodosName = name.substring(0, name.length()-7).replaceAll("[^A-Za-z0-9#]", ".").toUpperCase(); + } else { + String[] parts = name.replaceAll("[^A-Za-z0-9#]", ".").split("\\."); + if (parts.length > 1) { + String extension = parts[parts.length - 1].toUpperCase(); + String[] extParts = extension.split("\\#"); + if (extParts.length == 2) { + offset = Integer.parseInt(extParts[1], 16); + extension = extParts[0]; + } + try { + t = FileType.valueOf(extension); + } catch (IllegalArgumentException ex) { + 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) { @@ -128,6 +148,7 @@ public class FileNode extends DiskNode { super(ownerFilesystem); setPhysicalFile(file); setName(file.getName()); + allocate(); } @Override @@ -167,7 +188,7 @@ public class FileNode extends DiskNode { readFile(buffer, (block - 1)); } else { // Generate seedling index block - generateIndex(buffer, 1, dataBlocks+1); + generateIndex(buffer, 1, dataBlocks + 1); } break; case TREE: @@ -192,7 +213,6 @@ public class FileNode extends DiskNode { } 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++) { int base = getNodeSequence(i).getBaseBlock(); buffer[count] = (byte) (base & 0x0ff); diff --git a/src/main/java/jace/hardware/massStorage/FreespaceBitmap.java b/src/main/java/jace/hardware/massStorage/FreespaceBitmap.java index e09cf4c..7a1e458 100644 --- a/src/main/java/jace/hardware/massStorage/FreespaceBitmap.java +++ b/src/main/java/jace/hardware/massStorage/FreespaceBitmap.java @@ -22,25 +22,28 @@ import java.io.IOException; /** * 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 { + int size = (ProdosVirtualDisk.MAX_BLOCK + 1) / 8 / ProdosVirtualDisk.BLOCK_SIZE; + public FreespaceBitmap(ProdosVirtualDisk fs, int start) throws IOException { super(fs, start); - - for (int i=1; i < size; i++) { - SubNode subNode = new SubNode(i, this, start+i); - } + allocate(); } + @Override public void doDeallocate() { // } @Override - public void doAllocate() { -/// + public void doAllocate() throws IOException { + for (int i = 1; i < size; i++) { + SubNode subNode = new SubNode(i, this, getBaseBlock()); + } } @Override @@ -51,22 +54,18 @@ public class FreespaceBitmap extends DiskNode { @Override public void readBlock(int sequence, byte[] buffer) throws IOException { int startBlock = sequence * ProdosVirtualDisk.BLOCK_SIZE * 8; - int endBlock = (sequence+1)* ProdosVirtualDisk.BLOCK_SIZE * 8; - int bitCounter=0; - int pos=0; - int value=0; - for (int i=startBlock; i < endBlock; i++) { - if (!getOwnerFilesystem().isAllocated(i)) { - value++; - } - bitCounter++; - if (bitCounter < 8) { - value *= 2; - } else { - bitCounter = 0; - buffer[pos++]=(byte) value; - value = 0; + int endBlock = (sequence + 1) * ProdosVirtualDisk.BLOCK_SIZE * 8; + for (int i = startBlock; i < endBlock; i++) { + if (!getOwnerFilesystem().isBlockAllocated(i)) { + int pos = (i - startBlock) / 8; + int bit = 1 << (i % 8); + buffer[pos] |= bit; } } } -} \ No newline at end of file + + @Override + public int getLength() { + return (1 + getChildren().size()) * IDisk.BLOCK_SIZE; + } +} diff --git a/src/main/java/jace/hardware/massStorage/IDisk.java b/src/main/java/jace/hardware/massStorage/IDisk.java index fecff4c..d24a73e 100644 --- a/src/main/java/jace/hardware/massStorage/IDisk.java +++ b/src/main/java/jace/hardware/massStorage/IDisk.java @@ -28,7 +28,7 @@ import java.io.IOException; */ public interface IDisk { 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 mliRead(int block, int bufferAddress, RAM memory) throws IOException; diff --git a/src/main/java/jace/hardware/massStorage/ProdosVirtualDisk.java b/src/main/java/jace/hardware/massStorage/ProdosVirtualDisk.java index 34cddb9..1c469a7 100644 --- a/src/main/java/jace/hardware/massStorage/ProdosVirtualDisk.java +++ b/src/main/java/jace/hardware/massStorage/ProdosVirtualDisk.java @@ -18,6 +18,7 @@ */ package jace.hardware.massStorage; +import jace.Emulator; import jace.EmulatorUILogic; import jace.apple2e.MOS65C02; import jace.core.Computer; @@ -27,8 +28,6 @@ import jace.hardware.ProdosDriver.MLI_COMMAND_TYPE; import java.io.File; import java.io.IOException; import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -42,16 +41,17 @@ import java.util.logging.Logger; */ public class ProdosVirtualDisk implements IDisk { - public static int VOLUME_START = 2; - public static int FREESPACE_BITMAP_START = 6; + public static final int VOLUME_START = 2; + public static final int FREESPACE_BITMAP_START = 6; byte[] ioBuffer; File physicalRoot; - Map physicalMap; + private final DiskNode[] physicalMap; DirectoryNode rootDirectory; FreespaceBitmap freespaceBitmap; public ProdosVirtualDisk(File rootPath) throws IOException { ioBuffer = new byte[BLOCK_SIZE]; + physicalMap = new DiskNode[MAX_BLOCK]; initDiskStructure(); setPhysicalPath(rootPath); } @@ -59,29 +59,28 @@ public class ProdosVirtualDisk implements IDisk { @Override public void mliRead(int block, int bufferAddress, RAM memory) throws IOException { // System.out.println("Read block " + block + " to " + Integer.toHexString(bufferAddress)); - DiskNode node = physicalMap.get(block); - Arrays.fill(ioBuffer, (byte) (block & 0x0ff)); + DiskNode node = physicalMap[block]; + Arrays.fill(ioBuffer, (byte) 0); if (node == null) { - System.out.println("Reading unknown block " + Integer.toHexString(block)); - for (int i = 0; i < BLOCK_SIZE; i++) { - memory.write(bufferAddress + i, (byte) 0, false, false); - } + System.out.println("Unknown block " + Integer.toHexString(block)); } 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); - for (int i = 0; i < BLOCK_SIZE; i++) { - memory.write(bufferAddress + i, ioBuffer[i], false, false); - } } -// for (int i=0; i < 512; i++) { -// if (i % 32 == 0 && i > 0) System.out.println(); -// System.out.print(((ioBuffer[i]&0x0ff)<16 ? "0" : "") + Integer.toHexString(ioBuffer[i] & 0x0ff) + " "); + for (int i = 0; i < ioBuffer.length; i++) { + memory.write(bufferAddress + i, ioBuffer[i], false, false); + } +// 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 @@ -121,33 +120,51 @@ public class ProdosVirtualDisk implements IDisk { return mostLikelyMatch; } - public int getNextFreeBlock() throws IOException { + public int getNextFreeBlock(int start) throws IOException { // Don't allocate Zero block for anything! // for (int i = 0; i < MAX_BLOCK; i++) { - for (int i = 2; i < MAX_BLOCK; i++) { - if (!physicalMap.containsKey(i)) { + for (int i = start; i < MAX_BLOCK; i++) { + if (physicalMap[i] == null) { return i; } } 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) public void deallocateEntry(DiskNode node) { // 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)) { - physicalMap.remove(node.getBaseBlock()); + if (physicalMap[node.getBaseBlock()] != null && physicalMap[node.getBaseBlock()].equals(node)) { + physicalMap[node.getBaseBlock()] = null; } 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) -> { - physicalMap.remove(sub.getBaseBlock()); + physicalMap[sub.getBaseBlock()] = null; }); } // Is the specified block in use? - public boolean isAllocated(int i) { - return (physicalMap.containsKey(i)); + public boolean isBlockAllocated(int i) { + return (i >= physicalMap.length || physicalMap[i] != null); } @Override @@ -174,10 +191,9 @@ public class ProdosVirtualDisk implements IDisk { } private void initDiskStructure() throws IOException { - physicalMap = new HashMap<>(); freespaceBitmap = new FreespaceBitmap(this, FREESPACE_BITMAP_START); } - + private void setPhysicalPath(File f) throws IOException { if (physicalRoot != null && physicalRoot.equals(f)) { return; @@ -193,7 +209,6 @@ public class ProdosVirtualDisk implements IDisk { // Root directory ALWAYS starts on block 2! rootDirectory = new DirectoryNode(this, physicalRoot, VOLUME_START, true); rootDirectory.setName("VIRTUAL"); - rootDirectory.allocate(); } @Override @@ -208,6 +223,6 @@ public class ProdosVirtualDisk implements IDisk { @Override public int getSize() { - return 0x0ffff; + return MAX_BLOCK; } } diff --git a/src/main/java/jace/hardware/massStorage/SubNode.java b/src/main/java/jace/hardware/massStorage/SubNode.java index d832d8b..b222531 100644 --- a/src/main/java/jace/hardware/massStorage/SubNode.java +++ b/src/main/java/jace/hardware/massStorage/SubNode.java @@ -69,4 +69,9 @@ public class SubNode extends DiskNode { public void readBlock(int sequence, byte[] buffer) throws IOException { parent.readBlock(sequenceNumber, buffer); } + + @Override + public int getLength() { + return IDisk.BLOCK_SIZE; + } }