mirror of https://github.com/badvision/jace.git
321 lines
11 KiB
Java
321 lines
11 KiB
Java
/*
|
|
* Copyright (C) 2012 Brendan Robert (BLuRry) brendan.robert@gmail.com.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301 USA
|
|
*/
|
|
package jace.cheat;
|
|
|
|
import jace.apple2e.RAM128k;
|
|
import jace.core.Computer;
|
|
import jace.core.KeyHandler;
|
|
import jace.core.Keyboard;
|
|
import jace.core.RAMEvent;
|
|
import jace.core.RAMListener;
|
|
import java.awt.event.KeyEvent;
|
|
import java.util.ArrayList;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.TreeMap;
|
|
import java.util.logging.Level;
|
|
import java.util.logging.Logger;
|
|
import javax.swing.table.DefaultTableModel;
|
|
|
|
/**
|
|
* Basic mame-style cheats. The user interface is in MetaCheatForm.
|
|
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
|
|
*/
|
|
public class MetaCheats extends Cheats {
|
|
static MetaCheats singleton = null;
|
|
// This is used to help the handler exit faster when there is nothing to do
|
|
boolean noCheats = true;
|
|
|
|
public MetaCheats() {
|
|
super();
|
|
singleton = this;
|
|
}
|
|
|
|
|
|
Map<Integer, Integer> holdBytes = new TreeMap<Integer,Integer>();
|
|
Map<Integer, Integer> holdWords = new TreeMap<Integer,Integer>();
|
|
Set<Integer> disabled = new HashSet<Integer>();
|
|
Map<Integer, Integer> results = new TreeMap<Integer,Integer>();
|
|
@Override
|
|
protected String getDeviceName() {
|
|
return "Meta-cheat engine";
|
|
}
|
|
|
|
@Override
|
|
public void tick() {
|
|
// Do nothing
|
|
}
|
|
|
|
public static int MAX_RESULTS_SHOWN = 256;
|
|
|
|
public MetaCheatForm form = null;
|
|
public boolean isDrawing = false;
|
|
public void redrawResults() {
|
|
if (isDrawing) {
|
|
return;
|
|
}
|
|
isDrawing = true;
|
|
|
|
try {
|
|
Thread.sleep(10);
|
|
} catch (InterruptedException ex) {
|
|
Logger.getLogger(MetaCheats.class.getName()).log(Level.SEVERE, null, ex);
|
|
}
|
|
|
|
RAM128k ram = (RAM128k) Computer.getComputer().getMemory();
|
|
DefaultTableModel model = (DefaultTableModel) form.resultsTable.getModel();
|
|
if (results.size() > MAX_RESULTS_SHOWN) {
|
|
model.setRowCount(0);
|
|
isDrawing = false;
|
|
return;
|
|
}
|
|
boolean useWord = form.searchForWord.isSelected();
|
|
if (model.getRowCount() != results.size()) {
|
|
model.setRowCount(0);
|
|
List<Integer> iter = new ArrayList<Integer>(results.keySet());
|
|
for (Integer i : iter) {
|
|
int val = results.get(i);
|
|
if (useWord) {
|
|
int current = ram.readWordRaw(i) & 0x0ffff;
|
|
model.addRow(new Object[]{hex(i, 4), val + " ("+hex(val,4)+")", current + " ("+hex(current,4)+")"});
|
|
} else {
|
|
int current = ram.readRaw(i) & 0x0ff;
|
|
model.addRow(new Object[]{hex(i, 4), val + " ("+hex(val,2)+")", current + " ("+hex(current,2)+")"});
|
|
}
|
|
}
|
|
} else {
|
|
List<Integer> iter = new ArrayList<Integer>(results.keySet());
|
|
for (int i=0; i < iter.size(); i++) {
|
|
int val = results.get(iter.get(i));
|
|
if (useWord) {
|
|
int current = ram.readWordRaw(iter.get(i)) & 0x0ffff;
|
|
model.setValueAt(val + " ("+hex(val,4)+")", i, 1);
|
|
model.setValueAt(current + " ("+hex(current,4)+")", i, 2);
|
|
} else {
|
|
int current = ram.readRaw(iter.get(i)) & 0x0ff;
|
|
model.setValueAt(val + " ("+hex(val,2)+")", i, 1);
|
|
model.setValueAt(current + " ("+hex(current,2)+")", i, 2);
|
|
}
|
|
}
|
|
}
|
|
isDrawing = false;
|
|
}
|
|
|
|
public static String hex(int val, int size) {
|
|
String out = Integer.toHexString(val);
|
|
while (out.length() < size) {
|
|
out = "0"+out;
|
|
}
|
|
return "$"+out;
|
|
}
|
|
|
|
public void redrawCheats() {
|
|
noCheats = holdBytes.isEmpty() && holdWords.isEmpty() && disabled.isEmpty();
|
|
DefaultTableModel model = (DefaultTableModel) form.activeCheatsTable.getModel();
|
|
model.setRowCount(0);
|
|
for (Integer i : holdBytes.keySet()) {
|
|
String loc = hex(i, 4);
|
|
if (disabled.contains(i)) loc += " (off)";
|
|
int val = holdBytes.get(i);
|
|
model.addRow(new Object[]{loc, val + " ("+hex(val,2)+")"});
|
|
}
|
|
for (Integer i : holdWords.keySet()) {
|
|
String loc = hex(i, 4);
|
|
if (disabled.contains(i)) loc += " (off)";
|
|
int val = holdWords.get(i);
|
|
model.addRow(new Object[]{loc, val + " ("+hex(val,4)+")"});
|
|
}
|
|
}
|
|
public void showCheatForm() {
|
|
if (form == null) {
|
|
form = new MetaCheatForm();
|
|
}
|
|
form.setVisible(true);
|
|
}
|
|
|
|
MemorySpy spy = null;
|
|
public void showMemorySpy() {
|
|
if (spy == null) {
|
|
spy = new MemorySpy();
|
|
}
|
|
spy.setVisible(true);
|
|
}
|
|
|
|
|
|
@Override
|
|
public void attach() {
|
|
this.addCheat(new RAMListener(RAMEvent.TYPE.READ, RAMEvent.SCOPE.ANY, RAMEvent.VALUE.ANY) {
|
|
@Override
|
|
protected void doConfig() {
|
|
}
|
|
|
|
@Override
|
|
protected void doEvent(RAMEvent e) {
|
|
if (noCheats) return;
|
|
if (disabled.contains(e.getAddress())) return;
|
|
if (holdBytes.containsKey(e.getAddress())) {
|
|
e.setNewValue(holdBytes.get(e.getAddress()));
|
|
} else if (holdWords.containsKey(e.getAddress())) {
|
|
e.setNewValue(holdWords.get(e.getAddress()) & 0x0ff);
|
|
} else if (holdWords.containsKey(e.getAddress()-1)) {
|
|
if (disabled.contains(e.getAddress()-1)) return;
|
|
e.setNewValue((holdWords.get(e.getAddress()-1)>>8) & 0x0ff);
|
|
}
|
|
}
|
|
});
|
|
this.addCheat(new RAMListener(RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.ANY, RAMEvent.VALUE.ANY) {
|
|
@Override
|
|
protected void doConfig() {
|
|
}
|
|
|
|
@Override
|
|
protected void doEvent(RAMEvent e) {
|
|
if (results.isEmpty()) return;
|
|
if (results.size() > MAX_RESULTS_SHOWN || isDrawing) return;
|
|
if (results.containsKey(e.getAddress()) || results.containsKey(e.getAddress()-1)) {
|
|
Thread t = new Thread(new Runnable() {
|
|
@Override
|
|
public void run() {
|
|
redrawResults();
|
|
}
|
|
});
|
|
t.setName("Metacheat results updater");
|
|
t.start();
|
|
}
|
|
}
|
|
});
|
|
|
|
Keyboard.registerKeyHandler(new KeyHandler(KeyEvent.VK_END) {
|
|
@Override
|
|
public boolean handleKeyUp(KeyEvent e) {
|
|
showCheatForm();
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean handleKeyDown(KeyEvent e) {
|
|
return false;
|
|
}
|
|
}, this);
|
|
Keyboard.registerKeyHandler(new KeyHandler(KeyEvent.VK_HOME) {
|
|
@Override
|
|
public boolean handleKeyUp(KeyEvent e) {
|
|
showMemorySpy();
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean handleKeyDown(KeyEvent e) {
|
|
return false;
|
|
}
|
|
}, this);
|
|
}
|
|
|
|
@Override
|
|
public void detach() {
|
|
super.detach();
|
|
Keyboard.unregisterAllHandlers(this);
|
|
}
|
|
|
|
public void addByteCheat(int addr, int val) {
|
|
holdBytes.put(addr, val);
|
|
redrawCheats();
|
|
}
|
|
|
|
public void addWordCheat(int addr, int val) {
|
|
holdWords.put(addr, val);
|
|
redrawCheats();
|
|
}
|
|
|
|
void removeCheat(int i) {
|
|
holdBytes.remove(i);
|
|
holdWords.remove(i);
|
|
disabled.remove(i);
|
|
redrawCheats();
|
|
}
|
|
|
|
void resetSearch() {
|
|
results.clear();
|
|
redrawResults();
|
|
}
|
|
|
|
void performSearch(boolean useDeltaSearch, boolean searchForByteValues, int val) {
|
|
RAM128k ram = (RAM128k) Computer.getComputer().getMemory();
|
|
if (results.isEmpty()) {
|
|
int max = 0x010000;
|
|
if (!searchForByteValues) max--;
|
|
for (int i=0; i < max; i++) {
|
|
if (i >= 0x0c000 && i <= 0x0cfff) continue;
|
|
int v = searchForByteValues ? ram.readRaw(i) & 0x0ff : ram.readWordRaw(i) & 0x0ffff;
|
|
if (useDeltaSearch) {
|
|
results.put(i, v);
|
|
} else if (v == val) {
|
|
results.put(i, v);
|
|
}
|
|
}
|
|
} else {
|
|
Set<Integer> remove = new HashSet<Integer>();
|
|
for (Integer i : results.keySet()) {
|
|
int v = searchForByteValues ? ram.readRaw(i) & 0x0ff : ram.readWordRaw(i) & 0x0ffff;
|
|
if (useDeltaSearch) {
|
|
if (v - results.get(i) != val) {
|
|
remove.add(i);
|
|
} else {
|
|
results.put(i,v);
|
|
}
|
|
} else {
|
|
if (v != val) {
|
|
remove.add(i);
|
|
} else {
|
|
results.put(i,v);
|
|
}
|
|
}
|
|
}
|
|
for (Integer i : remove) {
|
|
results.remove(i);
|
|
}
|
|
}
|
|
form.resultsStatusLabel.setText("Search found "+results.size()+" result(s).");
|
|
redrawResults();
|
|
}
|
|
|
|
void enableCheat(int addr) {
|
|
disabled.remove(addr);
|
|
redrawCheats();
|
|
}
|
|
|
|
void disableCheat(int addr) {
|
|
disabled.add(addr);
|
|
redrawCheats();
|
|
}
|
|
|
|
void addWatches(int addrStart, int addrEnd) {
|
|
RAM128k ram = (RAM128k) Computer.getComputer().getMemory();
|
|
if (form == null) return;
|
|
boolean searchForByteValues = form.searchForByte.isSelected();
|
|
for (int i = addrStart; i <= addrEnd; i = i + (searchForByteValues ? 1 : 2)) {
|
|
int v = searchForByteValues ? ram.readRaw(i) & 0x0ff : ram.readWordRaw(i) & 0x0ffff;
|
|
results.put(i,v);
|
|
}
|
|
redrawResults();
|
|
}
|
|
}
|