AppleCommander ref. AppleCommander_win32_123.zip After downloading, unzip using WinZIP or a similar utility. The zip file contains all the required files for AppleCommander. Just unzip to a directory and double-click on AppleCommander.exe! This has been tested only on Windows XP but should run on Windows 98, Windows NT, Windows 2000, and Windows XP. John B. Matthews, M.D. A command-line Apple II disk image tool in Java. Introduction "ac" is a command line interface to Rob Greene's AppleCommander, a java based tool for working with Apple II disk images. It works with ProDOS, DOS 3.3, Pascal and other disk images. The program is ideally suited to automate the transfer of compiler/assembler output onto an Apple II disk image. The program is provided under the GNU Public License, a copy of which is attached. You should read the license before using ac, noting that there is NO WARRANTY OF ANY KIND. Because here is NO LIABILITY FOR DAMAGES, never use ac on an image for which you do not have a backup. Installing ac After downloading the file ac.tgz, create a directory to hold the files and unpack ac.tgz with the tar command: mkdir ac cd ac tar -zxvf ac.tgz Using ac The java classes for ac are in the file ac.jar. The -p140 command creates a blank 140K ProDOS disk image; -p800 creates an 800K image. To create an image named misc.dsk having the volume name test, enter the command: java -jar ac.jar -p140 misc.dsk test The -l command lists the directory of a disk image. To examine a disk image named misc.dsk, enter the command: java -jar ac.jar -l misc.dsk The -e command lets you examine a file by printing a readable version to standard output. To examine a file named fred on a disk named misc.dsk, enter the command: java -jar ac.jar -e fred misc.dsk Because binary files are difficult to read, you may want to send the output to a program that can show the data in hexadecimal form: java -jar ac.jar -e fred misc.dsk | hexdump The -g command gets a file in its raw form. To copy a file named fred from misc.dsk to a file named ethel on your file system, enter the command: java -jar ac.jar -g fred misc.dsk > ethel The -p command puts data on a disk image. Suppose ethel is a binary file meant to start at address 2048 ($800). To put the binary file named ethel back into a file named fred with starting address 2048 on the image named misc.dsk: cat ethel | java -jar ac.jar -p fred bin 2048 misc.dsk The -d command deletes a file from an image: java -jar ac.jar -p fred misc.dsk To copy a file from one image to another, do something like this: java -jar ac.jar -g fred one.dsk | java -jar ac.jar -p fred bin 2048 another.dsk For a quick list of ac options, enter the -h command: java -jar ac.jar -h The result is shown: AppleCommander command line options: -l list directory of image. -e export file from image to stdout. -g get raw file from image to stdout. -p put stdin in destname on image, using file type and address. -d delete file from image. -p140 create a 140K ProDOS image. -p800 create a 800K ProDOS image. Known problems To replace an existing file on an image, you must delete it first; ac will create as many duplicate entries as you specify, only one of which will be accessible to the operating system. Getting a file from a ProDOS image will recursively search all directories, returning the first match. In contrast, files can only be put in the top level directory. Changes to Apple Commander v1.2.3 The following changes were made to AppleCommander v1.2.3 to make it compatible with ac: 1. When compiling the package storage without swt, modify AppleWorksWordProcessorFileFilter to not import ui.AppleCommander or use AppleCommander.VERSION in the filter. 2. The format of Pascal dates is correct in the comments, but the shifts and mask are off. Also, Pascal months start at one. In AppleUtil /** * Extract a Pascal date from the buffer. * Bits 0-3: month (1-12) * Bits 4-8: day (1-31) * Bits 9-15: year (0-99) */ public static Date getPascalDate(byte[] buffer, int offset) { int pascalDate = getWordValue(buffer, offset); int month = pascalDate & 0x000f - 1; int day = (pascalDate & 0x01f0) >> 4; int year = (pascalDate & 0xfe00) >> 9; if (year < 50) year+= 2000; if (year < 100) year+= 1900; GregorianCalendar gc = new GregorianCalendar(year, month, day); return gc.getTime(); } /** * Set a Pascal date to the buffer. * Bits 0-3: month (1-12) * Bits 4-8: day (1-31) * Bits 9-15: year (0-99) */ public static void setPascalDate(byte[] buffer, int offset, Date date) { GregorianCalendar gc = new GregorianCalendar(); gc.setTime(date); int month = gc.get(GregorianCalendar.MONTH) + 1; int day = gc.get(GregorianCalendar.DAY_OF_MONTH); int year = gc.get(GregorianCalendar.YEAR) % 100; int pascalDate = (month & 0x000f) | ((day << 4) & 0x01f0) | ((year << 9) & 0xfe00); setWordValue(buffer, offset, pascalDate); } 3. In storage.PascalFileEntry.getFileColumnData use SimpleDateFormat("dd-MMM-yy"). 4. In PascalFormatDisk.getDiskName add a ":" to the volume name. 5. Delete spurious ':' in @author tags. 6. Review javadoc warnings about broken @see tags. 7. Like Pascal, ProDOS months start at one, while Java expects zero for January. In AppleUtil.getProdosDate, subtract one from the month: int month = ((ymd & 0x01e0) >> 5) - 1; // bits 5-8 In setProdosDate add one and fix the year: month = gc.get(GregorianCalendar.MONTH) + 1; ... if (year >= 2000) { year -= 2000; } else { year -= 1900; } 8. In ProdosFormatDisk.createFile(), add fileEntry.setKeyPointer(0); if this is a recyled directory entry, a subsequent call to setFileData will try to free blocks that previously belonged to the deleted file. These blocks may have subsequently been allocated to another file. fileEntry.setKeyPointer(0); //may have been recycled Also, call setSeedlingFile(), rather than setSaplingFile(). 9. A file entry needs a header pointer: In ProdosFileEntry add /** * Set the block number of the block for the directory * that describes this file. */ public void setHeaderPointer(int headerPointer) { byte[] entry = readFileEntry(); AppleUtil.setWordValue(entry, 0x25, headerPointer); writeFileEntry(entry); } 10. Initailize header pointer in ProdosFormatDisk.createFile(), add int headerBlock = blockNumber; ... fileEntry.setHeaderPointer(headerBlock); 11. In ProdosFileEntry.delete(), the file count in the entry's directory header should be decremented. [ProDOS Tech. Ref. B.2.2 B.2.3] Also, both the storage type and the name length should be set to zero. [ProDOS Tech. Ref. B.2.4] /** * Delete the file. */ public void delete() { getDisk().freeBlocks(this); //decrement file count in header block int headerBlock = getHeaderPointer(); byte[] data = getDisk().readBlock(headerBlock); int fileCount = AppleUtil.getWordValue(data, 0x25); if (fileCount != 0) fileCount--; AppleUtil.setWordValue(data, 0x25, fileCount); getDisk().writeBlock(headerBlock, data); //clear storage type and name length data = readFileEntry(); data[0] = 0; writeFileEntry(data); } 12. In TextFileFilter, use PrintWriter for cross-platform line endings: /** * Process the given FileEntry and return a byte array * with filtered data; use PrintWriter to get platform * agnostic line endings. */ public byte[] filter(FileEntry fileEntry) { byte[] fileData = fileEntry.getFileData(); int offset = 0; ByteArrayOutputStream byteArray = new ByteArrayOutputStream(fileData.length); PrintWriter printWriter = new PrintWriter(byteArray, true); while (offset < fileData.length) { char c = (char)(fileData[offset] & 0x7f); if (c != 0) { if (c == 0x0d) { //Apple line end printWriter.println(); } else { printWriter.print(c); } } offset++; } return byteArray.toByteArray(); } 13. Applesoft files require a starting address, usually 2049 ($801); to Prodos FileTypes.properties add: filetype.fc.address=true Copyright 2003 John B. Matthews Distribution permitted under the terms of the GPL: http://www.gnu.org/copyleft/gpl.html. Last updated 14-Sep-2003 Visit the AppleCommander home page on Source Forge. http://www.wright.edu/~john.matthews/ac.html http://sourceforge.net/projects/applecommander/