/* * AppleCommander - An Apple ][ image utility. * Copyright (C) 2002-3 by Robert Greene * robgreene at users.sourceforge.net * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.webcodepro.applecommander.storage.os.prodos; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Properties; import com.webcodepro.applecommander.storage.DirectoryEntry; import com.webcodepro.applecommander.storage.DiskFullException; import com.webcodepro.applecommander.storage.FileEntry; import com.webcodepro.applecommander.storage.FormattedDisk; import com.webcodepro.applecommander.storage.StorageBundle; import com.webcodepro.applecommander.storage.physical.ImageOrder; import com.webcodepro.applecommander.util.AppleUtil; import com.webcodepro.applecommander.util.TextBundle; /** * Manages a disk that is in the ProDOS format. *

* Date created: Oct 3, 2002 11:45:25 PM * @author Rob Greene */ public class ProdosFormatDisk extends FormattedDisk { private TextBundle textBundle = StorageBundle.getInstance(); /** * The location of the "next block" pointer in a directory entry. * This is a 2-byte word (lo/hi) format. $0000 is end of directory. */ private static final int NEXT_BLOCK_POINTER = 2; /** * The location of the "previous block" pointer in a directory entry. * This is a 2-byte word (lo/hi) format. $0000 is start of directory. */ private static final int PREV_BLOCK_POINTER = 0; /** * The Volume Directory block number. */ private static final int VOLUME_DIRECTORY_BLOCK = 2; /** * A complete list of all known ProDOS filetypes. Note that this * list really cannot be complete, as there are multiple mappings per * identifier in some cases - differentiated by AUXTYPE. This is * loaded via initialize when the first instance of ProdosFormatDisk * is created. */ private static ProdosFileType[] fileTypes; /** * This array of strings conains all filetypes. This is lazy * initialized by getFiletypes. */ private static String[] filetypeStrings; /** * Hold on to the volume directory header. */ private ProdosVolumeDirectoryHeader volumeHeader; /** * This class holds filetype mappings. */ private class ProdosFileType { private byte type; private String string; private boolean addressRequired; private boolean canCompile; public ProdosFileType(byte type, String string, boolean addressRequired, boolean canCompile) { this.type = type; this.string = string; this.addressRequired = addressRequired; this.canCompile = canCompile; } public byte getType() { return type; } public String getString() { return string; } public boolean needsAddress() { return addressRequired; } public boolean canCompile() { return canCompile; } } /** * Use this inner interface for managing the disk usage data. * This offloads format-specific implementation to the implementing class. */ private class ProdosDiskUsage implements DiskUsage { private int location = -1; private transient byte[] data = null; public boolean hasNext() { return location == -1 || location < getVolumeHeader().getTotalBlocks() - 1; } public void next() { location++; } /** * Get the free setting for the bitmap at the current location. */ public boolean isFree() { if (location == -1) { throw new IllegalArgumentException(StorageBundle.getInstance() .get("ProdosFormatDisk.InvalidDimensionError")); //$NON-NLS-1$ } if (data == null) { data = readVolumeBitMap(); } return isBlockFree(data, location); } public boolean isUsed() { return !isFree(); } } /** * Constructor for ProdosFormatDisk. * @param filename */ public ProdosFormatDisk(String filename, ImageOrder imageOrder) { super(filename, imageOrder); volumeHeader = new ProdosVolumeDirectoryHeader(this); initialize(); } /** * Initialize all file types. */ protected void initialize() { if (fileTypes != null) return; fileTypes = new ProdosFileType[256]; InputStream inputStream = getClass().getResourceAsStream("ProdosFileTypes.properties"); //$NON-NLS-1$ Properties properties = new Properties(); try { properties.load(inputStream); for (int i=0; i<256; i++) { String byt = AppleUtil.getFormattedByte(i).toLowerCase(); String string = (String) properties.get("filetype." + byt); //$NON-NLS-1$ if (string == null || string.length() == 0) { string = "$" + byt.toUpperCase(); //$NON-NLS-1$ } boolean addressRequired = Boolean.valueOf((String) properties.get( "filetype." + byt + ".address")).booleanValue(); //$NON-NLS-1$ //$NON-NLS-2$ boolean canCompile = Boolean.valueOf((String) properties.get( "filetype." + byt + ".compile")).booleanValue(); //$NON-NLS-1$ //$NON-NLS-2$ fileTypes[i] = new ProdosFileType((byte)i, string, addressRequired, canCompile); } } catch (IOException ignored) { // Ignored } } /** * Create a ProdosFormatDisk. */ public static ProdosFormatDisk[] create(String filename, String diskName, ImageOrder imageOrder) { ProdosFormatDisk disk = new ProdosFormatDisk(filename, imageOrder); disk.format(); disk.setDiskName(diskName); return new ProdosFormatDisk[] { disk }; } /** * Identify the operating system format of this disk. * @see com.webcodepro.applecommander.storage.FormattedDisk#getFormat() */ public String getFormat() { return textBundle.get("Prodos"); //$NON-NLS-1$ } /** * Create a FileEntry in the Volume Directory. */ public FileEntry createFile() throws DiskFullException { return createFile(volumeHeader); } /** * Create a FileEntry in the given directory. */ public FileEntry createFile(ProdosCommonDirectoryHeader directory) throws DiskFullException { int blockNumber = directory.getFileEntryBlock(); int headerBlock = blockNumber; while (blockNumber != 0) { byte[] block = readBlock(blockNumber); int offset = 4; while (offset+ProdosCommonEntry.ENTRY_LENGTH < BLOCK_SIZE) { int value = AppleUtil.getUnsignedByte(block[offset]); if ((value & 0xf0) == 0) { ProdosFileEntry fileEntry = new ProdosFileEntry(this, blockNumber, offset); fileEntry.setKeyPointer(0); //may have been recycled fileEntry.setCreationDate(new Date()); fileEntry.setProdosVersion(0); fileEntry.setMinimumProdosVersion(0); fileEntry.setCanDestroy(true); fileEntry.setCanRead(true); fileEntry.setCanRename(true); fileEntry.setCanWrite(true); fileEntry.setSeedlingFile(); fileEntry.setHeaderPointer(headerBlock); fileEntry.setFilename(textBundle.get("ProdosFormatDisk.Blank")); //$NON-NLS-1$ directory.incrementFileCount(); return fileEntry; } offset+= ProdosCommonEntry.ENTRY_LENGTH; } int nextBlockNumber = AppleUtil.getWordValue(block, NEXT_BLOCK_POINTER); if (nextBlockNumber == 0 && directory instanceof ProdosSubdirectoryHeader) { byte[] volumeBitmap = readVolumeBitMap(); nextBlockNumber = findFreeBlock(volumeBitmap); setBlockUsed(volumeBitmap, nextBlockNumber); writeVolumeBitMap(volumeBitmap); byte[] oldBlock = readBlock(blockNumber); AppleUtil.setWordValue(oldBlock, NEXT_BLOCK_POINTER, nextBlockNumber); writeBlock(blockNumber, oldBlock); byte[] nextBlock = new byte[BLOCK_SIZE]; AppleUtil.setWordValue(nextBlock, PREV_BLOCK_POINTER, blockNumber); writeBlock(nextBlockNumber, nextBlock); ProdosSubdirectoryHeader header = (ProdosSubdirectoryHeader) directory; int blockCount = header.getProdosDirectoryEntry().getBlocksUsed(); blockCount++; header.getProdosDirectoryEntry().setBlocksUsed(blockCount); header.getProdosDirectoryEntry().setEofPosition(blockCount * BLOCK_SIZE); } blockNumber = nextBlockNumber; } throw new DiskFullException(textBundle.get("ProdosFormatDisk.UnableToAllocateSpaceError")); //$NON-NLS-1$ } /** * Retrieve a list of files. * @see com.webcodepro.applecommander.storage.FormattedDisk#getFiles() */ public List getFiles() { return getFiles(VOLUME_DIRECTORY_BLOCK); } /** * Build a list of files, starting in the given block number. * This works for the master as well as the subdirectories. */ protected List getFiles(int blockNumber) { List files = new ArrayList(); while (blockNumber != 0) { byte[] block = readBlock(blockNumber); int offset = 4; while (offset+ProdosCommonEntry.ENTRY_LENGTH < BLOCK_SIZE) { ProdosCommonEntry tester = new ProdosCommonEntry(this, blockNumber, offset); if (tester.isVolumeHeader() || tester.isSubdirectoryHeader()) { // ignore it, we've already got it } else if (!tester.isEmpty()) { ProdosFileEntry fileEntry = new ProdosFileEntry(this, blockNumber, offset); if (fileEntry.isDirectory()) { int keyPointer = fileEntry.getKeyPointer(); ProdosDirectoryEntry directoryEntry = new ProdosDirectoryEntry(this, blockNumber, offset, new ProdosSubdirectoryHeader(this, keyPointer)); files.add(directoryEntry); } else { files.add(fileEntry); } } offset+= ProdosCommonEntry.ENTRY_LENGTH; } blockNumber = AppleUtil.getWordValue(block, NEXT_BLOCK_POINTER); } return files; } /** * Return the amount of free space in bytes. * @see com.webcodepro.applecommander.storage.FormattedDisk#getFreeSpace() */ public int getFreeSpace() { return getFreeBlocks() * BLOCK_SIZE; } /** * Return the number of free blocks on the disk. */ public int getFreeBlocks() { int freeBlocks = 0; int blocksToProcess = (volumeHeader.getTotalBlocks() + 4095) / 4096; int blockNumber = volumeHeader.getBitMapPointer(); for (int ix=0; ix 0) { // FIXME - this may break sparse files! byte[] indexBlock = readBlock(blockNumber); offset= getIndexBlockData(fileData, indexBlock, offset); } } } else { throw new IllegalArgumentException(textBundle.get("ProdosFormatDisk.UnknownStorageType")); //$NON-NLS-1$ } return fileData; } /** * Free blocks used by a ProdosFileEntry. */ protected void freeBlocks(ProdosFileEntry prodosFileEntry) { byte[] bitmap = readVolumeBitMap(); int block = prodosFileEntry.getKeyPointer(); if (block == 0) return; // new entry if (prodosFileEntry.isGEOSFile()) { // A GEOS file allocates another block, pointed to by the aux bytes. setBlockFree(bitmap,prodosFileEntry.getAuxiliaryType()); } setBlockFree(bitmap,block); if (prodosFileEntry.isSaplingFile()) { freeBlocksInIndex(bitmap,block,false); } else if (prodosFileEntry.isTreeFile()) { byte[] masterIndexBlock = readBlock(block); for (int i=0; i<0x100; i++) { if (!prodosFileEntry.isGEOSFile() || (prodosFileEntry.isGEOSFile() && (i < 0xfe))) { // As long as we're not deleting a GEOS file, delete all index entries. // GEOS uses records 0xfe and 0xff for space calculations, not pointers. int indexBlockNumber = AppleUtil.getWordValue( masterIndexBlock[i], masterIndexBlock[i+0x100]); if (indexBlockNumber > 0) freeBlocksInIndex(bitmap,indexBlockNumber,prodosFileEntry.isGEOSFile()); } } } writeVolumeBitMap(bitmap); } /** * Free the given index block and the data blocks it points to. */ private void freeBlocksInIndex(byte[] bitmap, int indexBlockNumber, boolean isGEOS) { setBlockFree(bitmap, indexBlockNumber); byte[] indexBlock = readBlock(indexBlockNumber); for (int i=0; i<0x100; i++) { if (!isGEOS || (isGEOS && (i < 0xfe))) { // As long as we're not deleting a GEOS file, delete all entries. // GEOS uses records 0xfe and 0xff for space calculations, not pointers. int blockNumber = AppleUtil.getWordValue(indexBlock[i], indexBlock[i+0x100]); if (blockNumber > 0) setBlockFree(bitmap, blockNumber); } } } /** * Read file data from the given index block. * Note that block number 0 is an unused block. */ protected int getIndexBlockData(byte[] fileData, byte[] indexBlock, int offset) { for (int i=0; i<0x100; i++) { int blockNumber = AppleUtil.getWordValue(indexBlock[i], indexBlock[i+0x100]); byte[] blockData = readBlock(blockNumber); if (offset + blockData.length > fileData.length) { // end of file int bytesToCopy = fileData.length - offset; if (blockNumber != 0) System.arraycopy(blockData, 0, fileData, offset, bytesToCopy); offset+= bytesToCopy; break; } if (blockNumber != 0) System.arraycopy(blockData, 0, fileData, offset, blockData.length); offset+= blockData.length; } return offset; } /** * Set the data associated with the specified ProdosFileEntry into sectors * on the disk. Automatically grows the filesystem structures from seedling * to sapling to tree. */ protected void setFileData(ProdosFileEntry fileEntry, byte[] fileData) throws DiskFullException { if (fileEntry.isGEOSFile()) { // If this is a GEOS file, things are a bit different. setGEOSFileData(fileEntry, fileData); } else { // compute free space and see if the data will fit! int numberOfDataBlocks = (fileData.length + BLOCK_SIZE - 1) / BLOCK_SIZE; int numberOfBlocks = numberOfDataBlocks; if (numberOfBlocks > 1) { numberOfBlocks+= ((numberOfDataBlocks-1) / 256) + 1; // that's 128K if (numberOfDataBlocks > 256) { numberOfBlocks++; } } if (numberOfBlocks > getFreeBlocks() + fileEntry.getBlocksUsed()) { throw new DiskFullException(textBundle. format("ProdosFormatDisk.NotEnoughSpaceOnDiskError", //$NON-NLS-1$ numberOfBlocks, getFreeBlocks())); } // free "old" data and just rewrite stuff... freeBlocks(fileEntry); byte[] bitmap = readVolumeBitMap(); int blockNumber = fileEntry.getKeyPointer(); if (blockNumber == 0) { blockNumber = findFreeBlock(bitmap); } int indexBlockNumber = 0; byte[] indexBlockData = null; int masterIndexBlockNumber = 0; byte[] masterIndexBlockData = new byte[BLOCK_SIZE]; int offset = 0; int blockCount = 0; while (offset < fileData.length) { if (blockCount > 0) blockNumber = findFreeBlock(bitmap); setBlockUsed(bitmap, blockNumber); blockCount++; byte[] blockData = new byte[BLOCK_SIZE]; int length = Math.min(BLOCK_SIZE, fileData.length - offset); System.arraycopy(fileData,offset,blockData,0,length); writeBlock(blockNumber, blockData); if (numberOfDataBlocks > 1) { // growing to a tree file if (offset > 0 && (offset / BLOCK_SIZE) % 256 == 0) { if (masterIndexBlockNumber == 0) { masterIndexBlockNumber = findFreeBlock(bitmap); setBlockUsed(bitmap, masterIndexBlockNumber); blockCount++; } writeBlock(indexBlockNumber, indexBlockData); indexBlockData = null; indexBlockNumber = 0; } // new index block if (indexBlockData == null) { // sapling files indexBlockNumber = findFreeBlock(bitmap); indexBlockData = new byte[BLOCK_SIZE]; setBlockUsed(bitmap, indexBlockNumber); blockCount++; // This is only used for Tree files (but we always record it): int position = (offset / (BLOCK_SIZE * 256)); byte low = (byte)(indexBlockNumber % 256); byte high = (byte)(indexBlockNumber / 256); masterIndexBlockData[position] = low; masterIndexBlockData[position + 0x100] = high; } // record last block position in index block int position = (offset / BLOCK_SIZE) % 256; byte low = (byte)(blockNumber % 256); byte high = (byte)(blockNumber / 256); indexBlockData[position] = low; indexBlockData[position + 0x100] = high; } offset+= BLOCK_SIZE; } if (numberOfDataBlocks == 1) { fileEntry.setKeyPointer(blockNumber); fileEntry.setSeedlingFile(); } else if (numberOfDataBlocks <= 256) { writeBlock(indexBlockNumber, indexBlockData); fileEntry.setKeyPointer(indexBlockNumber); fileEntry.setSaplingFile(); } else { writeBlock(indexBlockNumber, indexBlockData); writeBlock(masterIndexBlockNumber, masterIndexBlockData); fileEntry.setKeyPointer(masterIndexBlockNumber); fileEntry.setTreeFile(); } fileEntry.setBlocksUsed(blockCount); fileEntry.setEofPosition(fileData.length); fileEntry.setLastModificationDate(new Date()); writeVolumeBitMap(bitmap); } } /** * Set the data associated with the specified ProdosFileEntry into sectors * on the disk. Take GEOS file structures into account. */ protected void setGEOSFileData(ProdosFileEntry fileEntry, byte[] fileData) throws DiskFullException { // compute free space and see if the data will fit! int numberOfDataBlocks = (fileData.length - 1) / BLOCK_SIZE; int numberOfBlocks = numberOfDataBlocks; numberOfBlocks+= ((numberOfDataBlocks-1) / 254) + 1; // GEOS uses the last two blocks for eof calculations if (numberOfDataBlocks > 254) { numberOfBlocks++; } if (numberOfBlocks > getFreeBlocks() + fileEntry.getBlocksUsed()) { throw new DiskFullException(textBundle. format("ProdosFormatDisk.NotEnoughSpaceOnDiskError", //$NON-NLS-1$ numberOfBlocks, getFreeBlocks())); } // free "old" data and just rewrite stuff... freeBlocks(fileEntry); byte[] bitmap = readVolumeBitMap(); // Place the first BLOCK_SIZE bytes of data in a block pointed to by the aux address. int headerBlockNumber = findFreeBlock(bitmap); byte[] headerData = new byte[BLOCK_SIZE]; setBlockUsed(bitmap, headerBlockNumber); System.arraycopy(fileData,0,headerData,0,BLOCK_SIZE); writeBlock(headerBlockNumber, headerData); fileEntry.setAddress(headerBlockNumber); if (AppleUtil.getUnsignedByte(fileData[0x180]) >> 4 == 2) { setGEOSSaplingData(bitmap, fileEntry, fileData); } else { setGEOSTreeData(bitmap, fileEntry, fileData); } fileEntry.setGEOSMeta(headerData); } /** * Set the GEOS "sapling" file data. */ protected void setGEOSSaplingData(byte[] bitmap, ProdosFileEntry fileEntry, byte[] fileData) throws DiskFullException { int indexBlockNumber = findFreeBlock(bitmap); setBlockUsed(bitmap, indexBlockNumber); byte[] indexBlockData = new byte[BLOCK_SIZE]; int offset = BLOCK_SIZE; int blockNumber = 0; int blockCount = 1; // The header block counts for one while (offset < fileData.length) { blockNumber = findFreeBlock(bitmap); setBlockUsed(bitmap, blockNumber); blockCount++; byte[] blockData = new byte[BLOCK_SIZE]; int length = Math.min(BLOCK_SIZE, fileData.length - offset); System.arraycopy(fileData,offset,blockData,0,length); writeBlock(blockNumber, blockData); // record last block position in index block int position = ((offset - BLOCK_SIZE) / BLOCK_SIZE) % 256; byte low = (byte)(blockNumber % 256); byte high = (byte)(blockNumber / 256); indexBlockData[position] = low; indexBlockData[position + 0x100] = high; offset+= BLOCK_SIZE; } indexBlockData[255] = (byte)((fileData.length - BLOCK_SIZE) % 256); // Lo file eof indexBlockData[511] = (byte)((fileData.length - BLOCK_SIZE) / 256); // Med file eof writeBlock(indexBlockNumber, indexBlockData); fileEntry.setKeyPointer(indexBlockNumber); fileEntry.setSaplingFile(); fileEntry.setBlocksUsed(blockCount); fileEntry.setEofPosition(fileData.length - BLOCK_SIZE); fileEntry.setLastModificationDate(new Date()); writeVolumeBitMap(bitmap); } /** * Set the GEOS "tree" file data. */ protected void setGEOSTreeData(byte[] bitmap, ProdosFileEntry fileEntry, byte[] fileData) throws DiskFullException { int masterIndexBlockNumber = findFreeBlock(bitmap); setBlockUsed(bitmap, masterIndexBlockNumber); byte[] masterIndexBlockData = new byte[BLOCK_SIZE]; int offset = BLOCK_SIZE; int blockCount = 2; // Start by counting the header block and master index int eofCount = 0; for (int masterIterator = 0; masterIterator < 254; masterIterator++) { if ((fileData[0x100+masterIterator] != 0xff) && (offset < fileData.length)){ byte[] lengthData = new byte[BLOCK_SIZE]; System.arraycopy(fileData,offset,lengthData,0,BLOCK_SIZE); offset += BLOCK_SIZE; int recordLength = AppleUtil.getUnsignedByte(lengthData[0xff]) + AppleUtil.getUnsignedByte(lengthData[0x1ff])*256; int indexBlockNumber = findFreeBlock(bitmap); setBlockUsed(bitmap, indexBlockNumber); blockCount +=1; byte[] indexBlockData = new byte[BLOCK_SIZE]; int blockNumber = 0; int startingPoint = offset; while (offset < startingPoint + recordLength) { blockNumber = findFreeBlock(bitmap); setBlockUsed(bitmap, blockNumber); blockCount +=1; byte[] blockData = new byte[BLOCK_SIZE]; int length = Math.min(BLOCK_SIZE, recordLength - offset + startingPoint); System.arraycopy(fileData,offset,blockData,0,length); writeBlock(blockNumber, blockData); eofCount += length; // record last block position in index block int position = ((offset-startingPoint) / BLOCK_SIZE) % 256; byte low = (byte)(blockNumber % 256); byte high = (byte)(blockNumber / 256); indexBlockData[position] = low; indexBlockData[position + 0x100] = high; offset+= BLOCK_SIZE; } indexBlockData[0xff] = (byte) (recordLength & 0x0000ff); indexBlockData[0x1ff] = (byte)((recordLength & 0x00ff00) >> 8); indexBlockData[0x1fe] = (byte)((recordLength & 0xff0000) >> 16); writeBlock(indexBlockNumber, indexBlockData); byte low = (byte)(indexBlockNumber % 256); byte high = (byte)(indexBlockNumber / 256); masterIndexBlockData[masterIterator] = low; masterIndexBlockData[masterIterator + 0x100] = high; } else if (fileData[0x100+masterIterator] == 0xff) { masterIndexBlockData[masterIterator] = (byte)0xff; masterIndexBlockData[masterIterator+0x100] = (byte)0xff; } } masterIndexBlockData[0x0ff] = (byte) (eofCount & 0x0000ff); masterIndexBlockData[0x1ff] = (byte)((eofCount & 0x00ff00) >> 8); masterIndexBlockData[0x1fe] = (byte)((eofCount & 0xff0000) >> 16); writeBlock(masterIndexBlockNumber, masterIndexBlockData); fileEntry.setKeyPointer(masterIndexBlockNumber); fileEntry.setTreeFile(); fileEntry.setBlocksUsed(blockCount); fileEntry.setEofPosition(eofCount); fileEntry.setLastModificationDate(new Date()); writeVolumeBitMap(bitmap); } /** * Locate a free block in the Volume Bitmap. */ protected int findFreeBlock(byte[] volumeBitmap) throws DiskFullException { int block = 1; int blocksOnDisk = getBitmapLength(); while (block < blocksOnDisk) { if (isBlockFree(volumeBitmap,block)) { if ((block+1) * BLOCK_SIZE < getPhysicalSize()) { return block; } throw new ProdosDiskSizeDoesNotMatchException( textBundle.get("ProdosFormatDisk.ProdosDiskSizeDoesNotMatchError")); //$NON-NLS-1$ } block++; } throw new DiskFullException( textBundle.get("ProdosFormatDisk.NoFreeBlockAvailableError")); //$NON-NLS-1$ } /** * Read the Volume Bit Map. */ public byte[] readVolumeBitMap() { int volumeBitmapBlock = volumeHeader.getBitMapPointer(); int volumeBitmapBlocks = volumeHeader.getTotalBlocks(); int blocksToRead = (volumeBitmapBlocks / 4096) + 1; // Read in the entire volume bitmap: byte[] data = new byte[blocksToRead * BLOCK_SIZE]; for (int i=0; i 2) ? block-1 : 0; AppleUtil.setWordValue(data, 0, prevBlock); AppleUtil.setWordValue(data, 2, nextBlock); writeBlock(block, data); } // setup volume header information (each set will also save data) volumeHeader.setVolumeHeader(); volumeHeader.setVolumeName(volumeName); volumeHeader.setCreationDate(new Date()); volumeHeader.setProdosVersion(0); volumeHeader.setMinimumProdosVersion(0); volumeHeader.setHasChanged(true); volumeHeader.setCanDestroy(true); volumeHeader.setCanRead(true); volumeHeader.setCanRename(true); volumeHeader.setCanWrite(true); volumeHeader.setEntryLength(); volumeHeader.setEntriesPerBlock(); volumeHeader.setFileCount(0); volumeHeader.setBitMapPointer(6); volumeHeader.setTotalBlocks(totalBlocks); // setup bitmap usage byte[] bitmap = readVolumeBitMap(); for (int block=0; block 0) { String what = filename.substring(pos+1); ProdosFileType type = findFileType(what); if (type != null) { filetype = type.getString(); } } return filetype; } /** * Return the filetype of this file. This will be three characters, * according to ProDOS - a "$xx" if unknown. */ public String getFiletype(int filetype) { ProdosFileType prodostype = fileTypes[filetype]; return prodostype.getString(); } /** * Get the numerical filetype. */ public byte getFiletype(String filetype) { ProdosFileType type = findFileType(filetype); if (type != null) { return type.getType(); } return 0x00; } /** * Locate the associated ProdosFileType. */ public ProdosFileType findFileType(String filetype) { for (int i=0; i