From b5147523f7bc41d5cda4f89e6a7d8f87ea48735f Mon Sep 17 00:00:00 2001 From: Robert Greene Date: Mon, 22 Dec 2003 17:24:57 +0000 Subject: [PATCH] Improved support for CP/M disk images. Can now view files. There are a few bugs remaining, however. --- .../storage/cpm/CpmFileEntry.java | 263 ++++++++++++++--- .../storage/cpm/CpmFormatDisk.java | 270 +++++++++++++++--- 2 files changed, 454 insertions(+), 79 deletions(-) diff --git a/src/com/webcodepro/applecommander/storage/cpm/CpmFileEntry.java b/src/com/webcodepro/applecommander/storage/cpm/CpmFileEntry.java index b54c5da..be5d130 100644 --- a/src/com/webcodepro/applecommander/storage/cpm/CpmFileEntry.java +++ b/src/com/webcodepro/applecommander/storage/cpm/CpmFileEntry.java @@ -1,9 +1,11 @@ package com.webcodepro.applecommander.storage.cpm; +import com.webcodepro.applecommander.storage.BinaryFileFilter; import com.webcodepro.applecommander.storage.DiskFullException; import com.webcodepro.applecommander.storage.FileEntry; import com.webcodepro.applecommander.storage.FileFilter; import com.webcodepro.applecommander.storage.FormattedDisk; +import com.webcodepro.applecommander.storage.TextFileFilter; import com.webcodepro.applecommander.util.AppleUtil; import java.text.NumberFormat; @@ -11,13 +13,79 @@ import java.util.ArrayList; import java.util.List; /** - * @author Rob + * Support the CP/M file entry. Note that this may actually contain references + * to multiple file entries via the extent counter. + *

+ * @author Rob Greene */ public class CpmFileEntry implements FileEntry { /** * The standard CP/M file entry length. */ public static final int ENTRY_LENGTH = 0x20; + /** + * The maximum number of extents per file entry record. + */ + public static final int MAX_EXTENTS_PER_ENTRY = 0x80; + /** + * The number of bytes used if all records in an extent are filled. + * (MAX_EXTENTS_PER_ENTRY * CPM_SECTOR_SIZE) + */ + public static final int ALL_RECORDS_FILLED_SIZE = 16384; + /** + * The user number (UU) field is to distinguish multiple files with the + * same filename. This appears to be primarily with deleted files? + */ + public static final int USER_NUMBER_OFFSET = 0; + /** + * Offset to beginning of the filename. + */ + public static final int FILENAME_OFFSET = 1; + /** + * Filename length (excluding extension). + */ + public static final int FILENAME_LENGTH = 8; + /** + * Offset to beginning of the filetype. + */ + public static final int FILETYPE_OFFSET = 9; + /** + * Filetype length. + */ + public static final int FILETYPE_LENGTH = 3; + /** + * Offset to the filetype "T1" entry. + * Indicates read-only. + */ + public static final int FILETYPE_T1_OFFSET = FILETYPE_OFFSET; + /** + * Offset to the filetype "T2" entry. + * Indicates system or hidden file. + */ + public static final int FILETYPE_T2_OFFSET = FILETYPE_OFFSET+1; + /** + * Offset to the filetype "T3" entry. + * Backup bit (CP/M 3.1 and later). + */ + public static final int FILETYPE_T3_OFFSET = FILETYPE_OFFSET+2; + /** + * Offset to the extent counter (EX) field. + */ + public static final int EXTENT_COUNTER_OFFSET = 0xc; + /** + * Offset to the record count (RC) field. + */ + public static final int RECORD_COUNT_OFFSET = 0xf; + /** + * Beginning of block allocations. + */ + public static final int ALLOCATION_OFFSET = 0x10; + /** + * A short collection of known text-type files. + */ + public static final String[] TEXT_FILETYPES = { + "TXT", "ASM", "MAC", "DOC", "PRN", "PAS", "ME", "INC", "HLP" + }; /** * Reference to the disk this FileEntry is attached to. */ @@ -46,29 +114,52 @@ public class CpmFileEntry implements FileEntry { * Read the fileEntry bytes from the disk image. */ protected byte[] readFileEntry(int number) { - byte[] data = new byte[2048]; - System.arraycopy(disk.readCpmBlock(0), 0, data, 0, 1024); - System.arraycopy(disk.readCpmBlock(1), 0, data, 1024, 1024); + byte[] data = new byte[2 * CpmFormatDisk.CPM_BLOCKSIZE]; + System.arraycopy(disk.readCpmBlock(0), 0, data, + 0, CpmFormatDisk.CPM_BLOCKSIZE); + System.arraycopy(disk.readCpmBlock(1), 0, data, + CpmFormatDisk.CPM_BLOCKSIZE, CpmFormatDisk.CPM_BLOCKSIZE); byte[] entry = new byte[ENTRY_LENGTH]; int offset = ((Integer)offsets.get(number)).intValue(); System.arraycopy(data, offset, entry, 0, ENTRY_LENGTH); return entry; } + + /** + * Write the fileEntry bytes back to the disk image. + */ + protected void writeFileEntry(int number, byte[] data) { + byte[] block = new byte[CpmFormatDisk.CPM_BLOCKSIZE]; + System.arraycopy(data, 0, block, + 0, CpmFormatDisk.CPM_BLOCKSIZE); + disk.writeCpmBlock(0, block); + System.arraycopy(data, 0, block, + CpmFormatDisk.CPM_BLOCKSIZE, CpmFormatDisk.CPM_BLOCKSIZE); + disk.writeCpmBlock(1, block); + } /** * Answer with the name of the file. * @see com.webcodepro.applecommander.storage.FileEntry#getFilename() */ public String getFilename() { - return AppleUtil.getString(readFileEntry(0), 1, 8).trim(); + return AppleUtil.getString(readFileEntry(0), + FILENAME_OFFSET, FILENAME_LENGTH).trim(); } /** + * Set the filename. Note that this assumes the file extension + * is completely separate and does not validate characters that + * are being set! * @see com.webcodepro.applecommander.storage.FileEntry#setFilename(java.lang.String) */ public void setFilename(String filename) { - // TODO Auto-generated method stub - + for (int i=0; i 127) ? 0x80 : 0x00; + data[FILETYPE_OFFSET+1] |= (T2 > 127) ? 0x80 : 0x00; + data[FILETYPE_OFFSET+1] |= (T3 > 127) ? 0x80 : 0x00; + } } /** @@ -100,7 +202,16 @@ public class CpmFileEntry implements FileEntry { * file type and the high bit indicates read-only. */ public byte getFileTypeT1(int entryNumber) { - return readFileEntry(entryNumber)[0x9]; + return readFileEntry(entryNumber)[FILETYPE_T1_OFFSET]; + } + + /** + * Write the file type T1 entry. + */ + public void setFileTypeT1(int entryNumber, int t1) { + byte[] data = readFileEntry(entryNumber); + data[FILETYPE_T1_OFFSET] = (byte) t1; + writeFileEntry(entryNumber, data); } /** @@ -108,7 +219,16 @@ public class CpmFileEntry implements FileEntry { * file type and the high bit indicates a system or hidden file. */ public byte getFileTypeT2(int entryNumber) { - return readFileEntry(entryNumber)[0xa]; + return readFileEntry(entryNumber)[FILETYPE_T2_OFFSET]; + } + + /** + * Write the file type T2 entry. + */ + public void setFileTypeT2(int entryNumber, int t2) { + byte[] data = readFileEntry(entryNumber); + data[FILETYPE_T2_OFFSET] = (byte) t2; + writeFileEntry(entryNumber, data); } /** @@ -116,22 +236,38 @@ public class CpmFileEntry implements FileEntry { * file type and the high bit is the backup bit (CP/M 3.1 and later). */ public byte getFileTypeT3(int entryNumber) { - return readFileEntry(entryNumber)[0xb]; + return readFileEntry(entryNumber)[FILETYPE_T3_OFFSET]; + } + + /** + * Write the file type T3 entry. + */ + public void setFileTypeT3(int entryNumber, int t3) { + byte[] data = readFileEntry(entryNumber); + data[FILETYPE_T3_OFFSET] = (byte) t3; + writeFileEntry(entryNumber, data); } /** + * Set the locked status. This is interpreted as read-only. * @see com.webcodepro.applecommander.storage.FileEntry#setLocked(boolean) */ public void setLocked(boolean lock) { - // TODO Auto-generated method stub - + for (int i=0; i currentExtent) entry = i; } } - // Compute file size: - return getExtentCounterLow(entry) * 16384 + - getNumberOfRecordsUsed(entry) * 128; + return entry; + } + + /** + * Compute the number of blocks used. + */ + public int getBlocksUsed() { + int entry = findLargestExtent(); + return getExtentCounterLow(entry) * ALL_RECORDS_FILLED_SIZE + + (getNumberOfRecordsUsed(entry) - 1) / + CpmFormatDisk.CPM_SECTORS_PER_CPM_BLOCK + 1; } /** @@ -160,14 +314,15 @@ public class CpmFileEntry implements FileEntry { * 1 record = 128 bytes. */ public int getNumberOfRecordsUsed(int entryNumber) { - return AppleUtil.getUnsignedByte(readFileEntry(entryNumber)[0xf]); + return AppleUtil.getUnsignedByte( + readFileEntry(entryNumber)[RECORD_COUNT_OFFSET]); } /** + * Apple CP/M does not support directories. * @see com.webcodepro.applecommander.storage.FileEntry#isDirectory() */ public boolean isDirectory() { - // TODO Auto-generated method stub return false; } @@ -186,15 +341,27 @@ public class CpmFileEntry implements FileEntry { * conjunction with deleted files. */ public int getUserNumber(int entryNumber) { - return AppleUtil.getUnsignedByte(readFileEntry(entryNumber)[0x0]); + return AppleUtil.getUnsignedByte(readFileEntry(entryNumber)[USER_NUMBER_OFFSET]); + } + + /** + * Write the user number (UU). + */ + public void setUserNumber(int entryNumber, int userNumber) { + byte[] data = readFileEntry(entryNumber); + data[USER_NUMBER_OFFSET] = (byte) userNumber; + writeFileEntry(entryNumber, data); } /** + * There appears to be no disk map involved, so deleting a file consists + * of writing a 0xe5 to the user number. * @see com.webcodepro.applecommander.storage.FileEntry#delete() */ public void delete() { - // TODO Auto-generated method stub - + for (int i=0; i 0) { + byte[] block = readCpmBlock(blockNumber); + System.arraycopy(block, 0, + data, i * CPM_BLOCKSIZE, CPM_BLOCKSIZE); + } + } + return data; } /** + * Format the disk. Simply wipes the disk to all 0xE5 - this seems to + * be the standard (or a requirement). + *

+ * Note: Assumes that this is a 140K CP/M disk of 35 tracks and + * 16 physical sectors. * @see com.webcodepro.applecommander.storage.FormattedDisk#format() */ public void format() { - // TODO Auto-generated method stub - + byte[] sectorData = new byte[SECTOR_SIZE]; + for (int i=0; i 0 && !Character.isLetter(filetype.charAt(0))) { + newType.append('A'); + } + int i=0; + while (newType.length() < 3 && i - * Apparantly, data block #0 starts at track 3, sector 0. - * There should be 64 directory entries. - * Each block is 1024 bytes (16 128-byte sectors). - * One block should be the entire directory... */ public byte[] readCpmBlock(int block) { - int[] sectorSkew = { 0x0, 0x6, 0xc, 0x3, 0x9, 0xf, 0xe, 0x5, - 0xb, 0x2, 0x8, 0x7, 0xd, 0x4, 0xa, 0x1 }; - byte[] data = new byte[1024]; - int track = 3 + (block / 4); - int sector = (block % 4) * 4; - for (int i=0; i<4; i++) { + byte[] data = new byte[CPM_BLOCKSIZE]; + int track = computeTrack(block); + int sector = computeSector(block); + for (int i=0; i