diff --git a/app/cli-acx/build.gradle b/app/cli-acx/build.gradle index 302ec75..c9c2b41 100644 --- a/app/cli-acx/build.gradle +++ b/app/cli-acx/build.gradle @@ -17,6 +17,7 @@ dependencies { implementation "net.sf.applecommander:ShrinkItArchive:$shkVersion" implementation "net.sf.applecommander:applesingle-api:$asVersion" implementation "net.sf.applecommander:bastools-api:$btVersion" + implementation "net.sf.applecommander:acdasm:$acdasmVersion" testImplementation "junit:junit:$junitVersion" } diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/Main.java b/app/cli-acx/src/main/java/io/github/applecommander/acx/Main.java index fce9895..4d1e7c6 100644 --- a/app/cli-acx/src/main/java/io/github/applecommander/acx/Main.java +++ b/app/cli-acx/src/main/java/io/github/applecommander/acx/Main.java @@ -30,6 +30,7 @@ import io.github.applecommander.acx.command.CopyFileCommand; import io.github.applecommander.acx.command.CreateDiskCommand; import io.github.applecommander.acx.command.DeleteCommand; import io.github.applecommander.acx.command.DiskMapCommand; +import io.github.applecommander.acx.command.DumpCommand; import io.github.applecommander.acx.command.ExportCommand; import io.github.applecommander.acx.command.ImportCommand; import io.github.applecommander.acx.command.InfoCommand; @@ -60,6 +61,7 @@ import picocli.CommandLine.Option; CreateDiskCommand.class, DeleteCommand.class, DiskMapCommand.class, + DumpCommand.class, ExportCommand.class, HelpCommand.class, ImportCommand.class, diff --git a/app/cli-acx/src/main/java/io/github/applecommander/acx/command/DumpCommand.java b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/DumpCommand.java new file mode 100644 index 0000000..2e8f316 --- /dev/null +++ b/app/cli-acx/src/main/java/io/github/applecommander/acx/command/DumpCommand.java @@ -0,0 +1,139 @@ +/* + * AppleCommander - An Apple ][ image utility. + * Copyright (C) 2019-2022 by Robert Greene and others + * 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 io.github.applecommander.acx.command; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.function.BiFunction; +import java.util.stream.Collectors; + +import com.webcodepro.applecommander.storage.Disk; +import com.webcodepro.applecommander.util.AppleUtil; + +import io.github.applecommander.acx.base.ReadOnlyDiskImageCommandOptions; +import io.github.applecommander.disassembler.api.Disassembler; +import io.github.applecommander.disassembler.api.Instruction; +import io.github.applecommander.disassembler.api.mos6502.InstructionSet6502; +import picocli.CommandLine.ArgGroup; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +@Command(name = "dump", description = "Dump a block or sector.") +public class DumpCommand extends ReadOnlyDiskImageCommandOptions { + @ArgGroup(multiplicity = "1", heading = "%nCoordinate Selection:%n") + private CoordinateSelection coordinate = new CoordinateSelection(); + + @ArgGroup(heading = "%nOutput Selection:%n") + private OutputSelection output = new OutputSelection(); + + @Option(names = { "-a", "--address" }, description = "Starting address.") + private int address = 0x800; + + @Override + public int handleCommand() throws Exception { + byte[] data = coordinate.read(disk); + System.out.println(output.format(address, data)); + return 0; + } + + public static class CoordinateSelection { + @ArgGroup(exclusive = false) + private SectorCoordinateSelection sectorCoordinate; + @ArgGroup(exclusive = false) + private BlockCoordinateSelection blockCoordinate; + + public byte[] read(Disk disk) { + if (sectorCoordinate != null) { + return sectorCoordinate.read(disk); + } + else if (blockCoordinate != null) { + return blockCoordinate.read(disk); + } + return disk.readSector(0, 0); + } + + public static class SectorCoordinateSelection { + @Option(names = { "-t", "--track" }, required = true, description = "Track number.") + private Integer track; + @Option(names = { "-s", "--sector" }, required = true, description = "Sector number.") + private Integer sector; + + public byte[] read(Disk disk) { + return disk.readSector(track, sector); + } + } + public static class BlockCoordinateSelection { + @Option(names = { "-b", "--block" }, description = "Block number.") + private Integer block; + + public byte[] read(Disk disk) { + return disk.readBlock(block); + } + } + } + + public static class OutputSelection { + private BiFunction fn = this::formatHexDump; + public String format(int address, byte[] data) { + return fn.apply(address, data); + } + + @Option(names = "--hex", description = "Hex dump.") + public void selectHexDump(boolean flag) { + fn = this::formatHexDump; + } + + @Option(names = "--disassembly", description = "Disassembly.") + public void selectDisassembly(boolean flag) { + fn = this::formatDisassembly; + } + + public String formatHexDump(int address, byte[] data) { + return AppleUtil.getHexDump(address, data); + } + + public String formatDisassembly(int address, byte[] data) { + return Disassembler.with(data) + .startingAddress(address) + .use(InstructionSet6502.for6502()) + .decode() + .stream() + .map(this::formatInstruction) + .collect(Collectors.joining()); + + } + private String formatInstruction(Instruction instruction) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + pw.printf("%04X- ", instruction.getAddress()); + + byte[] code = instruction.getBytes(); + for (int i=0; i<3; i++) { + if (i >= code.length) { + pw.printf(" "); + } else { + pw.printf("%02X ", code[i]); + } + } + pw.printf(" %s\n", instruction.formatOperandWithValue()); + return sw.toString(); + } + } +} diff --git a/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/AppleUtil.java b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/AppleUtil.java index cd59083..5a2aa8c 100644 --- a/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/AppleUtil.java +++ b/lib/ac-api/src/main/java/com/webcodepro/applecommander/util/AppleUtil.java @@ -553,13 +553,17 @@ public class AppleUtil { * A = ASCII character. */ public static String getHexDump(byte[] bytes) { + return getHexDump(0, bytes); + } + + public static String getHexDump(int address, byte[] bytes) { ByteArrayOutputStream output = new ByteArrayOutputStream(); PrintWriter printer = new PrintWriter(output); printer.println(textBundle.get("AppleUtil.HexDumpLine1")); //$NON-NLS-1$ printer.println(textBundle.get("AppleUtil.HexDumpLine2")); //$NON-NLS-1$ for (int offset=0; offset