From f9114248d18998ec3a278887dc6b6956c54fd02c Mon Sep 17 00:00:00 2001 From: Brendan Robert Date: Fri, 15 Mar 2024 00:34:15 -0500 Subject: [PATCH] Metacheat speedup; program identity experimental feature --- .../src/main/java/jace/apple2e/Apple2e.java | 2 +- .../src/main/java/jace/apple2e/MOS65C02.java | 12 +- .../src/main/java/jace/apple2e/RAM128k.java | 2 +- .../jace/src/main/java/jace/cheat/Cheats.java | 3 +- .../src/main/java/jace/cheat/MemoryCell.java | 10 ++ .../src/main/java/jace/cheat/MetaCheat.java | 41 +++--- .../main/java/jace/cheat/ProgramIdentity.java | 119 ++++++++++++++++++ .../src/main/java/jace/ui/MetacheatUI.java | 24 ++-- .../jace/cheats/program-identities.txt | 66 ++++++++++ 9 files changed, 235 insertions(+), 44 deletions(-) create mode 100644 Platform/Apple/tools/jace/src/main/java/jace/cheat/ProgramIdentity.java create mode 100644 Platform/Apple/tools/jace/src/main/resources/jace/cheats/program-identities.txt diff --git a/Platform/Apple/tools/jace/src/main/java/jace/apple2e/Apple2e.java b/Platform/Apple/tools/jace/src/main/java/jace/apple2e/Apple2e.java index 37b08fc1..28a14195 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/apple2e/Apple2e.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/apple2e/Apple2e.java @@ -63,7 +63,7 @@ public class Apple2e extends Computer { static int IRQ_VECTOR = 0x003F2; @ConfigurableField(name = "Production mode", shortName = "production") - public boolean PRODUCTION_MODE = true; + public boolean PRODUCTION_MODE = false; @ConfigurableField(name = "Slot 1", shortName = "s1card") public DeviceSelection card1 = new DeviceSelection<>(Cards.class, null); @ConfigurableField(name = "Slot 2", shortName = "s2card") diff --git a/Platform/Apple/tools/jace/src/main/java/jace/apple2e/MOS65C02.java b/Platform/Apple/tools/jace/src/main/java/jace/apple2e/MOS65C02.java index 6a33bdaa..1c515ee4 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/apple2e/MOS65C02.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/apple2e/MOS65C02.java @@ -1246,13 +1246,11 @@ public class MOS65C02 extends CPU { } public String getState() { - return String.format("%s %s %s 01%s %s", - byte2(A), - byte2(X), - byte2(Y), - byte2(STACK), - getFlags() - ); + return byte2(A) + + " " + byte2(X) + + " " + byte2(Y) + + " 01" + byte2(STACK) + + getFlags(); } public String getFlags() { diff --git a/Platform/Apple/tools/jace/src/main/java/jace/apple2e/RAM128k.java b/Platform/Apple/tools/jace/src/main/java/jace/apple2e/RAM128k.java index c7abc32c..f6bc7061 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/apple2e/RAM128k.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/apple2e/RAM128k.java @@ -223,7 +223,7 @@ abstract public class RAM128k extends RAM { if (SoftSwitches.INTC8ROM.isOn()) { rstate += "C8"; } else { - rstate += String.format("C8%d", getActiveSlot()); + rstate += "C8"+getActiveSlot(); } } diff --git a/Platform/Apple/tools/jace/src/main/java/jace/cheat/Cheats.java b/Platform/Apple/tools/jace/src/main/java/jace/cheat/Cheats.java index 1d95af18..34504e5c 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/cheat/Cheats.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/cheat/Cheats.java @@ -39,7 +39,8 @@ public abstract class Cheats extends Device { Metacheat("Metacheat", MetaCheat.class, MetaCheat::new), MontezumasRevenge("Montezuma's Revenge", MontezumasRevengeCheats.class, MontezumasRevengeCheats::new), PrinceOfPersia("Prince of Persia", PrinceOfPersiaCheats.class, PrinceOfPersiaCheats::new), - LawlessHacks("Lawless Legends Enhancements", LawlessHacks.class, LawlessHacks::new); + LawlessHacks("Lawless Legends Enhancements", LawlessHacks.class, LawlessHacks::new), + ProgramIdentity("Identify program", ProgramIdentity.class, ProgramIdentity::new); Supplier factory; String name; diff --git a/Platform/Apple/tools/jace/src/main/java/jace/cheat/MemoryCell.java b/Platform/Apple/tools/jace/src/main/java/jace/cheat/MemoryCell.java index 9d578187..79aa70d8 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/cheat/MemoryCell.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/cheat/MemoryCell.java @@ -6,6 +6,7 @@ package jace.cheat; import java.util.ArrayList; + import javafx.beans.binding.BooleanBinding; import javafx.beans.property.IntegerProperty; import javafx.beans.property.SimpleIntegerProperty; @@ -77,6 +78,15 @@ public class MemoryCell implements Comparable { return address - o.address; } + @Override + public boolean equals(Object o) { + if (o instanceof MemoryCell) { + MemoryCell om = (MemoryCell) o; + return address == om.address || (x == om.x && y == om.y); + } + return false; + } + public boolean hasCounts() { return hasCount.get(); } diff --git a/Platform/Apple/tools/jace/src/main/java/jace/cheat/MetaCheat.java b/Platform/Apple/tools/jace/src/main/java/jace/cheat/MetaCheat.java index b103598a..46b83932 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/cheat/MetaCheat.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/cheat/MetaCheat.java @@ -64,7 +64,7 @@ public class MetaCheat extends Cheats { public int historyLength = 10; private int startAddress = 0; - private int endAddress = 0x0ffff; + private int endAddress = 0x0BFFF; private final StringProperty startAddressProperty = new SimpleStringProperty(Integer.toHexString(startAddress)); private final StringProperty endAddressProperty = new SimpleStringProperty(Integer.toHexString(endAddress)); private boolean byteSized = true; @@ -248,26 +248,33 @@ public class MetaCheat extends Cheats { int last = result.lastObservedValue; result.lastObservedValue = val; switch (searchType) { - case VALUE: + case VALUE -> { int compare = parseInt(searchValueProperty.get()); return compare != val; - case CHANGE: + } + case CHANGE -> { switch (searchChangeType) { - case AMOUNT: + case AMOUNT -> { int amount = parseInt(searchChangeByProperty().getValue()); return (val - last) != amount; - case GREATER: + } + case GREATER -> { return val <= last; - case ANY_CHANGE: + } + case ANY_CHANGE -> { return val == last; - case LESS: + } + case LESS -> { return val >= last; - case NO_CHANGE: + } + case NO_CHANGE -> { return val != last; + } } - break; - case TEXT: - break; + } + + case TEXT -> { + } } return false; }); @@ -309,15 +316,9 @@ public class MetaCheat extends Cheats { memoryCells.values().stream() .filter(MemoryCell::hasCounts) .forEach((cell) -> { - if (cell.execCount.get() > 0) { - cell.execCount.set(Math.max(0, cell.execCount.get() - fadeRate)); - } - if (cell.readCount.get() > 0) { - cell.readCount.set(Math.max(0, cell.readCount.get() - fadeRate)); - } - if (cell.writeCount.get() > 0) { - cell.writeCount.set(Math.max(0, cell.writeCount.get() - fadeRate)); - } + cell.execCount.set(Math.max(0, cell.execCount.get() - fadeRate)); + cell.readCount.set(Math.max(0, cell.readCount.get() - fadeRate)); + cell.writeCount.set(Math.max(0, cell.writeCount.get() - fadeRate)); if (MemoryCell.listener != null) { MemoryCell.listener.changed(null, cell, cell); } diff --git a/Platform/Apple/tools/jace/src/main/java/jace/cheat/ProgramIdentity.java b/Platform/Apple/tools/jace/src/main/java/jace/cheat/ProgramIdentity.java new file mode 100644 index 00000000..9914f0b0 --- /dev/null +++ b/Platform/Apple/tools/jace/src/main/java/jace/cheat/ProgramIdentity.java @@ -0,0 +1,119 @@ +package jace.cheat; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.zip.CRC32; + +import jace.apple2e.RAM128k; +import jace.apple2e.SoftSwitches; +import jace.core.PagedMemory; +import jace.core.RAMEvent; +import jace.core.RAMEvent.TYPE; + +public class ProgramIdentity extends Cheats { + private Map programIdentities; + + @Override + public void registerListeners() { + addCheat("Track execution", TYPE.ANY, this::trackActivity, 0, 0x0ffff); + } + + @Override + protected String getDeviceName() { + return "Program Identity"; + } + + int INTERVAL = 1000000; + int THRESHOLD_VALUE = 10000; + int CLIP_VALUE = THRESHOLD_VALUE * 2; + int DECAY = THRESHOLD_VALUE / 2; + + int[] programRegions = new int[512]; + private void trackActivity(RAMEvent e) { + int bank = e.getAddress() >> 8; + if (bank >= 0xc0 && bank < 0xd0) { + // Skip I/O region + return; + } + // Detect language card ram execution + if (bank >= 0xd0 && SoftSwitches.LCRAM.isOff()) { + // Skip rom execution + return; + } + if (!e.isMainMemory()) { + bank += 256; + } + if (e.getType() == RAMEvent.TYPE.EXECUTE) { + programRegions[bank] = Math.min(CLIP_VALUE, programRegions[bank] + 1); + } else if (e.getType() == RAMEvent.TYPE.WRITE) { + programRegions[bank] = 0; + } + } + + private String generateChecksum() { + CRC32 crc = new CRC32(); + RAM128k ram = (RAM128k) getMemory(); + int bankCount = 0; + for (int i=0; i < 512; i++) { + if (programRegions[i] > THRESHOLD_VALUE) { + PagedMemory mem = ram.getMainMemory(); + if (i >= 0x0d0 && i < 0x0100) { + mem = ram.getLanguageCard(); + } else if (i >= 0x0100 && i < 0x01d0) { + mem = ram.getAuxMemory(); + } else if (i >= 0x01d0) { + mem = ram.getAuxLanguageCard(); + } + bankCount++; + crc.update(mem.getMemoryPage((i & 0x0ff) << 8)); + } + } + return Long.toHexString(crc.getValue())+"-"+bankCount; + } + + @Override + public void resume() { + super.resume(); + Arrays.fill(programRegions, 0); + readProgramIdentities(); + } + + int counter = 0; + String lastChecksum = ""; + @Override + public void tick() { + if (counter++ >= INTERVAL) { + String checksum = generateChecksum(); + if (!checksum.equals(lastChecksum)) { + String identity = programIdentities.getOrDefault(checksum, "UNKNOWN"); + System.out.println(checksum + "," + identity); + lastChecksum = checksum; + } + counter = 0; + for (int i=0; i < 512; i++) { + programRegions[i] = Math.max(0, programRegions[i] - DECAY); + } + } + } + + private void readProgramIdentities() { + // Read from resources file + InputStream in = Cheats.class.getResourceAsStream("/jace/cheats/program-identities.txt"); + try { + programIdentities = new HashMap<>(); + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + String line; + while ((line = reader.readLine()) != null) { + String[] parts = line.split(","); + programIdentities.put(parts[0], parts[1]); + } + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/Platform/Apple/tools/jace/src/main/java/jace/ui/MetacheatUI.java b/Platform/Apple/tools/jace/src/main/java/jace/ui/MetacheatUI.java index 32b6eec7..27709802 100644 --- a/Platform/Apple/tools/jace/src/main/java/jace/ui/MetacheatUI.java +++ b/Platform/Apple/tools/jace/src/main/java/jace/ui/MetacheatUI.java @@ -1,9 +1,8 @@ package jace.ui; import java.io.File; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.ConcurrentSkipListSet; +import java.util.Map; +import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; @@ -384,8 +383,7 @@ public class MetacheatUI { public static final int MEMORY_BOX_TOTAL_SIZE = (MEMORY_BOX_SIZE + MEMORY_BOX_GAP); public int memoryViewColumns; public int memoryViewRows; - - public static Set redrawNodes = new ConcurrentSkipListSet<>(); + public static Map redrawNodes = new ConcurrentSkipListMap<>(); ScheduledExecutorService animationTimer = null; @SuppressWarnings("all") ScheduledFuture animationFuture = null; @@ -432,13 +430,11 @@ public class MetacheatUI { } private void processMemoryViewUpdates() { - boolean isRunning = Emulator.withComputer(c->c.getRunningProperty().get(), false); + boolean isRunning = Emulator.withComputer(c->c.getMotherboard().isRunning(), false); if (!isRunning) return; GraphicsContext context = memoryViewCanvas.getGraphicsContext2D(); - Set draw = new HashSet<>(redrawNodes); - redrawNodes.clear(); Platform.runLater(() -> { - draw.stream().forEach((jace.cheat.MemoryCell cell) -> { + redrawNodes.values().stream().forEach((jace.cheat.MemoryCell cell) -> { if (showValuesCheckbox.isSelected()) { int val = cell.value.get() & 0x0ff; context.setFill(Color.rgb(val, val, val)); @@ -450,10 +446,11 @@ public class MetacheatUI { } context.fillRect(cell.getX(), cell.getY(), cell.getWidth(), cell.getHeight()); }); + redrawNodes.clear(); }); } - public static int FRAME_RATE = 1000 / 60; + public static int FRAME_RATE = 1000 / 30; public void redrawMemoryView() { if (cheatEngine == null) { @@ -468,8 +465,6 @@ public class MetacheatUI { animationFuture.cancel(false); } - animationFuture = animationTimer.scheduleAtFixedRate(this::processMemoryViewUpdates, FRAME_RATE, FRAME_RATE, TimeUnit.MILLISECONDS); - cheatEngine.initMemoryView(); int pixelsPerBlock = 16 * MEMORY_BOX_TOTAL_SIZE; memoryViewColumns = (int) (memoryViewPane.getWidth() / pixelsPerBlock) * 16; @@ -490,13 +485,14 @@ public class MetacheatUI { (int) (row * MEMORY_BOX_TOTAL_SIZE * drawScale), (int) (MEMORY_BOX_SIZE * drawScale), (int) (MEMORY_BOX_SIZE * drawScale)); - redrawNodes.add(cell); + redrawNodes.put(cell.address, cell); } MemoryCell.setListener((javafx.beans.value.ObservableValue prop, jace.cheat.MemoryCell oldCell, jace.cheat.MemoryCell newCell) -> { - redrawNodes.add(newCell); + redrawNodes.put(newCell.address, newCell); }); setZoom(1/drawScale); + animationFuture = animationTimer.scheduleAtFixedRate(this::processMemoryViewUpdates, FRAME_RATE, FRAME_RATE, TimeUnit.MILLISECONDS); }); } diff --git a/Platform/Apple/tools/jace/src/main/resources/jace/cheats/program-identities.txt b/Platform/Apple/tools/jace/src/main/resources/jace/cheats/program-identities.txt new file mode 100644 index 00000000..3e2f5342 --- /dev/null +++ b/Platform/Apple/tools/jace/src/main/resources/jace/cheats/program-identities.txt @@ -0,0 +1,66 @@ +0-0,Firmware/Basic/Monitor +a3156625-1,Total Replay +158df73d-4,Miner 2049er +111cb7b9-5,Miner 2049er +d006d71b-6,A City Dies +d0d083aa-2,Alcazar (Title) +c05809d8-2,Alcazar (Menu) +6accc221-4,Alcazar (Game) +7381db02-4,Alien Downpour +580b57a0-5,Alien Downpour +f019024b-6,Alien Downpour (die) +2f1031b5-2,Alien Downpour (fail) +54d3700-3,Arctic Fox (Title) +b7fd2db6-13,Arctic Fox (Game) +d31a0d0a-14,Arctic Fox (Game) +82acd9ae-15,Arctic Fox (Game) +952fea92-16,Arctic Fox (Game) +bd7cf4f7-17,Arctic Fox (Game) +207242ca-2,Agent USA (Title) +9f9e6890-4,Agent USA (Menu) +536dad3-4,Agent USA (Menu) +90a1fd4f-2,Airheart (Title) +9a3b93f7-3,Airheart (Game) +6930a244-2,Apple Invader +aace1907-3,Apple Invader +c5c52757-2,Apple Invader (Title) +288ec83d-1,Apple Invader (Title) +bc3b2166-1,Arkanoid (Intro) +53a99672-7,Arkanoid (Demo) +87d4eab2-3,Arkanoid (Start) +cd93f077-3,Arkanoid (Game) +a6c1d770-3,Arkanoid (Game) +7b85b563-5,Arkanoid (Game) +53a99672-7,Arkanoid (Game) +6af90e52-1,Arkanoid (Game Over) +cde319e5-2,Montezuma's Revenge (Load) +eb28db32-3,Montezuma's Revenge (Title) +f447d42a-3,Montezuma's Revenge (Level Start 1 - Joystick) +312b8850-4,Montezuma's Revenge (Level Start 2 - Joystick) +73031104-5,Montezuma's Revenge (Level Start 3 - Joystick) +4b2cb3da-5,Montezuma's Revenge (Game 1 - Joystick) +72e913f2-3,Montezuma's Revenge (Game 2 - Joystick) +25e9bde4-4,Montezuma's Revenge (Game 3 - Joystick) +2e2db603-5,Montezuma's Revenge (Game 4 - Joystick) +160214dd-5,Montezuma's Revenge (Game 5 - Joystick) +43135e8-5,Montezuma's Revenge (Die) +b84a512-6,Montezuma's Revenge (Jump - Joystick) +d56c6cda-3,Montezuma's Revenge (Level Start 1 - Keyboard) +3cd3b2c5-4,Montezuma's Revenge (Level Start 2 - Keyboard) +23745a95-5,Montezuma's Revenge (Level Start 3 - Keyboard) +1b5bf84b-5,Montezuma's Revenge (Game - Keyboard) +b14ac573-6,Montezuma's Revenge (Jump - Keyboard) +f476b2d-2,Moon Patrol (Title) +4f94aa9-3,Moon Patrol (Menu) +396f7baf-3,Moon Patrol (Start 1) +6dfabc61-3,Moon Patrol (Start 2) +fb88733a-2,Moon Patrol (Start 3) +4e99aefb-5,Moon Patrol (Game 1) +dfd5fd5b-5,Moon Patrol (Game 2) +17e0326e-6,Moon Patrol (Game 3) +80243d6f-2,Moon Patrol (Game 5) +57a21d5d-6,Moon Patrol (Game - Shoot) +96130f7-6,Moon Patrol (Passed Pit or Checkpoint) +457104a-3,Moon Patrol (Die) +6a8bd750-2,Moon Patrol (Game Over) +1afccd0a-3,Moon Patrol (Bonus Screen) \ No newline at end of file