mirror of https://github.com/badvision/jace.git
576 lines
22 KiB
Java
576 lines
22 KiB
Java
package jace.ui;
|
|
|
|
import com.sun.glass.ui.Application;
|
|
import jace.Emulator;
|
|
import jace.JaceApplication;
|
|
import jace.cheat.DynamicCheat;
|
|
import jace.cheat.MemoryCell;
|
|
import jace.cheat.MetaCheat;
|
|
import jace.cheat.MetaCheat.SearchChangeType;
|
|
import jace.cheat.MetaCheat.SearchResult;
|
|
import jace.cheat.MetaCheat.SearchType;
|
|
import jace.state.State;
|
|
import java.io.File;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import java.util.Set;
|
|
import java.util.concurrent.ConcurrentSkipListSet;
|
|
import java.util.concurrent.ScheduledExecutorService;
|
|
import java.util.concurrent.ScheduledFuture;
|
|
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
|
import java.util.concurrent.TimeUnit;
|
|
import javafx.application.Platform;
|
|
import javafx.beans.value.ChangeListener;
|
|
import javafx.beans.value.ObservableValue;
|
|
import javafx.collections.FXCollections;
|
|
import javafx.event.ActionEvent;
|
|
import javafx.fxml.FXML;
|
|
import javafx.geometry.Insets;
|
|
import javafx.scene.Group;
|
|
import javafx.scene.SubScene;
|
|
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.TabPane;
|
|
import javafx.scene.control.TableColumn;
|
|
import javafx.scene.control.TableView;
|
|
import javafx.scene.control.TextField;
|
|
import javafx.scene.control.Toggle;
|
|
import javafx.scene.control.ToggleGroup;
|
|
import javafx.scene.control.Tooltip;
|
|
import javafx.scene.control.cell.CheckBoxTableCell;
|
|
import javafx.scene.control.cell.PropertyValueFactory;
|
|
import javafx.scene.control.cell.TextFieldTableCell;
|
|
import javafx.scene.input.MouseEvent;
|
|
import javafx.scene.layout.Background;
|
|
import javafx.scene.layout.BackgroundFill;
|
|
import javafx.scene.layout.StackPane;
|
|
import javafx.scene.layout.TilePane;
|
|
import javafx.scene.paint.Color;
|
|
import javafx.scene.shape.Rectangle;
|
|
import javafx.stage.FileChooser;
|
|
import javafx.stage.Screen;
|
|
import javafx.util.converter.DefaultStringConverter;
|
|
import javafx.util.converter.IntegerStringConverter;
|
|
|
|
public class MetacheatUI {
|
|
|
|
boolean isRetina;
|
|
double drawScale;
|
|
|
|
@FXML
|
|
private Button pauseButton;
|
|
|
|
@FXML
|
|
private TextField searchStartAddressField;
|
|
|
|
@FXML
|
|
private TextField searchEndAddressField;
|
|
|
|
@FXML
|
|
private ScrollPane memoryViewPane;
|
|
|
|
@FXML
|
|
private StackPane memoryViewContents;
|
|
|
|
@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 CheckBox showValuesCheckbox;
|
|
|
|
@FXML
|
|
private TilePane watchesPane;
|
|
|
|
@FXML
|
|
private ListView<State> snapshotsListView;
|
|
|
|
@FXML
|
|
private TableView<DynamicCheat> cheatsTableView;
|
|
|
|
@FXML
|
|
private TextField codeInspectorAddress;
|
|
|
|
@FXML
|
|
private ListView<String> codeInspectorWriteList;
|
|
|
|
@FXML
|
|
private ListView<String> codeInspectorReadList;
|
|
|
|
@FXML
|
|
void createSnapshot(ActionEvent event) {
|
|
|
|
}
|
|
|
|
@FXML
|
|
void deleteSnapshot(ActionEvent event) {
|
|
|
|
}
|
|
|
|
@FXML
|
|
void diffSnapshots(ActionEvent event) {
|
|
|
|
}
|
|
|
|
@FXML
|
|
void addCheat(ActionEvent event) {
|
|
cheatEngine.addCheat(new DynamicCheat(0, "?"));
|
|
}
|
|
|
|
@FXML
|
|
void deleteCheat(ActionEvent event) {
|
|
cheatsTableView.getSelectionModel().getSelectedItems().forEach(cheatEngine::removeCheat);
|
|
}
|
|
|
|
@FXML
|
|
void loadCheats(ActionEvent event) {
|
|
boolean resume = Emulator.computer.pause();
|
|
FileChooser chooser = new FileChooser();
|
|
chooser.setTitle("Load cheats");
|
|
chooser.setInitialFileName("cheat.txt");
|
|
File saveFile = chooser.showOpenDialog(JaceApplication.getApplication().primaryStage);
|
|
if (saveFile != null) {
|
|
cheatEngine.loadCheats(saveFile);
|
|
}
|
|
if (resume) {
|
|
Emulator.computer.resume();
|
|
}
|
|
}
|
|
|
|
@FXML
|
|
void saveCheats(ActionEvent event) {
|
|
boolean resume = Emulator.computer.pause();
|
|
FileChooser chooser = new FileChooser();
|
|
chooser.setTitle("Save current cheats");
|
|
chooser.setInitialFileName("cheat.txt");
|
|
File saveFile = chooser.showSaveDialog(JaceApplication.getApplication().primaryStage);
|
|
if (saveFile != null) {
|
|
cheatEngine.saveCheats(saveFile);
|
|
}
|
|
if (resume) {
|
|
Emulator.computer.resume();
|
|
}
|
|
}
|
|
|
|
@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 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 memoryViewContents != null : "fx:id=\"memoryViewContents\" 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 codeInspectorAddress != null : "fx:id=\"codeInspectorAddress\" was not injected: check your FXML file 'Metacheat.fxml'.";
|
|
assert codeInspectorWriteList != null : "fx:id=\"codeInspectorWriteList\" was not injected: check your FXML file 'Metacheat.fxml'.";
|
|
assert codeInspectorReadList != null : "fx:id=\"codeInspectorReadList\" was not injected: check your FXML file 'Metacheat.fxml'.";
|
|
assert cheatsTableView != null : "fx:id=\"cheatsTableView\" was not injected: check your FXML file 'Metacheat.fxml'.";
|
|
|
|
isRetina = Screen.getPrimary().getDpi() >= 110;
|
|
|
|
Emulator.computer.getRunningProperty().addListener((val, oldVal, newVal) -> {
|
|
Platform.runLater(() -> 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) -> {
|
|
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());
|
|
}
|
|
});
|
|
|
|
searchResultsListView.setEditable(true);
|
|
searchResultsListView.setOnEditStart((editEvent) -> {
|
|
editEvent.consume();
|
|
SearchResult result = cheatEngine.getSearchResults().get(editEvent.getIndex());
|
|
addWatch(result.getAddress());
|
|
});
|
|
|
|
showValuesCheckbox.selectedProperty().addListener((prop, oldVal, newVal) -> {
|
|
if (newVal) {
|
|
redrawMemoryView();
|
|
}
|
|
});
|
|
memoryViewPane.boundsInParentProperty().addListener((prop, oldVal, newVal) -> redrawMemoryView());
|
|
drawScale = isRetina ? 0.5 : 1.0;
|
|
|
|
watchesPane.setHgap(5);
|
|
watchesPane.setVgap(5);
|
|
|
|
searchStartAddressField.textProperty().addListener(addressRangeListener);
|
|
searchEndAddressField.textProperty().addListener(addressRangeListener);
|
|
|
|
TableColumn<DynamicCheat, Boolean> activeColumn = (TableColumn<DynamicCheat, Boolean>) cheatsTableView.getColumns().get(0);
|
|
activeColumn.setCellValueFactory(new PropertyValueFactory<>("active"));
|
|
activeColumn.setCellFactory((TableColumn<DynamicCheat, Boolean> param) -> new CheckBoxTableCell<>());
|
|
|
|
TableColumn<DynamicCheat, String> nameColumn = (TableColumn<DynamicCheat, String>) cheatsTableView.getColumns().get(1);
|
|
nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));
|
|
nameColumn.setCellFactory((TableColumn<DynamicCheat, String> param) -> new TextFieldTableCell<>(new DefaultStringConverter()));
|
|
|
|
TableColumn<DynamicCheat, Integer> addrColumn = (TableColumn<DynamicCheat, Integer>) cheatsTableView.getColumns().get(2);
|
|
addrColumn.setCellValueFactory(new PropertyValueFactory<>("address"));
|
|
addrColumn.setCellFactory((TableColumn<DynamicCheat, Integer> param) -> {
|
|
return new TextFieldTableCell<>(new IntegerStringConverter() {
|
|
@Override
|
|
public String toString(Integer value) {
|
|
return "$" + Integer.toHexString(value);
|
|
}
|
|
|
|
@Override
|
|
public Integer fromString(String value) {
|
|
return cheatEngine.parseInt(value);
|
|
}
|
|
});
|
|
});
|
|
|
|
TableColumn<DynamicCheat, String> exprColumn = (TableColumn<DynamicCheat, String>) cheatsTableView.getColumns().get(3);
|
|
exprColumn.setCellValueFactory(new PropertyValueFactory<>("expression"));
|
|
exprColumn.setCellFactory((TableColumn<DynamicCheat, String> param) -> new TextFieldTableCell<>(new DefaultStringConverter()));
|
|
|
|
codeInspectorAddress.textProperty().addListener((prop, oldValue, newValue) -> {
|
|
try {
|
|
int address = cheatEngine.parseInt(newValue);
|
|
MemoryCell cell = cheatEngine.getMemoryCell(address);
|
|
currentlyInspecting = address;
|
|
cheatEngine.onInspectorChanged();
|
|
codeInspectorReadList.setItems(cell.readInstructionsDisassembly);
|
|
codeInspectorWriteList.setItems(cell.writeInstructionsDisassembly);
|
|
} catch (NumberFormatException ex) {
|
|
}
|
|
});
|
|
}
|
|
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());
|
|
|
|
Application.invokeLater(this::redrawMemoryView);
|
|
}
|
|
|
|
ChangeListener<String> addressRangeListener = (prop, oldVal, newVal) -> Application.invokeLater(this::redrawMemoryView);
|
|
|
|
public static final int MEMORY_BOX_SIZE = 5;
|
|
public static final int MEMORY_BOX_GAP = 1;
|
|
public static final int MEMORY_BOX_TOTAL_SIZE = (MEMORY_BOX_SIZE + MEMORY_BOX_GAP);
|
|
public static final int UPDATE_NODE_LIMIT = 10000;
|
|
public int memoryViewColumns;
|
|
public int memoryViewRows;
|
|
|
|
public static Set<MemoryCell> redrawNodes = new ConcurrentSkipListSet<>();
|
|
ScheduledExecutorService animationTimer = null;
|
|
ScheduledFuture animationFuture = null;
|
|
Tooltip memoryWatchTooltip = new Tooltip();
|
|
|
|
private void memoryViewClicked(MouseEvent e, MemoryCell cell) {
|
|
if (cheatEngine != null) {
|
|
Watch currentWatch = (Watch) memoryWatchTooltip.getGraphic();
|
|
if (currentWatch != null) {
|
|
currentWatch.disconnect();
|
|
}
|
|
|
|
int addr = cell.address;
|
|
Watch watch = new Watch(addr, this);
|
|
|
|
Label addWatch = new Label("Watch >>");
|
|
addWatch.setOnMouseClicked((mouseEvent) -> {
|
|
Watch newWatch = addWatch(addr);
|
|
if (watch.holdingProperty().get()) {
|
|
newWatch.holdingProperty().set(true);
|
|
}
|
|
memoryWatchTooltip.hide();
|
|
});
|
|
watch.getChildren().add(addWatch);
|
|
|
|
Label addCheat = new Label("Cheat >>");
|
|
addCheat.setOnMouseClicked((mouseEvent) -> {
|
|
Platform.runLater(() -> addCheat(addr, watch.getValue()));
|
|
});
|
|
watch.getChildren().add(addCheat);
|
|
|
|
memoryWatchTooltip.setStyle("-fx-background-color:NAVY");
|
|
memoryWatchTooltip.onHidingProperty().addListener((prop, oldVal, newVal) -> {
|
|
watch.disconnect();
|
|
memoryWatchTooltip.setGraphic(null);
|
|
});
|
|
memoryWatchTooltip.setGraphic(watch);
|
|
memoryWatchTooltip.show(memoryViewPane.getContent(), e.getScreenX() + 5, e.getScreenY() - 15);
|
|
}
|
|
}
|
|
|
|
private void processMemoryViewUpdates() {
|
|
if (!Emulator.computer.getRunningProperty().get()) {
|
|
return;
|
|
}
|
|
Application.invokeLater(() -> {
|
|
Iterator<MemoryCell> i = redrawNodes.iterator();
|
|
for (int limit = 0; i.hasNext() && limit < UPDATE_NODE_LIMIT; limit++) {
|
|
MemoryCell cell = i.next();
|
|
i.remove();
|
|
if (showValuesCheckbox.isSelected()) {
|
|
int val = cell.value.get() & 0x0ff;
|
|
cell.getShape().setFill(Color.rgb(val, val, val));
|
|
} else {
|
|
cell.getShape().setFill(Color.rgb(
|
|
cell.writeCount.get(),
|
|
cell.readCount.get(),
|
|
cell.execCount.get()));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
public static int FRAME_RATE = 1000 / 60;
|
|
|
|
public void redrawMemoryView() {
|
|
if (cheatEngine == null) {
|
|
return;
|
|
}
|
|
boolean resume = Emulator.computer.pause();
|
|
|
|
if (animationTimer == null) {
|
|
animationTimer = new ScheduledThreadPoolExecutor(1);
|
|
}
|
|
|
|
if (animationFuture != null) {
|
|
animationFuture.cancel(false);
|
|
}
|
|
|
|
animationFuture = animationTimer.scheduleAtFixedRate(this::processMemoryViewUpdates, FRAME_RATE, FRAME_RATE, TimeUnit.MILLISECONDS);
|
|
|
|
cheatEngine.initMemoryView();
|
|
int pixelsPerBlock = 16 * MEMORY_BOX_TOTAL_SIZE;
|
|
memoryViewColumns = (int) (memoryViewPane.getWidth() / pixelsPerBlock) * 16;
|
|
memoryViewRows = ((cheatEngine.getEndAddress() - cheatEngine.getStartAddress()) / memoryViewColumns) + 1;
|
|
|
|
memoryViewContents.prefWidthProperty().bind(memoryViewPane.widthProperty().multiply(drawScale).subtract(8));
|
|
|
|
Group memoryView = new Group();
|
|
memoryViewContents.setBackground(new Background(new BackgroundFill(Color.rgb(40, 40, 40), null, null)));
|
|
for (int addr = cheatEngine.getStartAddress(); addr <= cheatEngine.getEndAddress(); addr++) {
|
|
int col = (addr - cheatEngine.getStartAddress()) % memoryViewColumns;
|
|
int row = (addr - cheatEngine.getStartAddress()) / memoryViewColumns;
|
|
MemoryCell cell = cheatEngine.getMemoryCell(addr);
|
|
Rectangle rect = new Rectangle(col * MEMORY_BOX_TOTAL_SIZE * drawScale, row * MEMORY_BOX_TOTAL_SIZE * drawScale, MEMORY_BOX_SIZE * drawScale, MEMORY_BOX_SIZE * drawScale);
|
|
rect.setOnMouseClicked(e -> memoryViewClicked(e, cell));
|
|
rect.setFill(Color.GRAY);
|
|
cell.setShape(rect);
|
|
memoryView.getChildren().add(rect);
|
|
redrawNodes.add(cell);
|
|
}
|
|
memoryViewContents.getChildren().clear();
|
|
memoryViewContents.getChildren().add(memoryView);
|
|
MemoryCell.setListener((javafx.beans.value.ObservableValue<? extends jace.cheat.MemoryCell> prop, jace.cheat.MemoryCell oldCell, jace.cheat.MemoryCell newCell) -> {
|
|
redrawNodes.add(newCell);
|
|
});
|
|
|
|
setZoom(1 / drawScale);
|
|
|
|
if (resume) {
|
|
Emulator.computer.resume();
|
|
}
|
|
}
|
|
|
|
private void changeZoom(double amount) {
|
|
if (memoryViewContents != null) {
|
|
double zoom = memoryViewContents.getScaleX();
|
|
zoom += amount;
|
|
setZoom(zoom);
|
|
}
|
|
}
|
|
|
|
private void setZoom(double zoom) {
|
|
if (memoryViewContents != null) {
|
|
memoryViewContents.setScaleX(zoom);
|
|
memoryViewContents.setScaleY(zoom);
|
|
// StackPane scrollArea = (StackPane) memoryViewCanvas.getParent();
|
|
// scrollArea.setPrefSize(memoryViewCanvas.getWidth() * zoom, memoryViewCanvas.getHeight() * zoom);
|
|
}
|
|
}
|
|
|
|
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();
|
|
memoryWatchTooltip.hide();
|
|
animationTimer.shutdown();
|
|
animationTimer = null;
|
|
cheatEngine = null;
|
|
}
|
|
|
|
private void updateSearchStats() {
|
|
int size = cheatEngine.getSearchResults().size();
|
|
searchStatusLabel.setText(size + (size == 1 ? " result" : " results") + " found.");
|
|
}
|
|
|
|
private Watch addWatch(int addr) {
|
|
Watch watch = new Watch(addr, this);
|
|
watch.setPadding(new Insets(5));
|
|
watch.setOpaqueInsets(new Insets(10));
|
|
|
|
Label addCheat = new Label("Cheat >>");
|
|
addCheat.setOnMouseClicked((mouseEvent) -> {
|
|
addCheat(addr, watch.getValue());
|
|
});
|
|
addCheat.setTextFill(Color.WHITE);
|
|
watch.getChildren().add(addCheat);
|
|
|
|
Label close = new Label("Close X");
|
|
close.setOnMouseClicked((mouseEvent) -> {
|
|
watch.disconnect();
|
|
watchesPane.getChildren().remove(watch);
|
|
});
|
|
close.setTextFill(Color.WHITE);
|
|
watch.getChildren().add(close);
|
|
|
|
watchesPane.getChildren().add(watch);
|
|
return watch;
|
|
}
|
|
|
|
private void addCheat(int addr, int val) {
|
|
cheatEngine.addCheat(new DynamicCheat(addr, String.valueOf(val)));
|
|
}
|
|
|
|
int currentlyInspecting = 0;
|
|
|
|
public void inspectAddress(int address) {
|
|
codeInspectorAddress.setText("$" + Integer.toHexString(address));
|
|
}
|
|
|
|
public boolean isInspecting(int address) {
|
|
return currentlyInspecting == address;
|
|
}
|
|
}
|