diff --git a/src/main/java/jace/cheat/Cheats.java b/src/main/java/jace/cheat/Cheats.java index 592024e..f796ab1 100644 --- a/src/main/java/jace/cheat/Cheats.java +++ b/src/main/java/jace/cheat/Cheats.java @@ -18,57 +18,94 @@ */ package jace.cheat; +import jace.apple2e.MOS65C02; +import jace.apple2e.SoftSwitches; import jace.core.Computer; import jace.core.Device; +import jace.core.RAMEvent; import jace.core.RAMListener; import java.util.HashSet; import java.util.Set; /** - * Represents some combination of hacks that can be enabled or disabled - * through the configuration interface. - * @author Brendan Robert (BLuRry) brendan.robert@gmail.com + * Represents some combination of hacks that can be enabled or disabled through + * the configuration interface. + * + * @author Brendan Robert (BLuRry) brendan.robert@gmail.com */ public abstract class Cheats extends Device { + Set listeners = new HashSet<>(); public Cheats(Computer computer) { super(computer); } + + public void bypassCode(int address, int addressEnd) { + addCheat(RAMEvent.TYPE.READ, address, addressEnd, (e) -> { + e.setNewValue(MOS65C02.COMMAND.NOP.ordinal()); + }); + } + + public void forceValue(int address, int value) { + addCheat(RAMEvent.TYPE.READ, address, (e) -> { + e.setNewValue(value); + }); + } - public void addCheat(RAMListener l) { + public void forceValue(int address, boolean auxFlag, int value) { + addCheat(RAMEvent.TYPE.READ, address, (e) -> { + if (address < 0x0100) { + if (SoftSwitches.AUXZP.getState() != auxFlag) { + return; + } + } else { + if (SoftSwitches.RAMRD.getState() != auxFlag) { + return; + } + } + e.setNewValue(value); + }); + } + + public void addCheat(RAMEvent.TYPE type, int address, RAMEvent.RAMEventHandler handler) { + RAMListener l = computer.getMemory().observe(type, address, handler); + listeners.add(l); + } + + public void addCheat(RAMEvent.TYPE type, int addressStart, int addressEnd, RAMEvent.RAMEventHandler handler) { + RAMListener l = computer.getMemory().observe(type, addressStart, addressEnd, handler); listeners.add(l); - computer.getMemory().addListener(l); } @Override public void attach() { registerListeners(); } - + @Override public void detach() { unregisterListeners(); - super.detach(); + super.detach(); } - + abstract void registerListeners(); - + protected void unregisterListeners() { listeners.stream().forEach((l) -> { computer.getMemory().removeListener(l); }); - listeners.clear(); + listeners.clear(); } - + @Override public void reconfigure() { unregisterListeners(); registerListeners(); } - + @Override public String getShortName() { return "cheat"; } -} \ No newline at end of file +} diff --git a/src/main/java/jace/cheat/MontezumasRevengeCheats.java b/src/main/java/jace/cheat/MontezumasRevengeCheats.java index 1c37cd5..9a4ad38 100644 --- a/src/main/java/jace/cheat/MontezumasRevengeCheats.java +++ b/src/main/java/jace/cheat/MontezumasRevengeCheats.java @@ -5,9 +5,6 @@ import jace.apple2e.MOS65C02; import jace.config.ConfigurableField; import jace.core.Computer; import jace.core.RAMEvent; -import jace.core.RAMListener; -import java.util.HashMap; -import java.util.Map; import javafx.event.EventHandler; import javafx.scene.Node; import javafx.scene.input.MouseButton; @@ -16,25 +13,25 @@ public class MontezumasRevengeCheats extends Cheats { @ConfigurableField(category = "Hack", name = "Repulsive", defaultValue = "false", description = "YOU STINK!") public static boolean repulsiveHack = false; - + @ConfigurableField(category = "Hack", name = "Feather Fall", defaultValue = "false", description = "Falling will not result in death") public static boolean featherFall = false; @ConfigurableField(category = "Hack", name = "Moon Jump", defaultValue = "false", description = "Wheeee!") public static boolean moonJump = false; - + @ConfigurableField(category = "Hack", name = "Infinite Lives", defaultValue = "false", description = "Game on!") - public static boolean infiniteLives = false; + public static boolean infiniteLives = false; @ConfigurableField(category = "Hack", name = "Score hack", defaultValue = "false", description = "Change the score") - public static boolean scoreHack = false; - + public static boolean scoreHack = false; + @ConfigurableField(category = "Hack", name = "Snake Charmer", defaultValue = "false", description = "Disable collision detection with enemies") public static boolean snakeCharmer = false; @ConfigurableField(category = "Hack", name = "Teleport", defaultValue = "false", description = "Click to teleport!") public static boolean mouseHack = false; - + public static int X_MAX = 80; public static int Y_MAX = 160; public static int MAX_VEL = 4; @@ -48,6 +45,8 @@ public class MontezumasRevengeCheats extends Cheats { public static int Y_VELOCITY = 0x01550; public static int CHAR_STATE = 0x01570; + public static int lastX = 0; + public MontezumasRevengeCheats(Computer computer) { super(computer); } @@ -62,152 +61,37 @@ public class MontezumasRevengeCheats extends Cheats { mouseClicked(event.getButton()); } }; - + @Override void registerListeners() { if (repulsiveHack) { - addCheat(new RAMListener(RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { - - @Override - protected void doConfig() { - setScopeStart(0x1508); - setScopeEnd(0x1518); - } - - int lastX = 0; - - @Override - protected void doEvent(RAMEvent e) { - int playerX = computer.getMemory().readRaw(PLAYER_X); - int playerY = computer.getMemory().readRaw(PLAYER_Y); - for (int num = 7; num > 0; num--) { - int monsterX = computer.getMemory().readRaw(PLAYER_X + num); - int monsterY = computer.getMemory().readRaw(PLAYER_Y + num); - if (monsterX != 0 && monsterY != 0) { - if (Math.abs(monsterY - playerY) < 19) { - if (Math.abs(monsterX - playerX) < 7) { - int movement = Math.max(1, Math.abs(lastX - playerX)); - if (monsterX > playerX) { - monsterX += movement; - } else { - monsterX -= movement; - if (monsterX <= 0) { - monsterX = 80; - } - } - computer.getMemory().write(PLAYER_X + num, (byte) monsterX, false, false); - } - } - } - } - lastX = playerX; - } - }); + addCheat(RAMEvent.TYPE.WRITE, 0x1508, 0x1518, this::repulsiveBehavior); } - + if (featherFall) { - addCheat(new RAMListener(RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { - @Override - protected void doConfig() { - setScopeStart(PLAYER_Y); - } - - @Override - protected void doEvent(RAMEvent e) { - if (e.getNewValue() != e.getOldValue()) { - int yVel = computer.getMemory().readRaw(Y_VELOCITY); - if (yVel > MAX_VEL) { - computer.getMemory().write(Y_VELOCITY, (byte) MAX_VEL, false, false); - } - } - } - }); - addCheat(new RAMListener(RAMEvent.TYPE.ANY, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { - @Override - protected void doConfig() { - setScopeStart(0x6bb3); - setScopeEnd(0x6bb4); - } - - @Override - protected void doEvent(RAMEvent e) { - e.setNewValue(MOS65C02.COMMAND.NOP.ordinal()); - } - }); + addCheat(RAMEvent.TYPE.WRITE, PLAYER_Y, this::featherFallBehavior); + // Bypass the part that realizes you should die when you hit the floor + bypassCode(0x6bb3, 0x6bb4); } if (moonJump) { - addCheat(new RAMListener(RAMEvent.TYPE.WRITE, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { - @Override - protected void doConfig() { - setScopeStart(Y_VELOCITY); - } - - @Override - protected void doEvent(RAMEvent e) { - if (inStartingSequence()) return; -// System.out.println(e.getNewValue()); - if (e.getNewValue() < 0 && e.getNewValue() < e.getOldValue()) { - e.setNewValue(MOON_JUMP_VELOCITY); - } - } - }); + addCheat(RAMEvent.TYPE.WRITE, Y_VELOCITY, this::moonJumpBehavior); } - - if (infiniteLives) { - addCheat(new RAMListener(RAMEvent.TYPE.READ, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { - @Override - protected void doConfig() { - setScopeStart(LIVES); - } - @Override - protected void doEvent(RAMEvent e) { - e.setNewValue(11); - } - }); - + if (infiniteLives) { + forceValue(LIVES, 11); } if (scoreHack) { - addCheat(new RAMListener(RAMEvent.TYPE.READ, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { - @Override - protected void doConfig() { - setScopeStart(SCORE); - setScopeEnd(SCORE_END); - } - - @Override - protected void doEvent(RAMEvent e) { - switch(e.getAddress()) { - case 0x0e8: - e.setNewValue(0x90); - break; - case 0x0e9: - e.setNewValue(0x09); - break; - case 0x0ea: - e.setNewValue(0x13); - break; - } - } - }); - + // Score: 900913 + forceValue(SCORE, 0x90); + forceValue(SCORE + 1, 0x09); + forceValue(SCORE + 2, 0x13); } - - if (snakeCharmer) { - addCheat(new RAMListener(RAMEvent.TYPE.READ, RAMEvent.SCOPE.RANGE, RAMEvent.VALUE.ANY) { - @Override - protected void doConfig() { - setScopeStart(0x07963); - setScopeEnd(0x07964); - } - @Override - protected void doEvent(RAMEvent e) { - e.setNewValue(MOS65C02.COMMAND.NOP.ordinal()); - } - }); + if (snakeCharmer) { + // Skip the code that determines you're touching an enemy + bypassCode(0x07963, 0x07964); } if (mouseHack) { EmulatorUILogic.addMouseListener(listener); @@ -216,28 +100,66 @@ public class MontezumasRevengeCheats extends Cheats { } } + private void repulsiveBehavior(RAMEvent e) { + int playerX = computer.getMemory().readRaw(PLAYER_X); + int playerY = computer.getMemory().readRaw(PLAYER_Y); + for (int num = 7; num > 0; num--) { + int monsterX = computer.getMemory().readRaw(PLAYER_X + num); + int monsterY = computer.getMemory().readRaw(PLAYER_Y + num); + if (monsterX != 0 && monsterY != 0) { + if (Math.abs(monsterY - playerY) < 19) { + if (Math.abs(monsterX - playerX) < 7) { + int movement = Math.max(1, Math.abs(lastX - playerX)); + if (monsterX > playerX) { + monsterX += movement; + } else { + monsterX -= movement; + if (monsterX <= 0) { + monsterX = 80; + } + } + computer.getMemory().write(PLAYER_X + num, (byte) monsterX, false, false); + } + } + } + } + lastX = playerX; + } + + private void featherFallBehavior(RAMEvent yCoordChangeEvent) { + if (yCoordChangeEvent.getNewValue() != yCoordChangeEvent.getOldValue()) { + int yVel = computer.getMemory().readRaw(Y_VELOCITY); + if (yVel > MAX_VEL) { + computer.getMemory().write(Y_VELOCITY, (byte) MAX_VEL, false, false); + } + } + } + + private void moonJumpBehavior(RAMEvent velocityChangeEvent) { + if (inStartingSequence()) { + return; + } + if (velocityChangeEvent.getNewValue() < 0 + && velocityChangeEvent.getNewValue() < velocityChangeEvent.getOldValue()) { + velocityChangeEvent.setNewValue(MOON_JUMP_VELOCITY); + } + } + private boolean inStartingSequence() { int roomLevel = computer.getMemory().readRaw(ROOM_LEVEL); return roomLevel == -1; } - + + @Override + public String getName() { + return "Montezuma's Revenge"; + } + @Override protected String getDeviceName() { return "Montezuma's Revenge"; } - Map oldValues = new HashMap<>(); - private void monitor(int start, int end) { - for (int i=start; i < end; i++) { - int old = oldValues.get(i) == null ? 0 : oldValues.get(i); - int val = computer.getMemory().readRaw(i) & 0x0ff; - if (old == val) continue; - oldValues.put(i, val); - System.out.println(Integer.toHexString(i) + ":" + val+";"+old+" ("+(val-old)+")"); - } - System.out.println("-------"); - } - @Override public void tick() { } diff --git a/src/main/java/jace/cheat/PrinceOfPersiaCheats.java b/src/main/java/jace/cheat/PrinceOfPersiaCheats.java index f9512fe..a60fcf5 100644 --- a/src/main/java/jace/cheat/PrinceOfPersiaCheats.java +++ b/src/main/java/jace/cheat/PrinceOfPersiaCheats.java @@ -25,20 +25,19 @@ import jace.config.ConfigurableField; import jace.core.Computer; import jace.core.PagedMemory; import jace.core.RAMEvent; -import jace.core.RAMListener; import javafx.event.EventHandler; import javafx.scene.Node; import javafx.scene.input.MouseButton; /** - * Prince of Persia game cheats. This would not have been possible without the + * Prince of Persia game cheats. This would not have been possible without the * source. I am eternally grateful to Jordan Mechner both for creating this * game, and for being so kind to release the source code to it so that we can * learn how it works. Where possible, I've indicated where I found the various * game variables in the original source so that it might help anyone else * trying to learn how this game works. * - * @author Brendan Robert (BLuRry) brendan.robert@gmail.com + * @author Brendan Robert (BLuRry) brendan.robert@gmail.com */ public class PrinceOfPersiaCheats extends Cheats { @@ -56,7 +55,6 @@ public class PrinceOfPersiaCheats extends Cheats { public static boolean swordHack; @ConfigurableField(category = "Hack", name = "Mouse", defaultValue = "false", description = "Left click kills/opens, Right click teleports") public static boolean mouseHack; - boolean mouseRegistered = false; public static int PREV = 0x02b; public static int SPREV = 0x02e; public static int CharPosn = 0x040; @@ -166,7 +164,7 @@ public class PrinceOfPersiaCheats extends Cheats { protected String getDeviceName() { return ("Prince of Persia"); } - + @Override public void tick() { // Do nothing @@ -175,94 +173,19 @@ public class PrinceOfPersiaCheats extends Cheats { @Override public void registerListeners() { if (velocityHack) { - addCheat(new RAMListener(RAMEvent.TYPE.READ_DATA, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { - @Override - protected void doConfig() { - setScopeStart(CharYVel); - } - - @Override - protected void doEvent(RAMEvent e) { - registerMouse(); - if (!SoftSwitches.AUXZP.getState()) { - return; - } - int newVel = e.getNewValue(); - if (newVel > 5) { - newVel = 1; - } - e.setNewValue(newVel & 0x0ff); - } - }); + addCheat(RAMEvent.TYPE.READ_DATA, CharYVel, this::velocityHackBehavior); } - if (invincibilityHack) { - addCheat(new RAMListener(RAMEvent.TYPE.READ_DATA, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { - @Override - protected void doConfig() { - setScopeStart(KidStrength); - } - - @Override - protected void doEvent(RAMEvent e) { - registerMouse(); - if (!SoftSwitches.AUXZP.getState()) { - return; - } - e.setNewValue(3); - } - }); + forceValue(KidStrength, true, 3); } if (sleepHack) { - addCheat(new RAMListener(RAMEvent.TYPE.READ_DATA, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { - @Override - protected void doConfig() { - setScopeStart(EnemyAlert); - } - - @Override - protected void doEvent(RAMEvent e) { - registerMouse(); - if (!SoftSwitches.AUXZP.getState()) { - return; - } - e.setNewValue(0); - } - }); + forceValue(EnemyAlert, true, 0); } if (swordHack) { - addCheat(new RAMListener(RAMEvent.TYPE.READ_DATA, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { - @Override - protected void doConfig() { - setScopeStart(hasSword); - } - - @Override - protected void doEvent(RAMEvent e) { - registerMouse(); - if (!SoftSwitches.AUXZP.getState()) { - return; - } - e.setNewValue(1); - } - }); + forceValue(hasSword, true, 1); } if (timeHack) { - addCheat(new RAMListener(RAMEvent.TYPE.READ_DATA, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { - @Override - protected void doConfig() { - setScopeStart(MinLeft); - } - - @Override - protected void doEvent(RAMEvent e) { - registerMouse(); - if (!SoftSwitches.AUXZP.getState()) { - return; - } - e.setNewValue(0x069); - } - }); + forceValue(MinLeft, true, 0x069); } if (mouseHack) { EmulatorUILogic.addMouseListener(listener); @@ -275,15 +198,18 @@ public class PrinceOfPersiaCheats extends Cheats { public void unregisterListeners() { super.unregisterListeners(); EmulatorUILogic.removeMouseListener(listener); - mouseRegistered = false; } public static int BlueType = 0x0b700; - public void registerMouse() { - if (mouseRegistered) { - EmulatorUILogic.addMouseListener(listener); - mouseRegistered = false; + private void velocityHackBehavior(RAMEvent e) { + if (!SoftSwitches.AUXZP.getState()) { + return; } + int newVel = e.getNewValue(); + if (newVel > 5) { + newVel = 1; + } + e.setNewValue(newVel & 0x0ff); } public void mouseClicked(MouseButton button) { @@ -291,7 +217,7 @@ public class PrinceOfPersiaCheats extends Cheats { // Offset y by three pixels to account for tiles above Double y = mouseY - 0.015625; // Now we have the x and y coordinates ranging from 0 to 1.0, scale to POP values - + int row = y < 0 ? -1 : (int) (y * 3); int col = (int) (x * 10); @@ -310,7 +236,6 @@ public class PrinceOfPersiaCheats extends Cheats { // Note: POP uses a 255-pixel horizontal axis, Pixels 0-57 are offscreen to the left // and 198-255 offscreen to the right. - // System.out.println("Clicked on " + col + "," + row + " -- screen " + (x * 280) + "," + (y * 192)); RAM128k mem = (RAM128k) computer.getMemory(); PagedMemory auxMem = mem.getAuxMemory(); @@ -428,4 +353,4 @@ public class PrinceOfPersiaCheats extends Cheats { } } } -} \ No newline at end of file +} diff --git a/src/main/java/jace/core/RAM.java b/src/main/java/jace/core/RAM.java index b8b8c13..fea2dd1 100644 --- a/src/main/java/jace/core/RAM.java +++ b/src/main/java/jace/core/RAM.java @@ -46,6 +46,7 @@ public abstract class RAM implements Reconfigurable { /** * Creates a new instance of RAM + * @param computer */ public RAM(Computer computer) { this.computer = computer; @@ -151,9 +152,6 @@ public abstract class RAM implements Reconfigurable { int lsb = 0x00ff & read(address, eventType, triggerEvent, requireSynchronization); int msb = (0x00ff & read(address + 1, eventType, triggerEvent, requireSynchronization)) << 8; int value = msb + lsb; -// if (generateEvent) { -// callListener(RAMEvent.TYPE.READ, address, value, value); -// } return value; } @@ -204,17 +202,47 @@ public abstract class RAM implements Reconfigurable { addListenerRange(l); }); } - - public void addListener(final RAMListener l) { + + public RAMListener observe(RAMEvent.TYPE type, int address, RAMEvent.RAMEventHandler handler) { + return addListener(new RAMListener(type, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { + @Override + protected void doConfig() { + setScopeStart(address); + } + + @Override + protected void doEvent(RAMEvent e) { + handler.handleEvent(e); + } + }); + } + + public RAMListener observe(RAMEvent.TYPE type, int addressStart, int addressEnd, RAMEvent.RAMEventHandler handler) { + return addListener(new RAMListener(type, RAMEvent.SCOPE.ADDRESS, RAMEvent.VALUE.ANY) { + @Override + protected void doConfig() { + setScopeStart(addressStart); + setScopeEnd(addressEnd); + } + + @Override + protected void doEvent(RAMEvent e) { + handler.handleEvent(e); + } + }); + } + + public RAMListener addListener(final RAMListener l) { boolean restart = computer.pause(); if (listeners.contains(l)) { - return; + return l; } listeners.add(l); addListenerRange(l); if (restart) { computer.resume(); } + return l; } public void removeListener(final RAMListener l) { diff --git a/src/main/java/jace/core/RAMEvent.java b/src/main/java/jace/core/RAMEvent.java index 529e578..4b8ec46 100644 --- a/src/main/java/jace/core/RAMEvent.java +++ b/src/main/java/jace/core/RAMEvent.java @@ -31,6 +31,10 @@ package jace.core; */ public class RAMEvent { + static public interface RAMEventHandler { + public void handleEvent(RAMEvent e); + } + public enum TYPE { READ(true), diff --git a/src/main/java/jace/core/RAMListener.java b/src/main/java/jace/core/RAMListener.java index 6fe85bd..3eb4d3a 100644 --- a/src/main/java/jace/core/RAMListener.java +++ b/src/main/java/jace/core/RAMListener.java @@ -29,8 +29,8 @@ import jace.core.RAMEvent.TYPE; * * @author Brendan Robert (BLuRry) brendan.robert@gmail.com */ -public abstract class RAMListener { - +public abstract class RAMListener implements RAMEvent.RAMEventHandler { + private RAMEvent.TYPE type; private RAMEvent.SCOPE scope; private RAMEvent.VALUE value; @@ -156,7 +156,8 @@ public abstract class RAMListener { // If we've made it this far then the event is valid. return true; } - + + @Override public void handleEvent(RAMEvent e) { if (isRelevant(e)) { doEvent(e);