mirror of
https://github.com/badvision/jace.git
synced 2024-11-24 15:30:51 +00:00
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:
parent
5f9352abb3
commit
732f4768a6
@ -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() {
|
||||
|
@ -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<DiskNode> 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<String> realFiles = new HashSet<>();
|
||||
File[] realFileList = physicalFile.listFiles(this);
|
||||
for (File f : realFileList) {
|
||||
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;
|
||||
if (!allocated) {
|
||||
allocate();
|
||||
} else {
|
||||
try {
|
||||
if (!super.checkFile()) {
|
||||
return 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;
|
||||
}
|
||||
|
||||
@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<DiskNode> 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);
|
||||
}
|
||||
}
|
||||
|
@ -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<DiskNode> 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();
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength() {
|
||||
return (1 + getChildren().size()) * IDisk.BLOCK_SIZE;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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<Integer, DiskNode> 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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user