Load State at Breakpoint (specific game event)
#1
This is the story of a cheat code I wanted to make, was unable to make, and the hack-y solution I came up with to get around it.

In 4P mode, I was looking for a way to jump back to the character selection screen after each race, that way in a tournament setting where the players change each race, you wouldn't need to quit to the main menu each time.  As you probably know, normal behavior is that after each race, you press "next race", and it goes to the cup selection screen assuming you want to play again with the same character/vehicle selections.

I started out by trying to figure out the instruction where that menu - the cup selection screen - is loaded after you press next race.  Using this list, I was able to find that the cup selection menu is referred to as 6E, so I went to work with some excruciating dynamic analysis, going frame by frame until I found something that was writing 6E.  Good news, I did!  8070ca50 contains stw r30, 0x0004 (r29), and r30 contains 6E after you press next race.  Furthermore, r9 = 17 when you're accessing the cup selection screen from a new match, and r9 = 5 when you're accessing it from the "Next Race" button.  So you'd think I could just write a code to use load instruction on r30 before that 6E is written, and put something like 6B (Character Select) instead, but that didn't work.  The byte gets written successfully, but the game still goes into the cup selection screen.

It was at this point that I realized, maybe I can do this without affecting game logic at all.  I'm going to be using Dolphin for the tournament anyway, so what if, instead of a cheat code, I just loaded a state when that 8070ca50, r9 == 5 breakpoint hit?  I could just use Python to watch dolphin.log, and send a hotkey to load a state that jumps to the character select screen.

So....



Here's a very silly solution to my problem:

This is adapted from my Logging Game Events to Text File post.

1) In Dolphin, go to  Options > Configuration > Interface > Enable Debugging UI.

Go to View, make sure Breakpoints, Log, and Log Configuration are checked. Then, in the Log Configuration tab, uncheck everything except "Master Log (MASTER)" and "Memory Interface & Memory Map (MI)".  At this point you should probably find Dolphin's log file (dolphin.log) and delete it, that way you're starting fresh (on Windows it is found at C:\Users\YourUserName\AppData\Roaming\Dolphin Emulator\Logs).

2) Add 8070ca50 as a breakpoint for NTSC-U.  In the condition box, write r9 == 5.

These are the breakpoints for other versions (untested)
  • NTSC-U  8070ca50
  • PAL 0x807144f4
  • NTSC-J 0x80713b60
  • NTSC-K 0x8070289c

3) Save a state.  In my example, I'm using State Slot 1, default hotkey is Shift+F1 to save, F1 to load.

4) Run this Python script.  You'll need to install the keyboard module (run cmd as admin, pip install keyboard):

load-state-after-bp.py
Code:
import time
import os
import keyboard

class LogFileWatcher:
    def __init__(self, log_file):
        self.log_file = log_file
        self.last_position = 0
        self.current_data = ""
        print("Watching log file:", log_file)

    def watch(self):
        try:
            while True:
                self.dump_changes()
                time.sleep(0.05)
        except KeyboardInterrupt:
            print("Watcher stopped.")

    def dump_changes(self):
        try:
            with open(self.log_file, 'r') as f:
                f.seek(self.last_position)
                new_data = f.read()
                if new_data:
                    if new_data.startswith('\r\n'):
                        new_data = new_data[2:]  # Remove the first two characters (CR LF)
                    elif new_data.startswith('\n'):
                        new_data = new_data[1:]  # Remove the first character (LF)
                    self.current_data = new_data
                    print("Changes detected:")
                    print(self.current_data)
                    if "1. Vars:  r9=5" in new_data:
                        print("True")
                        keyboard.press('f1')  # Press F1 key
                        time.sleep(0.1)  # Hold F1 for 100 milliseconds
                        keyboard.release('f1')  # Release F1 key
                    self.last_position = f.tell()
        except Exception as e:
            print("Error occurred:", str(e))

if __name__ == "__main__":
    log_file = "C:\\Users\\Sean\\AppData\\Roaming\\Dolphin Emulator\\Logs\\dolphin.log"
   
    watcher = LogFileWatcher(log_file)
    watcher.watch()


5) Be sure to edit the log_file variable near the bottom to use your computer's username instead of mine.

And that's it.  Again, not ideal, but it does work.  I'm also aware there's a Dolphin module for Python that avoids needing to use keypresses and can just load the state directly, feel free to have a go at that if you want.
Reply
#2
Big Grin 
I logged into the game and tried it right after reading your article. And it really worked. Great, excellent, wonderfull!
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)