/* * 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") // //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) ); }// //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 readActivity = new ConcurrentHashMap(); Map writeActivity = new ConcurrentHashMap(); Map pcActivity = new ConcurrentHashMap(); Set activeRam = new ConcurrentSkipListSet(); Map valueActivity = new ConcurrentHashMap(); Map setBy = new ConcurrentHashMap(); Map readBy = new ConcurrentHashMap(); 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 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 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 } }