So I threw this code into the Code Help/Support sub-forum because I'm too lazy to finish testing (lol). And this code is very un-optimized with some issues I'll explain later.
This is meant to replace any exception handlers you have. The code will create a report.txt file in your /shared2 directory of your NAND giving many details of your exception. If the crash is a DSI, ISI, Alignment, or Program exception, V-Report will attempt to generate a report on it.
It's designed to work for all Wii games. Thus, it's region free ofc, just slap the code on any game and you're good to go.
NOTE: This code easily exceeds the typical GCT limit, you need to add this code via main.dol patching of some sorts (i.e. Wiimm's wstrt or Joshua's GeckoLoader)
Equip this code with your other codes. Once a crash occurs and V-Report was successful generating a report, your screen will turn green, hard reboot your Wii, use some HBC app to view the /shared2/report.txt. If the screen is yellow, V-Report failed. Please note that the screen colors won't work on Dolphin, this is an issue with Dolphin, not the code. So as of right now, this code is only meant for real hardware use. It will still work on Dolphin, but your emulation will just freeze up and be glitchy, just stuck there until you shut it off.
---
Technical details/issues (noobs may not understand some of this)
First thing's first, some of the comments/notes on the sources may not be accurate, just an fyi, as I was constantly making small new changes along the way. V-Report functino pointer is saved into sprg3 (never used by any Wii game), and all the report data is stored in exception areas that are not hit by the string writes, in the gecko code handler itself (safe to do since we already crashed), and the unused Thermal interrupt.
V-Report can only fail in two main areas. The first is not being able to find the game's sprintf function. The second is some error return when fiddling with the IPC registers via my Universal IOS code.
The reason why I don't end the code off with something such as a OSFatal message, is because on some of my tests on diff Wii games, the game crashes on OSFatal! No idea why this is. Also tried some tests with OSReturnToMenu on certain tests, that would crash as well and that function literally takes zero args, you just call it.
A solution to all of this could be to write some code to setup the VI interface manually and eliminate all visual graphics and just have the console up and output printf messages on the TV screen, but that would NOT be a fun task.
Tests done so far:
DSI on PAL MKWii on real hardware
DSI on PAL MKWii on Dolphin
DSI on NTSC-U DBZ BT3 on Dolphin
DSI on NTSC-U Brawl on Dolphin
---
Planned improvements to V-Report (if I ever get around to it)
Mute the audio so the crash has no annoying noises (easy to do, just too lazy to work on this code lol)
Do something other than screen colors (custom console printf messages, hard to do)
Add more registers to the report such as all FPRs also being stored as paired singles (easy to do, just too lazy)
---
Without further a do, here's the code and the sources. Everything is licensed under GPLv2. A notice of the license is included in both sources.
Code creator: Vega
Code credits: Segher, Tmbinc, & Bushing (authors of the original ipc.c on WiiBrew)
// This program 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, version 2.0.
// This program 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 2.0 for more details.
#First Source (backup some things, call V-Report)
#START ASSEMBLY
#Write at all following addresses via 06 Gecko String Writes
#0x80000300; for DSI
#0x80000400; for ISI
#0x80000600; for Alignment
#0x80000700; for Program
#Backup r0
mtsprg0 r0
#Backup LR
mflr r0
mtsprg1 r0
#Do BL Trick to get Program Counter so V-Report can figure out what kind of exception occurred
bl getpc
getpc:
#Backup CTR
mfctr r0
mtsprg2 r0
#Get pointer to V-Report function; it's in sprg3
mfsprg3 r0
#Call V-Report!; pointer is already physical, good to go
mtctr r0
bctr
#END ASSEMBLY
Second Source (C0 code)
Code:
// This program 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, version 2.0.
// This program 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 2.0 for more details.
#============================
#Second Source (V-Report)
#START ASSEMBLY
#Put the entire code into one gigantic Brach-link table. We do this so the code doesn't have to reside in the unused EVA space via an 06 string write. Also the contents of a 06 string write are copy-pasted from the GCT to the designated hook address every frame. This will bog down on CPU preformance since the transfer of the 06-string write data occurs every frame.
#A better approach is to have our code within a C0 code but the C0 code will simply consist of storinig teh pointer to the codes contents at a spot in the EVA. This greatly helsp on CPU preofrmance and this mthod only tkes up one word of unused EVA space.
#Note
#r0 treated as literal 0; nice to use for physical work
#Variables
.set EVA_GPR, 0x1700 #Where GPRs are stored at
.set EVA_FPR, 0x1800 #Where FPRs are stored at
.set EVA_MISC, 0x324 #Where Misc registers are stored at
.set EVA_STACK_TRACE, 0x424 #Where the 5 rows Stack Trace contents are stored at
.set EVA_CALLSTACK, 0x624 #5 words of the call stack
#.set EVA_LR, EVA_MISC+0 #Not needed, just listed here for reference
.set EVA_CTR, EVA_MISC+4
.set EVA_CR, EVA_MISC+8
.set EVA_XER, EVA_MISC+12
.set EVA_srr0, EVA_MISC+16
.set EVA_srr1, EVA_MISC+20
.set EVA_DAR, EVA_MISC+24
.set EVA_DSISR, EVA_MISC+28
.set EVA_HID0, EVA_MISC+32
.set EVA_HID2, EVA_MISC+36
.set EVA_HID4, EVA_MISC+40
.set EVA_L2CR, EVA_MISC+44
.set EVA_FPSCR, EVA_FPR+256
.set HID0, 1008
.set HID2, 920
.set HID4, 1011
.set L2CR, 1017
.set DSISR, 12
.set DAR, 19
#Recover original r0
mfsprg0 r0
#Store every gpr starting to the unused thermal interrupt
stmw r0, EVA_GPR (r0)
#We cant store any FPRs in physical mode because...
#It will cause an FP unavailable exception due to MSR being set to 0x00001000 when the exception occured.
#Store misc other registers to the portion of the DSI vector that the 06 writes aren't using
#There are a crap ton more registers but honestly they having and will have nothing to do with execptions
mfsprg1 r20 #original LR
mfsprg2 r21 #original CTR
mfcr r22
mfxer r23
mfspr r24, srr0 #Address CPU crashed on
mfspr r25, srr1 #MSR value when crash occurred
mfspr r26, DAR
mfspr r27, DSISR
mfspr r28, HID0
mfspr r29, HID2
mfspr r30, HID4
mfspr r31, L2CR
stmw r20, EVA_MISC (r0)
#LR contains the exception type, place it in r30 since we need to use the Link Register now
mflr r30
#Place virtual address to jump to in srr0
mtspr srr0, r3
#We need to do some edits on the MSR (srr1). We need the following bits with the following settings..
#Remember you cannot edit the actual MSR register during an interrupt
#Bit 16 (EE aka External Interrupts) must be low
#Bit 18 (FP aka Floating Point Avialable) must be high
#Bit 20 (FE0 aka IEEE floating point exception mode 0) must be low
#Bit 23 (FE1 aka IEEE floating point exceptino mode 1) must be low
mfspr r3, srr1
rlwinm r3, r3, 0, 17, 15
ori r3, r3, 0x2000
rlwinm r3, r3, 0, 21, 19
rlwinm r3, r3, 0, 24, 22
mtspr srr1, r3
#Now finally ready to go to virtual mode!
rfi
#=======================
#Register notes
#r31 = Pointer to table (virtual)
#r30 = Exception Type
#r29 = 0x80000000
#r28 = Pointer to very start of 1st string
#r27 = Return Values of both sprintf calls added together
#r26 = fd
#r25 = 0x935E0000
#r18 = value for %1d
#r17 = secondary value for %1d
#r16 = loop tracker for sprintf loop writes
#r15 = temp addresses for the sprintfs
#r15 thru r22 = Search strings for sprinf/OShotreset; this is done before and after the group of sprintfs
#r14 = sprintf function address
#Store Stack Trace (not this isnt the Call Stack)
#Address; Backchain; LR Save
#Store EVA Pointer to store at the space intended for Stack Trace contents (5 rows max output)
ori r5, r29, EVA_STACK_TRACE
#Make a copy of the sp for initial 'Address' value in Stack Trace output
mr 10, sp
#Stack sizes are never this large, this is our upper bound size limit check
lis r4, 0x0001
#Check for upper bound on stack size limit
cmplw r3, r4
#Stack is valid, add on new stack trace row below
stw r10, 0 (r5) #Address
stw r11, 0x4 (r5) #Backchain
lwz r0, 0x4 (r10) #Load LR Save
stw r0, 0x8 (r5) #Store LR Save
#Increment r5 to point to next stack trace row
addi r5, r5, 0xC
#Do branch now based on cmpwi results
bgt- stack_trace_complete #If branching on this, that means we stored the last addr
#Follow further into the stack trace
mr r10, r11
bdnz+ trace_loop
stack_trace_complete:
#=====================
#Store Callstack (5 max)
#Callstack format...(just like Dolphin)
#addr = ... fyi the 1st addr is just LR-4.
#addr = ... fyi this is just the stack trace's 2nd LR-Save - 4
#addr = ... stack trace's 3rd LR-Save - 4
ori r3, r29, EVA_CALLSTACK-4
#Get top of call stack (OG Link Register - 4)
addi r20, r20, -4 #OG LR still residing in r20
stwu r20, 0x4 (r3)
#Loop to load 2nd thru 5th LR-Save of Stack trace, minus 4 on it, then store
ori r4, r29, EVA_STACK_TRACE+8
#Call sprintf for 1st string
#r3 = arg to dump shit to
#r4 = pointer to string that needs formatting
#r5+ = args
title_string_offset = title - table_start
#We need to make sure r3 will end up being 0x20 (32) byte aligned, later for ISFS_Write
#Increment sp by 0x40 then clear last 5 bits, so we have a 32-byte aligned r3 arg
addi r3, sp, 0x40
clrrwi r3, r3, 5
we_are_aligned:
mr r28, r3 #Backup for the write buffer input on ISFS_Write
addi r4, r31, title_string_offset #Points to start of string in the table
lwz r5, EVA_srr0 (r29) #Load srr0's original value (address CPU crashed on)
lwz r6, 0 (r5) #Load srr0's address's instruction
clrrwi r7, r30, 8 #Clear out last byte, r7 will ontain 0x300, 0x400, 0x600, or 0x700
lwz r8, EVA_DAR (r29) #Load DAR's value
lwz r9, EVA_DSISR (r29) #Load DSISR's value
mtctr r14
bctrl
#Backup value of bytes written into r27, ISFS Write will need it later
mr r27, r3
#====================
#Sprintf loop for the 5 stack trace rows
#Setup loop first laod address
ori r15, r29, EVA_STACK_TRACE-4
#Set loop amount; need to use a GVR due to function call within loop
li r16, 5
#Set macro to get the sprintf string offset in our table
stack_trace_string_offset = stack_trace_row_string - table_start
stack_trace_sprintf_loop:
add r3, r27, r28 #Add return value to r27 to keep movign the sprintf dump location properly further down in memory
addi r4, r31, stack_trace_string_offset
lwzu r5, 0x4 (r15)
lwzu r6, 0x4 (r15)
lwzu r7, 0x4 (r15)
mtctr r14
bctrl
cmpwi r3, 0
blt- HUGE_ERROR
#Update total bytes of formatted string for later use of ISFS_Write
add r27, r27, r3
subic. r16, r16, 1
bne+ stack_trace_sprintf_loop
#==================
#sprintf for call_stack_start_string
call_stack_string_offset = call_stack_start_string - table_start
add r3, r27, r28 #Update r3 to move further down in memory to keep dumping
addi r4, r31, call_stack_string_offset
lwz r5, EVA_CALLSTACK (r29)
lwz r6, EVA_CALLSTACK+4 (r29)
lwz r7, EVA_CALLSTACK+8 (r29)
lwz r8, EVA_CALLSTACK+12 (r29)
lwz r9, EVA_CALLSTACK+16 (r29)
mtctr r14
bctrl
cmpwi r3, 0
blt- HUGE_ERROR
#Update total bytes of formatted string for later use of ISFS_Write
add r27, r27, r3
#==================
#1st GPR sprintf (its 'title' aka gpr_start_string)
gpr_title_string_offset = gpr_start_string - table_start
add r3, r27, r28 #Keep updating dump spot
addi r4, r31, gpr_title_string_offset
li r5, 0
lwz r6, EVA_GPR (r29)
li r7, 1
lwz r8, EVA_GPR+4 (r29)
mtctr r14
bctrl
cmpwi r3, 0
blt- HUGE_ERROR
#Update total bytes of formatted string for later use of ISFS_Write
add r27, r27, r3
#===================
#Sprintf loop for the rest of the GPRs
#Set Loop first load address
ori r15, r29, EVA_GPR+4
#Set loop amount
li r16, 15 #30 gprs left, can only do 2 at a time
#Set the initial %1d's (starts at 3 and 4, cuz 1 and 2 already done)
li r17, 2
li r18, 3
#macro to set r4
gpr_loop_string_offset = gpr_string - table_start
gpr_sprintf_loop:
add r3, r27, r28 #Keep updating dump spot
addi r4, r31, gpr_loop_string_offset
mr r5, r17
lwzu r6, 0x4 (r15)
mr r7, r18
lwzu r8, 0x4 (r15)
mtctr r14
bctrl
cmpwi r3, 0
blt- HUGE_ERROR
#Update total bytes of formatted string for later use of ISFS_Write
add r27, r27, r3
#Update the %1d's
addi r17, r17, 2
addi r18, r18, 2
subic. r16, r16, 1
bne+ gpr_sprintf_loop
#====================
#1st FPR sprintf (its 'title' aka fpr_start_string)
fpr_title_string_offset = fpr_start_string - table_start
add r3, r27, r28 #Keep updating dump spot
addi r4, r31, fpr_title_string_offset
li r5, 0
lwz r6, EVA_FPR (r29)
lwz r7, EVA_FPR+4 (r29)
li r8, 1
lwz r9, EVA_FPR+8 (r29)
lwz r10, EVA_FPR+12 (r29)
mtctr r14
bctrl
cmpwi r3, 0
blt- HUGE_ERROR
#Update total bytes of formatted string for later use of ISFS_Write
add r27, r27, r3
#====================
#sprintf loop for rest of FPRs
#Setup loop first laod address
ori r15, r29, EVA_FPR+12
fpr_loop_string_offset = fpr_string - table_start
#Set loop amount
li r16, 15 #Can only do 2 fprs at a time cuz they are 16 hex digits in length
#Set the initial %1d's (starts at 3 and 4, cuz 1 and 2 already done)
li r17, 2
li r18, 3
#sprint for first 'half' of misc registers
misc_title_string_offset = misc_start_string - table_start
add r3, r27, r28 #Keep updating dump spot
addi r4, r31, misc_title_string_offset
lwz r5, EVA_MISC (r29) #EVA_LR
lwz r6, EVA_CTR (r29)
lwz r7, EVA_CR (r29)
lwz r8, EVA_srr1 (r29)
lwz r9, EVA_XER (r29)
mtctr r14
bctrl
cmpwi r3, 0
blt- HUGE_ERROR
#Update total bytes of formatted string for later use of ISFS_Write
add r27, r27, r3
#=======================
#final sprintf, the second 'half' of misc registers
misc_final_string_offset = misc_final_string - table_start
add r3, r27, r28 #Keep updating dump spot
addi r4, r31, misc_final_string_offset
lwz r5, EVA_FPSCR (r29)
lwz r6, EVA_HID0 (r29)
lwz r7, EVA_HID2 (r29)
lwz r8, EVA_HID4 (r29)
lwz r9, EVA_L2CR (r29)
mtctr r14
bctrl
cmpwi r3, 0
blt- HUGE_ERROR
#Update total bytes of formatted string for later use of ISFS_Write
add r27, r27, r3
#r27 now contains grant total of byte lenght of entire formatted string!
#========================
#Create report.txt (ios_ioctl using /dev/fs fd)
#IOS_Ioctl createFile structure (size 0x4C)
#0x0 (word) = Owner ID
#0x4 (halfword) = Group ID
#0x6 thru 0x45 = file path/name ; it's last byte must be null
#0x46 = Owner Permissions
#0x47 = Group Permissions
#0x48 = Other Permissions
#0x49 = u8 attributes
#0x4A & 0x4B = ??? (always appears to be null)
#Setup the IOCTL structure
#Transfer file name to it
#set all other value
#80002080 is where the structure is at
#First, null it all out
li r0, 0x13 #0x4C bytes is 0x13 (19) words
mtctr r0
#Transfer file name
file_path_offset = file_path - table_start
addi r3, r31, file_path_offset-4 #Need minus four for first iternation of loop's lwzu instruction
ori r4, r29, 0x2082 #-4 for stwu
#Lenght of file name/path (includes null byte at end) is exactly 5 words
li r0, 5
mtctr r0
#Complete rest of ioctl structure
li r0, 3
stb r0, 0x20C6 (r29)
stb r0, 0x20C7 (r29)
stb r0, 0x20C8 (r29)
#Note: There should be no need to null out the ios_structure. Universal ios places all the values in that structure, any unused values shouldn't have be null before any IPC fiddling, and the second half 0x20 bytes of the structure is completely ignored by IOS. Only putting this note here, just in case I am wrong and in the future ppl encounter bugs in this code, this may be the reason.
#=======================================
#Create the File!
#r3 = universal ios command
#r4 = 32 byte aligned pointer to the ios_structure
#r5 = fd of /dev/fs; always 2
#r6 = ioctl no. ; this is 9
#r7 = pointer to input buffer; must be 32 byte aligned, this is NOT the pointer to the ios_structure
#r8 = size of inpute buffer; 0x4C
li r3, 6 #ioctl command for universal ios
ori r4, r29, 0x2020
li r5, 2 #fd for /dev/fs, it's the same for every wii game
li r6, 9
ori r7, r29, 0x2080
li r8, 0x4C
bl universal_ios_function
cmpwi r3, 0
blt- HUGE_ERROR
#Open report.txt
#r3 = universal ios command
#r4 = 32 byte aligned pointer to the ios_structure
#r5 = pointer to file path
#r6 = permissions
li r3, 1
ori r4, r29, 0x2020
addi r5, r31, file_path_offset
li r6, 2
bl universal_ios_function
cmpwi r3, 0
blt- HUGE_ERROR
#Write to report.txt
#r3 = universal ios command
#r4 = 32 byte aligned pointer to the ios_structure
#r5 = fd
#r6 = pointer to stuff that will be written; must be 32-byte aligned
#r7 = amount of bytes to write
mr r26, r3 #Backup the returned fd for ISFS_Close afterwards
mr r5, r3 #fd needs to be in r5 for universal ios
li r3, 4 #ios_write for universal ios
ori r4, r29, 0x2020
mr r6, r28 #Backed up from earlier for sprintf r3 arg
mr r7, r27 #Backed up from earlier; return values added together from both sprintf calls
bl universal_ios_function
cmpwi r3, 0
blt- HUGE_ERROR
#Close report.txt
#r3 = universal ios command
#r4 = 32 byte aligned pointer to the ios_structure
#r5 = fd
li r3, 2 #ios_close for universal ios
ori r4, r29, 0x2020 #pointer to ios_structure; 32-byte aligned
mr r5, r26 #fd
bl universal_ios_function
cmpwi r3, 0
blt- HUGE_ERROR
#============================
#THE END!!!!
#We were sucessful, make the screen green
#Set green color (0,0)
lis r3, 0xCD80
lis r0, 0x0000 #li r0, 0 will work too, fyi
b update_vi
#Couldn't find sprintf or an ios error occurred, set screen to yellow
HUGE_ERROR:
lis r3, 0xCD80
lis r0, 0x00FF
update_vi:
ori r0, r0, 0xFF01 #Full brightness and flip enable bit high
stw r0, 0x0024 (r3)
loop_forever:
sync
nop
b loop_forever
#===========================
#Universal IOS; slightly modified, certain things not needed were commented out
#Disable External Interrupts; NOT NEEDED, interrupts already been disabled
#mfmsr r0
#rlwinm r11, r0, 0, 17, 15 #Flip off 'Allow interrupts' bit
#mtmsr r11 #Update MSR
#rlwinm r12, r0, 17, 31, 31 #Backup modified MSR state
#Backup 1st two Args
mr r31, r3
mr r30, r4
#r5 thru r8 remain untouched before final subroutine involving IPC, no need to back up them
#r9 and r10 remain untouched the entire time, no need to back them up
#Read/Write is being used; Note, only write is being used
#universal_readwrite: #the check below for read vs write not needed
#r5 = fd
#r6 = Input/Output Buffer
#r7 = Size
#Check if read (invalidate) or write (flush), adjust cache accordingly for IOS's 2nd arg
mr r3, r6 #still need these two instructions for the r3 and r4 arg setup for the flush
mr r4, r7
#cmpwi r31, 3 #Checks not needed!
#beq- its_read
#IOS_Write is being used, flush the input buffer
bl data_cache_flush
#b done_with_cache #With the read stuff being commented, no point having this as it just branches to very next instruction
#IOS_Read is being used, invalidate the output buffer; NOT NEEDED!!!!!!!!!!!!!!!!
#its_read:
#bl data_cache_invalidate
#Store fd, and other args to IOS structure; any mem pointer that is sent will be sent physically
#done_with_cache: #label name NOT needed
stw r5, 0x8 (r30)
clrlwi r0, r6, 1
stw r0, 0xC (r30)
stw r7, 0x10 (r30)
#Prep IOS structure, run IPC
b flush_ios_structure_then_run_ipc
#Store args to IOS structure; any mem pointer that is sent will be sent physically
clrlwi r0, r5, 1
stw r0, 0xC (r30) #No fd to store at 0x8
stw r6, 0x10 (r30)
#Prep IOS structure, run IPC
b flush_ios_structure_then_run_ipc
#Invalidate Output Buffer; NOT NEEDED!!!!!!!!!!!! our ioctl for createfile has no output buffer!
#mr r3, r9
#mr r4, r10
#bl data_cache_invalidate
#Store fd and other args to IOS structure; any mem pointer that is sent will be sent physically
stw r5, 0x8 (r30)
stw r6, 0xC (r30)
clrlwi r0, r7, 1
stw r0, 0x10 (r30)
stw r8, 0x14 (r30)
clrlwi r0, r9, 1
stw r0, 0x18 (r30)
stw r10, 0x1C (r30)
b flush_ios_structure_then_run_ipc
#IOS_Ioctlv; NOT NEEDED!!!!!!!!!!!!!!!!!!!!!!!!!!
#universal_ioctlv:
#r5 = fd
#r6 = ioctl_no
#r7 = number of input buffers
#r8 = number of output buffers
#r9 = pointer to Vector table
#Store fd and other args to IOS structure; any mem pointer that is sent will be sent physically
#stw r5, 0x8 (r30)
#stw r6, 0xC (r30)
#stw r7, 0x10 (r30)
#stw r8, 0x14 (r30)
#clrlwi r0, r9, 1
#stw r0, 0x18 (r30)
#b flush_ios_structure_then_run_ipc
#Subroutine for data cache flush
data_cache_flush:
rlwinm r11, r3, 0, 27, 31
add r4, r4, r11
addi r4, r4, 31
rlwinm r0, r4, 27, 5, 31
mtctr r0
flush_loop:
dcbf 0, r3
addi r3, r3, 32 #Move on to next cache block (each block is 0x20 in size)
bdnz+ flush_loop
sc #Update HID0
blr
#Subroutine for data cache invalidate
data_cache_invalidate:
rlwinm r11, r3, 0, 27, 31
add r4, r4, r11
addi r4, r4, 31
rlwinm r0, r4, 27, 5, 31
mtctr r0
inval_loop:
dcbi 0, r3
addi r3, r3, 32 #Move on to next cache block (each block is 0x20 in size)
bdnz+ inval_loop
blr
#Flush first 0x20 bytes of IOS structure
flush_ios_structure_then_run_ipc:
stw r31, 0 (r30) #Store IOS command
mr r3, r30
li r4, 0x20
bl data_cache_flush
#Run the IPC Engine
#Set Hollywood IPC/IRQ upper 16 bits
lis r6, 0xCD00 #You think this would be 0xCD80, but this is how Wii games do it.
#Tell IPC_PPCCTRL to execute command; do NOT modify the IY1 and IY2 bits.
lwz r0, 0x4 (r6)
clrrwi r0, r0, 4
ori r0, r0, 0x0001
stw r0, 0x4 (r6)
#Wait for Starlet to acknowledge our command
check_for_acknowledge:
lwz r0, 0x4 (r6)
andi. r0, r0, 0x22 #Check if IY2 and Y2 bits were flipped high by Starlet
cmpwi r0, 0x22
bne- check_for_acknowledge
#Starlet has acknowledged, rewrite Acknowledge bit flipped high to clear it
#Then disable IRQ-30, and now wait for Starlet to execute the command & have pointer (physical IOS structure) avialable in IPC_ARMMSG
lwz r0, 0x4 (r6)
clrrwi r0, r0, 4
ori r0, r0, 0x0002
stw r0, 0x4 (r6)
lis r5, 0x4000 #Keep value in r5 for later secondary use
stw r5, 0x30 (r6)
#Check if pointer is available in IPC_ARMMSG
lwz r4, 0x8 (r6) #Using r4 due to upcoming addis instruction
cmpwi r4, 0
li r3, -5
beq- restore_interrupts #No pointer, somehow a different IPC call was being done during our work?
#Turn IOS structure pointer back to virtual, then invalidate the first 0x20 bytes
addis r3, r4, 0x8000 #Could also just use mr r4, r30 here, w/e works
li r4, 0x20
bl data_cache_invalidate
#Starlet has executed our command, rewrite Execute bit flipped high to clear it
#Then disable IRQ-30 one more time, is is needed or else next IOS call keep IPC_PPCCTRL at 0x31 forever...
lwz r0, 0x4 (r6)
clrrwi r0, r0, 4
ori r0, r0, 0x0004
stw r0, 0x4 (r6)
stw r5, 0x30 (r6)
#Set IPC_PPCMSG X bits to relaunch
lwz r0, 0x4 (r6)
clrrwi r0, r0, 4
ori r0, r0, 0x0008
stw r0, 0x4 (r6)
#Put return value into r3
lwz r3, 0x4 (r30)
#Restore External Interrupts; NOT NEEDED!!!! leave the branch label destination though
restore_interrupts:
#cmpwi r12, 0
#mfmsr r4
#beq- clear_bit
#ori r0, r4, 0x8000 #Flip on 'Allow Interrupts' bit
#b skip_clear_bit
#clear_bit:
#rlwinm r0, r4, 0, 17, 15
#skip_clear_bit:
#mtmsr r0
#Store physical pointer in sprg3, no standard vector exceptions use this spare register, and afaik there's no reason for any game to use this register in any non-exception use.
clrlwi r11, r11, 1
mtsprg3 r11
Hi! I've been using Mission Mode to record some footage for a video I'm making. I've found it to be a quick and easy way to rid the race of all other CPUs without having to use a large assortment of codes.
It's possible to load a Mii as the character used in the mission itself, but adding Miis as CPUs causes the game to crash. So I was wondering if this could be fixed with a code.
Could a code be made that allows Miis from the user's console to be loaded as CPUs in Missions?
Perhaps it could allow the user to set the index of the mii from their console they want loaded over certain CPUs, would this be possible to make a code for?
lis r3, ItemDirector_staticInstance@ha
lwz r3, ItemDirector_staticInstance@l (r3)
lwz r4, 0x244 (r3)
lwz r4, 0x0 (r4) # first object of last item type
lhz r4, 0x8 (r4) # global id
lwz r3, 0x248 (r3) # number of objects of last item type
add r3, r4, r3 # maximum number of items
mulli r3, r3, 0x1b4 # one CtrlRace2DMapObject per item
addi r3, r3, 0x1a4 # size of expanded CtrlRace2DMap
Code:
# inject at 807ea450 (PAL)
# inject at 807e0ba4 (NTSC-U)
# inject at 807e9abc (NTSC-J)
# inject at 807d8810 (NTSC-K)
lis r5, ItemDirector_staticInstance@ha
lwz r5, ItemDirector_staticInstance@l (r5)
lwz r6, 0x244 (r5)
lwz r6, 0x0 (r6) # first object of last item type
lhz r6, 0x8 (r6) # global id
lwz r5, 0x248 (r5) # number of objects of last item type
add r5, r6, r5 # maximum number of items
add r26, r26, r5 # number of children
Code:
# inject at 807ea6e0 (PAL)
# inject at 807e0e34 (NTSC-U)
# inject at 807e9d4c (NTSC-J)
# inject at 807d8aa0 (NTSC-K)
lis r6, str_start_line@h # optimize common upper part
ori r4, r6, str_game_image@l # directory name
ori r5, r6, str_map_start_line@l # control file name
ori r6, r6, str_start_line@l # "section 3" name
li r7, 0x0 # animations
lis r12, ControlLoader_load@h
ori r12, r12, ControlLoader_load@l
mtctr r12
bctrl
mr r3, r23 # current control
lis r5, str_item@h # optimize common upper part
ori r4, r5, str_game_image@l # directory name
ori r5, r5, str_item@l # layout file name
lis r12, LayoutUIControl_setPictureSourceLayout@h
ori r12, r12, LayoutUIControl_setPictureSourceLayout@l
mtctr r12
bctrl
stb r26, 0x81 (r23) # store the item type
stb r25, 0x82 (r23) # store the object index
lis r12, LayoutUIControl_setPicture@h
ori r12, r12, LayoutUIControl_setPicture@l
mtctr r12
bctrl
addi r23, r23, 0x1b4 # move to the next control
addi r25, r25, 1 # increment the object index
lis r3, ItemDirector_staticInstance@ha
lwz r3, ItemDirector_staticInstance@l (r3)
mulli r4, r26, 0x24
add r3, r3, r4
lwz r3, 0x48 + 0x8 (r3) # maximum number of objects of the current type
cmpw r25, r3
blt object_loop
addi r26, r26, 1 # increment the item type
cmpwi r26, 15
blt item_type_loop
Code:
# inject at 807eaca8 (PAL)
# inject at 807e13fc (NTSC-U)
# inject at 807ea314 (NTSC-J)
# inject at 807d9068 (NTSC-K)
I'll try to keep this somewhat short w/o rambling.
I wanted to make a code to read Broadway's CPU temp
All 3 thermal registers are set to always be null in Broadway (they are on disabled on purpose, specifically stated so in the manual)
Can only read Broadway CPU temp with separate device sending amps to the diode and reading the returned voltage
Broadway Manual makes absolutely zero mention of a Thermal Interrupt routine
But the Thermal Interrupt is present at 0x1700
The Interrupt is unused though from my limited tests
Still somehow the Wii knows to shut itself off when I disable the Fan (via code) and the Wii gets too hot (40ish minutes on MKW TT mode)
So anyway.... on the Gamecube console, all the Thermal Registers are used. Thus we can get the Gekko CPU temp.
I have a GC but no cables!
Anyway here is a snippet of source to read Gekko's temp. It "works" on Dolphin, meaning when I place in fake bit values for the read-only bits, the code responds correctly to values of those read-only bits.
Code
Code:
#Temperature Read code crap, works on Dolphin when you simulate fake THRM register bit responses on the read-only bits
#Wont work on Wii, intended for GC, not tested on real hardware
#SPR list
#1020 = THRM1
#1021 = THRM2
#1022 = THRM3 (the TAU aka Thermal Assist Unit)
#TAU is currently running, disable it. Clear any previous data.
li r0, 0
mtspr 1022, r0
#We want to use TAU in single mode, disable usage of THRM2
#Clear the V (SPR valid) bit; bit 31
#I might be able to just write null to the register to disable it, but it may cause a program exception due to possibly writing new values to the read-only bits. Play it safe.
mfspr r0, 1021
rlwinm r0, r0, 0, 0, 30
mtspr 1021, r0
#Prepare THRM1
#Enable it (V high)
#Disable Thermal Interrupts (TIE low)
#Set initial threshold of 0x40 degress celisus (bits 2 thru 8)
#Clear TID bit so when TIN is high, we know above threshold, and when TIN is low we below it
mfspr r0, 1020
andis. r0, r0, 0xC000 #Clear everything out but the read-only bits
oris r0, r0, 0x2000 #Bits 2 thru 8 are set to value of 0x40
ori r0, r0, 0x0001 #Flip V (spr valid) bit high
mtspr 1020, r0
#Prepare THRM3
#Enable it (E bit high)
#Don't have any calibration set
#Set timer cycles to max (0x1FFF); the more cycles, the more accurate the reading; bits 18 thru 30 all high
li r0, 0x3FFF
#Start the TAU!
mtspr 1022, r0
#The most you can poll is 5 times, due to temperature readings being in 4*C increments. Poll 5 times for the highest accuracy.
#Set loop to 5
li r0, 5
mtctr r0
#Set initial threshold increment/decrement value (0x20)
li r5, 0x40 #Will be halved before first iteration, hence why we first set it to 0x40
#Temperature Check Loop
loop:
#Read THRM1
mfspr r3, 1020
#Temp is above threshold when following bits are this.. (combo only correct when TID is set to 0)
#TIN = 1
#TIV = 1
#Temp is below threshold when bits are this (only correct when TID is set to 0)
#TIN = 0
#TIV = 1
#Check if TIV is high (valid)
#This tells temperature has crossed the threshold
andis. r0, r3, 0x4000
beq- loop
#Temperature has crossed threshold, figure out if it crossed above or below (check TIN bit)
andis. r0, r3, 0x8000
#Extract the threshold from THRM1 and right align it, divide r5 by 2, adjust current threshold by new amount in r5
rlwinm r4, r3, 9, 25, 31
srwi r5, r5, 1
#Now branch
beq- lower_threshold
#Temperature is above threshold; raise it
add r0, r4, r5
b decrement_loop
#Temperature is below threshold; lower it
lower_threshold:
subf r0, r5, r4 #Equivalent to sub r0, r4, r5
#Place in new threshold value, update THRM1, decrement Loop
decrement_loop:
rlwimi r3, r0, 23, 2, 8 #Shift r0's value (new threshold) to be in bits 2 thru 8; then replace those bits into r3 while leaving all other r3's bits alone
mtspr 1020, r3
bdnz+ loop
#Loop is over, place latest iteration of r4 as the result; is this even accurate, lol?
mr r3, r4
If somebody can use devkitpro to compile a GC-specific dol file for a program that simply printf's the temperature, that would be appreciated.
I understand that the value is floating point, but changing the floating point value from one end is inefficient, so please let me know if you have any technique.
So, I had a MKW LAN party late last year using DWC emulator, and while it went great, I don't think I'll ever bother to set up (or maintain/keep around) DWC emulator again, since its honestly kind of a pain in the ass, and it doesn't seem to have been maintained in a few years now, so it'll only continue to be more and more of a pain in the ass as time goes on.
So, I wanted to find a better solution that wasn't "Fuck it, I'mma just use Wiimmfi", so I turned my attention back to the MKW LAN mod made by Chadderz and MrBean35000vr. The reasons I hadn't used it then were pretty simple: I couldn't USB Load with it (big deal breaker, this game is like $40 a pop!), I couldn't run any Gecko codes with it (I need my 150cc), and it doesn't work in Dolphin (which is through no fault of the mod as far as I know, its an issue with Dolphin, sadly). Well, I managed to find solutions to both how to USB load it, and how to run Gecko codes alongside the LAN mod, making it much easier to set up a LAN party without nearly as much fuss.
I set to work first to figure out how to get it to USB load. I already knew it couldn't be ISO patched at all, and while its not impossible for BrainSlug module support to be added into a USB loader directly, no one has done it. I've got to use the BrainSlug boot.dol no way around it. Well, it just so happens I lucked, out: You can go into your game settings and simply enable alternate dol and place the BSlug .dol according, and the USB loader will load BrainSlug, and then BrainSlug will load its modules and then the game .dol like a charm. I can't believe I didn't think to do that before, but now I don't have to worry about spending tons to get 6 copies of Mario Kart Wii.
The biggest effort I had to put in was not having Gecko Codes of course. The first thing I tried was turning on Ocarina codes in USB Loader GX, and no dice. Nothing changed. In the same vein as the last problem, I also tried to see if I could get BrainSlug to load GeckoOS or the other way around, and then load into the game, but I just couldn't find any way to do this. Finally, I simply tried to patch the codes into the game's main.dol with wstrt, and again nothing. Nothing changed at all even though no vanilla dol was available! Well, I guess BrainSlug just wacks off extra hooks or something, and there ain't much to change about that.
If I'm getting cheats to run with the LAN Mod, its going to be in BSlug module form. Originally I had no clue how to pull off making a Gecko codehandler module, and in this guide's original form I had instead just figured out how to do basic RAM writes directly to convert basic 00/02/04 codes into stand alone modules. For what it was worth, it did its job fine enough, but it left ASM/C2 codetypes unusable, and still had a lot to be desired tho. Thankfully InvoxiPlayGames dropped me some great info in this thread, and helped me out in Discord DMs, and I was able to make a complete Gecko codehandler module that loads GCTs right from the SD card. Its ended up being really simple (much simplier than coverting codes into modules lol), and it works for any game too. I can't really say I can take credit for it though, IPG basically make the code for me, and while I managed to figure out a good bit of it on my own before that I wouldn't have gotten anywhere without her, so big shout outs to InvoxiPlayGames!
I've left the old code conversion modules I made in the post below, if you happen to be interested for whatever reason.
MKW LAN Mod: Pros & Cons, Requirements
Requirements:
* All Wiis will need to have the homebrew channel installed (and cIOS if you plan to use a USB loader). Check out https://wii.guide/ for a good tutorial on Wii, and https://wiiu.hacks.guide/ on Wii U.
* You'll need at least half as many Wiis as you do players (you can have 1 or 2 players per Wii), 1 TV per Wii, 1 controller per player, and a router to network them all together. If you're at a huge event (tournament, convention, etc) you might want to be mindful of all the wireless talk around and use ethernet if you can, but otherwise Wifi works just fine.
Pros:
+ Simple, easy
+ Doesn't require any extra computers, just Wiis
+ Can be used with a USB loader and Gecko codes, for custom tracks and more
Cons:
- Can't use Dolphin (emulator bug)
- Can't invite remote players, if the whole gang can't be there in person
- Does not support NTSC-K (intentional crash)
If you know your way around Linux, got an extra computer/VPS/Rasberry Pi, and want to suppliment your Wiis with PCs running Dolphin and/or want to invite remote friends to join in, you'd be better off using DWC Emulator (or if you're not good with Linux, saying fuck it and using Wiimmfi).
If you just want simple and easy, and you've got enough Wiis, this is the guide for you.
Tutorial: How to get set up and running
0) Ensure you have a disc copy of MKW, or have a WBFS of the game on your USB and ready to rock for each Wii (U) console you will be using. You can use discs and WBFSs in any mixture, you just need 1 per Wii. Every following step will focus on just one Wii, repeat for each Wii.
2) Copy the contents of the release folder to the root of your SD card.
2.1) If you are USB Loading, you will need to make a copy of the boot.dol file in the /apps/brainslug folder and move/rename to the appropriate location to use it as an alternate dol. For USB Loader GX, you should rename boot.dol to RMCE01.dol (for NTSC-U, use RMCP01 for PAL, and RMCJ01 for NTSC-J) and put it on the root of your USB or SD card.
3) If you want to use Gecko Codes (ex: Force 150cc in Friend Rooms), download the attached files on this post for a zip folder containing a Gecko codehandler BrainSlug module, and copy the contents to the root of the SD card (you should have 4 new .mod files in /bslug/modules). Then just make a GCT of all the codes you want to use (https://mkwii.com/gct) and copy it to /codes/RMCE01.gct (RMCP01 for PAL, RMCJ01 for JP). You only need to make the GCT once, you can copy the same GCT for every Wii.
4) Once you have everything copied over, insert your SD card (and USB) into your Wii and turn it on. If you are playing on a disc, you can just launch BrainSlug from the homebrew channel. BrainSlug will now load all your modules and boot the game. This Wii is now ready to go, rinse and repeat this process for all Wiis.
4.1) If you are USB loading, launch your USB loader, and turn "alternate dol" on, then launch the game. You should see BrainSlug appear and start loading modules. It should boot into the game once it is done. This Wii is now ready.
Pro tip: BrainSlug modules are loaded entirely into RAM, so once the game boots you can remove the SD card and reuse it on the next Wii!
5) Now that all the Wiis are ready, if they are all set up on the same network, you should be able to hit 1 or 2 player LAN Multiplayer on all of them, have one of them open a room, and the rest of them can check the friends list and join the host. Thats it! Its time to play!
Misc Notes
Just some useful observations that I honestly don't know where to put.
1) The Gecko codehandler module I made and provided in this post has a GCT limit much lower than you'd normally expect out of other codehandlers. Your GCT can only be about ~230 lines long or 1880 bytes long. Any longer, and you'll get a crash on the strap screen. I'm honestly not entirely sure what the issue is, since I swapped the USB Gecko modules codehandler with JoshuaMKW's full codehandler, which is supposed to do up to 450 codes. Source code is provided with the Gecko codehandler module, so if you think you can extend/increase the modules GCT space before it crashs, feel free to give it a crack.
TL;DR: Keep your GCT under 230 lines of gecko codes. This shouldn't be too hard to do, so long as you stay away from MrBean's item rain code (or other obscenely long codes). That thing is 201 lines alone! Yeesh!
2) Despite being able to use alternate dol in USB Loader GX to load BrainSlug and then load into the WBFS, trying to load into BrainSlug from Riivolution just results in a crash. BrainSlug seems to shake off hooks or something as it boots, which includes ingoring sections of the dol it is booting. Even tho you can inject a GCT directly into MKW's dol file with wstrt, it does nothing after BrainSlug boots it. This is probably due to how BrainSlug modules work, being relocatable ELF files in a similar way to how StaticR.rel works, and Brainslug ends up dropping the dol section with the GCT. Due to the above two factors combined, you won't be able to use an Riivolution mod packs you already have with the LAN mod. InvoxiPlayGames does have a Emvolution module that reads game files off the SD card similar to Riivolution, but it currently requires you complie the module yourself to use your own list of file replacements, so I wouldn't recommend it for casual player. A modded WBFS works better, since a lot of mods out there provide a pre-made WBFS, but most all mod packs now a days both use some Gecko code or another somewhere, and usually embed it into the game dol, so even with a pre-made WBFS, you'll still be missing the critical Gecko codes other parts of the mod may rely on if launched with BrainSlug.
TL;DR: While its possible to play with custom tracks using this guide, its beyond the scope of this guide to help you do that. If you don't have the tech know how to do it, tehn you should just use CTGP instead. It has the LAN Mod built in, and you get access to all of CTGP's usual features. All you have to do is install CTGP and change your online setting from Wiimmfi to LAN in the CTGP channel. This will mean you'll need a real disc for every Wii though, so I hope your wallet is ready.
Conclusion: Thanks to all you dedicated modders
Honestly, I already put all my words (too many words!) into the rest of this thread, so I don't have too much more to say. A massive thanks and shout out to all the dedicated modders out there that have done so much to learn and document so much stuff about the Wii and modding it, Mario Kart Wii and how it works, as well as mod the game to hell and back with ASM cheat codes, custom tracks, and just tons of insane work I can't even begin to mention like the LAN Mod this guide is about, and all the tools and info out there to be find and used. I wouldn't have been able to do any of it if I didn't have all this stuff to use as a foundation. Thank you all so much!
I'd especially like to give a shout out to InvoxiPlayGames, thank you so much for the help with making a Gecko codehandler BrainSlug module!