mirror of
https://github.com/badvision/jace.git
synced 2024-06-15 09:29:36 +00:00
490 lines
18 KiB
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
|
||
|
}
|
||
|
}
|