Start of MetaCheat revamp effort

This commit is contained in:
Brendan Robert 2015-08-18 00:37:06 -05:00
parent e7f731900a
commit eec3fdbcf7
11 changed files with 838 additions and 59 deletions

View File

@ -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) {

View File

@ -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;

View File

@ -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<? extends Card> 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<RAMListener> hints = new ArrayList<>();
ScheduledExecutorService animationTimer = new ScheduledThreadPoolExecutor(1);
@ -462,4 +465,4 @@ public class Apple2e extends Computer {
public String getShortName() {
return "computer";
}
}
}

View File

@ -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;

View File

@ -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<RAMListener> cheatList = FXCollections.observableArrayList();
private final ObservableList<SearchResult> resultList = FXCollections.observableArrayList();
private final ObservableList<State> 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<? extends String> 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<Boolean> signedProperty() {
return signedProperty;
}
public Property<String> searchValueProperty() {
return searchValueProperty;
}
public Property<String> searchChangeByProperty() {
return changeByProperty;
}
public ObservableList<RAMListener> getCheats() {
return cheatList;
}
public ObservableList<SearchResult> getSearchResults() {
return resultList;
}
public ObservableList<State> getSnapshots() {
return snapshotList;
}
public Property<String> startAddressProperty() {
return startAddressProperty;
}
public Property<String> 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;
});
}
}

View File

@ -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<String> newLog = new ArrayList<>();
ArrayList<String> log = traceLog;
ArrayList<String> 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) {

View File

@ -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<Boolean> 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<Card> 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

View File

@ -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();

View File

@ -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();
}

View File

@ -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<MetaCheat.SearchResult> searchResultsListView;
@FXML
private TilePane watchesPane;
@FXML
private ListView<State> snapshotsListView;
@FXML
private TableView<RAMListener> 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<? extends Toggle> val, Toggle oldVal, Toggle newVal) -> {
if (cheatEngine != null) {
cheatEngine.setSearchChangeType((SearchChangeType) newVal.getUserData());
}
});
searchTypeByte.setUserData(true);
searchTypeWord.setUserData(false);
searchSize.selectedToggleProperty().addListener((ObservableValue<? extends Toggle> 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.");
}
}

View File

@ -0,0 +1,208 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.text.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="492.0" prefWidth="702.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="jace.ui.MetacheatUI">
<children>
<ToolBar prefHeight="40.0" prefWidth="200.0" VBox.vgrow="NEVER">
<items>
<Button fx:id="pauseButton" mnemonicParsing="false" onAction="#pauseClicked" text="Pause" />
<Button mnemonicParsing="false" onAction="#zoomIn" text="Zoom in" />
<Button mnemonicParsing="false" onAction="#zoomOut" text="Zoom out" />
<Label text="Start:" />
<TextField fx:id="searchStartAddressField" prefHeight="26.0" prefWidth="50.0" text="0000" />
<Label text="End:" />
<TextField fx:id="searchEndAddressField" prefHeight="26.0" prefWidth="48.0" text="FFFF" />
</items>
</ToolBar>
<SplitPane dividerPositions="0.6304347826086957" prefHeight="363.0" prefWidth="600.0" VBox.vgrow="ALWAYS">
<items>
<ScrollPane fx:id="memoryViewPane" prefHeight="450.0" prefWidth="391.0" />
<Accordion>
<panes>
<TitledPane prefHeight="200.0" prefWidth="200.0" text="Search">
<content>
<VBox prefHeight="200.0" prefWidth="100.0">
<children>
<TabPane fx:id="searchTypesTabPane" prefHeight="147.0" prefWidth="233.0" tabClosingPolicy="UNAVAILABLE" VBox.vgrow="NEVER">
<tabs>
<Tab text="Value">
<content>
<VBox prefHeight="200.0" prefWidth="100.0">
<children>
<FlowPane prefHeight="43.0" prefWidth="233.0">
<children>
<Label text="Search for value:">
<padding>
<Insets right="3.0" />
</padding>
</Label>
<TextField fx:id="searchValueField" prefHeight="26.0" prefWidth="75.0" />
</children>
<padding>
<Insets top="3.0" />
</padding>
<VBox.margin>
<Insets bottom="4.0" />
</VBox.margin>
</FlowPane>
<Separator prefWidth="200.0" />
<Label text="These affect Change searches also.">
<font>
<Font name="System Italic" size="13.0" />
</font>
</Label>
<HBox prefHeight="100.0" prefWidth="200.0">
<children>
<RadioButton fx:id="searchTypeByte" mnemonicParsing="false" selected="true" text="Byte">
<HBox.margin>
<Insets left="2.0" />
</HBox.margin>
<toggleGroup>
<ToggleGroup fx:id="searchSize" />
</toggleGroup>
</RadioButton>
<RadioButton fx:id="searchTypeWord" mnemonicParsing="false" text="Word" toggleGroup="$searchSize">
<HBox.margin>
<Insets />
</HBox.margin>
<padding>
<Insets left="2.0" right="2.0" />
</padding>
</RadioButton>
<CheckBox fx:id="searchTypeSigned" mnemonicParsing="false" text="Signed" />
</children>
<opaqueInsets>
<Insets />
</opaqueInsets>
<padding>
<Insets bottom="3.0" top="3.0" />
</padding>
</HBox>
</children>
<padding>
<Insets left="5.0" />
</padding>
</VBox>
</content>
</Tab>
<Tab text="Change">
<content>
<VBox nodeOrientation="LEFT_TO_RIGHT" prefHeight="200.0" prefWidth="100.0">
<children>
<RadioButton fx:id="searchChangeNoneOption" mnemonicParsing="false" text="No changes since last search">
<opaqueInsets>
<Insets />
</opaqueInsets>
<padding>
<Insets bottom="3.0" top="2.0" />
</padding>
<toggleGroup>
<ToggleGroup fx:id="changeSearchType" />
</toggleGroup>
</RadioButton>
<RadioButton fx:id="searchChangeAnyOption" mnemonicParsing="false" text="Any changes since last search" toggleGroup="$changeSearchType">
<padding>
<Insets bottom="3.0" />
</padding>
</RadioButton>
<RadioButton fx:id="searchChangeLessOption" mnemonicParsing="false" text="Less than last search" toggleGroup="$changeSearchType">
<padding>
<Insets bottom="3.0" />
</padding>
</RadioButton>
<RadioButton fx:id="searchChangeGreaterOption" mnemonicParsing="false" text="Greater than last search" toggleGroup="$changeSearchType" />
<FlowPane prefHeight="200.0" prefWidth="200.0">
<children>
<RadioButton fx:id="searchChangeByOption" mnemonicParsing="false" text="Change by: " toggleGroup="$changeSearchType" />
<TextField fx:id="searchChangeByField" prefHeight="26.0" prefWidth="76.0" />
</children>
</FlowPane>
</children>
<opaqueInsets>
<Insets />
</opaqueInsets>
<padding>
<Insets left="2.0" />
</padding>
</VBox>
</content>
</Tab>
<Tab text="Text" />
</tabs>
</TabPane>
<ToolBar prefHeight="40.0" prefWidth="200.0" VBox.vgrow="NEVER">
<items>
<Button mnemonicParsing="false" onAction="#newSearch" text="New Search" />
<Button mnemonicParsing="false" onAction="#search" text="Search" />
</items>
</ToolBar>
<HBox prefHeight="19.0" prefWidth="235.0">
<children>
<Label text="Results:" HBox.hgrow="NEVER" />
<Label fx:id="searchStatusLabel" prefHeight="16.0" prefWidth="181.0" textAlignment="RIGHT" wrapText="true" HBox.hgrow="ALWAYS" />
</children>
</HBox>
<ListView fx:id="searchResultsListView" prefHeight="125.0" prefWidth="233.0" VBox.vgrow="ALWAYS" />
</children>
</VBox>
</content>
</TitledPane>
<TitledPane animated="false" text="Watches">
<content>
<TilePane fx:id="watchesPane" prefHeight="200.0" prefWidth="200.0" />
</content>
</TitledPane>
<TitledPane animated="false" text="Snapshots">
<content>
<BorderPane minWidth="-Infinity">
<bottom>
<ToolBar prefHeight="40.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<items>
<Button mnemonicParsing="false" onAction="#createSnapshot" text="Create" />
<Button mnemonicParsing="false" onAction="#deleteSnapshot" text="Delete" />
<Button mnemonicParsing="false" onAction="#diffSnapshots" text="Diff" />
</items>
</ToolBar>
</bottom>
<center>
<ListView fx:id="snapshotsListView" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
</center>
</BorderPane>
</content>
</TitledPane>
<TitledPane animated="false" text="Cheats">
<content>
<BorderPane prefHeight="200.0" prefWidth="200.0">
<bottom>
<ToolBar prefHeight="40.0" prefWidth="200.0" BorderPane.alignment="CENTER">
<items>
<Button mnemonicParsing="false" onAction="#addCheat" text="Add" />
<Button mnemonicParsing="false" onAction="#deleteCheat" text="Delete" />
<Button mnemonicParsing="false" onAction="#loadCheats" text="Load" />
<Button mnemonicParsing="false" onAction="#saveCheats" text="Save" />
</items>
</ToolBar>
</bottom>
<center>
<TableView fx:id="cheatsTableView" prefHeight="321.0" prefWidth="207.0" BorderPane.alignment="CENTER">
<columns>
<TableColumn prefWidth="35.0" text="On" />
<TableColumn prefWidth="63.0" text="Address" />
<TableColumn prefWidth="127.0" text="Effect" />
</columns>
</TableView>
</center>
</BorderPane>
</content>
</TitledPane>
</panes>
</Accordion>
</items>
</SplitPane>
</children>
</VBox>