/* * 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.apple2e; import jace.Emulator; import jace.cheat.Cheats; import jace.config.ClassSelection; import jace.config.ConfigurableField; import jace.core.Card; import jace.core.Computer; import jace.core.Motherboard; import jace.core.RAM; import jace.core.RAMEvent; import jace.core.RAMListener; import jace.core.Utility; import jace.state.Stateful; import jace.core.Video; import jace.hardware.CardDiskII; import jace.hardware.CardExt80Col; import jace.hardware.ConsoleProbe; import jace.hardware.Joystick; import jace.hardware.NoSlotClock; import jace.hardware.massStorage.CardMassStorage; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; /** * Apple2e is a computer with a 65c02 CPU, 128k of bankswitched ram, * double-hires graphics, and up to seven peripheral I/O cards installed. Pause * and resume are implemented by the Motherboard class. This class provides * overall configuration of the computer, but the actual operation of the * computer and its timing characteristics are managed in the Motherboard class. * * @author Brendan Robert (BLuRry) brendan.robert@gmail.com */ @Stateful public class Apple2e extends Computer { static int IRQ_VECTOR = 0x003F2; @ConfigurableField(name = "Slot 1", shortName = "s1card") public ClassSelection card1 = new ClassSelection(Card.class, null); @ConfigurableField(name = "Slot 2", shortName = "s2card") public ClassSelection card2 = new ClassSelection(Card.class, null); @ConfigurableField(name = "Slot 3", shortName = "s3card") public ClassSelection card3 = new ClassSelection(Card.class, null); @ConfigurableField(name = "Slot 4", shortName = "s4card") public ClassSelection card4 = new ClassSelection(Card.class, null); @ConfigurableField(name = "Slot 5", shortName = "s5card") public ClassSelection card5 = new ClassSelection(Card.class, null); @ConfigurableField(name = "Slot 6", shortName = "s6card") public ClassSelection card6 = new ClassSelection(Card.class, CardDiskII.class); @ConfigurableField(name = "Slot 7", shortName = "s7card") public ClassSelection card7 = new ClassSelection(Card.class, CardMassStorage.class); @ConfigurableField(name = "Debug rom", shortName = "debugRom", description = "Use debugger //e rom") public boolean useDebugRom = false; @ConfigurableField(name = "Console probe", description = "Enable console redirection (experimental!)") public boolean useConsoleProbe = false; private ConsoleProbe probe = new ConsoleProbe(); @ConfigurableField(name = "Helpful hints", shortName = "hints") public boolean enableHints = true; @ConfigurableField(name = "Renderer", shortName = "video", description = "Video rendering implementation") public ClassSelection videoRenderer = new ClassSelection(Video.class, VideoNTSC.class); @ConfigurableField(name = "Aux Ram", shortName = "ram", description = "Aux ram card") public ClassSelection ramCard = new ClassSelection(RAM128k.class, CardExt80Col.class); @ConfigurableField(name = "Joystick 1 Enabled", shortName = "joy1", description = "If unchecked, then there is no joystick support.", enablesDevice = true) public boolean joy1enabled = true; @ConfigurableField(name = "Joystick 2 Enabled", shortName = "joy2", description = "If unchecked, then there is no joystick support.", enablesDevice = true) public boolean joy2enabled = false; @ConfigurableField(name = "No-Slot Clock Enabled", shortName = "clock", description = "If checked, no-slot clock will be enabled", enablesDevice = true) public boolean clockEnabled = true; public Joystick joystick1; public Joystick joystick2; @ConfigurableField(name = "Activate Cheats", shortName = "cheat", defaultValue = "") public ClassSelection cheatEngine = new ClassSelection(Cheats.class, null); public Cheats activeCheatEngine = null; public NoSlotClock clock; /** * Creates a new instance of Apple2e */ public Apple2e() { super(); try { reconfigure(); setCpu(new MOS65C02(this)); reinitMotherboard(); } catch (Throwable t) { System.err.println("Unable to initalize virtual machine"); t.printStackTrace(System.err); } } @Override public String getName() { return "Computer (Apple //e)"; } private void reinitMotherboard() { if (motherboard != null && motherboard.isRunning()) { motherboard.suspend(); } setMotherboard(new Motherboard(this, motherboard)); reconfigure(); motherboard.reconfigure(); } @Override public void coldStart() { pause(); reinitMotherboard(); for (SoftSwitches s : SoftSwitches.values()) { s.getSwitch().reset(); } getMemory().configureActiveMemory(); getVideo().configureVideoMode(); for (Optional<Card> c : getMemory().getAllCards()) { c.ifPresent(Card::reset); } reboot(); resume(); } public void reboot() { RAM r = getMemory(); r.write(IRQ_VECTOR, (byte) 0x00, false, true); r.write(IRQ_VECTOR + 1, (byte) 0x00, false, true); r.write(IRQ_VECTOR + 2, (byte) 0x00, false, true); warmStart(); } @Override public void warmStart() { boolean restart = pause(); for (SoftSwitches s : SoftSwitches.values()) { s.getSwitch().reset(); } getMemory().configureActiveMemory(); getVideo().configureVideoMode(); getCpu().reset(); for (Optional<Card> c : getMemory().getAllCards()) { c.ifPresent(Card::reset); } getCpu().resume(); resume(); } public Cheats getActiveCheatEngine() { return activeCheatEngine; } private void insertCard(Class<? extends Card> type, int slot) throws NoSuchMethodException, IllegalArgumentException, InvocationTargetException { if (getMemory().getCard(slot).isPresent()) { if (getMemory().getCard(slot).get().getClass().equals(type)) { return; } getMemory().removeCard(slot); } if (type != null) { try { Card card = type.getConstructor(Computer.class).newInstance(this); getMemory().addCard(card, slot); } catch (InstantiationException | IllegalAccessException ex) { Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex); } } } @Override public final void reconfigure() { boolean restart = pause(); if (Utility.isHeadlessMode()) { joy1enabled = false; joy2enabled = false; } super.reconfigure(); RAM128k currentMemory = (RAM128k) getMemory(); if (currentMemory != null && ramCard.getValue() != null && !(currentMemory.getClass().equals(ramCard.getValue()))) { try { RAM128k newMemory = (RAM128k) ramCard.getValue().getConstructor(Computer.class).newInstance(this); newMemory.copyFrom(currentMemory); setMemory(newMemory); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException ex) { Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex); } } if (getMemory() == null) { try { currentMemory = (RAM128k) ramCard.getValue().getConstructor(Computer.class).newInstance(this); } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException ex) { Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex); } catch (IllegalArgumentException | InvocationTargetException ex) { Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex); } try { setMemory(currentMemory); for (SoftSwitches s : SoftSwitches.values()) { s.getSwitch().register(this); } } catch (Throwable ex) { } } currentMemory.reconfigure(); if (motherboard != null) { if (joy1enabled) { if (joystick1 == null) { joystick1 = new Joystick(0, this); motherboard.miscDevices.add(joystick1); joystick1.attach(); } } else if (joystick1 != null) { joystick1.detach(); motherboard.miscDevices.remove(joystick1); joystick1 = null; } if (joy2enabled) { if (joystick2 == null) { joystick2 = new Joystick(1, this); motherboard.miscDevices.add(joystick2); joystick2.attach(); } } else if (joystick2 != null) { joystick2.detach(); motherboard.miscDevices.remove(joystick2); joystick2 = null; } if (clockEnabled) { if (clock == null) { clock = new NoSlotClock(this); motherboard.miscDevices.add(clock); clock.attach(); } } else if (clock != null) { motherboard.miscDevices.remove(clock); clock.detach(); clock = null; } } try { if (useConsoleProbe) { probe.init(this); } else { probe.shutdown(); } if (useDebugRom) { loadRom("jace/data/apple2e_debug.rom"); } else { loadRom("jace/data/apple2e.rom"); } if (getVideo() == null || getVideo().getClass() != videoRenderer.getValue()) { if (getVideo() != null) { getVideo().suspend(); } try { setVideo((Video) videoRenderer.getValue().getConstructor(Computer.class).newInstance(this)); getVideo().configureVideoMode(); getVideo().reconfigure(); Emulator.resizeVideo(); getVideo().resume(); } catch (InstantiationException | IllegalAccessException ex) { Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex); } catch (NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException ex) { Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex); } } try { // Add all new cards insertCard(card1.getValue(), 1); insertCard(card2.getValue(), 2); insertCard(card3.getValue(), 3); insertCard(card4.getValue(), 4); insertCard(card5.getValue(), 5); insertCard(card6.getValue(), 6); insertCard(card7.getValue(), 7); } catch (NoSuchMethodException | IllegalArgumentException | InvocationTargetException ex) { Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex); } if (enableHints) { enableHints(); } else { disableHints(); } getMemory().configureActiveMemory(); if (cheatEngine.getValue() == null) { if (activeCheatEngine != null) { activeCheatEngine.detach(); motherboard.miscDevices.remove(activeCheatEngine); } activeCheatEngine = null; } else { boolean startCheats = true; if (activeCheatEngine != null) { if (activeCheatEngine.getClass().equals(cheatEngine.getValue())) { startCheats = false; } else { activeCheatEngine.detach(); activeCheatEngine = null; motherboard.miscDevices.remove(activeCheatEngine); } } if (startCheats) { try { activeCheatEngine = (Cheats) cheatEngine.getValue().getConstructor(Computer.class).newInstance(this); } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException ex) { Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex); } activeCheatEngine.attach(); motherboard.miscDevices.add(activeCheatEngine); } } } catch (IOException ex) { Logger.getLogger(Apple2e.class.getName()).log(Level.SEVERE, null, ex); } if (restart) { resume(); } } @Override protected void doPause() { if (motherboard == null) { return; } motherboard.pause(); } @Override protected void doResume() { if (motherboard == null) { return; } motherboard.resume(); } // public boolean isRunning() { // if (motherboard == null) { // return false; // } // return motherboard.isRunning() && !motherboard.isPaused; // } private List<RAMListener> hints = new ArrayList<>(); ScheduledExecutorService animationTimer = new ScheduledThreadPoolExecutor(1); Runnable drawHints = () -> { if (getCpu().getProgramCounter() >> 8 != 0x0c6) { return; } int row = 2; for (String s : new String[]{ " Welcome to", " _ __ ___ ____ ", " | | / /\\ / / ` | |_ ", " \\_|_| /_/--\\ \\_\\_, |_|__ ", "", " Java Apple Computer Emulator", "", " Presented by BLuRry", " http://goo.gl/SnzqG", "", "To insert a disk, please drag it over", "this window and drop on the desired", "drive icon.", "", "Press CTRL+SHIFT+C for configuration.", "Press CTRL+SHIFT+I for IDE window.", "", "O-A: Alt/Option", "C-A: Shortcut/Command", "Reset: Delete/Backspace" }) { int addr = 0x0401 + VideoDHGR.calculateTextOffset(row++); for (char c : s.toCharArray()) { getMemory().write(addr++, (byte) (c | 0x080), false, true); } } }; int animAddr, animCycleNumber; byte animOldValue; final String animation = "+xX*+-"; ScheduledFuture animationSchedule; Runnable doAnimation = () -> { if (animAddr == 0 || animCycleNumber >= animation.length()) { if (animAddr > 0) { getMemory().write(animAddr, animOldValue, true, true); } int animX = (int) (Math.random() * 24.0) + 7; int animY = (int) (Math.random() * 3.0) + 3; animAddr = 0x0400 + VideoDHGR.calculateTextOffset(animY) + animX; animOldValue = getMemory().readRaw(animAddr); animCycleNumber = 0; } if (getCpu().getProgramCounter() >> 8 == 0x0c6) { getMemory().write(animAddr, (byte) (animation.charAt(animCycleNumber) | 0x080), true, true); animCycleNumber++; } else { getMemory().write(animAddr, animOldValue, true, true); animationSchedule.cancel(false); animAddr = 0; } }; private void enableHints() { if (hints.isEmpty()) { hints.add(getMemory().observe(RAMEvent.TYPE.EXECUTE, 0x0FB63, (e)->{ animationTimer.schedule(drawHints, 1, TimeUnit.SECONDS); animationSchedule = animationTimer.scheduleAtFixedRate(doAnimation, 1250, 100, TimeUnit.MILLISECONDS); })); // Latch to the PRODOS SYNTAX CHECK parser /* hints.add(new RAMListener(RAMEvent.TYPE.EXECUTE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { @Override protected void doConfig() {setScopeStart(0x0a685);} @Override protected void doEvent(RAMEvent e) { String in = ""; for (int i=0x0200; i < 0x0300; i++) { char c = (char) (getMemory().readRaw(i) & 0x07f); if (c == 0x0d) break; in += c; } System.err.println("Intercepted command: "+in); } }); */ } } private void disableHints() { hints.stream().forEach((hint) -> { getMemory().removeListener(hint); }); } @Override public String getShortName() { return "computer"; } }