jace/src/main/java/jace/cheat/MemorySpyGrid.java

490 lines
18 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.core.Computer;
import jace.core.RAM;
import jace.core.RAMEvent;
import jace.core.RAMEvent.TYPE;
import jace.core.RAMListener;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JLabel;
import javax.swing.JViewport;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
* This is the memory view of the memory spy module. The window is defined by
* the MemorySpy class. This class registers memory listeners needed to provide
* the relevant memory views.
*
* @author Brendan Robert (BLuRry) brendan.robert@gmail.com
*/
public class MemorySpyGrid extends javax.swing.JPanel {
/**
* Creates new form MemorySpyGrid
*/
public MemorySpyGrid() {
initComponents();
gradient = bwGradient;
}
// This is used primarily during scroll events to repaint the whole area
ChangeListener viewportChangeListener = new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
repaint();
}
};
@Override
public void validate() {
super.validate();
if (Computer.getComputer() != null) {
adjustSize();
}
if (getParent() != null) {
JViewport viewport = (JViewport) getParent();
viewport.removeChangeListener(viewportChangeListener);
viewport.addChangeListener(viewportChangeListener);
}
}
@Override
public void paint(Graphics g) {
super.paint(g);
if (mode == Modes.ACTIVITY) {
for (int a = startAddress; a <= endAddress; a++) {
if (!activeRam.contains(a)) {
drawActivity(a, 0, 0, 0, (Graphics2D) g);
} else {
drawActivity(a,
getInt(writeActivity.get(a)),
getInt(readActivity.get(a)),
getInt(pcActivity.get(a)),
(Graphics2D) g);
}
}
} else {
RAM ram = Computer.getComputer().getMemory();
for (int a = startAddress; a <= endAddress; a++) {
drawValue(a, ram.readRaw(a) & 0x0ff, (Graphics2D) g);
}
}
}
Color[] gradient;
static Color[] colorGradient, bwGradient;
static {
colorGradient = new Color[256];
bwGradient = new Color[256];
for (int i = 0; i < 256; i++) {
float hue = ((float) i) / 360.0f;
colorGradient[i] = Color.getHSBColor(0.67f - hue, 1, 1);
bwGradient[i] = new Color(i, i, i);
}
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
setBackground(new java.awt.Color(1, 1, 1));
setCursor(new java.awt.Cursor(java.awt.Cursor.CROSSHAIR_CURSOR));
addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
formMouseClicked(evt);
}
public void mouseExited(java.awt.event.MouseEvent evt) {
formMouseExited(evt);
}
public void mouseEntered(java.awt.event.MouseEvent evt) {
formMouseEntered(evt);
}
});
addMouseMotionListener(new java.awt.event.MouseMotionAdapter() {
public void mouseMoved(java.awt.event.MouseEvent evt) {
formMouseMoved(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 400, Short.MAX_VALUE)
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGap(0, 300, Short.MAX_VALUE)
);
}// </editor-fold>//GEN-END:initComponents
boolean mousePresent = false;
int mouseX;
int mouseY;
private void formMouseMoved(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_formMouseMoved
mouseX = evt.getX();
mouseY = evt.getY();
}//GEN-LAST:event_formMouseMoved
private void formMouseEntered(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_formMouseEntered
mousePresent = true;
}//GEN-LAST:event_formMouseEntered
private void formMouseExited(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_formMouseExited
mousePresent = false;
}//GEN-LAST:event_formMouseExited
private void formMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_formMouseClicked
int addr = convertXYtoAddr(mouseX, mouseY);
if (addr >= 0) {
MetaCheats.singleton.addWatches(addr, addr);
}
}//GEN-LAST:event_formMouseClicked
private int convertXYtoAddr(int x, int y) {
int offset = x / zoomAmount;
if (offset < 0 || offset > columns) {
return -1;
}
int row = y / zoomAmount;
int addr = startAddress + (row * columns) + offset;
if (addr > endAddress) {
return -1;
}
return addr;
}
private String spaceFill(String s, int size) {
while (s.length() < size) {
s = s + " ";
}
return s;
}
private void updateDetails() {
final JLabel status = MemorySpy.singleton.statusLabel;
if (mousePresent) {
final int addr = convertXYtoAddr(mouseX, mouseY);
if (addr >= 0) {
final int value = Computer.getComputer().getMemory().readRaw(addr) & 0x0ff;
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
Integer lastChange = setBy.get(addr);
Integer lastRead = readBy.get(addr);
char c1 = (char) Math.max(32, value & 0x07f);
char c2 = (char) (64 + (value % 32));
char c3 = (char) (32 + (value % 96));
status.setText("$"
+ spaceFill(Integer.toHexString(addr), 4) + ": "
+ spaceFill(String.valueOf(value), 3) + " ($"
+ spaceFill(Integer.toHexString(value), 2) + ") " + c1 + " " + c2 + " " + c3
+ (lastChange != null
? " Written by $"
+ spaceFill(Integer.toHexString(lastChange), 4)
: "")
+ (lastRead != null
? " Read by $"
+ spaceFill(Integer.toHexString(lastRead), 4)
: ""));
}
});
}
} else {
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
status.setText("Nothing selected");
}
});
}
}
// Variables declaration - do not modify//GEN-BEGIN:variables
// End of variables declaration//GEN-END:variables
Modes mode = Modes.ACTIVITY;
void setDisplayMode(Modes newMode) {
stopDisplay();
mode = newMode;
repaint();
startDisplay();
}
int zoomAmount = 5;
void setZoomAmount(int value) {
zoomAmount = value;
recalibrate();
}
int startAddress = 0;
int endAddress = 0x0ffff;
void updateRange(int startAddr, int endAddr) {
startAddress = startAddr;
endAddress = endAddr;
activeRam.clear();
valueActivity.clear();
readActivity.clear();
writeActivity.clear();
pcActivity.clear();
recalibrate();
}
int columns = 1;
int lastKnownWidth = 0;
int lastKnownHeight = 0;
public void adjustSize() {
int newWidth = getParent().getWidth();
if (newWidth == lastKnownWidth && getParent().getHeight() == lastKnownHeight) {
return;
}
lastKnownWidth = newWidth;
lastKnownHeight = getParent().getHeight();
recalibrate();
}
public void recalibrate() {
java.awt.EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
stopDisplay();
int size = endAddress - startAddress + 1;
int width = getParent().getWidth();
columns = ((width / zoomAmount) / 16) * 16;
int height = ((size / columns) + 1) * zoomAmount;
setSize(width, height);
setPreferredSize(new Dimension(width, height));
getParent().validate();
repaint();
startDisplay();
}
});
}
protected void drawValue(int addr, int value, Graphics2D g) {
int offset = addr - startAddress;
int x = zoomAmount * (offset % columns);
int y = zoomAmount * (offset / columns);
value = value & 0x0ff;
g.setColor(gradient[value]);
g.fillRect(x, y, zoomAmount, zoomAmount);
}
protected void drawActivity(int addr, int read, int write, int pc, Graphics2D g) {
int offset = addr - startAddress;
int x = zoomAmount * (offset % columns);
int y = zoomAmount * (offset / columns);
g.setColor(new Color(read & 0x0ff, write & 0x0ff, pc & 0x0ff));
g.fillRect(x, y, zoomAmount, zoomAmount);
}
private int getInt(Integer i) {
if (i != null) {
return i;
}
return 0;
}
boolean isDisplayActive = false;
Thread redrawThread = null;
// 30 fps or so
static int UPDATE_INTERVAL = 10;
static int ACTIVITY_INCREMENT = 150;
static int ACTIVITY_DECREMENT = 5;
static int ACTIVITY_MAX = 255;
RAMListener memoryListener = null;
Map<Integer, Integer> readActivity = new ConcurrentHashMap<Integer, Integer>();
Map<Integer, Integer> writeActivity = new ConcurrentHashMap<Integer, Integer>();
Map<Integer, Integer> pcActivity = new ConcurrentHashMap<Integer, Integer>();
Set<Integer> activeRam = new ConcurrentSkipListSet<Integer>();
Map<Integer, Integer> valueActivity = new ConcurrentHashMap<Integer, Integer>();
Map<Integer, Integer> setBy = new ConcurrentHashMap<Integer, Integer>();
Map<Integer, Integer> readBy = new ConcurrentHashMap<Integer, Integer>();
void startDisplay() {
if (memoryListener != null) {
Computer.getComputer().getMemory().removeListener(memoryListener);
memoryListener = null;
}
isDisplayActive = true;
memoryListener = new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) {
@Override
protected void doConfig() {
setScopeStart(startAddress);
setScopeEnd(endAddress);
}
@Override
protected void doEvent(RAMEvent e) {
int addr = e.getAddress();
if (addr < startAddress || addr > endAddress) {
return;
}
int pc = Computer.getComputer().getCpu().programCounter;
if (e.getType() == TYPE.EXECUTE || e.getType() == TYPE.READ_OPERAND) {
if (pcActivity.containsKey(addr)) {
pcActivity.put(addr, Math.min(ACTIVITY_MAX, getInt(pcActivity.get(addr)) + ACTIVITY_INCREMENT));
} else {
pcActivity.put(addr, ACTIVITY_INCREMENT);
}
} else if (e.getType().isRead()) {
if (readActivity.containsKey(addr)) {
readActivity.put(addr, Math.min(ACTIVITY_MAX, getInt(readActivity.get(addr)) + ACTIVITY_INCREMENT));
} else {
readActivity.put(addr, ACTIVITY_INCREMENT);
}
readBy.put(addr, pc);
} else {
if (writeActivity.containsKey(addr)) {
writeActivity.put(addr, Math.min(ACTIVITY_MAX, getInt(writeActivity.get(addr)) + ACTIVITY_INCREMENT));
} else {
writeActivity.put(addr, ACTIVITY_INCREMENT);
}
valueActivity.put(addr, e.getNewValue());
setBy.put(addr, pc);
}
activeRam.add(addr);
}
};
Computer.getComputer().getMemory().addListener(memoryListener);
redrawThread = new Thread(new Runnable() {
@Override
public void run() {
if (mode == Modes.ACTIVITY) {
// Activity heatmap mode
while (isDisplayActive) {
updateDetails();
Graphics2D g = (Graphics2D) getGraphics();
for (Iterator<Integer> i = activeRam.iterator(); i.hasNext();) {
boolean remove = true;
int addr = i.next();
int read = getInt(readActivity.get(addr));
if (read <= 0) {
read = 0;
} else {
remove = false;
readActivity.put(addr, read - ACTIVITY_DECREMENT);
}
int write = getInt(writeActivity.get(addr));
if (write <= 0) {
write = 0;
} else {
remove = false;
writeActivity.put(addr, write - ACTIVITY_DECREMENT);
}
int pc = getInt(pcActivity.get(addr));
if (pc <= 0) {
pc = 0;
} else {
remove = false;
pcActivity.put(addr, pc - ACTIVITY_DECREMENT);
}
if (remove) {
i.remove();
pcActivity.remove(addr);
writeActivity.remove(addr);
readActivity.remove(addr);
}
drawActivity(addr, write, read, pc, g);
}
g.dispose();
try {
Thread.sleep(UPDATE_INTERVAL);
} catch (InterruptedException ex) {
Logger.getLogger(MemorySpyGrid.class.getName()).log(Level.SEVERE, null, ex);
}
}
} else {
// Redraw value, no activity counts needed
while (isDisplayActive) {
updateDetails();
Graphics2D g = (Graphics2D) getGraphics();
for (Iterator<Integer> i = valueActivity.keySet().iterator(); i.hasNext();) {
int addr = i.next();
int value = getInt(valueActivity.get(addr));
i.remove();
drawValue(addr, value, g);
}
g.dispose();
try {
Thread.sleep(UPDATE_INTERVAL);
} catch (InterruptedException ex) {
Logger.getLogger(MemorySpyGrid.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
});
redrawThread.setName("Memory spy redraw");
redrawThread.start();
}
void stopDisplay() {
isDisplayActive = false;
if (memoryListener != null) {
Computer.getComputer().getMemory().removeListener(memoryListener);
memoryListener = null;
}
if (redrawThread != null && redrawThread.isAlive()) {
try {
redrawThread.join();
} catch (InterruptedException ex) {
Logger.getLogger(MemorySpyGrid.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
void setColorGradient(boolean useColor) {
if (useColor) {
gradient = colorGradient;
} else {
gradient = bwGradient;
}
repaint();
}
void setFastFade(boolean fast) {
if (fast) {
ACTIVITY_DECREMENT = 20;
} else {
ACTIVITY_DECREMENT = 5;
}
}
public static enum Modes {
ACTIVITY, VALUE
}
}