Logging game events to text file
#21
And now, just for fun, the conditional version!

C278894C 00000006
83E3008C 3D808000
39600001 2C1F0009
4082001C 807B0000
80630000 88630010
2C030000 41810008
996C165F 00000000

Source:

# NOTE this is meant for four player mode, edit the second cmpwi if in another mode
# Insertion Address: 8078894C
lwz r31, 0x008C (r3) # Default instruction, loads item to r31
lis r12, 0x8000 #Set the upper 16 bits of the EVA
li r11, 1 # Get our value ready in case a human uses a star

# Find out if a star was used
cmpwi r31, 9
bne end_code # If not, skip to the end

# Load the slot of player that just used an item
lwz r3, 0x0 (r27) # Slot loading code start, load mem address from r27 into r3
lwz r3, 0x0 (r3)  # Load new word from a memory address based on the word we just loaded
lbz r3, 0x10 (r3) # Load new byte from a memory address based on the word we just loaded
cmpwi r3, 3 # EDIT SECOND NUMBER IF IN ANOTHER MODE, use 2 for 3 player, 1 for 2 player, 0 for 1 player.
bgt end_code # If the value in r3 is greater than 3, a non-human player used the star, cancel
stb r11, 0x165F (r12) #Store a 1 to the EVA at address 8000165F
end_code:
Reply
#22
Good work. You will need some code to be included that will reset the flag (to 0) if the conditions are NOT met.

Code:
#Default instruction, load item value
lwz r31, 0x008C (r3)

#Set EVA Upper
lis r12, 0x8000

#Check for Star
cmpwi r31, 9
bne+ reset_flag

#Load Slot code is hooked on
lwz r3, 0x0 (r27)
lwz r3, 0x0 (r3)
lbz r3, 0x10 (r3)

#Check if slot is P1 thru P4 (0 thru 3)
cmplwi r3, 3
bgt+ reset_flag

#All conditions met, set flag
li r11, 1
b write_flag_value

#Reset flag
reset_flag:
li r11, 0

#Write Flag Value
write_flag_value:
stb r11, 0x165F (r12)

Some notes:
  • The bne has a plus appended because it's more likely the item will NOT be a star
  • The bgt has a plus appended because it's more likely the loaded slot value will be higher than 3
  • The comparison for the slot value is done logically because slot values are never negative values. And whenever you are doing a comparison with a later bge/bgt/ble/blt branch, you should choose a logical comparison unless you know negative numbers are possible.
Reply
#23
Thanks! That reset flag may certainly be useful in other cases, but in mine, leaving it at 1 is fine because dolphin will still log every write of that value, even if it's already there. In fact, it may be better to leave it, in case Dolphin logs faster than Chataigne can read.

I do have some questions about beq+ and beq-, I came across those in the tutorial but didn't really understand the purpose. I know that they give Broadway a prediction as to what will happen, but how does that affect things in practice? For example, what if you had the wrong type of prediction (plus where there should be a minus?) What would be different?
Reply
#24
It helps performance (speed) of your code. Having the code handler equipped already slows down the CPU quite a bit, and having "slow" codes on top of it will make it worse. Will this make any "real-time" difference? No, unless we are talking about having multiple codes that all do something intense such as NAND/file editing.

Providing the wrong branch hint (+ when you're not suppose to on forward branches and - when you're not suppose to on backwards branches) will do a lot worse though than leaving the branch hint as default (less likely). When you omit a branch hint, it's the same as applying (-) for forward branches and (+) for backwards branches.

To give you an idea about what speeds we are talking about. A tick/clock in Broadway under normal conditions is a little over 16 nanoseconds iirc. A missed branch prediction (if said branch is not in the Branch History Table or Branch Target Cache), can cause around.... idk... 10 extra clocks?? (this varies A TON on what code is present and Its been a minute since I've done any instruction timing stuff). So 160+ nanoseconds, lol.
Reply
#25
Ah, I get it now, I guess I should've figured it was a speed thing.  Thanks for explaining!  I could see how those seconds could add up.

Not to get too off-topic, but now that I have a handle on assembly I've made a few log-specific codes.  I made codes to log human player race positions, log which characters players select, and log which players are holding which items (last one isn't working yet but I'm very close).  I'm wondering, should I just dump them in this thread, or post them in the Misc section?  Working on getting ram dumps for the other regions so I'm able to port them all as well.

I want to sincerely thank you for all your help, with your assistance on this thread plus your incredibly detailed tutorials I've completed all of my main objectives AND all of my stretch goals save for one which doesn't have to do with logging.  Much appreciated!
Reply
#26
You could. If you have codes in the future that are meant for public use, you can of course post them in the appropriate sub-forum in the Codes forum. Others post them in their GitHub.
Reply
#27
Alright folks, my project is complete.  For anyone coming across this in the future that needs to do what I did, I'm going to write up a summary of what I did, what I learned, and how you can do this too:

After going through these steps, I can now log to a text file:
  • P1-P4's character choices
  • P1-P4's position throughout the race
  • Whenever P1-P4 has a star in their inventory
  • Whenever P1-P4 uses a star
  • Whenever anyone uses a lighning bolt
  • Whenever anyone uses a blooper
  • When a course starts
  • When a course is complete
  • When the final lap begins



1) Configuring Dolphin

First step, enable the debugging features within Dolphin.  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) Adding Breakpoints

Now, on the Breakpoints tab, we can choose to log when certain events in the game happen.  Start up MKWii, then click New, and enter either an instruction address or a memory address.  At the bottom, be sure to choose the option that says "Write To Log", you don't want to "break" (or pause) the game when the event happens.

Here's a list of breakpoints that I found. Note that the top section of breakpoints won't work until after you've completed section 3, cheat codes.  You only need to put in the 8-character address before the #, the info after # denotes the type of breakpoint and my comments on each code.  You'll need to pay attention to whether it is an instruction or memory breakpoint, and if memory, whether to choose read or write.

Please note that all of these are for the NTSC-U version of the gameBreakpoints are different depending on what version of the game you have.  The easiest way to port breakpoints to another version is to download SZS Tools and inside the bin folder, open command prompt and use wstrt.exe to port the code.  Full documentation here. The command should look like:

Code:
wstrt.exe port usa 8078894C

Note that the breakpoints that need cheat codes do not need to be ported, but the cheat codes themselves will need to be ported, which involves changing the 6 characters after "C2" in the first line.  More on porting codes here.

Generated by Cheat Codes (Cheat Codes Required for These To Work!)

Character Breakpoints (see list of characters here to decode this value)
800015AF # MEMORY WRITE # P1's Character
800015BF # MEMORY WRITE # P2's Character
800015CF # MEMORY WRITE # P3's Character
800015DF # MEMORY WRITE # P4's Character

Position Breakpoints
8000160F # MEMORY WRITE # P1's Position
8000161F # MEMORY WRITE # P2's Position
8000162F # MEMORY WRITE # P3's Position
8000163F # MEMORY WRITE # P4's Position

Item Breakpoints
8000165F # MEMORY WRITE # Human Used A Star
8000167F # MEMORY WRITE # Human Has A Star
8000165E # MEMORY WRITE # Someone used a blooper

----
Natural Breakpoints (You can Use These Without Cheat Codes!)

80835D50 # INSTRUCTION # Countdown begins, hits on First num of countdown
8070492C # INSTRUCTION # Lap increase.  Put r31 == 3 in conditions and will only log "1. Vars:  r31=3" on final lap.
8070a944 # INSTRUCTION # Course Clear, FINISH appears on screen, race complete
80530D9C # INSTRUCTION # Breaks on both course overview and on course start.  Condition r24 == 0 for Course Overview, r24 == 1 for Course Start
8088D508 # MEMORY READ/WRITE # Lightning used

---
Unused (I didn't wind up needing these, but they are natural, maybe you'll find them useful!)

80531134 # INSTRUCTION # Exact moment GO hits after countdown (can't use, too much nonsense on log)
8058f2a8 # INSTRUCTION # Also hits on GO
808B12F4 # INSTRUCTION # Blooper ink hit (Don't Use, too much on output)
8078894C # INSTRUCTION # Any time any player or CPU uses an item



3) Cheat Codes
 
Now, you might be thinking, why do I need to use cheat codes if I don't want to affect gameplay, just get information?  Well, because Dolphin only supports getting information from the RAM, and some pieces of information are stored in other places, or in dynamic parts of the ram whose addresses change on each boot.  Because of this, we need to write cheat codes that can write information from those places to the empty parts of the RAM (also known as the EVA, or exception vector area), so Dolphin can log it.

So basically, without these codes, all the addresses I provided in the first section are just empty parts of ram.  Here's how to add cheat codes to Dolphin if you aren't familiar:

1) Right Click Mario Kart Wii, click Properties
2) Go to Gecko Codes
3) On the bottom, click Add New Code
4) Paste the code (not the source code) into the Code box.

Human Has A Star

Code:

Code:
C278EFE8 00000005
387D0054 3D808000
39600001 2C1B0003
41810010 2C1C0009
40820008 996C167F
60000000 00000000


Source Code (For Reference):

Code:
# HUMAN HAS A STAR
# Insertion Address: 8078efe8

addi r3, r29, 84      # Default instruction
lis r12, 0x8000       # Set the upper 16 bits of the EVA
li r11, 1             # set value for if true
cmpwi r27, 3          # Was it a human player?
bgt end_code
cmpwi r28, 9
bne end_code
stb r11, 0x167F (r12) #Store a 1 to the EVA at address 8000167F

end_code:

Blooper or Star Used

Code:

Code:
C278894C 00000008
83E3008C 3D808000
39600001 2C1F0009
40820020 807B0000
80630000 88630010
2C030003 41810018
996C165F 48000010
2C1F000C 40820008
996C165E 00000000


Source Code:

Code:
# NOTE this is meant for four player mode, edit the second cmpwi if in another mode
# Insertion Address: 8078894C
lwz r31, 0x008C (r3) # Default instruction, loads item to r31
lis r12, 0x8000 #Set the upper 16 bits of the EVA
li r11, 1 # Get our value ready in case a human uses a star

# Find out if a star was used
cmpwi r31, 9
bne blooper_check # If not, maybe it was a blooper

# Load the slot of player that just used an item
lwz r3, 0x0 (r27) # Slot loading code start, load mem address from r27 into r3
lwz r3, 0x0 (r3)  # Load new word from a memory address based on the word we just loaded
lbz r3, 0x10 (r3) # Load new byte from a memory address based on the word we just loaded
cmpwi r3, 3 # EDIT SECOND NUMBER IF IN ANOTHER MODE, use 2 for 3 player, 1 for 2 player, 0 for 1 player.
bgt end_code # If the value in r3 is greater than 3, a non-human player used the star, cancel
stb r11, 0x165F (r12) #Store a 1 to the EVA at address 8000165F
b end_code

blooper_check:
cmpwi r31, 0x0C # check if blooper
bne end_code # If not cancel
stb r11, 0x165E (r12) #If blooper Store a 1 to the EVA at address 8000165E

end_code:


Store All Player Positions 4P Mode

Code:

Code:
C278494C 00000011
98830161 3D808000
2C070000 41820024
2C070001 41820030
2C070002 4182003C
2C070003 41820048
2C070003 41810054
896C160F 7C045800
41820048 988C160F
48000040 896C161F
7C045800 41820034
988C161F 4800002C
896C162F 7C045800
41820020 988C162F
48000018 896C163F
7C045800 4182000C
988C163F 48000004
60000000 00000000


Source Code:

Code:
# Store All Player Positions 4P Mode
# Insertion Address: 8078494C

stb r4, 0x0161 (r3)   # Default Instruction
lis r12, 0x8000       # Set the upper 16 bits of the EVA
cmpwi r7, 0           # Are we checking player 1
beq p1_check
cmpwi r7, 1           # Are we checking player 2
beq p2_check
cmpwi r7, 2           # Are we checking player 3
beq p3_check
cmpwi r7, 3           # Are we checking player 4
beq p4_check
cmpwi r7, 3           # When game is checking a human player, r7 will be between zero and three
bgt end_code          # If number is greater, game is not checking a human, skip to end
p1_check:
lbz r11, 0x160F (r12) # Load the byte from our EVA address 8000160F into r11
cmpw r4, r11          # If current pos at r4 is same as what we already have...
beq end_code          # ...then don't bother writing.
stb r4, 0x160F (r12)  # Store P1 Position from r4 to the EVA at address 8000160F
b end_code
p2_check:
lbz r11, 0x161F (r12) # Load the byte from our EVA address 8000160F into r11
cmpw r4, r11          # If current pos at r4 is same as what we already have...
beq end_code          # ...then don't bother writing.
stb r4, 0x161F (r12)  # Store P2 Position from r4 to the EVA at address 8000161F
b end_code
p3_check:
lbz r11, 0x162F (r12) # Load the byte from our EVA address 8000160F into r11
cmpw r4, r11          # If current pos at r4 is same as what we already have...
beq end_code          # ...then don't bother writing.
stb r4, 0x162F (r12)  # Store P3 Position from r4 to the EVA at address 8000162F
b end_code
p4_check:
lbz r11, 0x163F (r12) # Load the byte from our EVA address 8000160F into r11
cmpw r4, r11          # If current pos at r4 is same as what we already have...
beq end_code          # ...then don't bother writing.
stb r4, 0x163F (r12)  # Store P4 Position from r4 to the EVA at address 8000163F
b end_code
end_code:


Store All Character Choices 4P

Code:

Code:
C284DAA4 0000000B
90A40010 3D808000
2C160000 41820024
2C160001 41820024
2C160002 41820024
2C160003 41820024
2C160003 48000024
98AC15AF 4800001C
98AC15BF 48000014
98AC15CF 4800000C
98AC15DF 48000004
60000000 00000000


Source Code:

Code:
#HERE'S HOW IT WORKS:
#At instruction breakpoint 8084daa4, we write if a player changed character!
#r22 = The player that changed characters
#r5 = The character choice

# These changes happen on the character select screen

# Insertion Position: 8084daa4
stw r5, 0x0010 (r4)   # Default Instruction
lis r12, 0x8000       # Set the upper 16 bits of the EVA

cmpwi r22, 0           # Did player 1 change character
beq p1_change
cmpwi r22, 1           # Did player 2 change character
beq p2_change
cmpwi r22, 2           # Did player 3 change character
beq p3_change
cmpwi r22, 3           # Did player 4 change character
beq p4_change
cmpwi r22, 3
b end_code             # If we somehow called this instruction and nobody changed character, skip to end

p1_change:
stb r5, 0x15AF (r12)  # Store P1 Character from r5 to the EVA at address 800015AF
b end_code

p2_change:
stb r5, 0x15BF (r12)  # Store P2 Character from r5 to the EVA at address 800015BF
b end_code

p3_change:
stb r5, 0x15CF (r12)  # Store P3 Character from r5 to the EVA at address 800015CF
b end_code

p4_change:
stb r5, 0x15DF (r12)  # Store P4 Character from r5 to the EVA at address 800015DF
b end_code

end_code:


4) Dolphin is Logging Stuff, Now What?

Well, the reason I needed this was so I could feed it to Chataigne and control RGB lights during game events.  You can also use something like Advanced Scene Switcher in OBS to read from a text file and do certain things.  But note that the dolphin.log is *additive*, meaning all the previous stuff is kept in a big running log, and it isn't reset to nothing unless you delete the file while dolphin is closed.  This also means that each time a program reads it, it reads everything, not just the newest changes.  To solve this I made a python script to read from the original log file and just write the newest changes to a new text file.  I can then use the new text file in an external program to just read the newest changes of Dolphin's log on each update.  You'll need to adjust the path near the end of this script in order to use this.

dolphin-log-change-finder.py
Code:
import time
import os

class LogFileWatcher:
    def __init__(self, log_file, output_file):
        self.log_file = log_file
        self.output_file = output_file
        self.last_position = 0
        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)
                    with open(self.output_file, 'w') as out:
                        out.write(new_data)
                    print("Changes dumped to", self.output_file)
                    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"
    output_file = "output.txt"
   
    watcher = LogFileWatcher(log_file, output_file)
    watcher.watch()


Also, Chataigne cannot natively read text files, so I needed to write my own module for that.


And that's everything.  Hope this helps someone in the future!  Feel free to reach out with any questions, this forum's e-mail notifications don't work so I may not see replies to this thread but I'm also reachable via Discord @SeanMcNally98.  Enjoy!
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)