Adding disassembly extract/view support.

This commit is contained in:
Rob Greene 2022-03-06 14:32:10 -06:00
parent 0842f1f31f
commit d19676fa9e
12 changed files with 331 additions and 21 deletions

View File

@ -0,0 +1,77 @@
/*
* 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;
import java.util.Arrays;
import java.util.List;
import java.util.function.Supplier;
import com.webcodepro.applecommander.storage.FileFilter;
import com.webcodepro.applecommander.storage.filters.AppleWorksDataBaseFileFilter;
import com.webcodepro.applecommander.storage.filters.AppleWorksSpreadSheetFileFilter;
import com.webcodepro.applecommander.storage.filters.AppleWorksWordProcessorFileFilter;
import com.webcodepro.applecommander.storage.filters.ApplesoftFileFilter;
import com.webcodepro.applecommander.storage.filters.AssemblySourceFileFilter;
import com.webcodepro.applecommander.storage.filters.BinaryFileFilter;
import com.webcodepro.applecommander.storage.filters.BusinessBASICFileFilter;
import com.webcodepro.applecommander.storage.filters.DisassemblyFileFilter;
import com.webcodepro.applecommander.storage.filters.GraphicsFileFilter;
import com.webcodepro.applecommander.storage.filters.GutenbergFileFilter;
import com.webcodepro.applecommander.storage.filters.HexDumpFileFilter;
import com.webcodepro.applecommander.storage.filters.IntegerBasicFileFilter;
import com.webcodepro.applecommander.storage.filters.PascalTextFileFilter;
import com.webcodepro.applecommander.storage.filters.TextFileFilter;
import io.github.applecommander.filters.AppleSingleFileFilter;
import io.github.applecommander.filters.RawFileFilter;
public enum ExportMethod {
APPLESINGLE(AppleSingleFileFilter::new, "as", "applesingle"),
APPLESOFT(ApplesoftFileFilter::new, "bas", "applesoft"),
APPLEWORKS_DATABASE(AppleWorksDataBaseFileFilter::new, "adb"),
APPLEWORKS_SPREADSHEET(AppleWorksSpreadSheetFileFilter::new, "asp"),
APPLEWORKS_WORDPROCESSOR(AppleWorksWordProcessorFileFilter::new, "awp"),
ASSEMBLY_SOURCE(AssemblySourceFileFilter::new, "asm", "assembly"),
BINARY(BinaryFileFilter::new, "bin", "binary"),
BUSINESS_BASIC(BusinessBASICFileFilter::new, "bbas", "business-basic"),
DISASSEMBLY(DisassemblyFileFilter::new, "disasm", "disassembly"),
GRAPHICS(GraphicsFileFilter::new, "gr", "graphics"),
GUTENBERG_FILE(GutenbergFileFilter::new, "gutenberg"),
HEX_DUMP(HexDumpFileFilter::new, "hex"),
INTEGER_BASIC(IntegerBasicFileFilter::new, "int", "integer"),
PASCAL_TEXT(PascalTextFileFilter::new, "ptext", "pascal-text"),
RAW(RawFileFilter::new, "raw"),
TEXT(TextFileFilter::new, "text");
private Supplier<FileFilter> constructor;
private List<String> codes;
private ExportMethod(Supplier<FileFilter> constructor, String... codes) {
this.constructor = constructor;
this.codes = Arrays.asList(codes);
}
public FileFilter create() {
return constructor.get();
}
public List<String> getCodes() {
return codes;
}
}

View File

@ -39,9 +39,10 @@ import com.webcodepro.applecommander.util.filestreamer.FileStreamer;
import com.webcodepro.applecommander.util.filestreamer.FileTuple;
import com.webcodepro.applecommander.util.filestreamer.TypeOfFile;
import io.github.applecommander.acx.ExportMethod;
import io.github.applecommander.acx.base.ReadOnlyDiskImageCommandOptions;
import io.github.applecommander.filters.AppleSingleFileFilter;
import io.github.applecommander.filters.RawFileFilter;
import io.github.applecommander.acx.converter.ExportMethodConverter;
import io.github.applecommander.acx.converter.ExportMethodConverter.ExportMethodCandidates;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Command;
import picocli.CommandLine.Model.CommandSpec;
@ -143,13 +144,21 @@ public class ExportCommand extends ReadOnlyDiskImageCommandOptions {
private static class FileExtractMethods {
private Function<FileEntry,FileFilter> extractFunction = this::asSuggestedFile;
@Option(names = { "--method" }, converter = ExportMethodConverter.class,
completionCandidates = ExportMethodCandidates.class,
description = "Select a specific export method type (${COMPLETION-CANDIDATES}).")
public void selectExportMethod(final ExportMethod exportMethod) {
this.extractFunction = fileFilter -> exportMethod.create();
}
// Short-cuts to some of the more common, non-suggested, filters
@Option(names = { "--raw", "--binary" }, description = "Extract file in native format.")
public void setBinaryExtraction(boolean flag) {
this.extractFunction = this::asRawFile;
selectExportMethod(ExportMethod.BINARY);
}
@Option(names = { "--hex", "--dump" }, description = "Extract file in hex dump format.")
public void setHexDumpExtraction(boolean flag) {
this.extractFunction = this::asHexDumpFile;
selectExportMethod(ExportMethod.HEX_DUMP);
}
@Option(names = { "--suggested" }, description = "Extract file as suggested by AppleCommander (default)")
public void setSuggestedExtraction(boolean flag) {
@ -157,12 +166,13 @@ public class ExportCommand extends ReadOnlyDiskImageCommandOptions {
}
@Option(names = { "--as", "--applesingle" }, description = "Extract file to AppleSingle file.")
public void setAppleSingleExtraction(boolean flag) {
this.extractFunction = this::asAppleSingleFile;
selectExportMethod(ExportMethod.APPLESINGLE);
}
@Option(names = { "--disassembly" }, description = "Dissassembly file.")
public void setDisassemblyExtraction(boolean flag) {
selectExportMethod(ExportMethod.DISASSEMBLY);
}
public FileFilter asRawFile(FileEntry entry) {
return new RawFileFilter();
}
public FileFilter asSuggestedFile(FileEntry entry) {
FileFilter ff = entry.getSuggestedFilter();
if (ff instanceof BinaryFileFilter) {
@ -170,11 +180,5 @@ public class ExportCommand extends ReadOnlyDiskImageCommandOptions {
}
return ff;
}
public FileFilter asHexDumpFile(FileEntry entry) {
return new HexDumpFileFilter();
}
public FileFilter asAppleSingleFile(FileEntry entry) {
return new AppleSingleFileFilter();
}
}
}

View File

@ -0,0 +1,57 @@
/*
* 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.converter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import io.github.applecommander.acx.ExportMethod;
import picocli.CommandLine.ITypeConverter;
import picocli.CommandLine.TypeConversionException;
public class ExportMethodConverter implements ITypeConverter<ExportMethod> {
public static final Map<String,ExportMethod> EXPORTS = new HashMap<>();
static {
for (ExportMethod x : ExportMethod.values()) {
for (String code : x.getCodes()) {
EXPORTS.put(code, x);
}
}
}
@Override
public ExportMethod convert(String value) throws Exception {
if (EXPORTS.containsKey(value)) {
return EXPORTS.get(value);
}
throw new TypeConversionException(String.format("Export method not found: %s", value));
}
public static class ExportMethodCandidates extends ArrayList<String> {
private static final long serialVersionUID = -744232190636905235L;
ExportMethodCandidates() {
super(EXPORTS.keySet());
Collections.sort(this);
}
}
}

View File

@ -16,3 +16,4 @@ commonsCsvVersion=1.8
gsonVersion=2.8.6
picocliVersion=4.6.2
springBoot=2.6.1
acdasmVersion=0.3.0

View File

@ -16,6 +16,7 @@ repositories {
dependencies {
implementation "net.sf.applecommander:ShrinkItArchive:$shkVersion"
implementation "net.sf.applecommander:acdasm:$acdasmVersion"
implementation "org.apache.commons:commons-csv:$commonsCsvVersion"
implementation "com.google.code.gson:gson:$gsonVersion"

View File

@ -0,0 +1,75 @@
/*
* AppleCommander - An Apple ][ image utility.
* Copyright (C) 2002-2022 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.filters;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import java.util.stream.Collectors;
import com.webcodepro.applecommander.storage.FileEntry;
import com.webcodepro.applecommander.storage.FileFilter;
import io.github.applecommander.disassembler.api.Disassembler;
import io.github.applecommander.disassembler.api.Instruction;
import io.github.applecommander.disassembler.api.mos6502.InstructionSet6502;
/**
* Disassemble the given set of bytes.
*/
public class DisassemblyFileFilter implements FileFilter {
public byte[] filter(FileEntry fileEntry) {
List<Instruction> instructions = Disassembler.with(fileEntry.getFileData())
.startingAddress(fileEntry.getAddress())
.use(InstructionSet6502.for6502())
.decode();
String code = instructions.stream()
.map(this::emitRaw)
.collect(Collectors.joining());
return code.getBytes();
}
public String getSuggestedFileName(FileEntry fileEntry) {
String fileName = fileEntry.getFilename().trim();
if (!fileName.toLowerCase().endsWith(".asm")) {
fileName = fileName + ".asm";
}
return fileName;
}
public String emitRaw(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();
}
}

View File

@ -37,6 +37,7 @@ import com.webcodepro.applecommander.storage.filters.ApplesoftFileFilter;
import com.webcodepro.applecommander.storage.filters.AssemblySourceFileFilter;
import com.webcodepro.applecommander.storage.filters.BinaryFileFilter;
import com.webcodepro.applecommander.storage.filters.BusinessBASICFileFilter;
import com.webcodepro.applecommander.storage.filters.DisassemblyFileFilter;
import com.webcodepro.applecommander.storage.filters.GraphicsFileFilter;
import com.webcodepro.applecommander.storage.filters.IntegerBasicFileFilter;
import com.webcodepro.applecommander.storage.filters.TextFileFilter;
@ -589,6 +590,8 @@ public class ProdosFileEntry extends ProdosCommonEntry implements FileEntry {
filter.setMode(GraphicsFileFilter.MODE_QUICKDRAW2_ICON);
return filter;
}
case 0xff:
return new DisassemblyFileFilter();
}
return new BinaryFileFilter();
}

View File

@ -351,6 +351,9 @@ FileViewerWindow.RawDumpTooltip=Displays file as a raw hex dump (F4)
FileViewerWindow.CopyButton=Copy
FileViewerWindow.CopyTooltip=Copies selection to the clipboard (CTRL+C)
FileViewerWindow.PrintTooltip=Print contents... (CTRL+P)
FileViewerWindow.DisassemblyButton=Disassembly
FileViewerWindow.DisassemblyTooltip=Displays file disassembled
# DiskWindow
DiskWindow.Title=AppleCommander Disk View - {0}

View File

@ -30,6 +30,7 @@ import org.junit.Test;
import com.webcodepro.applecommander.storage.FormattedDisk.DiskUsage;
import com.webcodepro.applecommander.storage.filters.ApplesoftFileFilter;
import com.webcodepro.applecommander.storage.filters.BinaryFileFilter;
import com.webcodepro.applecommander.storage.filters.DisassemblyFileFilter;
import com.webcodepro.applecommander.storage.filters.GraphicsFileFilter;
import com.webcodepro.applecommander.storage.filters.IntegerBasicFileFilter;
import com.webcodepro.applecommander.storage.filters.TextFileFilter;
@ -69,7 +70,7 @@ public class DiskHelperTest {
FormattedDisk[] disks = showDirectory(config.getDiskDir() + "/Prodos.dsk"); //$NON-NLS-1$
assertApplesoftFile(disks[0], "COPY.ME"); //$NON-NLS-1$
assertBinaryFile(disks[0], "SETTINGS"); //$NON-NLS-1$
assertBinaryFile(disks[0], "PRODOS"); //$NON-NLS-1$
assertDisassemblyFile(disks[0], "PRODOS"); //$NON-NLS-1$
}
@Test
@ -233,6 +234,14 @@ public class DiskHelperTest {
fileEntry.getSuggestedFilter() instanceof BinaryFileFilter);
}
protected void assertDisassemblyFile(FormattedDisk disk, String filename) throws DiskException {
assertNotNull(filename + " test: Disk should not be null", disk);
FileEntry fileEntry = disk.getFile(filename);
assertNotNull(filename + " test: File not found", disk);
assertTrue("DisassemblyFileFilter was not chosen",
fileEntry.getSuggestedFilter() instanceof DisassemblyFileFilter);
}
protected void assertGraphicsFile(FormattedDisk disk, String filename) throws DiskException {
assertNotNull(filename + " test: Disk should not be null", disk); //$NON-NLS-1$
FileEntry fileEntry = disk.getFile(filename);

View File

@ -21,6 +21,7 @@ package com.webcodepro.applecommander.ui.swt;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
@ -48,6 +49,7 @@ import com.webcodepro.applecommander.storage.filters.AppleWorksWordProcessorFile
import com.webcodepro.applecommander.storage.filters.ApplesoftFileFilter;
import com.webcodepro.applecommander.storage.filters.AssemblySourceFileFilter;
import com.webcodepro.applecommander.storage.filters.BusinessBASICFileFilter;
import com.webcodepro.applecommander.storage.filters.DisassemblyFileFilter;
import com.webcodepro.applecommander.storage.filters.GraphicsFileFilter;
import com.webcodepro.applecommander.storage.filters.IntegerBasicFileFilter;
import com.webcodepro.applecommander.storage.filters.PascalTextFileFilter;
@ -56,6 +58,7 @@ import com.webcodepro.applecommander.storage.filters.GutenbergFileFilter;
import com.webcodepro.applecommander.ui.UiBundle;
import com.webcodepro.applecommander.ui.swt.filteradapter.ApplesoftFilterAdapter;
import com.webcodepro.applecommander.ui.swt.filteradapter.BusinessBASICFilterAdapter;
import com.webcodepro.applecommander.ui.swt.filteradapter.DisassemblyFilterAdapter;
import com.webcodepro.applecommander.ui.swt.filteradapter.FilterAdapter;
import com.webcodepro.applecommander.ui.swt.filteradapter.GraphicsFilterAdapter;
import com.webcodepro.applecommander.ui.swt.filteradapter.HexFilterAdapter;
@ -90,6 +93,7 @@ public class FileViewerWindow {
private ToolBar toolBar;
private ToolItem nativeToolItem;
private ToolItem hexDumpToolItem;
private Optional<ToolItem> disassemblyToolItem = Optional.empty(); // May or may not be setup
private ToolItem rawDumpToolItem;
private ToolItem copyToolItem;
@ -103,6 +107,7 @@ public class FileViewerWindow {
private FilterAdapter nativeFilterAdapter;
private FilterAdapter hexFilterAdapter;
private FilterAdapter rawDumpFilterAdapter;
private FilterAdapter disassemblyFilterAdapter;
/**
* Construct the file viewer window.
@ -222,6 +227,11 @@ public class FileViewerWindow {
textBundle.get("FileViewerWindow.TextTooltip"), //$NON-NLS-1$
imageManager.get(ImageManager.ICON_VIEW_AS_TEXTFILE)
));
nativeFilterAdapterMap.put(DisassemblyFileFilter.class,
new DisassemblyFilterAdapter(this, textBundle.get("FileViewerWindow.DisassemblyButton"),
textBundle.get("FileViewerWindow.DisassemblyTooltip"),
imageManager.get(ImageManager.ICON_COMPILE_FILE)
));
}
/**
@ -261,6 +271,10 @@ public class FileViewerWindow {
nativeFilterAdapter = hexFilterAdapter;
}
rawDumpToolItem = createRawDumpToolItem();
// Add the disassembly button only if it's not the default and if this filetype has a start address.
if (fileEntry != null && fileEntry.needsAddress() && !(nativeFilter instanceof DisassemblyFileFilter)) {
disassemblyToolItem = Optional.of(createDisassemblyToolItem());
}
new ToolItem(toolBar, SWT.SEPARATOR);
copyToolItem = createCopyToolItem();
new ToolItem(toolBar, SWT.SEPARATOR);
@ -291,7 +305,19 @@ public class FileViewerWindow {
ToolItem toolItem = rawDumpFilterAdapter.create(toolBar);
return toolItem;
}
/**
* Create the disassembly tool item (button).
*/
protected ToolItem createDisassemblyToolItem() {
disassemblyFilterAdapter = new DisassemblyFilterAdapter(this, textBundle.get("FileViewerWindow.DisassemblyButton"), //$NON-NLS-1$
textBundle.get("FileViewerWindow.DisassemblyTooltip"), //$NON-NLS-1$
imageManager.get(ImageManager.ICON_COMPILE_FILE));
disassemblyFilterAdapter.setDisassemblySelected();
ToolItem toolItem = disassemblyFilterAdapter.create(toolBar);
return toolItem;
}
/**
* Create the copy tool item (button).
*/
@ -351,15 +377,15 @@ public class FileViewerWindow {
switch (event.keyCode) {
case SWT.F2: // the "native" file format (image, text, etc)
getNativeFilterAdapter().display();
setFilterToolItemSelection(true, false, false);
setFilterToolItemSelection(true, false, false, false);
break;
case SWT.F3: // Hex format
getHexFilterAdapter().display();
setFilterToolItemSelection(false, true, false);
setFilterToolItemSelection(false, true, false, false);
break;
case SWT.F4: // "Raw" hex format
getRawDumpFilterAdapter().display();
setFilterToolItemSelection(false, false, true);
setFilterToolItemSelection(false, false, true, false);
break;
}
}
@ -395,10 +421,11 @@ public class FileViewerWindow {
public Color getBlueColor() {
return blue;
}
public void setFilterToolItemSelection(boolean nativeSelected, boolean hexSelected, boolean dumpSelected) {
public void setFilterToolItemSelection(boolean nativeSelected, boolean hexSelected, boolean dumpSelected, boolean disassemblySelected) {
if (nativeToolItem != null) nativeToolItem.setSelection(nativeSelected);
hexDumpToolItem.setSelection(hexSelected);
rawDumpToolItem.setSelection(dumpSelected);
disassemblyToolItem.ifPresent(toolItem -> toolItem.setSelection(disassemblySelected));
}
protected ContentTypeAdapter getContentTypeAdapter() {
return contentTypeAdapter;

View File

@ -0,0 +1,40 @@
/*
* AppleCommander - An Apple ][ image utility.
* Copyright (C) 2002-2022 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.ui.swt.filteradapter;
import org.eclipse.swt.graphics.Image;
import com.webcodepro.applecommander.storage.filters.DisassemblyFileFilter;
import com.webcodepro.applecommander.ui.swt.FileViewerWindow;
/**
* Provides a view of the dissasembly of a program as seen when loaded from the disk.
*
* @author Rob Greene
*/
public class DisassemblyFilterAdapter extends TextFilterAdapter {
public DisassemblyFilterAdapter(FileViewerWindow window, String text, String toolTipText, Image image) {
super(window, text, toolTipText, image);
}
protected String createTextContent() {
return new String(new DisassemblyFileFilter().filter(getFileEntry()));
}
}

View File

@ -50,6 +50,7 @@ public abstract class FilterAdapter {
private boolean nativeSelected = true;
private boolean hexSelected = false;
private boolean dumpSelected = false;
private boolean disassemblySelected = false;
public FilterAdapter(FileViewerWindow window, String text, String toolTipText,
Image image) {
@ -77,7 +78,7 @@ public abstract class FilterAdapter {
public void widgetSelected(SelectionEvent e) {
display();
getWindow().setFilterToolItemSelection(
isNativeSelected(), isHexSelected(), isDumpSelected());
isNativeSelected(), isHexSelected(), isDumpSelected(), isDisassemblySelected());
}
});
}
@ -130,16 +131,25 @@ public abstract class FilterAdapter {
nativeSelected = false;
hexSelected = false;
dumpSelected = true;
disassemblySelected = false;
}
public void setHexSelected() {
nativeSelected = false;
hexSelected = true;
dumpSelected = false;
disassemblySelected = false;
}
public void setDisassemblySelected() {
nativeSelected = false;
hexSelected = false;
dumpSelected = false;
disassemblySelected = true;
}
public void setNativeSelected() {
nativeSelected = true;
hexSelected = false;
dumpSelected = false;
disassemblySelected = false;
}
protected boolean isDumpSelected() {
return dumpSelected;
@ -147,6 +157,9 @@ public abstract class FilterAdapter {
protected boolean isHexSelected() {
return hexSelected;
}
protected boolean isDisassemblySelected() {
return disassemblySelected;
}
protected boolean isNativeSelected() {
return nativeSelected;
}