/* * ac - an AppleCommander command line utility * Copyright (C) 2002 by Robert Greene * robgreene at users.sourceforge.net * Copyright (C) 2003, 2004 by John B. Matthews * matthewsj 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.ui; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintStream; import java.util.List; import com.webcodepro.applecommander.storage.DirectoryEntry; import com.webcodepro.applecommander.storage.Disk; import com.webcodepro.applecommander.storage.DiskException; import com.webcodepro.applecommander.storage.FileEntry; import com.webcodepro.applecommander.storage.FileFilter; import com.webcodepro.applecommander.storage.FormattedDisk; import com.webcodepro.applecommander.storage.FormattedDisk.DiskInformation; import com.webcodepro.applecommander.storage.filters.BinaryFileFilter; import com.webcodepro.applecommander.storage.filters.HexDumpFileFilter; import com.webcodepro.applecommander.storage.os.dos33.DosFormatDisk; import com.webcodepro.applecommander.storage.os.pascal.PascalFormatDisk; import com.webcodepro.applecommander.storage.os.prodos.ProdosFormatDisk; import com.webcodepro.applecommander.storage.physical.ByteArrayImageLayout; import com.webcodepro.applecommander.storage.physical.DosOrder; import com.webcodepro.applecommander.storage.physical.ImageOrder; import com.webcodepro.applecommander.storage.physical.ProdosOrder; import com.webcodepro.applecommander.util.AppleSingle; import com.webcodepro.applecommander.util.AppleSingle.ProdosFileInfo; import com.webcodepro.applecommander.util.AppleUtil; import com.webcodepro.applecommander.util.StreamUtil; import com.webcodepro.applecommander.util.TextBundle; /** * ac provides a command line interface to key AppleCommander functions. Text * similar to this is produced in response to the -h option. * *
* CommandLineHelp = AppleCommander command line options [{0}]: * -i <imagename> [<imagename>] display information about image(s). * -ls <imagename> [<imagename>] list brief directory of image(s). * -l <imagename> [<imagename>] list directory of image(s). * -ll <imagename> [<imagename>] list detailed directory of image(s). * -e <imagename> <filename> [<output>] export file from image to stdout * or to an output file. * -x <imagename> [<directory>] extract all files from image to directory. * -g <imagename> <filename> [<output>] get raw file from image to stdout * or to an output file. * -p <imagename> <filename> <type> [[$|0x]<addr>] put stdin * in filename on image, using file type and address [0x2000]. * -d <imagename> <filename> delete file from image. * -k <imagename> <filename> lock file on image. * -u <imagename> <filename> unlock file on image. * -n <imagename> <volname> change volume name (ProDOS or Pascal). * -cc65 <imagename> <filename> <type> put stdin with cc65 header * in filename on image, using file type and address from header. DEPRECATED. * -dos <imagename> <filename> <type> put stdin with cc65 header * in filename on image, using file type and address from header. * -as <imagename> [<filename>] put stdin with AppleSingle format * in filename on image, using file type, address, and (optionally) name * from the AppleSingle file. * -geos <imagename> interpret stdin as a ProDOS GEOS transfer file and place on image. * -dos140 <imagename> create a 140K DOS 3.3 image. * -pro140 <imagename> <volname> create a 140K ProDOS image. * -pro800 <imagename> <volname> create an 800K ProDOS image. * -pas140 <imagename> <volname> create a 140K Pascal image. * -pas800 <imagename> <volname> create an 800K Pascal image. * -convert <filename> <imagename> uncompress a ShrinkIt file or disk image * or convert a DiskCopy 4.2 image into a ProDOS disk image. ** * @author John B. Matthews * * Changed at: Dec 1, 2017 * @author Lisias Toledo */ public class ac { private static TextBundle textBundle = UiBundle.getInstance(); public static void main(String[] args) { try { if (args.length == 0) { help(); } else if ("-i".equalsIgnoreCase(args[0])) { //$NON-NLS-1$ getDiskInfo(args); } else if ("-ls".equalsIgnoreCase(args[0])) { //$NON-NLS-1$ showDirectory(args, FormattedDisk.FILE_DISPLAY_STANDARD); } else if ("-l".equalsIgnoreCase(args[0])) { //$NON-NLS-1$ showDirectory(args, FormattedDisk.FILE_DISPLAY_NATIVE); } else if ("-ll".equalsIgnoreCase(args[0])) { //$NON-NLS-1$ showDirectory(args, FormattedDisk.FILE_DISPLAY_DETAIL); } else if ("-e".equalsIgnoreCase(args[0])) { //$NON-NLS-1$ getFile(args[1], args[2], true, (args.length > 3 ? new PrintStream(new FileOutputStream(args[3])) : System.out)); } else if ("-x".equalsIgnoreCase(args[0])) { //$NON-NLS-1$ getFiles(args[1], (args.length > 2 ? args[2] : "")); } else if ("-g".equalsIgnoreCase(args[0])) { //$NON-NLS-1$ getFile(args[1], args[2], false, (args.length > 3 ? new PrintStream(new FileOutputStream(args[3])) : System.out)); } else if ("-p".equalsIgnoreCase(args[0])) { //$NON-NLS-1$ putFile(args[1], new Name(args[2]), args[3], (args.length > 4 ? args[4] : "0x2000")); } else if ("-d".equalsIgnoreCase(args[0])) { //$NON-NLS-1$ deleteFile(args[1], args[2]); } else if ("-k".equalsIgnoreCase(args[0])) { //$NON-NLS-1$ setFileLocked(args[1], args[2], true); } else if ("-u".equalsIgnoreCase(args[0])) { //$NON-NLS-1$ setFileLocked(args[1], args[2], false); } else if ("-n".equalsIgnoreCase(args[0])) { //$NON-NLS-1$ setDiskName(args[1], args[2]); } else if ("-cc65".equalsIgnoreCase(args[0])) { //$NON-NLS-1$ System.err.println("Note: -cc65 is deprecated. Please use -as or -dos as appropriate."); putDOS(args[1], new Name(args[2]), args[3]); } else if ("-dos".equalsIgnoreCase(args[0])) { //$NON-NLS-1$ putDOS(args[1], new Name(args[2]), args[3]); } else if ("-as".equalsIgnoreCase(args[0])) { putAppleSingle(args[1], args.length >= 3 ? args[2] : null); } else if ("-geos".equalsIgnoreCase(args[0])) { //$NON-NLS-1$ putGEOS(args[1]); } else if ("-dos140".equalsIgnoreCase(args[0])) { //$NON-NLS-1$ createDosDisk(args[1], Disk.APPLE_140KB_DISK); } else if ("-pas140".equalsIgnoreCase(args[0])) { //$NON-NLS-1$ createPasDisk(args[1], args[2], Disk.APPLE_140KB_DISK); } else if ("-pas800".equalsIgnoreCase(args[0])) { //$NON-NLS-1$ createPasDisk(args[1], args[2], Disk.APPLE_800KB_DISK); } else if ("-pro140".equalsIgnoreCase(args[0])) { //$NON-NLS-1$ createProDisk(args[1], args[2], Disk.APPLE_140KB_DISK); } else if ("-pro800".equalsIgnoreCase(args[0])) { //$NON-NLS-1$ createProDisk(args[1], args[2], Disk.APPLE_800KB_DISK); } else if ("-convert".equalsIgnoreCase(args[0])) { //$NON-NLS-1$ if (args.length > 3) convert(args[1], args[2], Integer.parseInt(args[3])); else convert(args[1], args[2]); } else { help(); } } catch (Exception ex) { System.err.println(textBundle.format("CommandLineErrorMessage", //$NON-NLS-1$ ex.getLocalizedMessage())); ex.printStackTrace(); help(); } } /** * Put fileName from the local filesytem into the file named fileOnImageName on the disk named imageName; * Note: only volume level supported; input size unlimited. */ public static void putFile(String fileName, String imageName, String fileOnImageName, String fileType, String address) throws IOException, DiskException { Name name = new Name(fileOnImageName); File file = new File(fileName); if (!file.canRead()) { throw new IOException("Unable to read input file named "+fileName+"."); // FIXME - NLS } ByteArrayOutputStream buf = new ByteArrayOutputStream(); byte[] inb = new byte[1024]; int byteCount = 0; try (InputStream is = new FileInputStream(file)) { while ((byteCount = is.read(inb)) > 0) { buf.write(inb, 0, byteCount); } Disk disk = new Disk(imageName); FormattedDisk[] formattedDisks = disk.getFormattedDisks(); FormattedDisk formattedDisk = formattedDisks[0]; FileEntry entry = name.createEntry(formattedDisk); if (entry != null) { entry.setFiletype(fileType); entry.setFilename(name.name); entry.setFileData(buf.toByteArray()); if (entry.needsAddress()) { entry.setAddress(stringToInt(address)); } formattedDisk.save(); } } } /** * Put <stdin>. into the file named fileName on the disk named imageName; * Note: only volume level supported; input size unlimited. */ static void putFile(String imageName, Name name, String fileType, String address) throws IOException, DiskException { putFile(imageName, name, fileType, address, System.in); } /** * Put InputStream into the file named fileName on the disk named imageName; * Note: only volume level supported; input size unlimited. */ static void putFile(String imageName, Name name, String fileType, String address, InputStream inputStream) throws IOException, DiskException { ByteArrayOutputStream buf = new ByteArrayOutputStream(); StreamUtil.copy(inputStream, buf); Disk disk = new Disk(imageName); FormattedDisk[] formattedDisks = disk.getFormattedDisks(); if (formattedDisks == null) System.out.println("Dude, formattedDisks is null!"); FormattedDisk formattedDisk = formattedDisks[0]; if (!disk.isSDK() && !disk.isDC42()) { FileEntry entry = name.createEntry(formattedDisk); if (entry != null) { entry.setFiletype(fileType); entry.setFilename(name.name); entry.setFileData(buf.toByteArray()); if (entry.needsAddress()) { entry.setAddress(stringToInt(address)); } formattedDisk.save(); } else { throw new IOException("Unable to create entry..."); } } else throw new IOException(textBundle.get("CommandLineSDKReadOnly")); //$NON-NLS-1$ } /** * Put file fileName into the file named fileOnImageName on the disk named imageName; * Assume a cc65 style four-byte header with start address in bytes 0-1. */ public static void putDOS(String fileName, String imageName, String fileOnImageName, String fileType) throws IOException, DiskException { byte[] header = new byte[4]; if (System.in.read(header, 0, 4) == 4) { int address = AppleUtil.getWordValue(header, 0); putFile(fileName, imageName, fileOnImageName, fileType, Integer.toString(address)); } } /** * Put <stdin> into the file named fileName on the disk named imageName; * Assume an DOS 3.x style four-byte header with start address in bytes 0-1. */ static void putDOS(String imageName, Name name, String fileType) throws IOException, DiskException { byte[] header = new byte[4]; if (System.in.read(header, 0, 4) == 4) { int address = AppleUtil.getWordValue(header, 0); putFile(imageName, name, fileType, Integer.toString(address)); } } /** * Put file from AppleSingle format into ProDOS image. */ public static void putAppleSingle(String imageName, String fileName) throws IOException, DiskException { putAppleSingle(imageName, fileName, System.in); } /** * AppleSingle shim to allow for unit testing. */ public static void putAppleSingle(String imageName, String fileName, InputStream inputStream) throws IOException, DiskException { AppleSingle as = new AppleSingle(inputStream); if (fileName == null) { fileName = as.getRealName(); } if (fileName == null) { throw new IOException("Please specify a file name - this AppleSingle does not have one."); } if (as.getProdosFileInfo() == null) { throw new IOException("This AppleSingle does not contain a ProDOS file."); } if (as.getDataFork() == null || as.getDataFork().length == 0) { throw new IOException("This AppleSingle does not contain a data fork."); } Name name = new Name(fileName); ProdosFileInfo info = as.getProdosFileInfo(); String fileType = ProdosFormatDisk.getFiletype(info.getFileType()); putFile(imageName, name, fileType, Integer.toString(info.getAuxType()), new ByteArrayInputStream(as.getDataFork())); } /** * Interpret <stdin> as a GEOS file and place it on the disk named imageName. * This would only make sense for a ProDOS-formatted disk. */ static void putGEOS(String imageName) throws IOException, DiskException { putFile(imageName, new Name("GEOS-Should Be ProDOS"), "GEO", "0"); //$NON-NLS-2$ $NON-NLS-3$ } /** * Delete the file named fileName from the disk named imageName. */ static void deleteFile(String imageName, String fileName) throws IOException, DiskException { Disk disk = new Disk(imageName); Name name = new Name(fileName); if (!disk.isSDK() && !disk.isDC42()) { FormattedDisk[] formattedDisks = disk.getFormattedDisks(); for (int i = 0; i < formattedDisks.length; i++) { FormattedDisk formattedDisk = formattedDisks[i]; FileEntry entry = name.getEntry(formattedDisk); if (entry != null) { entry.delete(); disk.save(); } else { System.err.println(textBundle.format( "CommandLineNoMatchMessage", name.fullName)); //$NON-NLS-1$ } } } else throw new IOException(textBundle.get("CommandLineSDKReadOnly")); } /** * Get the file named filename from the disk named imageName; the file is * filtered according to its type and sent to <stdout>. */ static void getFile(String imageName, String fileName, boolean filter, PrintStream out) throws IOException, DiskException { Disk disk = new Disk(imageName); Name name = new Name(fileName); FormattedDisk[] formattedDisks = disk.getFormattedDisks(); if (out == null) out = System.out; for (int i = 0; i < formattedDisks.length; i++) { FormattedDisk formattedDisk = formattedDisks[i]; FileEntry entry = name.getEntry(formattedDisk); if (entry != null) { if (filter) { FileFilter ff = entry.getSuggestedFilter(); if (ff instanceof BinaryFileFilter) ff = new HexDumpFileFilter(); byte[] buf = ff.filter(entry); out.write(buf, 0, buf.length); } else { byte[] buf = entry.getFileData(); out.write(buf, 0, buf.length); } } else { System.err.println(textBundle.format( "CommandLineNoMatchMessage", name.fullName)); //$NON-NLS-1$ } } } /** * Extract all files in the image according to their respective filetype. */ static void getFiles(String imageName, String directory) throws IOException, DiskException { Disk disk = new Disk(imageName); if ((directory != null) && (directory.length() > 0)) { // Add a final directory separator if the user didn't supply one if (!directory.endsWith(File.separator)) directory = directory + File.separator; } else { directory = "."+File.separator; } FormattedDisk[] formattedDisks = disk.getFormattedDisks(); for (int i = 0; i < formattedDisks.length; i++) { FormattedDisk formattedDisk = formattedDisks[i]; writeFiles(formattedDisk.getFiles(), directory); } } /** * Recursive routine to write directory and file entries. */ static void writeFiles(List