/* This file is part of jpcsp. Jpcsp is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Jpcsp 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 General Public License for more details. You should have received a copy of the GNU General Public License along with Jpcsp. If not, see <http://www.gnu.org/licenses/>. */ package jpcsp.GUI; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import javax.swing.UIManager; import jpcsp.Emulator; import jpcsp.Memory; import jpcsp.MemoryMap; import jpcsp.State; import jpcsp.WindowPropSaver; import jpcsp.util.JpcspDialogManager; import jpcsp.util.Utilities; import static jpcsp.util.Utilities.parseHexLong; public class CheatsGUI extends javax.swing.JFrame implements KeyListener { private static final long serialVersionUID = 2885526629263842635L; public static final String identifierForConfig = "cheatsGUI"; private static final int cheatsThreadSleepMillis = 5; private CheatsThread cheatsThread = null; public CheatsGUI() { initComponents(); WindowPropSaver.loadWindowProperties(this); } @Override public void keyTyped(KeyEvent e) { // do nothing } @Override public void keyPressed(KeyEvent e) { // do nothing } @Override public void keyReleased(KeyEvent e) { // do nothing } private static class CheatsThread extends Thread { private String[] codes; private int currentCode; private final CheatsGUI cheats; private volatile boolean exit; public CheatsThread(CheatsGUI cheats) { this.cheats = cheats; } public void exit() { exit = true; } private String getNextCode() { String code; while (true) { if (currentCode >= codes.length) { code = null; break; } code = codes[currentCode++].trim(); if (code.startsWith("_L")) { code = code.substring(2).trim(); break; } else if (code.startsWith("0")) { break; } } return code; } private void skipCodes(int count) { for (int i = 0; i < count; i++) { if (getNextCode() == null) { break; } } } private void skipAllCodes() { currentCode = codes.length; } private static int getAddress(int value) { // The User space base address has to be added to given value return (value + MemoryMap.START_USERSPACE) & Memory.addressMask; } @Override public void run() { Memory mem = Memory.getInstance(); // only read here, as the text area is disabled on thread enabling codes = cheats.getCodesList(); while (!exit) { // Sleep a little bit to not use the CPU at 100% Utilities.sleep(cheatsThreadSleepMillis, 0); currentCode = 0; while (true) { String code = getNextCode(); if (code == null) { break; } String[] parts = code.split(" "); if (parts == null || parts.length < 2) { continue; } int value; int comm = (int) parseHexLong(parts[0].trim(), false); int arg = (int) parseHexLong(parts[1].trim(), false); int addr = getAddress(comm & 0x0FFFFFFF); switch (comm >>> 28) { case 0: // 8-bit write. if (Memory.isAddressGood(addr)) { mem.write8(addr, (byte) arg); } break; case 0x1: // 16-bit write. if (Memory.isAddressGood(addr)) { mem.write16(addr, (short) arg); } break; case 0x2: // 32-bit write. if (Memory.isAddressGood(addr)) { mem.write32(addr, arg); } break; case 0x3: // Increment/Decrement addr = getAddress(arg); value = 0; int increment = 0; // Read value from memory switch ((comm >> 20) & 0xF) { case 1: case 2: // 8-bit value = mem.read8(addr); increment = comm & 0xFF; break; case 3: case 4: // 16-bit value = mem.read16(addr); increment = comm & 0xFFFF; break; case 5: case 6: // 32-bit value = mem.read32(addr); code = getNextCode(); parts = code.split(" "); if (parts != null && parts.length >= 1) { increment = (int) parseHexLong(parts[0].trim(), false); } break; } // Increment/Decrement value switch ((comm >> 20) & 0xF) { case 1: case 3: case 5: // Increment value += increment; break; case 2: case 4: case 6: // Decrement value -= increment; break; } // Write value back to memory switch ((comm >> 20) & 0xF) { case 1: case 2: // 8-bit mem.write8(addr, (byte) value); break; case 3: case 4: // 16-bit mem.write16(addr, (short) value); break; case 5: case 6: // 32-bit mem.write32(addr, value); break; } break; case 0x4: // 32-bit patch code. code = getNextCode(); parts = code.split(" "); if (parts != null && parts.length >= 1) { int data = (int) parseHexLong(parts[0].trim(), false); int dataAdd = (int) parseHexLong(parts[1].trim(), false); int maxAddr = (arg >> 16) & 0xFFFF; int stepAddr = (arg & 0xFFFF) * 4; for (int a = 0; a < maxAddr; a++) { if (Memory.isAddressGood(addr)) { mem.write32(addr, data); } addr += stepAddr; data += dataAdd; } } break; case 0x5: // Memcpy command. code = getNextCode(); parts = code.split(" "); if (parts != null && parts.length >= 1) { int destAddr = (int) parseHexLong(parts[0].trim(), false); if (Memory.isAddressGood(addr) && Memory.isAddressGood(destAddr)) { mem.memcpy(destAddr, addr, arg); } } break; case 0x6: // Pointer commands code = getNextCode(); parts = code.split(" "); if (parts != null && parts.length >= 2) { int arg2 = (int) parseHexLong(parts[0].trim(), false); int offset = (int) parseHexLong(parts[1].trim(), false); int baseOffset = (arg2 >>> 20) * 4; int base = mem.read32(addr + baseOffset); int count = arg2 & 0xFFFF; int type = (arg2 >> 16) & 0xF; for (int i = 1; i < count; i++) { if (i + 1 < count) { code = getNextCode(); parts = code.split(" "); if (parts != null && parts.length >= 2) { int arg3 = (int) parseHexLong(parts[0].trim(), false); int arg4 = (int) parseHexLong(parts[1].trim(), false); int comm3 = arg3 >>> 28; switch (comm3) { case 0x1: // type copy byte int srcAddr = mem.read32(addr) + offset; int dstAddr = mem.read32(addr + baseOffset) + (arg3 & 0x0FFFFFFF); mem.memcpy(dstAddr, srcAddr, arg); type = -1; // Done break; case 0x2: case 0x3: // type pointer walk int walkOffset = arg3 & 0x0FFFFFFF; if (comm3 == 0x3) { walkOffset = -walkOffset; } base = mem.read32(base + walkOffset); int comm4 = arg4 >>> 28; switch (comm4) { case 0x2: case 0x3: // type pointer walk walkOffset = arg4 & 0x0FFFFFFF; if (comm4 == 0x3) { walkOffset = -walkOffset; } base = mem.read32(base + walkOffset); break; } break; case 0x9: // type multi address write base += arg3 & 0x0FFFFFFF; arg += arg4; // CHECKME Not sure about this? break; } } } } switch (type) { case 0: // 8-bit write mem.write8(base + offset, (byte) arg); break; case 1: // 16-bit write mem.write16(base + offset, (short) arg); break; case 2: // 32-bit write mem.write32(base + offset, arg); break; case 3: // 8-bit inverse write mem.write8(base - offset, (byte) arg); break; case 4: // 16-bit inverse write mem.write16(base - offset, (short) arg); break; case 5: // 32-bit inverse write mem.write32(base - offset, arg); break; case -1: // Operation already performed, nothing to do break; } } break; case 0x7: // Boolean commands. switch (arg >> 16) { case 0x0000: // 8-bit OR. if (Memory.isAddressGood(addr)) { byte val1 = (byte) (arg & 0xFF); byte val2 = (byte) mem.read8(addr); mem.write8(addr, (byte) (val1 | val2)); } break; case 0x0002: // 8-bit AND. if (Memory.isAddressGood(addr)) { byte val1 = (byte) (arg & 0xFF); byte val2 = (byte) mem.read8(addr); mem.write8(addr, (byte) (val1 & val2)); } break; case 0x0004: // 8-bit XOR. if (Memory.isAddressGood(addr)) { byte val1 = (byte) (arg & 0xFF); byte val2 = (byte) mem.read8(addr); mem.write8(addr, (byte) (val1 ^ val2)); } break; case 0x0001: // 16-bit OR. if (Memory.isAddressGood(addr)) { short val1 = (short) (arg & 0xFFFF); short val2 = (short) mem.read16(addr); mem.write16(addr, (short) (val1 | val2)); } break; case 0x0003: // 16-bit AND. if (Memory.isAddressGood(addr)) { short val1 = (short) (arg & 0xFFFF); short val2 = (short) mem.read16(addr); mem.write16(addr, (short) (val1 & val2)); } break; case 0x0005: // 16-bit XOR. if (Memory.isAddressGood(addr)) { short val1 = (short) (arg & 0xFFFF); short val2 = (short) mem.read16(addr); mem.write16(addr, (short) (val1 ^ val2)); } break; } break; case 0x8: // 8-bit and 16-bit patch code. code = getNextCode(); parts = code.split(" "); if (parts != null && parts.length >= 1) { int data = (int) parseHexLong(parts[0].trim(), false); int dataAdd = (int) parseHexLong(parts[1].trim(), false); boolean is8Bit = (data >> 16) == 0x0000; int maxAddr = (arg >> 16) & 0xFFFF; int stepAddr = (arg & 0xFFFF) * (is8Bit ? 1 : 2); for (int a = 0; a < maxAddr; a++) { if (Memory.isAddressGood(addr)) { if (is8Bit) { mem.write8(addr, (byte) (data & 0xFF)); } else { mem.write16(addr, (short) (data & 0xFFFF)); } } addr += stepAddr; data += dataAdd; } } break; case 0xB: // Time command // CHECKME Not sure what to do for this code? break; case 0xC: // Code stopper if (Memory.isAddressGood(addr)) { value = mem.read32(addr); if (value != arg) { skipAllCodes(); } } break; case 0xD: // Test commands & Jocker codes switch (arg >>> 28) { case 0: case 2: // Test commands, single skip boolean is8Bit = (arg >> 28) == 0x2; if (Memory.isAddressGood(addr)) { int memoryValue = is8Bit ? mem.read8(addr) : mem.read16(addr); int testValue = arg & (is8Bit ? 0xFF : 0xFFFF); boolean executeNextLine = false; switch ((arg >> 20) & 0xF) { case 0x0: // Equal executeNextLine = memoryValue == testValue; break; case 0x1: // Not Equal executeNextLine = memoryValue != testValue; break; case 0x2: // Less Than executeNextLine = memoryValue < testValue; break; case 0x3: // Greater Than executeNextLine = memoryValue > testValue; break; } if (!executeNextLine) { skipCodes(1); } } break; case 4: case 5: case 6: case 7: // Address Test commands int addr1 = addr; int addr2 = getAddress(arg & 0x0FFFFFFF); if (Memory.isAddressGood(addr1) && Memory.isAddressGood(addr2)) { code = getNextCode(); parts = code.split(" "); if (parts != null && parts.length >= 1) { int skip = (int) parseHexLong(parts[0].trim(), false); int type = (int) parseHexLong(parts[1].trim(), false); int value1 = 0; int value2 = 0; switch (type & 0xF) { case 0: // 8 bit value1 = mem.read8(addr1); value2 = mem.read8(addr2); break; case 1: // 16 bit value1 = mem.read16(addr1); value2 = mem.read16(addr2); break; case 2: // 32 bit value1 = mem.read32(addr1); value2 = mem.read32(addr2); break; } boolean executeNextLines = false; switch (arg >>> 28) { case 4: // Equal executeNextLines = value1 == value2; break; case 5: // Not Equal executeNextLines = value1 != value2; break; case 6: // Less Than executeNextLines = value1 < value2; break; case 7: // Greater Than executeNextLines = value1 > value2; break; } if (!executeNextLines) { skipCodes(skip); } } } break; case 1: // Joker code case 3: // Inverse Joker code int testButtons = arg & 0x0FFFFFFF; int buttons = jpcsp.State.controller.getButtons(); boolean executeNextLines; if ((arg >>> 28) == 1) { executeNextLines = testButtons == buttons; } else { executeNextLines = testButtons != buttons; } if (!executeNextLines) { int skip = (comm & 0xFF) + 1; skipCodes(skip); } break; } break; case 0xE: // Test commands, multiple skip boolean is8Bit = (comm >> 24) == 0x1; addr = getAddress(arg & 0x0FFFFFFF); if (Memory.isAddressGood(addr)) { int memoryValue = is8Bit ? mem.read8(addr) : mem.read16(addr); int testValue = comm & (is8Bit ? 0xFF : 0xFFFF); boolean executeNextLines = false; switch (arg >>> 28) { case 0x0: // Equal executeNextLines = memoryValue == testValue; break; case 0x1: // Not Equal executeNextLines = memoryValue != testValue; break; case 0x2: // Less Than executeNextLines = memoryValue < testValue; break; case 0x3: // Greater Than executeNextLines = memoryValue > testValue; break; } if (!executeNextLines) { int skip = (comm >> 16) & (is8Bit ? 0xFF : 0xFFF); skipCodes(skip); } } break; } } } // Exiting... cheats.onCheatsThreadEnded(); } } public String[] getCodesList() { return taCheats.getText().split("\n"); } private void addCheatLine(String line) { String cheatCodes = taCheats.getText(); if (cheatCodes == null || cheatCodes.length() <= 0) { cheatCodes = line; } else { cheatCodes += "\n" + line; } taCheats.setText(cheatCodes); } public void onCheatsThreadEnded() { cheatsThread = null; } @Override public void dispose() { if (cheatsThread != null) { cheatsThread.exit(); } Emulator.getMainGUI().endWindowDialog(); super.dispose(); } /** * 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() { jScrollPane1 = new javax.swing.JScrollPane(); taCheats = new javax.swing.JTextArea(); btnImportCheatDB = new javax.swing.JButton(); btnClear = new javax.swing.JButton(); btnOnOff = new javax.swing.JToggleButton(); java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("jpcsp/languages/jpcsp"); // NOI18N setTitle(bundle.getString("CheatsGUI.title")); // NOI18N setMinimumSize(new java.awt.Dimension(360, 360)); setName("frmCheatsGUI"); // NOI18N taCheats.setColumns(30); taCheats.setFont(new java.awt.Font("Monospaced", 0, 12)); // NOI18N taCheats.setRows(20); taCheats.setTabSize(2); jScrollPane1.setViewportView(taCheats); btnImportCheatDB.setText(bundle.getString("CheatsGUI.btnImportCheatDB.text")); // NOI18N btnImportCheatDB.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { btnImportCheatDBActionPerformed(evt); } }); btnClear.setText(bundle.getString("ClearButton.text")); // NOI18N btnClear.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { btnClearActionPerformed(evt); } }); btnOnOff.setText(bundle.getString("CheatsGUI.btnOnOff.text")); // NOI18N btnOnOff.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { btnOnOffActionPerformed(evt); } }); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane()); getContentPane().setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addComponent(jScrollPane1) .addGroup(layout.createSequentialGroup() .addComponent(btnOnOff, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(btnImportCheatDB, javax.swing.GroupLayout.DEFAULT_SIZE, 211, Short.MAX_VALUE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(btnClear, javax.swing.GroupLayout.DEFAULT_SIZE, 100, Short.MAX_VALUE))) .addContainerGap()) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addComponent(jScrollPane1) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(btnImportCheatDB) .addComponent(btnClear) .addComponent(btnOnOff)) .addContainerGap()) ); pack(); }// </editor-fold>//GEN-END:initComponents private void btnClearActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnClearActionPerformed taCheats.setText(""); }//GEN-LAST:event_btnClearActionPerformed private void btnImportCheatDBActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnImportCheatDBActionPerformed File cheatDBFile = new File("cheat.db"); if (cheatDBFile.canRead()) { try { BufferedReader reader = new BufferedReader(new FileReader(cheatDBFile)); boolean insideApplicationid = false; while (reader.ready()) { String line = reader.readLine(); if (line == null) { // end of file break; } line = line.trim(); if (line.startsWith("_S ")) { String applicationId = line.substring(2).trim().replace("-", ""); insideApplicationid = applicationId.equalsIgnoreCase(State.discId); } if (insideApplicationid) { // Add the line to the cheat codes addCheatLine(line); } } reader.close(); } catch (IOException e) { Emulator.log.error("Import from cheat.db", e); } } else { JpcspDialogManager.showInformation(this, java.util.ResourceBundle.getBundle("jpcsp/languages/jpcsp").getString("CheatsGUI.strReadFromDB.text")); } }//GEN-LAST:event_btnImportCheatDBActionPerformed private void btnOnOffActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnOnOffActionPerformed if (btnOnOff.isSelected()) { if (taCheats.getText().isEmpty()) { JpcspDialogManager.showInformation(this, java.util.ResourceBundle.getBundle("jpcsp/languages/jpcsp").getString("CheatsGUI.strNoCheatsEntered.text")); btnOnOff.setSelected(false); return; } if (cheatsThread == null) { taCheats.setEditable(false); taCheats.setBackground(UIManager.getColor("Panel.background")); btnClear.setEnabled(false); btnImportCheatDB.setEnabled(false); cheatsThread = new CheatsThread(this); cheatsThread.setPriority(Thread.MIN_PRIORITY); cheatsThread.setName("HLECheatThread"); cheatsThread.setDaemon(true); cheatsThread.start(); } } else { if (cheatsThread != null) { taCheats.setEditable(true); taCheats.setBackground(UIManager.getColor("TextArea.background")); btnClear.setEnabled(true); btnImportCheatDB.setEnabled(true); cheatsThread.exit(); } } }//GEN-LAST:event_btnOnOffActionPerformed // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JButton btnClear; private javax.swing.JButton btnImportCheatDB; private javax.swing.JToggleButton btnOnOff; private javax.swing.JScrollPane jScrollPane1; private javax.swing.JTextArea taCheats; // End of variables declaration//GEN-END:variables }