mirror of
https://github.com/badvision/jace.git
synced 2024-11-28 10:52:33 +00:00
Start of MetaCheat revamp effort
This commit is contained in:
parent
e7f731900a
commit
eec3fdbcf7
@ -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,6 +28,7 @@ import javafx.stage.Stage;
|
||||
public class JaceApplication extends Application {
|
||||
|
||||
static JaceApplication singleton;
|
||||
|
||||
Stage primaryStage;
|
||||
JaceUIController controller;
|
||||
|
||||
@ -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)-> {
|
||||
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) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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,27 +1037,25 @@ 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) {
|
||||
if (traceEntry != null && warnAboutExtendedOpcodes && opcode != null && opcode.isExtendedOpcode) {
|
||||
System.out.println(">>EXTENDED OPCODE DETECTED " + Integer.toHexString(opcode.code) + "<<");
|
||||
System.out.println(t);
|
||||
System.out.println(traceEntry);
|
||||
if (isLogEnabled()) {
|
||||
log(">>EXTENDED OPCODE DETECTED " + Integer.toHexString(opcode.code) + "<<");
|
||||
log(t);
|
||||
}
|
||||
} else {
|
||||
if (isTraceEnabled()) {
|
||||
System.out.println(t);
|
||||
}
|
||||
if (isLogEnabled()) {
|
||||
log(t);
|
||||
}
|
||||
log(traceEntry);
|
||||
}
|
||||
}
|
||||
if (opcode == null) {
|
||||
|
240
src/main/java/jace/cheat/MetaCheat.java
Normal file
240
src/main/java/jace/cheat/MetaCheat.java
Normal 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;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
|
@ -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));
|
||||
@ -120,10 +140,9 @@ public abstract class Computer implements Reconfigurable {
|
||||
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
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
275
src/main/java/jace/ui/MetacheatUI.java
Normal file
275
src/main/java/jace/ui/MetacheatUI.java
Normal 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.");
|
||||
}
|
||||
}
|
208
src/main/resources/fxml/Metacheat.fxml
Normal file
208
src/main/resources/fxml/Metacheat.fxml
Normal 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>
|
Loading…
Reference in New Issue
Block a user