diff --git a/src/main/java/jace/cheat/MetaCheat.java b/src/main/java/jace/cheat/MetaCheat.java index 162f51c..2186027 100644 --- a/src/main/java/jace/cheat/MetaCheat.java +++ b/src/main/java/jace/cheat/MetaCheat.java @@ -1,445 +1,445 @@ -package jace.cheat; - -import jace.Emulator; -import jace.JaceApplication; -import jace.core.CPU; -import jace.core.Computer; -import jace.core.RAM; -import jace.core.RAMEvent; -import jace.core.RAMListener; -import jace.state.State; -import jace.ui.MetacheatUI; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Level; -import java.util.logging.Logger; -import javafx.application.Platform; -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.Property; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleStringProperty; -import javafx.beans.property.StringProperty; -import javafx.beans.value.ObservableValue; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; -import javax.script.Invocable; -import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; - -public class MetaCheat extends Cheats { - - static final ScriptEngine NASHORN_ENGINE = new ScriptEngineManager().getEngineByName("nashorn"); - static Invocable NASHORN_INVOCABLE = (Invocable) NASHORN_ENGINE; - - public static enum SearchType { - VALUE, TEXT, CHANGE - } - - public static enum SearchChangeType { - NO_CHANGE, ANY_CHANGE, LESS, GREATER, AMOUNT - } - - public static class SearchResult { - - int address; - int lastObservedValue = 0; - - private SearchResult(int address, int val) { - this.address = address; - lastObservedValue = val; - } - - @Override - public String toString() { - return Integer.toHexString(address) + ": " + lastObservedValue + " (" + Integer.toHexString(lastObservedValue) + ")"; - } - - public int getAddress() { - return address; - } - } - - MetacheatUI ui; - - public int fadeRate = 1; - public int lightRate = 30; - public int historyLength = 10; - - private int startAddress = 0; - private int endAddress = 0x0ffff; - private final StringProperty startAddressProperty = new SimpleStringProperty(Integer.toHexString(startAddress)); - private final StringProperty endAddressProperty = new SimpleStringProperty(Integer.toHexString(endAddress)); - private boolean byteSized = true; - private SearchType searchType = SearchType.VALUE; - private SearchChangeType searchChangeType = SearchChangeType.NO_CHANGE; - private final BooleanProperty signedProperty = new SimpleBooleanProperty(false); - private final StringProperty searchValueProperty = new SimpleStringProperty("0"); - private final StringProperty changeByProperty = new SimpleStringProperty("0"); - private final ObservableList cheatList = FXCollections.observableArrayList(); - private final ObservableList resultList = FXCollections.observableArrayList(); - private final ObservableList snapshotList = FXCollections.observableArrayList(); - - public MetaCheat(Computer computer) { - super(computer); - addNumericValidator(startAddressProperty); - addNumericValidator(endAddressProperty); - addNumericValidator(searchValueProperty); - addNumericValidator(changeByProperty); - startAddressProperty.addListener((prop, oldVal, newVal) -> { - startAddress = Math.max(0, Math.min(65535, parseInt(newVal))); - }); - endAddressProperty.addListener((prop, oldVal, newVal) -> { - endAddress = Math.max(0, Math.min(65535, parseInt(newVal))); - }); - } - - private void addNumericValidator(StringProperty stringProperty) { - stringProperty.addListener((ObservableValue prop, String oldVal, String newVal) -> { - if (newVal == null || newVal.isEmpty()) { - return; - } - if (!newVal.matches("(\\+|-)?(x|$)?[0-9a-fA-F]*")) { - stringProperty.set(""); - } - }); - } - - public int parseInt(String s) throws NumberFormatException { - if (s == null || s.isEmpty()) { - return 0; - } - if (s.matches("(\\+|-)?[0-9]+")) { - return Integer.parseInt(s); - } else { - String upper = s.toUpperCase(); - boolean positive = !upper.startsWith("-"); - for (int i = 0; i < upper.length(); i++) { - char c = upper.charAt(i); - if ((c >= '0' && c <= '9') || (c >= 'A' & c <= 'F')) { - int value = Integer.parseInt(s.substring(i), 16); - if (!positive) { - value *= -1; - } - return value; - } - } - } - throw new NumberFormatException("Could not interpret int value " + s); - } - - @Override - public void registerListeners() { - } - - public void addCheat(DynamicCheat cheat) { - cheatList.add(cheat); - computer.getMemory().addListener(cheat); - cheat.addressProperty().addListener((prop, oldVal, newVal) -> { - computer.getMemory().removeListener(cheat); - cheat.doConfig(); - computer.getMemory().addListener(cheat); - }); - } - - public void removeCheat(DynamicCheat cheat) { - cheat.active.set(false); - computer.getMemory().removeListener(cheat); - cheatList.remove(cheat); - } - - @Override - protected void unregisterListeners() { - super.unregisterListeners(); - cheatList.stream().forEach(computer.getMemory()::removeListener); - } - - @Override - protected String getDeviceName() { - return "MetaCheat"; - } - - @Override - public void detach() { - super.detach(); - ui.detach(); - } - - @Override - public void attach() { - ui = JaceApplication.getApplication().showMetacheat(); - ui.registerMetacheatEngine(this); - super.attach(); - } - - public int getStartAddress() { - return startAddress; - } - - public int getEndAddress() { - return endAddress; - } - - public void setByteSized(boolean b) { - byteSized = b; - } - - public void setSearchType(SearchType searchType) { - this.searchType = searchType; - } - - public void setSearchChangeType(SearchChangeType searchChangeType) { - this.searchChangeType = searchChangeType; - } - - public Property signedProperty() { - return signedProperty; - } - - public Property searchValueProperty() { - return searchValueProperty; - } - - public Property searchChangeByProperty() { - return changeByProperty; - } - - public ObservableList getCheats() { - return cheatList; - } - - public ObservableList getSearchResults() { - return resultList; - } - - public ObservableList getSnapshots() { - return snapshotList; - } - - public Property startAddressProperty() { - return startAddressProperty; - } - - public Property endAddressProperty() { - return endAddressProperty; - } - - public void newSearch() { - RAM memory = Emulator.computer.getMemory(); - resultList.clear(); - int compare = parseInt(searchValueProperty.get()); - for (int i = 0; i < 0x10000; i++) { - boolean signed = signedProperty.get(); - int val - = byteSized - ? signed ? memory.readRaw(i) : memory.readRaw(i) & 0x0ff - : signed ? memory.readWordRaw(i) : memory.readWordRaw(i) & 0x0ffff; - if (!searchType.equals(SearchType.VALUE) || val == compare) { - SearchResult result = new SearchResult(i, val); - resultList.add(result); - } - } - } - - public void performSearch() { - RAM memory = Emulator.computer.getMemory(); - boolean signed = signedProperty.get(); - resultList.removeIf((SearchResult result) -> { - int val = byteSized - ? signed ? memory.readRaw(result.address) : memory.readRaw(result.address) & 0x0ff - : signed ? memory.readWordRaw(result.address) : memory.readWordRaw(result.address) & 0x0ffff; - int last = result.lastObservedValue; - result.lastObservedValue = val; - switch (searchType) { - case VALUE: - int compare = parseInt(searchValueProperty.get()); - return compare != val; - case CHANGE: - switch (searchChangeType) { - case AMOUNT: - int amount = parseInt(searchChangeByProperty().getValue()); - return (val - last) != amount; - case GREATER: - return val <= last; - case ANY_CHANGE: - return val == last; - case LESS: - return val >= last; - case NO_CHANGE: - return val != last; - } - break; - case TEXT: - break; - } - return false; - }); - } - - RAMListener memoryViewListener = null; - private final Map memoryCells = new ConcurrentHashMap<>(); - - public MemoryCell getMemoryCell(int address) { - return memoryCells.get(address); - } - - public void initMemoryView() { - RAM memory = Emulator.computer.getMemory(); - for (int addr = getStartAddress(); addr <= getEndAddress(); addr++) { - if (getMemoryCell(addr) == null) { - MemoryCell cell = new MemoryCell(); - cell.address = addr; - cell.value.set(memory.readRaw(addr)); - memoryCells.put(addr, cell); - } - } - if (memoryViewListener == null) { - memoryViewListener = memory.observe(RAMEvent.TYPE.ANY, startAddress, endAddress, this::processMemoryEvent); - listeners.add(memoryViewListener); - } - } - - int fadeCounter = 0; - int FADE_TIMER_VALUE = (int) (Emulator.computer.getMotherboard().cyclesPerSecond / 60); - - @Override - public void tick() { - computer.cpu.performSingleTrace(); - if (fadeCounter-- <= 0) { - fadeCounter = FADE_TIMER_VALUE; - memoryCells.values().stream() - .filter((cell) -> cell.hasCounts()) - .forEach((cell) -> { - if (cell.execCount.get() > 0) { - cell.execCount.set(Math.max(0, cell.execCount.get() - fadeRate)); - } - if (cell.readCount.get() > 0) { - cell.readCount.set(Math.max(0, cell.readCount.get() - fadeRate)); - } - if (cell.writeCount.get() > 0) { - cell.writeCount.set(Math.max(0, cell.writeCount.get() - fadeRate)); - } - if (MemoryCell.listener != null) { - MemoryCell.listener.changed(null, cell, cell); - } - }); - } - } - - AtomicInteger pendingInspectorUpdates = new AtomicInteger(0); - public void onInspectorChanged() { - pendingInspectorUpdates.set(0); - } - - private void processMemoryEvent(RAMEvent e) { - MemoryCell cell = getMemoryCell(e.getAddress()); - if (cell != null) { - CPU cpu = Emulator.computer.getCpu(); - int pc = cpu.getProgramCounter(); - String trace = cpu.getLastTrace(); - switch (e.getType()) { - case EXECUTE: - cell.execInstructionsDisassembly.add(trace); - if (cell.execInstructionsDisassembly.size() > historyLength) { - cell.execInstructionsDisassembly.remove(0); - } - case READ_OPERAND: - cell.execCount.set(Math.min(255, cell.execCount.get() + lightRate)); - break; - case WRITE: - cell.writeCount.set(Math.min(255, cell.writeCount.get() + lightRate)); - if (ui.isInspecting(cell.address)) { - if (pendingInspectorUpdates.incrementAndGet() < 5) { - Platform.runLater(() -> { - pendingInspectorUpdates.decrementAndGet(); - cell.writeInstructions.add(pc); - cell.writeInstructionsDisassembly.add(trace); - if (cell.writeInstructions.size() > historyLength) { - cell.writeInstructions.remove(0); - cell.writeInstructionsDisassembly.remove(0); - } - }); - } - } else { - cell.writeInstructions.add(cpu.getProgramCounter()); - cell.writeInstructionsDisassembly.add(cpu.getLastTrace()); - if (cell.writeInstructions.size() > historyLength) { - cell.writeInstructions.remove(0); - cell.writeInstructionsDisassembly.remove(0); - } - } - break; - default: - cell.readCount.set(Math.min(255, cell.readCount.get() + lightRate)); - if (ui.isInspecting(cell.address)) { - if (pendingInspectorUpdates.incrementAndGet() < 5) { - Platform.runLater(() -> { - pendingInspectorUpdates.decrementAndGet(); - cell.readInstructions.add(pc); - cell.readInstructionsDisassembly.add(trace); - if (cell.readInstructions.size() > historyLength) { - cell.readInstructions.remove(0); - cell.readInstructionsDisassembly.remove(0); - } - }); - } - } else { - cell.readInstructions.add(cpu.getProgramCounter()); - cell.readInstructionsDisassembly.add(cpu.getLastTrace()); - if (cell.readInstructions.size() > historyLength) { - cell.readInstructions.remove(0); - cell.readInstructionsDisassembly.remove(0); - } - } - } - cell.value.set(e.getNewValue()); - } - } - - public void saveCheats(File saveFile) { - FileWriter writer = null; - try { - writer = new FileWriter(saveFile); - for (DynamicCheat cheat : cheatList) { - writer.write(cheat.serialize()); - writer.write("\n"); - } - writer.close(); - } catch (IOException ex) { - Logger.getLogger(MetaCheat.class.getName()).log(Level.SEVERE, null, ex); - } finally { - try { - writer.close(); - } catch (IOException ex) { - Logger.getLogger(MetaCheat.class.getName()).log(Level.SEVERE, null, ex); - } - } - } - - public void loadCheats(File saveFile) { - BufferedReader in = null; - try { - in = new BufferedReader(new FileReader(saveFile)); - StringBuilder guts = new StringBuilder(); - String line; - while ((line = in.readLine()) != null) { - DynamicCheat cheat = DynamicCheat.deserialize(line); - addCheat(cheat); - } - in.close(); - } catch (IOException ex) { - Logger.getLogger(MetaCheat.class.getName()).log(Level.SEVERE, null, ex); - } finally { - try { - in.close(); - } catch (IOException ex) { - Logger.getLogger(MetaCheat.class.getName()).log(Level.SEVERE, null, ex); - } - } - } -} +package jace.cheat; + +import jace.Emulator; +import jace.JaceApplication; +import jace.core.CPU; +import jace.core.Computer; +import jace.core.RAM; +import jace.core.RAMEvent; +import jace.core.RAMListener; +import jace.state.State; +import jace.ui.MetacheatUI; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.logging.Level; +import java.util.logging.Logger; +import javafx.application.Platform; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javax.script.Invocable; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; + +public class MetaCheat extends Cheats { + + static final ScriptEngine NASHORN_ENGINE = new ScriptEngineManager().getEngineByName("nashorn"); + static Invocable NASHORN_INVOCABLE = (Invocable) NASHORN_ENGINE; + + public static enum SearchType { + VALUE, TEXT, CHANGE + } + + public static enum SearchChangeType { + NO_CHANGE, ANY_CHANGE, LESS, GREATER, AMOUNT + } + + public static class SearchResult { + + int address; + int lastObservedValue = 0; + + private SearchResult(int address, int val) { + this.address = address; + lastObservedValue = val; + } + + @Override + public String toString() { + return Integer.toHexString(address) + ": " + lastObservedValue + " (" + Integer.toHexString(lastObservedValue) + ")"; + } + + public int getAddress() { + return address; + } + } + + MetacheatUI ui; + + public int fadeRate = 1; + public int lightRate = 30; + public int historyLength = 10; + + private int startAddress = 0; + private int endAddress = 0x0ffff; + private final StringProperty startAddressProperty = new SimpleStringProperty(Integer.toHexString(startAddress)); + private final StringProperty endAddressProperty = new SimpleStringProperty(Integer.toHexString(endAddress)); + private boolean byteSized = true; + private SearchType searchType = SearchType.VALUE; + private SearchChangeType searchChangeType = SearchChangeType.NO_CHANGE; + private final BooleanProperty signedProperty = new SimpleBooleanProperty(false); + private final StringProperty searchValueProperty = new SimpleStringProperty("0"); + private final StringProperty changeByProperty = new SimpleStringProperty("0"); + private final ObservableList cheatList = FXCollections.observableArrayList(); + private final ObservableList resultList = FXCollections.observableArrayList(); + private final ObservableList snapshotList = FXCollections.observableArrayList(); + + public MetaCheat(Computer computer) { + super(computer); + addNumericValidator(startAddressProperty); + addNumericValidator(endAddressProperty); + addNumericValidator(searchValueProperty); + addNumericValidator(changeByProperty); + startAddressProperty.addListener((prop, oldVal, newVal) -> { + startAddress = Math.max(0, Math.min(65535, parseInt(newVal))); + }); + endAddressProperty.addListener((prop, oldVal, newVal) -> { + endAddress = Math.max(0, Math.min(65535, parseInt(newVal))); + }); + } + + private void addNumericValidator(StringProperty stringProperty) { + stringProperty.addListener((ObservableValue prop, String oldVal, String newVal) -> { + if (newVal == null || newVal.isEmpty()) { + return; + } + if (!newVal.matches("(\\+|-)?(x|$)?[0-9a-fA-F]*")) { + stringProperty.set(""); + } + }); + } + + public int parseInt(String s) throws NumberFormatException { + if (s == null || s.isEmpty()) { + return 0; + } + if (s.matches("(\\+|-)?[0-9]+")) { + return Integer.parseInt(s); + } else { + String upper = s.toUpperCase(); + boolean positive = !upper.startsWith("-"); + for (int i = 0; i < upper.length(); i++) { + char c = upper.charAt(i); + if ((c >= '0' && c <= '9') || (c >= 'A' & c <= 'F')) { + int value = Integer.parseInt(s.substring(i), 16); + if (!positive) { + value *= -1; + } + return value; + } + } + } + throw new NumberFormatException("Could not interpret int value " + s); + } + + @Override + public void registerListeners() { + } + + public void addCheat(DynamicCheat cheat) { + cheatList.add(cheat); + computer.getMemory().addListener(cheat); + cheat.addressProperty().addListener((prop, oldVal, newVal) -> { + computer.getMemory().removeListener(cheat); + cheat.doConfig(); + computer.getMemory().addListener(cheat); + }); + } + + public void removeCheat(DynamicCheat cheat) { + cheat.active.set(false); + computer.getMemory().removeListener(cheat); + cheatList.remove(cheat); + } + + @Override + protected void unregisterListeners() { + super.unregisterListeners(); + cheatList.stream().forEach(computer.getMemory()::removeListener); + } + + @Override + protected String getDeviceName() { + return "MetaCheat"; + } + + @Override + public void detach() { + super.detach(); + ui.detach(); + } + + @Override + public void attach() { + ui = JaceApplication.getApplication().showMetacheat(); + ui.registerMetacheatEngine(this); + super.attach(); + } + + public int getStartAddress() { + return startAddress; + } + + public int getEndAddress() { + return endAddress; + } + + public void setByteSized(boolean b) { + byteSized = b; + } + + public void setSearchType(SearchType searchType) { + this.searchType = searchType; + } + + public void setSearchChangeType(SearchChangeType searchChangeType) { + this.searchChangeType = searchChangeType; + } + + public Property signedProperty() { + return signedProperty; + } + + public Property searchValueProperty() { + return searchValueProperty; + } + + public Property searchChangeByProperty() { + return changeByProperty; + } + + public ObservableList getCheats() { + return cheatList; + } + + public ObservableList getSearchResults() { + return resultList; + } + + public ObservableList getSnapshots() { + return snapshotList; + } + + public Property startAddressProperty() { + return startAddressProperty; + } + + public Property endAddressProperty() { + return endAddressProperty; + } + + public void newSearch() { + RAM memory = Emulator.computer.getMemory(); + resultList.clear(); + int compare = parseInt(searchValueProperty.get()); + for (int i = 0; i < 0x10000; i++) { + boolean signed = signedProperty.get(); + int val + = byteSized + ? signed ? memory.readRaw(i) : memory.readRaw(i) & 0x0ff + : signed ? memory.readWordRaw(i) : memory.readWordRaw(i) & 0x0ffff; + if (!searchType.equals(SearchType.VALUE) || val == compare) { + SearchResult result = new SearchResult(i, val); + resultList.add(result); + } + } + } + + public void performSearch() { + RAM memory = Emulator.computer.getMemory(); + boolean signed = signedProperty.get(); + resultList.removeIf((SearchResult result) -> { + int val = byteSized + ? signed ? memory.readRaw(result.address) : memory.readRaw(result.address) & 0x0ff + : signed ? memory.readWordRaw(result.address) : memory.readWordRaw(result.address) & 0x0ffff; + int last = result.lastObservedValue; + result.lastObservedValue = val; + switch (searchType) { + case VALUE: + int compare = parseInt(searchValueProperty.get()); + return compare != val; + case CHANGE: + switch (searchChangeType) { + case AMOUNT: + int amount = parseInt(searchChangeByProperty().getValue()); + return (val - last) != amount; + case GREATER: + return val <= last; + case ANY_CHANGE: + return val == last; + case LESS: + return val >= last; + case NO_CHANGE: + return val != last; + } + break; + case TEXT: + break; + } + return false; + }); + } + + RAMListener memoryViewListener = null; + private final Map memoryCells = new ConcurrentHashMap<>(); + + public MemoryCell getMemoryCell(int address) { + return memoryCells.get(address); + } + + public void initMemoryView() { + RAM memory = Emulator.computer.getMemory(); + for (int addr = getStartAddress(); addr <= getEndAddress(); addr++) { + if (getMemoryCell(addr) == null) { + MemoryCell cell = new MemoryCell(); + cell.address = addr; + cell.value.set(memory.readRaw(addr)); + memoryCells.put(addr, cell); + } + } + if (memoryViewListener == null) { + memoryViewListener = memory.observe(RAMEvent.TYPE.ANY, startAddress, endAddress, this::processMemoryEvent); + listeners.add(memoryViewListener); + } + } + + int fadeCounter = 0; + int FADE_TIMER_VALUE = (int) (Emulator.computer.getMotherboard().getSpeedInHz() / 60); + + @Override + public void tick() { + computer.cpu.performSingleTrace(); + if (fadeCounter-- <= 0) { + fadeCounter = FADE_TIMER_VALUE; + memoryCells.values().stream() + .filter((cell) -> cell.hasCounts()) + .forEach((cell) -> { + if (cell.execCount.get() > 0) { + cell.execCount.set(Math.max(0, cell.execCount.get() - fadeRate)); + } + if (cell.readCount.get() > 0) { + cell.readCount.set(Math.max(0, cell.readCount.get() - fadeRate)); + } + if (cell.writeCount.get() > 0) { + cell.writeCount.set(Math.max(0, cell.writeCount.get() - fadeRate)); + } + if (MemoryCell.listener != null) { + MemoryCell.listener.changed(null, cell, cell); + } + }); + } + } + + AtomicInteger pendingInspectorUpdates = new AtomicInteger(0); + public void onInspectorChanged() { + pendingInspectorUpdates.set(0); + } + + private void processMemoryEvent(RAMEvent e) { + MemoryCell cell = getMemoryCell(e.getAddress()); + if (cell != null) { + CPU cpu = Emulator.computer.getCpu(); + int pc = cpu.getProgramCounter(); + String trace = cpu.getLastTrace(); + switch (e.getType()) { + case EXECUTE: + cell.execInstructionsDisassembly.add(trace); + if (cell.execInstructionsDisassembly.size() > historyLength) { + cell.execInstructionsDisassembly.remove(0); + } + case READ_OPERAND: + cell.execCount.set(Math.min(255, cell.execCount.get() + lightRate)); + break; + case WRITE: + cell.writeCount.set(Math.min(255, cell.writeCount.get() + lightRate)); + if (ui.isInspecting(cell.address)) { + if (pendingInspectorUpdates.incrementAndGet() < 5) { + Platform.runLater(() -> { + pendingInspectorUpdates.decrementAndGet(); + cell.writeInstructions.add(pc); + cell.writeInstructionsDisassembly.add(trace); + if (cell.writeInstructions.size() > historyLength) { + cell.writeInstructions.remove(0); + cell.writeInstructionsDisassembly.remove(0); + } + }); + } + } else { + cell.writeInstructions.add(cpu.getProgramCounter()); + cell.writeInstructionsDisassembly.add(cpu.getLastTrace()); + if (cell.writeInstructions.size() > historyLength) { + cell.writeInstructions.remove(0); + cell.writeInstructionsDisassembly.remove(0); + } + } + break; + default: + cell.readCount.set(Math.min(255, cell.readCount.get() + lightRate)); + if (ui.isInspecting(cell.address)) { + if (pendingInspectorUpdates.incrementAndGet() < 5) { + Platform.runLater(() -> { + pendingInspectorUpdates.decrementAndGet(); + cell.readInstructions.add(pc); + cell.readInstructionsDisassembly.add(trace); + if (cell.readInstructions.size() > historyLength) { + cell.readInstructions.remove(0); + cell.readInstructionsDisassembly.remove(0); + } + }); + } + } else { + cell.readInstructions.add(cpu.getProgramCounter()); + cell.readInstructionsDisassembly.add(cpu.getLastTrace()); + if (cell.readInstructions.size() > historyLength) { + cell.readInstructions.remove(0); + cell.readInstructionsDisassembly.remove(0); + } + } + } + cell.value.set(e.getNewValue()); + } + } + + public void saveCheats(File saveFile) { + FileWriter writer = null; + try { + writer = new FileWriter(saveFile); + for (DynamicCheat cheat : cheatList) { + writer.write(cheat.serialize()); + writer.write("\n"); + } + writer.close(); + } catch (IOException ex) { + Logger.getLogger(MetaCheat.class.getName()).log(Level.SEVERE, null, ex); + } finally { + try { + writer.close(); + } catch (IOException ex) { + Logger.getLogger(MetaCheat.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + public void loadCheats(File saveFile) { + BufferedReader in = null; + try { + in = new BufferedReader(new FileReader(saveFile)); + StringBuilder guts = new StringBuilder(); + String line; + while ((line = in.readLine()) != null) { + DynamicCheat cheat = DynamicCheat.deserialize(line); + addCheat(cheat); + } + in.close(); + } catch (IOException ex) { + Logger.getLogger(MetaCheat.class.getName()).log(Level.SEVERE, null, ex); + } finally { + try { + in.close(); + } catch (IOException ex) { + Logger.getLogger(MetaCheat.class.getName()).log(Level.SEVERE, null, ex); + } + } + } +} diff --git a/src/main/java/jace/core/TimedDevice.java b/src/main/java/jace/core/TimedDevice.java index 5aed2de..e447492 100644 --- a/src/main/java/jace/core/TimedDevice.java +++ b/src/main/java/jace/core/TimedDevice.java @@ -21,24 +21,25 @@ package jace.core; import jace.config.ConfigurableField; /** - * A timed device is a device which executes so many ticks in a given time - * interval. This is the core of the emulator timing mechanics. + * A timed device is a device which executes so many ticks in a given time interval. This is the core of the emulator + * timing mechanics. * - * @author Brendan Robert (BLuRry) brendan.robert@gmail.com + * @author Brendan Robert (BLuRry) brendan.robert@gmail.com */ public abstract class TimedDevice extends Device { /** * Creates a new instance of TimedDevice + * * @param computer */ public TimedDevice(Computer computer) { super(computer); - setSpeed(cyclesPerSecond); + setSpeedInHz(cyclesPerSecond); } @ConfigurableField(name = "Speed", description = "(Percentage)") public int speedRatio = 100; - public long cyclesPerSecond = defaultCyclesPerSecond(); + private long cyclesPerSecond = defaultCyclesPerSecond(); @ConfigurableField(name = "Max speed") public boolean maxspeed = false; @@ -57,10 +58,11 @@ public abstract class TimedDevice extends Device { public boolean suspend() { disableTempMaxSpeed(); boolean result = super.suspend(); - if (worker != null && worker.isAlive()) { + Thread w = worker; + if (w != null && w.isAlive()) { try { - worker.interrupt(); - worker.join(1000); + w.interrupt(); + w.join(1000); } catch (InterruptedException ex) { } } @@ -114,13 +116,44 @@ public abstract class TimedDevice extends Device { long cyclesPerInterval; // How many cycles to wait until a pause interval long nextSync; // When was the last pause? - public final void setSpeed(long cyclesPerSecond) { + public final int getSpeedRatio() { + return speedRatio; + } + + public final void setMaxSpeed(boolean enabled) { + maxspeed = enabled; + if (!enabled) { + disableTempMaxSpeed(); + } + } + + public final boolean isMaxSpeed() { + return maxspeed; + } + + public final long getSpeedInHz() { + return cyclesPerInterval * 100L; + } + + public final void setSpeedInHz(long cyclesPerSecond) { +// System.out.println("Raw set speed for " + getName() + " to " + cyclesPerSecond + "hz"); + speedRatio = (int) Math.round(cyclesPerSecond * 100.0 / defaultCyclesPerSecond()); cyclesPerInterval = cyclesPerSecond / 100L; nanosPerInterval = (long) (cyclesPerInterval * NANOS_PER_SECOND / cyclesPerSecond); // System.out.println("Will pause " + nanosPerInterval + " nanos every " + cyclesPerInterval + " cycles"); cycleTimer = 0; resetSyncTimer(); } + + public final void setSpeedInPercentage(int ratio) { +// System.out.println("Setting " + getName() + " speed ratio to " + speedRatio); + cyclesPerSecond = defaultCyclesPerSecond() * ratio / 100; + if (cyclesPerSecond == 0) { + cyclesPerSecond = defaultCyclesPerSecond(); + } + setSpeedInHz(cyclesPerSecond); + } + long skip = 0; long wait = 0; @@ -171,11 +204,6 @@ public abstract class TimedDevice extends Device { @Override public void reconfigure() { - cyclesPerSecond = defaultCyclesPerSecond() * speedRatio / 100; - if (cyclesPerSecond == 0) { - cyclesPerSecond = defaultCyclesPerSecond(); - } - setSpeed(cyclesPerSecond); } public abstract long defaultCyclesPerSecond(); diff --git a/src/main/java/jace/hardware/CardMockingboard.java b/src/main/java/jace/hardware/CardMockingboard.java index f4dd453..e2f1147 100644 --- a/src/main/java/jace/hardware/CardMockingboard.java +++ b/src/main/java/jace/hardware/CardMockingboard.java @@ -27,7 +27,6 @@ import jace.core.RAMEvent; import jace.core.RAMEvent.TYPE; import jace.core.RAMListener; import jace.core.SoundMixer; -import static jace.core.Utility.*; import jace.hardware.mockingboard.PSG; import jace.hardware.mockingboard.R6522; import java.util.concurrent.TimeUnit; @@ -80,7 +79,7 @@ public class CardMockingboard extends Card implements Runnable { Condition playbackFinished = timerSync.newCondition(); @ConfigurableField(name = "Idle sample threshold", description = "Number of samples to wait before suspending sound") private int MAX_IDLE_SAMPLES = SAMPLE_RATE; - + @Override public String getDeviceName() { return "Mockingboard"; @@ -90,7 +89,7 @@ public class CardMockingboard extends Card implements Runnable { super(computer); controllers = new R6522[2]; for (int i = 0; i < 2; i++) { - //don't ask... + // has to be final to be used inside of anonymous class below final int j = i; controllers[i] = new R6522(computer) { int controller = j; @@ -131,6 +130,13 @@ public class CardMockingboard extends Card implements Runnable { public String getShortName() { return "timer" + j; } + + public void doTick() { + super.doTick(); + if (controller == 0) { + doSoundTick(); + } + } }; } } @@ -140,6 +146,18 @@ public class CardMockingboard extends Card implements Runnable { suspend(); } RAMListener mainListener = null; + + boolean heatbeatUnclocked = false; + long heartbeatReclockTime = 0L; + long unclockTime = 5000L; + + private void setUnclocked(boolean unclocked) { + heatbeatUnclocked = unclocked; + for (R6522 controller : controllers) { + controller.setUnclocked(unclocked); + } + heartbeatReclockTime = System.currentTimeMillis() + unclockTime; + } @Override protected void handleFirmwareAccess(int register, TYPE type, int value, RAMEvent e) { @@ -152,7 +170,7 @@ public class CardMockingboard extends Card implements Runnable { chip++; } if (chip >= 2) { - System.err.println("Could not determine which PSG to communicate to"); + System.err.println("Could not determine which PSG to communicate to for access to regsiter + " + Integer.toHexString(register)); e.setNewValue(computer.getVideo().getFloatingBus()); return; } @@ -177,13 +195,25 @@ public class CardMockingboard extends Card implements Runnable { @Override public void tick() { - for (R6522 c : controllers) { - if (c == null || !c.isRunning()) { - continue; + if (heatbeatUnclocked) { + if (System.currentTimeMillis() - heartbeatReclockTime >= unclockTime) { + setUnclocked(false); + } else { + for (R6522 c : controllers) { + if (c == null || !c.isRunning()) { + continue; + } + c.doTick(); + } } - c.tick(); } - + } + + public boolean isRunning() { + return super.isRunning() && playbackThread != null && playbackThread.isAlive(); + } + + private void doSoundTick() { if (isRunning() && !pause) { // buildMixerTable(); timerSync.lock(); @@ -194,7 +224,7 @@ public class CardMockingboard extends Card implements Runnable { while (isRunning() && ticksSinceLastPlayback >= ticksBetweenPlayback) { if (!playbackFinished.await(1, TimeUnit.SECONDS)) { // gripe("The mockingboard playback thread has stalled. Disabling mockingboard."); - suspend(); + suspendSound(); } } } @@ -263,14 +293,15 @@ public class CardMockingboard extends Card implements Runnable { @Override public void resume() { pause = false; - if (!isRunning()) { - if (chips == null) { - initPSG(); - for (PSG psg : chips) { - psg.setRate(phasorMode ? CLOCK_SPEED * 2 : CLOCK_SPEED, SAMPLE_RATE); - psg.reset(); - } + if (chips == null) { + initPSG(); + for (PSG psg : chips) { + psg.setRate(phasorMode ? CLOCK_SPEED * 2 : CLOCK_SPEED, SAMPLE_RATE); + psg.reset(); } + } + if (!isRunning()) { + setUnclocked(true); for (R6522 controller : controllers) { controller.attach(); controller.resume(); @@ -290,6 +321,10 @@ public class CardMockingboard extends Card implements Runnable { controller.suspend(); controller.detach(); } + return suspendSound(); + } + + public boolean suspendSound() { if (playbackThread == null || !playbackThread.isAlive()) { return false; } @@ -325,11 +360,11 @@ public class CardMockingboard extends Card implements Runnable { int zeroSamples = 0; setRun(true); LockSupport.parkNanos(5000); - while (isRunning()) { + while (isRunning() && !Thread.interrupted()) { while (isRunning() && !computer.isRunning()) { - Thread.currentThread().yield(); + Thread.sleep(1000); } - if (isRunning()) { + if (isRunning() && !Thread.interrupted()) { playSound(leftBuffer, rightBuffer); int p = 0; for (int idx = 0; idx < BUFFER_LENGTH; idx++) { @@ -394,6 +429,8 @@ public class CardMockingboard extends Card implements Runnable { } catch (LineUnavailableException ex) { Logger.getLogger(CardMockingboard.class .getName()).log(Level.SEVERE, null, ex); + } catch (InterruptedException ex) { + Logger.getLogger(CardMockingboard.class.getName()).log(Level.SEVERE, null, ex); } finally { computer.getMotherboard().cancelSpeedRequest(this); System.out.println("Mockingboard playback stopped"); diff --git a/src/main/java/jace/hardware/mockingboard/R6522.java b/src/main/java/jace/hardware/mockingboard/R6522.java index 72e9dea..dcff920 100644 --- a/src/main/java/jace/hardware/mockingboard/R6522.java +++ b/src/main/java/jace/hardware/mockingboard/R6522.java @@ -1,346 +1,365 @@ -/* - * 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.hardware.mockingboard; - -import jace.core.Computer; -import jace.core.Device; - -/** - * Implementation of 6522 VIA chip - * - * @author Brendan Robert (BLuRry) brendan.robert@gmail.com - */ -public abstract class R6522 extends Device { - - public R6522(Computer computer) { - super(computer); - timer1freerun = true; - timer1running = true; - timer1latch = 0x1fff; - timer1interruptEnabled = false; - setRun(true); - } - - // 6522 VIA - // http://www.applevault.com/twiki/Main/Mockingboard/6522.pdf - // I/O registers - public static enum Register { - ORB(0), // Output Register B - ORA(1), // Output Register A - DDRB(2),// Data direction reg B - DDRA(3),// Data direction reg A - T1CL(4),// T1 low-order latches (low-order counter for read operations) - T1CH(5),// T1 high-order counter - T1LL(6),// T1 low-order latches - T1LH(7),// T1 high-order latches - T2CL(8),// T2 low-order latches (low-order counter for read operations) - T2CH(9),// T2 high-order counter - SR(10),// Shift register - ACR(11),// Aux control register - PCR(12),// Perripheral control register - IFR(13),// Interrupt flag register - IER(14),// Interrupt enable register - ORAH(15);// Output Register A (no handshake) - - int val; - - Register(int v) { - val = v; - } - - static public Register fromInt(int i) { - for (Register r : Register.values()) { - if (r.val == i) { - return r; - } - } - return null; - } - } - // state variables - public int oraReg = 0; - public int iraReg = 0; - public int orbReg = 0; - public int irbReg = 0; - // DDRA and DDRB must be set to output for mockingboard to do anything - // Common values for this are FF for DDRA and 7 for DDRB - // DDRB bits 0-2 are used to control AY chips but bits 3-7 are not connected. - // that's why it is common to see mockingboard drivers init the port with a 7 - public int dataDirectionA = 0; - public int dataDirectionB = 0; - - // Though this is necessary for a complete emulation of the 6522, it isn't needed by the mockingboard - // set by bit 0 of ACR -// public boolean latchEnabledA = false; - // set by bit 1 of ACR -// public boolean latchEnabledB = false; - //Bits 2,3,4 of ACR -// static public enum ShiftRegisterControl { -// interruptDisabled(0), -// shiftInT2(4), -// shiftIn02(8), -// shiftInExt(12), -// shiftOutFree(16), -// shiftOutT2(20), -// shiftOut02(24), -// shiftOutExt(28); -// -// int val; -// private ShiftRegisterControl(int v) { -// val = v; -// } -// -// public static ShiftRegisterControl fromBits(int b) { -// b=b&28; -// for (ShiftRegisterControl s : values()) { -// if (s.val == b) return s; -// } -// return null; -// } -// } -// public ShiftRegisterControl shiftMode = ShiftRegisterControl.interruptDisabled; -// //Bit 5 of ACR (false = timed interrupt, true = count down pulses on PB6) -// public boolean t2countPulses = false; -// //Bit 6 of ACR (true = continuous, false = one-shot) -// public boolean t1continuous = false; -// //Bit 7 of ACR (true = enable PB7, false = interruptDisabled) -// public boolean t1enablePB7 = false; -// // NOTE: Mockingboard did not use PB6 or PB7, they are not connected to anything - public boolean timer1interruptEnabled = true; - public boolean timer1IRQ = false; // True if timer interrupt flag is set - public int timer1latch = 0; - public int timer1counter = 0; - public boolean timer1freerun = false; - public boolean timer1running = false; - public boolean timer2interruptEnabled = true; - public boolean timer2IRQ = false; // True if timer interrupt flag is set - public int timer2latch = 0; - public int timer2counter = 0; - public boolean timer2running = false; - - @Override - protected String getDeviceName() { - return "6522 VIA Chip"; - } - - @Override - public void tick() { - if (timer1running) { - timer1counter--; - if (timer1counter < 0) { - timer1counter = timer1latch; - if (!timer1freerun) { - timer1running = false; - } - if (timer1interruptEnabled) { -// System.out.println("Timer 1 generated interrupt"); - timer1IRQ = true; - computer.getCpu().generateInterrupt(); - } - } - } - if (timer2running) { - timer2counter--; - if (timer2counter < 0) { - timer2running = false; - timer2counter = timer2latch; - if (timer2interruptEnabled) { - timer2IRQ = true; - computer.getCpu().generateInterrupt(); - } - } - } - if (!timer1running && !timer2running) { - setRun(false); - } - } - - @Override - public void attach() { - // Start chip - } - - @Override - public void reconfigure() { - // Reset - } - - public void writeRegister(int reg, int val) { - int value = val & 0x0ff; - Register r = Register.fromInt(reg); -// System.out.println("Writing "+(value&0x0ff)+" to register "+r.toString()); - switch (r) { - case ORB: - if (dataDirectionB == 0) { - break; - } - sendOutputB(value & dataDirectionB); - break; - case ORA: -// case ORAH: - if (dataDirectionA == 0) { - break; - } - sendOutputA(value & dataDirectionA); - break; - case DDRB: - dataDirectionB = value; - break; - case DDRA: - dataDirectionA = value; - break; - case T1CL: - case T1LL: - timer1latch = (timer1latch & 0x0ff00) | value; - break; - case T1CH: - timer1latch = (timer1latch & 0x0ff) | (value << 8); - timer1IRQ = false; - timer1counter = timer1latch; - timer1running = true; - setRun(true); - break; - case T1LH: - timer1latch = (timer1latch & 0x0ff) | (value << 8); - timer1IRQ = false; - break; - case T2CL: - timer2latch = (timer2latch & 0x0ff00) | value; - break; - case T2CH: - timer2latch = (timer2latch & 0x0ff) | (value << 8); - timer2IRQ = false; - timer2counter = timer2latch; - timer2running = true; - setRun(true); - break; - case SR: - // SHIFT REGISTER NOT IMPLEMENTED - break; - case ACR: - // SHIFT REGISTER NOT IMPLEMENTED - timer1freerun = (value & 64) != 0; - if (timer1freerun) { - timer1running = true; - setRun(true); - } - break; - case PCR: - // TODO: Implement if Votrax (SSI) is to be supported - break; - case IFR: - if ((value & 64) != 0) { - timer1IRQ = false; - } - if ((value & 32) != 0) { - timer2IRQ = false; - } - break; - case IER: - boolean enable = (value & 128) != 0; - if ((value & 64) != 0) { - timer1interruptEnabled = enable; - } - if ((value & 32) != 0) { - timer2interruptEnabled = enable; - } - break; - default: - } - } - - // Whatever uses 6522 will want to know when it is outputting values - // So to hook that in, these abstract methods will be defined as appropriate - public abstract void sendOutputA(int value); - - public abstract void sendOutputB(int value); - - public int readRegister(int reg) { - Register r = Register.fromInt(reg); -// System.out.println("Reading register "+r.toString()); - switch (r) { - case ORB: - if (dataDirectionB == 0x0ff) { - break; - } - return receiveOutputB() & (dataDirectionB ^ 0x0ff); - case ORA: - case ORAH: - if (dataDirectionA == 0x0ff) { - break; - } - return receiveOutputA() & (dataDirectionA ^ 0x0ff); - case DDRB: - return dataDirectionB; - case DDRA: - return dataDirectionA; - case T1CL: - timer1IRQ = false; - return timer1counter & 0x0ff; - case T1CH: - return (timer1counter & 0x0ff00) >> 8; - case T1LL: - return timer1latch & 0x0ff; - case T1LH: - return (timer1latch & 0x0ff00) >> 8; - case T2CL: - timer2IRQ = false; - return timer2counter & 0x0ff; - case T2CH: - return (timer2counter & 0x0ff00) >> 8; - case SR: - // SHIFT REGISTER NOT IMPLEMENTED - return 0; - case ACR: - // SHIFT REGISTER NOT IMPLEMENTED - if (timer1freerun) { - return 64; - } - return 0; - case PCR: - break; - case IFR: - int val = 0; - if (timer1IRQ) { - val |= 64; - } - if (timer2IRQ) { - val |= 32; - } - if (val != 0) { - val |= 128; - } - return val; - case IER: - val = 128; - if (timer1interruptEnabled) { - val |= 64; - } - if (timer2interruptEnabled) { - val |= 32; - } - return val; - } - return 0; - } - - public abstract int receiveOutputA(); - - public abstract int receiveOutputB(); -} +/* + * 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.hardware.mockingboard; + +import jace.core.Computer; +import jace.core.Device; +import jace.core.TimedDevice; + +/** + * Implementation of 6522 VIA chip + * + * @author Brendan Robert (BLuRry) brendan.robert@gmail.com + */ +public abstract class R6522 extends TimedDevice { + public static long SPEED = 1020484L; // (NTSC) + + public R6522(Computer computer) { + super(computer); + timer1freerun = true; + timer1running = true; + timer1latch = 0x1fff; + timer1interruptEnabled = false; + setSpeedInHz(SPEED); + setRun(true); + } + + @Override + public long defaultCyclesPerSecond() { + return SPEED; + } + + // 6522 VIA + // http://www.applevault.com/twiki/Main/Mockingboard/6522.pdf + // I/O registers + public static enum Register { + ORB(0), // Output Register B + ORA(1), // Output Register A + DDRB(2),// Data direction reg B + DDRA(3),// Data direction reg A + T1CL(4),// T1 low-order latches (low-order counter for read operations) + T1CH(5),// T1 high-order counter + T1LL(6),// T1 low-order latches + T1LH(7),// T1 high-order latches + T2CL(8),// T2 low-order latches (low-order counter for read operations) + T2CH(9),// T2 high-order counter + SR(10),// Shift register + ACR(11),// Aux control register + PCR(12),// Perripheral control register + IFR(13),// Interrupt flag register + IER(14),// Interrupt enable register + ORAH(15);// Output Register A (no handshake) + + int val; + + Register(int v) { + val = v; + } + + static public Register fromInt(int i) { + for (Register r : Register.values()) { + if (r.val == i) { + return r; + } + } + return null; + } + } + // state variables + public int oraReg = 0; + public int iraReg = 0; + public int orbReg = 0; + public int irbReg = 0; + // DDRA and DDRB must be set to output for mockingboard to do anything + // Common values for this are FF for DDRA and 7 for DDRB + // DDRB bits 0-2 are used to control AY chips but bits 3-7 are not connected. + // that's why it is common to see mockingboard drivers init the port with a 7 + public int dataDirectionA = 0; + public int dataDirectionB = 0; + + // Though this is necessary for a complete emulation of the 6522, it isn't needed by the mockingboard + // set by bit 0 of ACR +// public boolean latchEnabledA = false; + // set by bit 1 of ACR +// public boolean latchEnabledB = false; + //Bits 2,3,4 of ACR +// static public enum ShiftRegisterControl { +// interruptDisabled(0), +// shiftInT2(4), +// shiftIn02(8), +// shiftInExt(12), +// shiftOutFree(16), +// shiftOutT2(20), +// shiftOut02(24), +// shiftOutExt(28); +// +// int val; +// private ShiftRegisterControl(int v) { +// val = v; +// } +// +// public static ShiftRegisterControl fromBits(int b) { +// b=b&28; +// for (ShiftRegisterControl s : values()) { +// if (s.val == b) return s; +// } +// return null; +// } +// } +// public ShiftRegisterControl shiftMode = ShiftRegisterControl.interruptDisabled; +// //Bit 5 of ACR (false = timed interrupt, true = count down pulses on PB6) +// public boolean t2countPulses = false; +// //Bit 6 of ACR (true = continuous, false = one-shot) +// public boolean t1continuous = false; +// //Bit 7 of ACR (true = enable PB7, false = interruptDisabled) +// public boolean t1enablePB7 = false; +// // NOTE: Mockingboard did not use PB6 or PB7, they are not connected to anything + public boolean timer1interruptEnabled = true; + public boolean timer1IRQ = false; // True if timer interrupt flag is set + public int timer1latch = 0; + public int timer1counter = 0; + public boolean timer1freerun = false; + public boolean timer1running = false; + public boolean timer2interruptEnabled = true; + public boolean timer2IRQ = false; // True if timer interrupt flag is set + public int timer2latch = 0; + public int timer2counter = 0; + public boolean timer2running = false; + public boolean unclocked = false; + + @Override + protected String getDeviceName() { + return "6522 VIA Chip"; + } + + @Override + public void tick() { + if (!unclocked) { + doTick(); + } + } + + public void setUnclocked(boolean unclocked) { + this.unclocked = unclocked; + } + + public void doTick() { + if (timer1running) { + timer1counter--; + if (timer1counter < 0) { + timer1counter = timer1latch; + if (!timer1freerun) { + timer1running = false; + } + if (timer1interruptEnabled) { +// System.out.println("Timer 1 generated interrupt"); + timer1IRQ = true; + computer.getCpu().generateInterrupt(); + } + } + } + if (timer2running) { + timer2counter--; + if (timer2counter < 0) { + timer2running = false; + timer2counter = timer2latch; + if (timer2interruptEnabled) { + timer2IRQ = true; + computer.getCpu().generateInterrupt(); + } + } + } + if (!timer1running && !timer2running) { + setRun(false); + } + } + + @Override + public void attach() { + // Start chip + } + + @Override + public void reconfigure() { + // Reset + } + + public void writeRegister(int reg, int val) { + int value = val & 0x0ff; + Register r = Register.fromInt(reg); +// System.out.println("Writing "+(value&0x0ff)+" to register "+r.toString()); + switch (r) { + case ORB: + if (dataDirectionB == 0) { + break; + } + sendOutputB(value & dataDirectionB); + break; + case ORA: +// case ORAH: + if (dataDirectionA == 0) { + break; + } + sendOutputA(value & dataDirectionA); + break; + case DDRB: + dataDirectionB = value; + break; + case DDRA: + dataDirectionA = value; + break; + case T1CL: + case T1LL: + timer1latch = (timer1latch & 0x0ff00) | value; + break; + case T1CH: + timer1latch = (timer1latch & 0x0ff) | (value << 8); + timer1IRQ = false; + timer1counter = timer1latch; + timer1running = true; + setRun(true); + break; + case T1LH: + timer1latch = (timer1latch & 0x0ff) | (value << 8); + timer1IRQ = false; + break; + case T2CL: + timer2latch = (timer2latch & 0x0ff00) | value; + break; + case T2CH: + timer2latch = (timer2latch & 0x0ff) | (value << 8); + timer2IRQ = false; + timer2counter = timer2latch; + timer2running = true; + setRun(true); + break; + case SR: + // SHIFT REGISTER NOT IMPLEMENTED + break; + case ACR: + // SHIFT REGISTER NOT IMPLEMENTED + timer1freerun = (value & 64) != 0; + if (timer1freerun) { + timer1running = true; + setRun(true); + } + break; + case PCR: + // TODO: Implement if Votrax (SSI) is to be supported + break; + case IFR: + if ((value & 64) != 0) { + timer1IRQ = false; + } + if ((value & 32) != 0) { + timer2IRQ = false; + } + break; + case IER: + boolean enable = (value & 128) != 0; + if ((value & 64) != 0) { + timer1interruptEnabled = enable; + } + if ((value & 32) != 0) { + timer2interruptEnabled = enable; + } + break; + default: + } + } + + // Whatever uses 6522 will want to know when it is outputting values + // So to hook that in, these abstract methods will be defined as appropriate + public abstract void sendOutputA(int value); + + public abstract void sendOutputB(int value); + + public int readRegister(int reg) { + Register r = Register.fromInt(reg); +// System.out.println("Reading register "+r.toString()); + switch (r) { + case ORB: + if (dataDirectionB == 0x0ff) { + break; + } + return receiveOutputB() & (dataDirectionB ^ 0x0ff); + case ORA: + case ORAH: + if (dataDirectionA == 0x0ff) { + break; + } + return receiveOutputA() & (dataDirectionA ^ 0x0ff); + case DDRB: + return dataDirectionB; + case DDRA: + return dataDirectionA; + case T1CL: + timer1IRQ = false; + return timer1counter & 0x0ff; + case T1CH: + return (timer1counter & 0x0ff00) >> 8; + case T1LL: + return timer1latch & 0x0ff; + case T1LH: + return (timer1latch & 0x0ff00) >> 8; + case T2CL: + timer2IRQ = false; + return timer2counter & 0x0ff; + case T2CH: + return (timer2counter & 0x0ff00) >> 8; + case SR: + // SHIFT REGISTER NOT IMPLEMENTED + return 0; + case ACR: + // SHIFT REGISTER NOT IMPLEMENTED + if (timer1freerun) { + return 64; + } + return 0; + case PCR: + break; + case IFR: + int val = 0; + if (timer1IRQ) { + val |= 64; + } + if (timer2IRQ) { + val |= 32; + } + if (val != 0) { + val |= 128; + } + return val; + case IER: + val = 128; + if (timer1interruptEnabled) { + val |= 64; + } + if (timer2interruptEnabled) { + val |= 32; + } + return val; + } + return 0; + } + + public abstract int receiveOutputA(); + + public abstract int receiveOutputB(); +}