/* * Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA */ package jace; import jace.apple2e.MOS65C02; import jace.apple2e.RAM128k; import jace.apple2e.SoftSwitches; import jace.config.ConfigurationPanel; import jace.config.InvokableAction; import jace.core.CPU; import jace.core.Computer; import jace.core.Debugger; import jace.core.RAM; import jace.core.RAMEvent; import jace.core.RAMListener; import static jace.core.Utility.*; import jace.library.MediaLibrary; import jace.ui.AbstractEmulatorFrame; import jace.ui.DebuggerPanel; import java.awt.Color; import java.awt.HeadlessException; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.imageio.ImageIO; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTextField; /** * This class contains miscellaneous user-invoked actions such as debugger * operations and running arbitrary files in the emulator. It is possible for * these methods to be later refactored into more sensible locations. Created on * April 16, 2007, 10:30 PM * * @author Brendan Robert (BLuRry) brendan.robert@gmail.com */ public class EmulatorUILogic { static Debugger debugger; static { debugger = new Debugger() { @Override public void updateStatus() { enableDebug(true); MOS65C02 cpu = (MOS65C02) Computer.getComputer().getCpu(); updateCPURegisters(cpu); } }; } public static void updateCPURegisters(MOS65C02 cpu) { DebuggerPanel debuggerPanel = Emulator.getFrame().getDebuggerPanel(); debuggerPanel.valueA.setText(Integer.toHexString(cpu.A)); debuggerPanel.valueX.setText(Integer.toHexString(cpu.X)); debuggerPanel.valueY.setText(Integer.toHexString(cpu.Y)); debuggerPanel.valuePC.setText(Integer.toHexString(cpu.getProgramCounter())); debuggerPanel.valueSP.setText(Integer.toHexString(cpu.getSTACK())); debuggerPanel.valuePC2.setText(cpu.getFlags()); debuggerPanel.valueINST.setText(cpu.disassemble()); } public static void enableDebug(boolean b) { DebuggerPanel debuggerPanel = Emulator.getFrame().getDebuggerPanel(); debugger.setActive(b); debuggerPanel.enableDebug.setSelected(b); debuggerPanel.setBackground( b ? Color.RED : new Color(0, 0, 0x040)); } public static void enableTrace(boolean b) { Computer.getComputer().getCpu().setTraceEnabled(b); } public static void stepForward() { debugger.step = true; } static void registerDebugger() { Computer.getComputer().getCpu().setDebug(debugger); } public static Integer getValidAddress(String s) { try { int addr = Integer.parseInt(s.toUpperCase(), 16); if (addr >= 0 && addr < 0x10000) { return addr; } return null; } catch (NumberFormatException ex) { return null; } } public static List watches = new ArrayList<>(); public static void updateWatchList(final DebuggerPanel panel) { java.awt.EventQueue.invokeLater(() -> { watches.stream().forEach((oldWatch) -> { Computer.getComputer().getMemory().removeListener(oldWatch); }); if (panel == null) { return; } addWatch(panel.textW1, panel.valueW1); addWatch(panel.textW2, panel.valueW2); addWatch(panel.textW3, panel.valueW3); addWatch(panel.textW4, panel.valueW4); }); } private static void addWatch(JTextField watch, final JLabel watchValue) { final Integer address = getValidAddress(watch.getText()); if (address != null) { //System.out.println("Adding watch for "+Integer.toString(address, 16)); RAMListener newListener = new RAMListener(RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { @Override protected void doConfig() { setScopeStart(address); } @Override protected void doEvent(RAMEvent e) { watchValue.setText(Integer.toHexString(e.getNewValue() & 0x0FF)); } }; Computer.getComputer().getMemory().addListener(newListener); watches.add(newListener); // Print out the current value right away byte b = Computer.getComputer().getMemory().readRaw(address); watchValue.setText(Integer.toString(b & 0x0ff, 16)); } else { watchValue.setText("00"); } } public static void updateBreakpointList(final DebuggerPanel panel) { java.awt.EventQueue.invokeLater(() -> { Integer address; debugger.getBreakpoints().clear(); if (panel == null) { return; } address = getValidAddress(panel.textBP1.getText()); if (address != null) { debugger.getBreakpoints().add(address); } address = getValidAddress(panel.textBP2.getText()); if (address != null) { debugger.getBreakpoints().add(address); } address = getValidAddress(panel.textBP3.getText()); if (address != null) { debugger.getBreakpoints().add(address); } address = getValidAddress(panel.textBP4.getText()); if (address != null) { debugger.getBreakpoints().add(address); } debugger.updateBreakpoints(); }); } @InvokableAction( name = "BRUN file", category = "file", description = "Loads a binary file in memory and executes it. File should end with #06xxxx, where xxxx is the start address in hex", alternatives = "Execute program;Load binary;Load program;Load rom;Play single-load game") public static void runFile() { Computer.pause(); JFileChooser select = new JFileChooser(); select.showDialog(Emulator.getFrame(), "Execute binary file"); File binary = select.getSelectedFile(); if (binary == null) { Computer.resume(); return; } runFile(binary); } public static void runFile(File binary) { String fileName = binary.getName().toLowerCase(); try { if (fileName.contains("#06")) { String addressStr = fileName.substring(fileName.length() - 4); int address = Integer.parseInt(addressStr, 16); brun(binary, address); } else if (fileName.contains("#fc")) { gripe("BASIC not supported yet"); } } catch (NumberFormatException | IOException ex) { } Computer.getComputer().getCpu().resume(); } public static void brun(File binary, int address) throws FileNotFoundException, IOException { // If it was halted already, then it was initiated outside of an opcode execution // If it was not yet halted, then it is the case that the CPU is processing another opcode // So if that is the case, the program counter will need to be decremented here to compensate // TODO: Find a better mousetrap for this one -- it's an ugly hack Computer.pause(); FileInputStream in = new FileInputStream(binary); byte[] data = new byte[in.available()]; in.read(data); RAM ram = Computer.getComputer().getMemory(); for (int i = 0; i < data.length; i++) { ram.write(address + i, data[i], false, true); } CPU cpu = Computer.getComputer().getCpu(); Computer.getComputer().getCpu().setProgramCounter(address); Computer.resume(); } @InvokableAction( name = "Adjust display", category = "display", description = "Adjusts window size to 1:1 aspect ratio for optimal viewing.", alternatives = "Adjust screen;Adjust window size;Adjust aspect ratio;Fix screen;Fix window size;Fix aspect ratio;Correct aspect ratio;") static public void scaleIntegerRatio() { AbstractEmulatorFrame frame = Emulator.getFrame(); if (frame == null) { return; } Computer.pause(); frame.enforceIntegerRatio(); Computer.resume(); } @InvokableAction( name = "Toggle Debug", category = "debug", description = "Show/hide the debug panel", alternatives = "Show Debug;Hide Debug") public static void toggleDebugPanel() { AbstractEmulatorFrame frame = Emulator.getFrame(); if (frame == null) { return; } frame.setShowDebug(!frame.isShowDebug()); frame.reconfigure(); Emulator.resizeVideo(); } public static void toggleFullscreen() { AbstractEmulatorFrame frame = Emulator.getFrame(); if (frame == null) { return; } Computer.pause(); frame.toggleFullscreen(); Computer.resume(); } @InvokableAction( name = "Save Raw Screenshot", category = "general", description = "Save raw (RAM) format of visible screen", alternatives = "screendump, raw screenshot") public static void saveScreenshotRaw() throws FileNotFoundException, IOException { SimpleDateFormat df = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss"); String timestamp = df.format(new Date()); String type; int start = Computer.getComputer().getVideo().getCurrentWriter().actualWriter().getYOffset(0); int len = 0; if (start < 0x02000) { // Lo-res or double-lores len = 0x0400; type = "gr"; } else { // Hi-res or double-hires len = 0x02000; type = "hgr"; } boolean dres = SoftSwitches._80COL.getState() && (SoftSwitches.DHIRES.getState() || start < 0x02000); if (dres) { type = "d" + type; } File outFile = new File("screen_" + type + "_a" + Integer.toHexString(start) + "_" + timestamp); try (FileOutputStream out = new FileOutputStream(outFile)) { RAM128k ram = (RAM128k) Computer.getComputer().memory; Computer.pause(); if (dres) { for (int i = 0; i < len; i++) { out.write(ram.getAuxVideoMemory().readByte(start + i)); } } for (int i = 0; i < len; i++) { out.write(ram.getMainMemory().readByte(start + i)); } } System.out.println("Wrote screenshot to " + outFile.getAbsolutePath()); } @InvokableAction( name = "Save Screenshot", category = "general", description = "Save image of visible screen", alternatives = "Save image,save framebuffer,screenshot") public static void saveScreenshot() throws HeadlessException, IOException { JFileChooser select = new JFileChooser(); Computer.pause(); BufferedImage i = Computer.getComputer().getVideo().getFrameBuffer(); BufferedImage j = new BufferedImage(i.getWidth(), i.getHeight(), i.getType()); j.getGraphics().drawImage(i, 0, 0, null); select.showSaveDialog(Emulator.getFrame()); File targetFile = select.getSelectedFile(); if (targetFile == null) { return; } String filename = targetFile.getName(); System.out.println("Writing screenshot to " + filename); String extension = filename.substring(filename.lastIndexOf(".") + 1); ImageIO.write(j, extension, targetFile); } public static final String CONFIGURATION_DIALOG_NAME = "Configuration"; @InvokableAction( name = "Configuration", category = "general", description = "Edit emulator configuraion", alternatives = "Reconfigure,Preferences,Settings") public static void showConfig() { if (Emulator.getFrame().getModalDialogUI(CONFIGURATION_DIALOG_NAME) == null) { JPanel ui = new ConfigurationPanel(); Emulator.getFrame().registerModalDialog(ui, CONFIGURATION_DIALOG_NAME, null, false); } Emulator.getFrame().showDialog(CONFIGURATION_DIALOG_NAME); } public static final String MEDIA_MANAGER_DIALOG_NAME = "Media Manager"; public static final String MEDIA_MANAGER_EDIT_DIALOG_NAME = "Media Details"; @InvokableAction( name = "Media Manager", category = "general", description = "Show the media manager", alternatives = "Insert disk;Eject disk;Browse;Download;Select") public static void showMediaManager() { if (Emulator.getFrame().getModalDialogUI(MEDIA_MANAGER_DIALOG_NAME) == null) { Emulator.getFrame().registerModalDialog(MediaLibrary.getInstance().buildUserInterface(), MEDIA_MANAGER_DIALOG_NAME, null, false); } Emulator.getFrame().showDialog(MEDIA_MANAGER_DIALOG_NAME); } public static boolean confirm(String message) { return JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(Emulator.getFrame(), message); } }