diff --git a/src/main/java/jace/apple2e/Apple2e.java b/src/main/java/jace/apple2e/Apple2e.java index 04e4262..235c30b 100644 --- a/src/main/java/jace/apple2e/Apple2e.java +++ b/src/main/java/jace/apple2e/Apple2e.java @@ -34,6 +34,7 @@ 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; @@ -59,7 +60,6 @@ public class Apple2e extends Computer { @ConfigurableField(name = "Slot 1", shortName = "s1card") public ClassSelection card1 = new ClassSelection(Card.class, null); @ConfigurableField(name = "Slot 2", shortName = "s2card") -// public Class card2 = CardSSC.class; public ClassSelection card2 = new ClassSelection(Card.class, null); @ConfigurableField(name = "Slot 3", shortName = "s3card") public ClassSelection card3 = new ClassSelection(Card.class, null); @@ -86,12 +86,15 @@ public class Apple2e extends Computer { public boolean joy1enabled = false; @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 @@ -218,27 +221,43 @@ public class Apple2e extends Computer { } } currentMemory.reconfigure(); - - if (joy1enabled) { - if (joystick1 == null) { - joystick1 = new Joystick(0, this); - motherboard.miscDevices.add(joystick1); + + 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; } - } 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); + + 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; } - } else if (joystick2 != null) { - joystick2.detach(); - motherboard.miscDevices.remove(joystick2); - joystick2 = null; } try { diff --git a/src/main/java/jace/hardware/CardThunderclock.java b/src/main/java/jace/hardware/CardThunderclock.java index 7916831..f6891b8 100644 --- a/src/main/java/jace/hardware/CardThunderclock.java +++ b/src/main/java/jace/hardware/CardThunderclock.java @@ -67,8 +67,6 @@ public class CardThunderclock extends Card { Logger.getLogger(CardDiskII.class.getName()).log(Level.SEVERE, null, ex); } clockIcon = Utility.loadIconLabel("clock.png"); - clockFixIcon = Utility.loadIconLabel("clock_fix.png"); - clockFixIcon.setText("Fixed"); } // Raw format: 40 bits, in BCD form (it actually streams out in the reverse order of this, bit 0 first) @@ -151,7 +149,7 @@ public class CardThunderclock extends Card { shiftMode = isShift; if (isRead) { if (attemptYearPatch) { - performProdosPatch(); + performProdosPatch(computer); } getTime(); clockIcon.setText("Slot " + getSlot()); @@ -288,14 +286,14 @@ public class CardThunderclock extends Card { (byte) 0x0f2 }; static int DRIVER_OFFSET = -26; - int patchLoc = -1; + static int patchLoc = -1; /** * Scan active memory for the Prodos clock driver and patch the internal * code to use a fixed value for the present year. This means Prodos will * always tell time correctly. */ - private void performProdosPatch() { + public static void performProdosPatch(Computer computer) { PagedMemory ram = computer.getMemory().activeRead; if (patchLoc > 0) { // We've already patched, just validate @@ -325,6 +323,8 @@ public class CardThunderclock extends Card { ram.writeByte(patchLoc + 1, (byte) year); ram.writeByte(patchLoc + 2, (byte) MOS65C02.OPCODE.NOP.getCode()); ram.writeByte(patchLoc + 3, (byte) MOS65C02.OPCODE.NOP.getCode()); - EmulatorUILogic.addIndicator(this, clockFixIcon, 4000); + Label clockFixIcon = Utility.loadIconLabel("clock_fix.png"); + clockFixIcon.setText("Fixed"); + EmulatorUILogic.addIndicator(CardThunderclock.class, clockFixIcon, 4000); } } \ No newline at end of file diff --git a/src/main/java/jace/hardware/NoSlotClock.java b/src/main/java/jace/hardware/NoSlotClock.java new file mode 100644 index 0000000..7de0bd6 --- /dev/null +++ b/src/main/java/jace/hardware/NoSlotClock.java @@ -0,0 +1,167 @@ +package jace.hardware; + +import jace.EmulatorUILogic; +import jace.apple2e.SoftSwitches; +import jace.config.ConfigurableField; +import jace.core.Computer; +import jace.core.Device; +import jace.core.RAMEvent; +import jace.core.RAMListener; +import jace.core.Utility; +import java.util.Calendar; +import javafx.scene.control.Label; + +/** + * Provide No-slot-clock compatibility + * + * @author blurry + */ +public class NoSlotClock extends Device { + + boolean clockActive; + public long detectSequence = 0x5ca33ac55ca33ac5L; + public long testSequence = 0; + public long testMask = 0; + public long dataRegister = 0; + public long dataRegisterBit = 0; + public int patternCount = 0; + public boolean writeEnabled = false; + @ConfigurableField(category = "Clock", name = "Patch Prodos", description = "If enabled, prodos clock routines will be patched directly", defaultValue = "false") + public boolean patchProdosClock = false; + Label clockIcon; + + private final RAMListener listener = new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { + @Override + protected void doConfig() { + setScopeStart(0x0C100); + setScopeEnd(0x0CFFF); + } + + @Override + public boolean isRelevant(RAMEvent e) { + // Ref: Sather UAIIe 5-28 + if (SoftSwitches.CXROM.isOn()) { + return true; + } + if ((e.getAddress() & 0x0ff00) == 0x0c300 && SoftSwitches.SLOTC3ROM.isOff()) { + return true; + } + return e.getAddress() >= 0x0c800 && SoftSwitches.INTC8ROM.isOn(); + } + + @Override + protected void doEvent(RAMEvent e) { + boolean readMode = (e.getAddress() & 0x04) != 0; + if (clockActive) { + if (readMode) { + int val = e.getOldValue() & 0b011111110; + int bit = getNextDataBit(); + val |= bit; + e.setNewValue(val); + } else if (writeEnabled) { + fakeWrite(e); + } else { + return; + } + dataRegisterBit++; + if (dataRegisterBit >= 64) { + deactivateClock(); + } + } else if (readMode) { + writeEnabled = true; + testSequence = detectSequence; + patternCount = 0; + } else if (writeEnabled) { + int bit = e.getAddress() & 0x01; + if (bit == (testSequence & 0x01)) { + testSequence >>= 1; + patternCount++; + if (patternCount == 64) { + activateClock(); + } + } else { + writeEnabled = false; + } + } + } + }; + + public NoSlotClock(Computer computer) { + super(computer); + this.clockIcon = Utility.loadIconLabel("clock.png"); + this.clockIcon.setText("No Slot Clock"); + } + + @Override + protected String getDeviceName() { + return "No Slot Clock"; + } + + @Override + public String getShortName() { + return "clock"; + } + + @Override + public void tick() { + } + + @Override + public void reconfigure() { + } + + @Override + public void attach() { + computer.getMemory().addListener(listener); + } + + @Override + public void detach() { + computer.getMemory().removeListener(listener); + } + + public void activateClock() { + Calendar now = Calendar.getInstance(); + dataRegisterBit = 0; + dataRegister = 0L; + storeBCD(now.get(Calendar.MILLISECOND) / 10, 0); + storeBCD(now.get(Calendar.SECOND), 1); + storeBCD(now.get(Calendar.MINUTE), 2); + storeBCD(now.get(Calendar.HOUR), 3); + storeBCD(now.get(Calendar.DAY_OF_WEEK), 4); + storeBCD(now.get(Calendar.DAY_OF_MONTH), 5); + storeBCD(now.get(Calendar.MONTH) + 1, 6); + storeBCD(now.get(Calendar.YEAR) % 100, 7); + clockActive = true; + EmulatorUILogic.addIndicator(this, clockIcon, 1000); + if (patchProdosClock) { + CardThunderclock.performProdosPatch(computer); + } + } + + public void storeBCD(int val, int offset) { + storeNibble(val % 10, offset * 8); + storeNibble(val / 10, offset * 8 + 4); + } + + public void storeNibble(int val, int offset) { + for (int i = 0; i < 4; i++) { + if ((val & 1) != 0) { + dataRegister |= (1L << (offset + i)); + } + val >>= 1; + } + } + + public void deactivateClock() { + clockActive = false; + } + + public int getNextDataBit() { + return (int) ((dataRegister >> dataRegisterBit) & 0x01); + } + + public void fakeWrite(RAMEvent e) { + } + +}