/* * AppleCommander - An Apple ][ image utility. * Copyright (C) 2002 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.rdos; import java.util.ArrayList; import java.util.BitSet; import java.util.List; 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 RDOS format. *

* Note that the RDOS block interleave is different than the standard DOS 3.3 format. * Thus, when the image is made, the sectors are skewed differently - use readRdosBlock * to read the appropriate block number. *

* Also note that the operating system is itself the first file. Block #0 is really * track 0, sector 0 - meaning that the first file should not (cannot) be deleted. *

* RDOS appears to have been placed on 13 sector disks. This limits the number of blocks * to 455. It also may also cause incompatibilities with other formats and other cracks. *

* Date created: Oct 7, 2002 2:03:58 PM * @author Rob Greene */ public class RdosFormatDisk extends FormattedDisk { private TextBundle textBundle = StorageBundle.getInstance(); /** * The RDOS disks are structured in a different order than DOS 3.3. * This table interpolates between the RDOS ordering and the DOS * ordering. It appears that RDOS may use the physical sector number * instead of the logical sector. */ private static final int sectorSkew[] = { 0, 7, 0x0e, 6, 0x0d, 5, 0x0c, 4, 0x0b, 3, 0x0a, 2, 9, 1, 8, 0x0f }; /** * Specifies the length of a file entry. */ public static final int ENTRY_LENGTH = 0x20; /** * Specifies the number of blocks on the disk. * RDOS apparantly only worked on 5.25" disks. */ public static final int BLOCKS_ON_DISK = 455; /** * The known filetypes for a RDOS disk. */ public static final String[] filetypes = { "B", "A", "T" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ /** * Use this inner interface for managing the disk usage data. * This off-loads format-specific implementation to the implementing class. * A BitSet is used to track all blocks, as RDOS disks do not have a * bitmap stored on the disk. This is safe since we know the number of blocks * that exist. (BitSet length is of last set bit - unset bits at the end are * "lost".) *

* Note one really unique point about RDOS - the entire disk is mapped out * by the file entries. There are no blocks marked off, by default, by the * operating system. However, the first file (RDOS itself) starts on block * 0 (track 0, sector 0) and runs for 26 blocks - which covers all of track 0 * (the operating system) and the 10 sectors used for file entries. */ private class RdosDiskUsage implements DiskUsage { private int location = -1; private BitSet bitmap = null; public boolean hasNext() { return location == -1 || location < BLOCKS_ON_DISK - 1; } public void next() { if (bitmap == null) { bitmap = new BitSet(BLOCKS_ON_DISK); // mark all blocks as unused for (int b=0; b * Note that sectorSkew has the full 16 sectors, even though RDOS * itself is a 13 sector format. */ public byte[] readRdosBlock(int block) { int track = block / 13; int sector = sectorSkew[block % 13]; return readSector(track, sector); } /** * Write an RDOS block. The sector skewing for RDOS seems to be different. * This routine will convert the block number to a DOS track and sector, * handling the sector change-over. The writeSector method then should * take care of various image formats. *

* Note that sectorSkew has the full 16 sectors, even though RDOS * itself is a 13 sector format. */ public void writeRdosBlock(int block, byte[] data) { int track = block / 13; int sector = sectorSkew[block % 13]; writeSector(track, sector, data); } /** * RDOS really does not have a disk name. Fake one. */ public String getDiskName() { byte[] block = readRdosBlock(4); return AppleUtil.getString(block, 0xe0, 0x20); } /** * Retrieve a list of files. */ public List getFiles() { List files = new ArrayList<>(); for (int b=13; b<23; b++) { byte[] data = readRdosBlock(b); for (int i=0; i getDiskInformation() { List list = super.getDiskInformation(); list.add(new DiskInformation(textBundle.get("TotalBlocks"), BLOCKS_ON_DISK)); //$NON-NLS-1$ list.add(new DiskInformation(textBundle.get("FreeBlocks"), getFreeBlocks())); //$NON-NLS-1$ list.add(new DiskInformation(textBundle.get("UsedBlocks"), getUsedBlocks())); //$NON-NLS-1$ return list; } /** * Get the standard file column header information. * This default implementation is intended only for standard mode. */ public List getFileColumnHeaders(int displayMode) { List list = new ArrayList<>(); switch (displayMode) { case FILE_DISPLAY_NATIVE: list.add(new FileColumnHeader(textBundle.get("Type"), 1, FileColumnHeader.ALIGN_CENTER)); //$NON-NLS-1$ list.add(new FileColumnHeader(textBundle.get("Blocks"), 3, FileColumnHeader.ALIGN_RIGHT)); //$NON-NLS-1$ list.add(new FileColumnHeader(textBundle.get("Name"), 24, FileColumnHeader.ALIGN_LEFT)); //$NON-NLS-1$ list.add(new FileColumnHeader(textBundle.get("RdosFormatDisk.Size"), 6, FileColumnHeader.ALIGN_RIGHT)); //$NON-NLS-1$ list.add(new FileColumnHeader(textBundle.get("RdosFormatDisk.StartingBlock"), 3, FileColumnHeader.ALIGN_RIGHT)); //$NON-NLS-1$ break; case FILE_DISPLAY_DETAIL: list.add(new FileColumnHeader(textBundle.get("Type"), 1, FileColumnHeader.ALIGN_CENTER)); //$NON-NLS-1$ list.add(new FileColumnHeader(textBundle.get("Blocks"), 3, FileColumnHeader.ALIGN_RIGHT)); //$NON-NLS-1$ list.add(new FileColumnHeader(textBundle.get("Name"), 24, FileColumnHeader.ALIGN_LEFT)); //$NON-NLS-1$ list.add(new FileColumnHeader(textBundle.get("RdosFormatDisk.Size"), 6, FileColumnHeader.ALIGN_RIGHT)); //$NON-NLS-1$ list.add(new FileColumnHeader(textBundle.get("RdosFormatDisk.StartingBlock"), 3, FileColumnHeader.ALIGN_RIGHT)); //$NON-NLS-1$ list.add(new FileColumnHeader(textBundle.get("RdosFormatDisk.Address"), 5, FileColumnHeader.ALIGN_RIGHT)); //$NON-NLS-1$ list.add(new FileColumnHeader(textBundle.get("DeletedQ"), 7, FileColumnHeader.ALIGN_CENTER)); //$NON-NLS-1$ break; default: // FILE_DISPLAY_STANDARD list.addAll(super.getFileColumnHeaders(displayMode)); break; } return list; } /** * Indicates if this disk format supports "deleted" files. */ public boolean supportsDeletedFiles() { return true; } /** * Indicates if this disk image can read data from a file. */ public boolean canReadFileData() { return true; } /** * Indicates if this disk image can write data to a file. */ public boolean canWriteFileData() { return false; // FIXME - not implemented } /** * RDOS dos not support directories. */ public boolean canHaveDirectories() { return false; } /** * Indicates if this disk image can delete a file. */ public boolean canDeleteFile() { return false; // FIXME - not implemented } /** * Get the data associated with the specified FileEntry. */ public byte[] getFileData(FileEntry fileEntry) { if ( !(fileEntry instanceof RdosFileEntry)) { throw new IllegalArgumentException(textBundle.get("RdosFormatDisk.IncorrectFileEntryError")); //$NON-NLS-1$ } RdosFileEntry rdosEntry = (RdosFileEntry) fileEntry; int startingBlock = rdosEntry.getStartingBlock(); byte[] fileData = new byte[rdosEntry.getSizeInBlocks() * SECTOR_SIZE]; int offset = 0; for (int blockOffset = 0; blockOffset < rdosEntry.getSizeInBlocks(); blockOffset++) { byte[] blockData = readRdosBlock(startingBlock + blockOffset); System.arraycopy(blockData, 0, fileData, offset, blockData.length); offset+= blockData.length; } return fileData; } /** * Format the disk as an RDOS disk. * FIXME - RDOS does not "like" an AppleCommander formatted disk. * This appears to be because the &CAT command * reads from track 1 sector 9 (whatever RDOS block that * would be) and executes that code for the directory. * AppleCommander will need to either clone the code or write * its own routine. This is RDOS block #25. * @see com.webcodepro.applecommander.storage.FormattedDisk#format() */ public void format() { getImageOrder().format(); writeBootCode(); // minor hack - ensure that AppleCommander itself recognizes the // RDOS disk! byte[] block = readSector(0, 0x0d); AppleUtil.setString(block, 0xe0, textBundle.get("RdosFormatDisk.IdentifierText"), 0x20); //$NON-NLS-1$ writeSector(0, 0x0d, block); // a hack - until real code goes here. block = new byte[256]; block[0] = 0x60; writeSector(1, 9, block); // write the first directory entry // FIXME - this should use FileEntry! byte[] data = readRdosBlock(13); AppleUtil.setString(data, 0x00, textBundle.get("RdosFormatDisk.InitialSystemFile"), 0x18); //$NON-NLS-1$ AppleUtil.setString(data, 0x18, "B", 0x01); //$NON-NLS-1$ data[0x19] = 26; AppleUtil.setWordValue(data, 0x1a, 0x1000); AppleUtil.setWordValue(data, 0x1c, 6656); AppleUtil.setWordValue(data, 0x1e, 0); writeRdosBlock(13, data); } /** * Returns the logical disk number. Returns a 0 to indicate no numbering. */ public int getLogicalDiskNumber() { return 0; } /** * Returns a valid filename for the given filename. RDOS * pretty much allows anything - so it is cut to 24 characters * and trimmed (trailing whitespace may cause confusion). */ public String getSuggestedFilename(String filename) { int len = Math.min(filename.length(), 24); return filename.toUpperCase().substring(0, len).trim(); } /** * Returns a valid filetype for the given filename. The most simple * format will just assume a filetype of binary. This method is * available for the interface to make an intelligent first guess * as to the filetype. */ public String getSuggestedFiletype(String filename) { String filetype = "B"; //$NON-NLS-1$ int pos = filename.lastIndexOf("."); //$NON-NLS-1$ if (pos > 0) { String what = filename.substring(pos+1); if ("txt".equalsIgnoreCase(what)) { //$NON-NLS-1$ filetype = "T"; //$NON-NLS-1$ } } return filetype; } /** * Returns a list of possible file types. Since the filetype is * specific to each operating system, a simple String is used. */ public String[] getFiletypes() { return filetypes; } /** * Indicates if this filetype requires an address component. * Presumably, the "B" filetype is binary and will need an * address. */ public boolean needsAddress(String filetype) { return "B".equals(filetype); //$NON-NLS-1$ } /** * Indicates if this FormattedDisk supports a disk map. */ public boolean supportsDiskMap() { return true; } /** * Change to a different ImageOrder. Remains in RDOS format but * the underlying order can change. * @see ImageOrder */ public void changeImageOrder(ImageOrder imageOrder) { AppleUtil.changeImageOrderByTrackAndSector(getImageOrder(), imageOrder); setImageOrder(imageOrder); } /** * Writes the raw bytes into the file. This bypasses any special formatting * of the data (such as prepending the data with a length and/or an address). * Typically, the FileEntry.setFileData method should be used. */ public void setFileData(FileEntry fileEntry, byte[] fileData) throws DiskFullException { // TODO implement setFileData } /** * Create a new DirectoryEntry. * @see com.webcodepro.applecommander.storage.DirectoryEntry#createDirectory(String) */ public DirectoryEntry createDirectory(String name) throws DiskFullException { throw new UnsupportedOperationException(textBundle.get("DirectoryCreationNotSupported")); //$NON-NLS-1$ } }