diff --git a/src/main/java/jace/JaceApplication.java b/src/main/java/jace/JaceApplication.java index 741435f..3b20e1f 100644 --- a/src/main/java/jace/JaceApplication.java +++ b/src/main/java/jace/JaceApplication.java @@ -8,6 +8,7 @@ package jace; import jace.core.RAMEvent; import jace.core.RAMListener; import jace.core.Utility; +import jace.ui.MetacheatUI; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; @@ -16,7 +17,9 @@ import javafx.application.Platform; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; import javafx.stage.Stage; +import javafx.stage.StageStyle; /** * @@ -25,11 +28,12 @@ import javafx.stage.Stage; public class JaceApplication extends Application { static JaceApplication singleton; + Stage primaryStage; JaceUIController controller; static boolean romStarted = false; - + @Override public void start(Stage stage) throws Exception { singleton = this; @@ -68,6 +72,30 @@ public class JaceApplication extends Application { return singleton; } + Stage cheatStage; + private MetacheatUI cheatController; + + public MetacheatUI showMetacheat() { + if (cheatController == null) { + cheatStage = new Stage(StageStyle.DECORATED); + FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/fxml/Metacheat.fxml")); + fxmlLoader.setResources(null); + try { + VBox node = fxmlLoader.load(); + cheatController = fxmlLoader.getController(); + Scene s = new Scene(node); + cheatStage.setScene(s); + cheatStage.setTitle("Jace: MetaCheat"); + cheatStage.getIcons().add(Utility.loadIcon("woz_figure.gif")); + } catch (IOException exception) { + throw new RuntimeException(exception); + } + + } + cheatStage.show(); + return cheatController; + } + /** * @param args the command line arguments */ @@ -76,19 +104,20 @@ public class JaceApplication extends Application { } /** - * Start the computer and make sure it runs through the expected rom routine for cold boot + * Start the computer and make sure it runs through the expected rom routine + * for cold boot */ private void bootWatchdog() { romStarted = false; RAMListener startListener = Emulator.computer.getMemory(). - observe(RAMEvent.TYPE.EXECUTE, 0x0FA62, (e)-> { - romStarted = true; - }); + observe(RAMEvent.TYPE.EXECUTE, 0x0FA62, (e) -> { + romStarted = true; + }); Emulator.computer.coldStart(); try { Thread.sleep(250); if (!romStarted) { - Logger.getLogger(getClass().getName()).log(Level.WARNING,"Boot not detected, performing a cold start"); + Logger.getLogger(getClass().getName()).log(Level.WARNING, "Boot not detected, performing a cold start"); Emulator.computer.coldStart(); } } catch (InterruptedException ex) { diff --git a/src/main/java/jace/JaceUIController.java b/src/main/java/jace/JaceUIController.java index a47344d..4bc3640 100644 --- a/src/main/java/jace/JaceUIController.java +++ b/src/main/java/jace/JaceUIController.java @@ -7,6 +7,7 @@ package jace; import com.sun.glass.ui.Application; +import jace.cheat.MetaCheat; import jace.core.Card; import jace.core.Computer; import jace.library.MediaCache; @@ -48,6 +49,7 @@ import javafx.scene.paint.Color; * @author blurry */ public class JaceUIController { + @FXML private URL location; diff --git a/src/main/java/jace/apple2e/Apple2e.java b/src/main/java/jace/apple2e/Apple2e.java index 5b24d83..42c8313 100644 --- a/src/main/java/jace/apple2e/Apple2e.java +++ b/src/main/java/jace/apple2e/Apple2e.java @@ -124,7 +124,7 @@ public class Apple2e extends Computer { if (motherboard != null && motherboard.isRunning()) { motherboard.suspend(); } - motherboard = new Motherboard(this, motherboard); + setMotherboard(new Motherboard(this, motherboard)); reconfigure(); motherboard.reconfigure(); } @@ -169,6 +169,10 @@ public class Apple2e extends Computer { resume(); } + public Cheats getActiveCheatEngine() { + return activeCheatEngine; + } + private void insertCard(Class type, int slot) throws NoSuchMethodException, IllegalArgumentException, InvocationTargetException { if (getMemory().getCard(slot).isPresent()) { if (getMemory().getCard(slot).get().getClass().equals(type)) { @@ -355,13 +359,12 @@ public class Apple2e extends Computer { motherboard.resume(); } - @Override - protected boolean isRunning() { - if (motherboard == null) { - return false; - } - return motherboard.isRunning() && !motherboard.isPaused; - } +// public boolean isRunning() { +// if (motherboard == null) { +// return false; +// } +// return motherboard.isRunning() && !motherboard.isPaused; +// } private List hints = new ArrayList<>(); ScheduledExecutorService animationTimer = new ScheduledThreadPoolExecutor(1); @@ -462,4 +465,4 @@ public class Apple2e extends Computer { public String getShortName() { return "computer"; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/main/java/jace/apple2e/MOS65C02.java b/src/main/java/jace/apple2e/MOS65C02.java index 43503fc..62721a6 100644 --- a/src/main/java/jace/apple2e/MOS65C02.java +++ b/src/main/java/jace/apple2e/MOS65C02.java @@ -24,6 +24,7 @@ import jace.core.Computer; import jace.core.RAM; import jace.core.RAMEvent.TYPE; import jace.state.Stateful; +import java.util.logging.Logger; /** * This is a full implementation of a MOS-65c02 processor, including the BBR, @@ -1036,29 +1037,27 @@ public class MOS65C02 extends CPU { } int pc = getProgramCounter(); -// RAM ram = cpu.getMemory(); -// int op = 0x00ff & cpu.getMemory().read(pc, false); + String traceEntry = null; + if (isTraceEnabled() || isLogEnabled() || warnAboutExtendedOpcodes) { + traceEntry = getState().toUpperCase() + " " + Integer.toString(pc, 16) + " : " + disassemble(); + if (isTraceEnabled()) { + Logger.getLogger(getClass().getName()).info(traceEntry); + } + if (isLogEnabled()) { + log(traceEntry); + } + } // This makes it possible to trap the memory read of an opcode, when PC == Address, you know it is executing that opcode. int op = 0x00ff & getMemory().read(pc, TYPE.EXECUTE, true, false); OPCODE opcode = opcodes[op]; - if (isTraceEnabled() || isLogEnabled() || (warnAboutExtendedOpcodes && opcode != null && opcode.isExtendedOpcode)) { - String t = getState().toUpperCase() + " " + Integer.toString(pc, 16) + " : " + disassemble(); - if (warnAboutExtendedOpcodes && opcode != null && opcode.isExtendedOpcode) { - System.out.println(">>EXTENDED OPCODE DETECTED " + Integer.toHexString(opcode.code) + "<<"); - System.out.println(t); - if (isLogEnabled()) { - log(">>EXTENDED OPCODE DETECTED " + Integer.toHexString(opcode.code) + "<<"); - log(t); - } - } else { - if (isTraceEnabled()) { - System.out.println(t); - } - if (isLogEnabled()) { - log(t); - } + if (traceEntry != null && warnAboutExtendedOpcodes && opcode != null && opcode.isExtendedOpcode) { + System.out.println(">>EXTENDED OPCODE DETECTED " + Integer.toHexString(opcode.code) + "<<"); + System.out.println(traceEntry); + if (isLogEnabled()) { + log(">>EXTENDED OPCODE DETECTED " + Integer.toHexString(opcode.code) + "<<"); + log(traceEntry); } - } + } if (opcode == null) { // handle bad opcode as a NOP int wait = 0; diff --git a/src/main/java/jace/cheat/MetaCheat.java b/src/main/java/jace/cheat/MetaCheat.java new file mode 100644 index 0000000..c8c28fa --- /dev/null +++ b/src/main/java/jace/cheat/MetaCheat.java @@ -0,0 +1,240 @@ +package jace.cheat; + +import jace.Emulator; +import jace.JaceApplication; +import jace.core.Computer; +import jace.core.RAM; +import jace.core.RAMListener; +import jace.state.State; +import jace.ui.MetacheatUI; +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; + +public class MetaCheat extends Cheats { + + 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) + ")"; + } + } + + MetacheatUI ui; + private int startAddress = 0; + private int endAddress = 0x0ffff; + private final StringProperty startAddressProperty = new SimpleStringProperty("0"); + private final StringProperty endAddressProperty = new SimpleStringProperty("FFFF"); + private boolean byteSized = false; + 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 = parseInt(newVal); + }); + endAddressProperty.addListener((prop, oldVal, newVal) -> { + endAddress = 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.matches("(\\+|-)?[0-9]+")) { + return Integer.parseInt(s); + } else if (s.matches("(\\+|-)?[0-9a-fA-F]+")) { + return Integer.parseInt(s.toUpperCase(), 16); + } else if (s.matches("(\\+|-)?(x|$)[0-9a-fA-F]+")) { + String upper = s.toUpperCase(); + boolean positive = true; + if (upper.startsWith("-")) { + positive = false; + } + 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 + void registerListeners() { + } + + @Override + protected String getDeviceName() { + return "MetaCheat"; + } + + @Override + public void tick() { + } + + @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; + }); + } + +} diff --git a/src/main/java/jace/core/CPU.java b/src/main/java/jace/core/CPU.java index 8693822..36c3d2b 100644 --- a/src/main/java/jace/core/CPU.java +++ b/src/main/java/jace/core/CPU.java @@ -33,6 +33,7 @@ import java.util.logging.Logger; * @author Brendan Robert (BLuRry) brendan.robert@gmail.com */ public abstract class CPU extends Device { + private static final Logger LOG = Logger.getLogger(CPU.class.getName()); public CPU(Computer computer) { super(computer); @@ -74,14 +75,12 @@ public abstract class CPU extends Device { public void dumpTrace() { computer.pause(); ArrayList newLog = new ArrayList<>(); - ArrayList log = traceLog; + ArrayList oldLog = traceLog; traceLog = newLog; computer.resume(); - System.out.println("Most recent " + traceLength + " instructions:"); - log.stream().forEach((s) -> { - System.out.println(s); - }); - traceLog.clear(); + LOG.log(Level.INFO, "Most recent {0} instructions:", traceLength); + oldLog.stream().forEach(LOG::info); + oldLog.clear(); } public void setDebug(Debugger d) { diff --git a/src/main/java/jace/core/Computer.java b/src/main/java/jace/core/Computer.java index 63ea451..4c99ea1 100644 --- a/src/main/java/jace/core/Computer.java +++ b/src/main/java/jace/core/Computer.java @@ -24,6 +24,9 @@ import jace.config.Reconfigurable; import jace.state.StateManager; import java.io.IOException; import java.util.Optional; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.value.ChangeListener; /** * This is a very generic stub of a Computer and provides a generic set of @@ -45,6 +48,7 @@ public abstract class Computer implements Reconfigurable { @ConfigurableField(category = "advanced", name = "State management", shortName = "rewind", description = "This enables rewind support, but consumes a lot of memory when active.") public boolean enableStateManager; public final SoundMixer mixer; + final private BooleanProperty runningProperty = new SimpleBooleanProperty(false); /** * Creates a new instance of Computer @@ -63,6 +67,22 @@ public abstract class Computer implements Reconfigurable { return motherboard; } + ChangeListener runningPropertyListener = (prop, oldVal, newVal) -> runningProperty.set(newVal); + public void setMotherboard(Motherboard m) { + if (motherboard != null && motherboard.isRunning()) { + motherboard.suspend(); + } + motherboard = m; + } + + public BooleanProperty getRunningProperty() { + return runningProperty; + } + + public boolean isRunning() { + return getRunningProperty().get(); + } + public void notifyVBLStateChanged(boolean state) { for (Optional c : getMemory().cards) { c.ifPresent(card -> card.notifyVBLStateChanged(state)); @@ -113,17 +133,16 @@ public abstract class Computer implements Reconfigurable { video.suspend(); mixer.detach(); } - + @InvokableAction( name = "Cold boot", description = "Process startup sequence from power-up", category = "general", alternatives = "Full reset;reset emulator", consumeKeyEvent = true, - defaultKeyMapping = {"Ctrl+Shift+Backspace","Ctrl+Shift+Delete"}) + defaultKeyMapping = {"Ctrl+Shift+Backspace", "Ctrl+Shift+Delete"}) public void invokeColdStart() { if (!romLoaded) { - System.out.println("Computer booted before rom was loaded"); Thread delayedStart = new Thread(() -> { while (!romLoaded) { Thread.yield(); @@ -135,6 +154,7 @@ public abstract class Computer implements Reconfigurable { coldStart(); } } + public abstract void coldStart(); @InvokableAction( @@ -142,32 +162,33 @@ public abstract class Computer implements Reconfigurable { description = "Process user-initatiated reboot (ctrl+apple+reset)", category = "general", alternatives = "reboot;reset;three-finger-salute", - defaultKeyMapping = {"Ctrl+Ignore Alt+Ignore Meta+Backspace","Ctrl+Ignore Alt+Ignore Meta+Delete"}) + defaultKeyMapping = {"Ctrl+Ignore Alt+Ignore Meta+Backspace", "Ctrl+Ignore Alt+Ignore Meta+Delete"}) public void invokeWarmStart() { warmStart(); } + public abstract void warmStart(); public Keyboard getKeyboard() { return this.keyboard; } - protected abstract boolean isRunning(); - protected abstract void doPause(); protected abstract void doResume(); - @InvokableAction(name = "Pause", description = "Stops the computer, allowing reconfiguration of core elements", alternatives = "freeze;halt", defaultKeyMapping = {"meta+pause","alt+pause"}) + @InvokableAction(name = "Pause", description = "Stops the computer, allowing reconfiguration of core elements", alternatives = "freeze;halt", defaultKeyMapping = {"meta+pause", "alt+pause"}) public boolean pause() { - boolean result = isRunning(); + boolean result = getRunningProperty().get(); doPause(); + getRunningProperty().set(false); return result; } - @InvokableAction(name = "Resume", description = "Resumes the computer if it was previously paused", alternatives = "unpause;unfreeze;resume", defaultKeyMapping = {"meta+shift+pause","alt+shift+pause"}) + @InvokableAction(name = "Resume", description = "Resumes the computer if it was previously paused", alternatives = "unpause;unfreeze;resume", defaultKeyMapping = {"meta+shift+pause", "alt+shift+pause"}) public void resume() { doResume(); + getRunningProperty().set(true); } @Override diff --git a/src/main/java/jace/core/Device.java b/src/main/java/jace/core/Device.java index 9990f03..07bba73 100644 --- a/src/main/java/jace/core/Device.java +++ b/src/main/java/jace/core/Device.java @@ -20,6 +20,8 @@ package jace.core; import jace.state.Stateful; import jace.config.Reconfigurable; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.SimpleBooleanProperty; /** * Device is a very simple abstraction of any emulation component. A device @@ -37,6 +39,8 @@ import jace.config.Reconfigurable; @Stateful public abstract class Device implements Reconfigurable { protected Computer computer; + private Device() { + } public Device(Computer computer) { this.computer = computer; } @@ -45,10 +49,14 @@ public abstract class Device implements Reconfigurable { @Stateful private int waitCycles = 0; @Stateful - private boolean run = true; + private final BooleanProperty run = new SimpleBooleanProperty(true); @Stateful public boolean isPaused = false; + public BooleanProperty getRunningProperty() { + return run; + } + public void addWaitCycles(int wait) { waitCycles += wait; } @@ -65,7 +73,7 @@ public abstract class Device implements Reconfigurable { waitCycles--; */ - if (!run) { + if (!run.get()) { // System.out.println("Device stopped: " + getName()); isPaused = true; return; @@ -82,13 +90,13 @@ public abstract class Device implements Reconfigurable { } public boolean isRunning() { - return run; + return run.get(); } public synchronized void setRun(boolean run) { // System.out.println(Thread.currentThread().getName() + (run ? " resuming " : " suspending ")+ getDeviceName()); isPaused = false; - this.run = run; + this.run.set(run); } protected abstract String getDeviceName(); diff --git a/src/main/java/jace/tracker/PlaybackEngine.java b/src/main/java/jace/tracker/PlaybackEngine.java index 0103c39..b56cd81 100644 --- a/src/main/java/jace/tracker/PlaybackEngine.java +++ b/src/main/java/jace/tracker/PlaybackEngine.java @@ -44,11 +44,6 @@ public class PlaybackEngine extends Computer { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } - @Override - protected boolean isRunning() { - throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. - } - @Override protected void doPause() { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. @@ -93,7 +88,7 @@ public class PlaybackEngine extends Computer { } @Override - protected boolean isRunning() { + public boolean isRunning() { return motherboard.isRunning(); } diff --git a/src/main/java/jace/ui/MetacheatUI.java b/src/main/java/jace/ui/MetacheatUI.java new file mode 100644 index 0000000..73c648d --- /dev/null +++ b/src/main/java/jace/ui/MetacheatUI.java @@ -0,0 +1,275 @@ +package jace.ui; + +import com.sun.glass.ui.Application; +import jace.Emulator; +import jace.cheat.MetaCheat; +import jace.cheat.MetaCheat.SearchChangeType; +import jace.cheat.MetaCheat.SearchType; +import jace.core.RAMEvent; +import jace.core.RAMListener; +import jace.state.State; +import javafx.application.Platform; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.Label; +import javafx.scene.control.ListView; +import javafx.scene.control.RadioButton; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.SingleSelectionModel; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import javafx.scene.control.TableView; +import javafx.scene.control.TextField; +import javafx.scene.control.Toggle; +import javafx.scene.control.ToggleGroup; +import javafx.scene.layout.TilePane; + +public class MetacheatUI { + + @FXML + private Button pauseButton; + + @FXML + private TextField searchStartAddressField; + + @FXML + private TextField searchEndAddressField; + + @FXML + private ScrollPane memoryViewPane; + + @FXML + private TabPane searchTypesTabPane; + + @FXML + private TextField searchValueField; + + @FXML + private RadioButton searchTypeByte; + + @FXML + private ToggleGroup searchSize; + + @FXML + private RadioButton searchTypeWord; + + @FXML + private CheckBox searchTypeSigned; + + @FXML + private RadioButton searchChangeNoneOption; + + @FXML + private ToggleGroup changeSearchType; + + @FXML + private RadioButton searchChangeAnyOption; + + @FXML + private RadioButton searchChangeLessOption; + + @FXML + private RadioButton searchChangeGreaterOption; + + @FXML + private RadioButton searchChangeByOption; + + @FXML + private TextField searchChangeByField; + + @FXML + private Label searchStatusLabel; + + @FXML + private ListView searchResultsListView; + + @FXML + private TilePane watchesPane; + + @FXML + private ListView snapshotsListView; + + @FXML + private TableView cheatsTableView; + + @FXML + void addCheat(ActionEvent event) { + + } + + @FXML + void createSnapshot(ActionEvent event) { + + } + + @FXML + void deleteCheat(ActionEvent event) { + + } + + @FXML + void deleteSnapshot(ActionEvent event) { + + } + + @FXML + void diffSnapshots(ActionEvent event) { + + } + + @FXML + void loadCheats(ActionEvent event) { + + } + + @FXML + void newSearch(ActionEvent event) { + Platform.runLater(() -> { + cheatEngine.newSearch(); + updateSearchStats(); + }); + } + + @FXML + void pauseClicked(ActionEvent event) { + Application.invokeLater(() -> { + if (Emulator.computer.isRunning()) { + Emulator.computer.pause(); + } else { + Emulator.computer.resume(); + } + }); + } + + @FXML + void saveCheats(ActionEvent event) { + + } + + @FXML + void search(ActionEvent event) { + Platform.runLater(() -> { + cheatEngine.performSearch(); + updateSearchStats(); + }); + } + + @FXML + void zoomIn(ActionEvent event) { + changeZoom(0.1); + } + + @FXML + void zoomOut(ActionEvent event) { + changeZoom(-0.1); + } + + @FXML + void initialize() { + assert pauseButton != null : "fx:id=\"pauseButton\" was not injected: check your FXML file 'Metacheat.fxml'."; + assert searchStartAddressField != null : "fx:id=\"searchStartAddressField\" was not injected: check your FXML file 'Metacheat.fxml'."; + assert searchEndAddressField != null : "fx:id=\"searchEndAddressField\" was not injected: check your FXML file 'Metacheat.fxml'."; + assert memoryViewPane != null : "fx:id=\"memoryViewPane\" was not injected: check your FXML file 'Metacheat.fxml'."; + assert searchTypesTabPane != null : "fx:id=\"searchTypesTabPane\" was not injected: check your FXML file 'Metacheat.fxml'."; + assert searchValueField != null : "fx:id=\"searchValueField\" was not injected: check your FXML file 'Metacheat.fxml'."; + assert searchTypeByte != null : "fx:id=\"searchTypeByte\" was not injected: check your FXML file 'Metacheat.fxml'."; + assert searchSize != null : "fx:id=\"searchSize\" was not injected: check your FXML file 'Metacheat.fxml'."; + assert searchTypeWord != null : "fx:id=\"searchTypeWord\" was not injected: check your FXML file 'Metacheat.fxml'."; + assert searchTypeSigned != null : "fx:id=\"searchTypeSigned\" was not injected: check your FXML file 'Metacheat.fxml'."; + assert searchChangeNoneOption != null : "fx:id=\"searchChangeNoneOption\" was not injected: check your FXML file 'Metacheat.fxml'."; + assert changeSearchType != null : "fx:id=\"changeSearchType\" was not injected: check your FXML file 'Metacheat.fxml'."; + assert searchChangeAnyOption != null : "fx:id=\"searchChangeAnyOption\" was not injected: check your FXML file 'Metacheat.fxml'."; + assert searchChangeLessOption != null : "fx:id=\"searchChangeLessOption\" was not injected: check your FXML file 'Metacheat.fxml'."; + assert searchChangeGreaterOption != null : "fx:id=\"searchChangeGreaterOption\" was not injected: check your FXML file 'Metacheat.fxml'."; + assert searchChangeByOption != null : "fx:id=\"searchChangeByOption\" was not injected: check your FXML file 'Metacheat.fxml'."; + assert searchChangeByField != null : "fx:id=\"searchChangeByField\" was not injected: check your FXML file 'Metacheat.fxml'."; + assert searchStatusLabel != null : "fx:id=\"searchStatusLabel\" was not injected: check your FXML file 'Metacheat.fxml'."; + assert searchResultsListView != null : "fx:id=\"searchResultsListView\" was not injected: check your FXML file 'Metacheat.fxml'."; + assert watchesPane != null : "fx:id=\"watchesPane\" was not injected: check your FXML file 'Metacheat.fxml'."; + assert snapshotsListView != null : "fx:id=\"snapshotsListView\" was not injected: check your FXML file 'Metacheat.fxml'."; + assert cheatsTableView != null : "fx:id=\"cheatsTableView\" was not injected: check your FXML file 'Metacheat.fxml'."; + + Emulator.computer.getRunningProperty().addListener((val, oldVal, newVal) -> pauseButton.setText(newVal ? "Pause" : "Resume")); + + searchTypesTabPane.getTabs().get(0).setUserData(SearchType.VALUE); + searchTypesTabPane.getTabs().get(1).setUserData(SearchType.CHANGE); + searchTypesTabPane.getTabs().get(2).setUserData(SearchType.TEXT); + searchTypesTabPane.getSelectionModel().selectedItemProperty().addListener((prop, oldVal, newVal) -> { + System.out.println("Tab selected: " + newVal.getText()); + if (cheatEngine != null) { + cheatEngine.setSearchType((SearchType) newVal.getUserData()); + } + }); + + searchChangeAnyOption.setUserData(SearchChangeType.ANY_CHANGE); + searchChangeByOption.setUserData(SearchChangeType.AMOUNT); + searchChangeGreaterOption.setUserData(SearchChangeType.GREATER); + searchChangeLessOption.setUserData(SearchChangeType.LESS); + searchChangeNoneOption.setUserData(SearchChangeType.NO_CHANGE); + changeSearchType.selectedToggleProperty().addListener((ObservableValue val, Toggle oldVal, Toggle newVal) -> { + if (cheatEngine != null) { + cheatEngine.setSearchChangeType((SearchChangeType) newVal.getUserData()); + } + }); + + searchTypeByte.setUserData(true); + searchTypeWord.setUserData(false); + searchSize.selectedToggleProperty().addListener((ObservableValue val, Toggle oldVal, Toggle newVal) -> { + if (cheatEngine != null) { + cheatEngine.setByteSized((boolean) newVal.getUserData()); + } + }); + } + + MetaCheat cheatEngine = null; + + public void registerMetacheatEngine(MetaCheat engine) { + cheatEngine = engine; + + cheatsTableView.setItems(cheatEngine.getCheats()); + searchResultsListView.setItems(cheatEngine.getSearchResults()); + snapshotsListView.setItems(cheatEngine.getSnapshots()); + searchTypeSigned.selectedProperty().bindBidirectional(cheatEngine.signedProperty()); + searchStartAddressField.textProperty().bindBidirectional(cheatEngine.startAddressProperty()); + searchEndAddressField.textProperty().bindBidirectional(cheatEngine.endAddressProperty()); + searchValueField.textProperty().bindBidirectional(cheatEngine.searchValueProperty()); + searchChangeByField.textProperty().bindBidirectional(cheatEngine.searchChangeByProperty()); + + engine.addCheat(RAMEvent.TYPE.ANY, this::processMemoryEvent, 0, 0x0ffff); + } + + private void changeZoom(double amount) { + double zoom = memoryViewPane.getScaleX(); + zoom += amount; + memoryViewPane.setScaleX(zoom); + memoryViewPane.setScaleY(zoom); + } + + private void processMemoryEvent(RAMEvent e) { + if (e.getAddress() < cheatEngine.getStartAddress() || e.getAddress() > cheatEngine.getEndAddress()) { + return; + } + + } + + public void detach() { + cheatsTableView.setItems(FXCollections.emptyObservableList()); + searchResultsListView.setItems(FXCollections.emptyObservableList()); + searchTypeSigned.selectedProperty().unbind(); + searchStartAddressField.textProperty().unbind(); + searchStartAddressField.textProperty().unbind(); + searchEndAddressField.textProperty().unbind(); + searchValueField.textProperty().unbind(); + searchChangeByField.textProperty().unbind(); + cheatEngine = null; + } + + private void updateSearchStats() { + int size = cheatEngine.getSearchResults().size(); + searchStatusLabel.setText(size + (size == 1 ? " result" : " results") + " found."); + } +} diff --git a/src/main/resources/fxml/Metacheat.fxml b/src/main/resources/fxml/Metacheat.fxml new file mode 100644 index 0000000..0fafde8 --- /dev/null +++ b/src/main/resources/fxml/Metacheat.fxml @@ -0,0 +1,208 @@ + + + + + + + + + + + + +