John B. Matthews, M.D.

Return home.


Download files comprising the current release candidate.
Visit the AppleCommander web site on Source Forge.


AppleCommander: a Java based graphical tool for working with Apple II disk images.

AppleCommander 1.3.5.x

AppleCommander is Rob Greene's Java based graphical tool for working with Apple II disk images. The latest version adds support for cross-development with cc65, execution on i386 Mac OS X, and bug fixes. As before, support for Apple UCSD Pascal has been expanded to include import and delete, as well as 800K disk images. Export and view have been improved for several file types. More details may be found on the project web site, as they become available. The latest sources may be found in the CVS section of the AppleCommander project page on SourceForge. The command-line jar requires Java 1.3+, while the GUI requires Java 1.4+. The default ant target builds everything. Due to limited resources, the download link above is limited to the current release candidate, until changes are integrated into the next stable release.

Command Line

"ac" is a command line interface to Rob Greene's AppleCommander, a Java based tool for working with Apple II disk images. Because it is written in Java, ac runs on numerous platforms including Mac OS X and GNU/Linux. It works with ProDOS, Pascal, DOS 3.3 and other disk images. The program is ideally suited to automate the transfer of externally managed files to/from an Apple II disk image. The most recent version of the guide to command-line options is inclued with the current release candidate; the most recent stable release has been integrated into the AppleCommander website. Note that the order of some command options has been changed to make them more consistent. Use the -h option to see the current format.

The program is provided under the GNU Public License, a copy of which is included. 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.

Changes to Apple Commander v1.3.5

The following changes were made to AppleCommander v1.3.5:

  1. Handle multiple disks on command-line for -i, -ls, -l, -ll.
  2. Added lock, unlock and volume rename options.
  3. Fixed broken links in web site; added guide to release candidate.
  4. Fixed a bug in which 2IMG disks with optional comments were being misread.
  5. Fixed a bug in which DosFormatDisk.getFiles() was iterating tracks instead of sectors.
  6. Fix a bug in which file and volume names were truncated in disk create wizard.
  7. Added MS DOS script example to command line guide.

Changes to Apple Commander v1.3.4

The following changes were made to AppleCommander v1.3.4:

  1. Added -cc65 option to use header information for start address.
  2. Fixed a bug in which DosFormatDisk.createFile() was iterating tracks instead of sectors.
  3. Fixed a related bug in which the DOS start address wasn't set correctly when recycling a previously used directory entry.
  4. Enabled 800K Pascal disk images; added Pascal unit tests.
  5. Added command-line guide to web site.

Changes to Apple Commander v1.3.3

The following changes were made to AppleCommander v1.3.3 to make it compatible with ac:

  1. Note several new options, including new disk formats and a summary of disk information. The input file size limit has been eliminated.
  2. The Integer Basic file filter fails when listing identifiers with numeric characters, e.g. A1. The rewritten filter fixes this.
  3. Only 800 KB .2mg files have the $40 (64) byte offset applied. Additional sizes should be added to the Disk constructor in storage.Disk:
        int offset = UniversalDiskImageLayout.OFFSET;
        if (diskImage.length == APPLE_800KB_DISK + offset ||
            diskImage.length == APPLE_5MB_HARDDISK + offset ||
            diskImage.length == APPLE_10MB_HARDDISK + offset ||
            diskImage.length == APPLE_20MB_HARDDISK + offset ||
            diskImage.length == APPLE_32MB_HARDDISK + offset) {
            diskImageManager = new UniversalDiskImageLayout(diskImage);
        } else {
            diskImageManager = new ByteArrayImageLayout(diskImage);
        }
    
  4. The constant OFFSET, defined in UniversalDiskImageLayout, should be used throughout the code.
  5. The word at offset $12 (18) of the Pascal volume directory entry is not a date; it is the first block of the volume. The word at offset $14 (20) of this entry is a date. For normal volumes, it is the date put there by whatever program created the volume; it remains unchanged until the volume is reformatted. The Pascal 1.3 system formatter inserts the date "11/17/1984", while AppleCommander uses the current date. For boot volumes, this field is the last date set in the Filer; it appears at boot time. The methods in PascalFormatDisk related to disk information have been changed accordingly.
  6. Pascal strings do not have the high bit set. You were thinking of DOS:-) AppleUtil.setPascalString() has been modified to call setString with false for highBitOn:
        setString(buffer, offset+1, string, len, false);
    
  7. Extensive changes have been made to allow creating and deleting files on Pascal format disks. Text files are paged correctly for the compiler, and line endings are coverted to CR. Leading spaces are compressed using DLE, as described in PascalTextFileFilter.java. In contrast, code and data files are written verbatim. See the files PascalFormatDisk.java and PascalFileEntry.java.
  8. Pascal text files usually end with ".TEXT". This may imply a change in PascalTextFileFilter.getSuggestedFileName().

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
  3.     /**
         * 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 data 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);
        }
    
  4. In storage.PascalFileEntry.getFileColumnData use SimpleDateFormat("dd-MMM-yy").
  5. In PascalFormatDisk.getDiskName add a ":" to the volume name.
  6. Delete spurious ':' in @author tags.
  7. Review javadoc warnings about broken @see tags.
  8. 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;
        }
    
  9. 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
    
  10. Also, call setSeedlingFile(), rather than setSaplingFile().
  11. 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);
        }
    
  12. Initailize header pointer in ProdosFormatDisk.createFile(), add
        int headerBlock = blockNumber;
        ...
        fileEntry.setHeaderPointer(headerBlock);
    
  13. 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);
        }
    
  14. 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();
        }
    
  15. 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 07-Jun-2008
Return home.