Staff Ghosts Swapper [Melg]
#1
This code allows you to easily replace staff ghosts with ghosts of your choice. If used for recording purposes, it is best used with this code: https://mariokartwii.com/showthread.php?tid=63

Benefits over replacing the ghosts manually in the ISO or changing the ghosts in your save game:
-Allows you to have more than 1 ghost per track. The number you use for the rkg does not have to match any sort of track ID. You can for example use 00.rkg for N64 Bowser's Castle.
-Much faster and more practical to use.
-Since there are 64 staff ghosts, allows you to use up to 64 custom ghosts.
-The NAND version is dynamic; the staff ghosts get loaded when you press "Single Player", so you can change the staff ghosts even after the game is launched.
-You just have to rename the rkgs to XX.rkg, where XX is a number from 00 to 63, no matter the track. The code and the game will place the ghosts on their correct track for you.
-You do not need to have unlocked the expert staff ghosts.

Limitations:
-The Ghost List Manager (Screen 0xA7: https://wiki.tockdom.com/wiki/List_of_Identifiers) gets constructed with an array of 38 elements for the ghosts. That means that the sum "Personal Best Times + Downloaded Ghosts + Custom ghosts from this code" is limited to 38. It shouldn't be a problem except if you want 50 ghosts on one track for some reason. This limitation isn't specific to this code however.

Instructions:
This code comes in two variants:
-The NAND variant fetches the ghosts from the root of the NAND. It is destined to be used on Dolphin. Create a folder named "Ghosts" in "Documents/Dolphin Emulator/Wii", download your RKGs, rename them to any number from 00 to 63, and dump them in the folder.
-The DISC variant gets the ghosts from the files. Create a folder named "Ghosts" int he files of your ISO, download your RKGs, rename them to any number from 00 to 63, and dump them in the folder. This works on both dolphin and console. For console, you can easily achieve that by using Riivolution, creating a Ghosts folder on the root of your SD (or anywhere you mention in the XML), and adding this patch to your XML:  
Code:
<patch id="LoadGhosts">
     <folder external="/Ghosts" disc="/Ghosts" create="true"/>
</patch>


NAND Variant:
NTSC-U
C200B510 00000013
907F0050 48000015
2F47686F 7374732F
30302E72 6B670000
887F0016 2C030031
7C6802A6 A09F0024
40A2001C 3884CFD0
5480EFFF 38840302
41A20008 388400F6
38843030 B0830008
38800001 3D808016
618CADBC 7D8903A6
4E800421 2C030000
41800034 809F004C
7C7F1B78 38A02800
3D808016 618CB15C
7D8903A6 4E800421
7FE3FB78 3D808016
618CB2E4 7D8903A6
4E800421 00000000
C253D2A4 0000000C
3C80809C 8084D508
80840000 808401CC
80641918 8084191C
54842036 7C83202E
80630264 80A30068
80C3006C 8065000C
3BC00002 7C632050
28031A28 40A10010
8066000C 3BC00001
7C632050 388000D8
7C632396 2C1E0002
60000000 00000000
045CC5A0 38600002

PAL
C200B5B0 00000013
907F0050 48000015
2F47686F 7374732F
30302E72 6B670000
887F0016 2C030031
7C6802A6 A09F0024
40A2001C 3884CFD0
5480EFFF 38840302
41A20008 388400F6
38843030 B0830008
38800001 3D808016
618CAE5C 7D8903A6
4E800421 2C030000
41800034 809F004C
7C7F1B78 38A02800
3D808016 618CB1FC
7D8903A6 4E800421
7FE3FB78 3D808016
618CB384 7D8903A6
4E800421 00000000
C25427e0 0000000C
3C80809C 80841E38
80840000 808401CC
80641918 8084191C
54842036 7C83202E
80630264 80A30068
80C3006C 8065000C
3BC00002 7C632050
28031A28 40A10010
8066000C 3BC00001
7C632050 388000D8
7C632396 2C1E0002
60000000 00000000
045E237C 38600002

NTSC-J
C200B4D4 00000013
907F0050 48000015
2F47686F 7374732F
30302E72 6B670000
887F0016 2C030031
7C6802A6 A09F0024
40A2001C 3884CFD0
5480EFFF 38840302
41A20008 388400F6
38843030 B0830008
38800001 3D808016
618CAD7C 7D8903A6
4E800421 2C030000
41800034 809F004C
7C7F1B78 38A02800
3D808016 618CB11C
7D8903A6 4E800421
7FE3FB78 3D808016
618CB2A4 7D8903A6
4E800421 00000000
C2542160 0000000C
3C80809C 80840E98
80840000 808401CC
80641918 8084191C
54842036 7C83202E
80630264 80A30068
80C3006C 8065000C
3BC00002 7C632050
28031A28 40A10010
8066000C 3BC00001
7C632050 388000D8
7C632396 2C1E0002
60000000 00000000
045E1C58 38600002

NTSC-K
C200B65C 00000013
907F0050 48000015
2F47686F 7374732F
30302E72 6B670000
887F0016 2C030031
7C6802A6 A09F0024
40A2001C 3884CFD0
5480EFFF 38840302
41A20008 388400F6
38843030 B0830008
38800001 3D808016
618CAEF8 7D8903A6
4E800421 2C030000
41800034 809F004C
7C7F1B78 38A02800
3D808016 618CB298
7D8903A6 4E800421
7FE3FB78 3D808016
618CB420 7D8903A6
4E800421 00000000
C2530838 0000000C
3C80809B 80840478
80840000 808401CC
80641918 8084191C
54842036 7C83202E
80630264 80A30068
80C3006C 8065000C
3BC00002 7C632050
28031A28 40A10010
8066000C 3BC00001
7C632050 388000D8
7C632396 2C1E0002
60000000 00000000
045D0518 38600002


DISC Variant:
NTSC-U
C200B4F4 0000000F
48000015 2F47686F
7374732F 30302E72
6B670000 A07F0016
2C030031 A07F0024
40A2001C 3863CFD0
5460EFFF 38630302
41A20008 386300F6
38633030 7FE802A6
B07F0008 7C6802A6
3D808015 618CDEAC
7D8903A6 4E800421
2C03FFFF 7FE3FB78
83ED9400 809F004C
40820008 7FE3FB78
60000000 00000000
C253D2A4 0000000C
3C80809C 8084D508
80840000 808401CC
80641918 8084191C
54842036 7C83202E
80630264 80A30068
80C3006C 8065000C
3BC00002 7C632050
28031A28 40A10010
8066000C 3BC00001
7C632050 388000D8
7C632396 2C1E0002
60000000 00000000
045CC5A0 38600002

PAL
C200B594 0000000F
48000015 2F47686F
7374732F 30302E72
6B670000 A07F0016
2C030031 A07F0024
40A2001C 3863CFD0
5460EFFF 38630302
41A20008 386300F6
38633030 7FE802A6
B07F0008 7C6802A6
3D808015 618CDF4C
7D8903A6 4E800421
2C03FFFF 7FE3FB78
83ED9400 809F004C
40820008 7FE3FB78
60000000 00000000
C25427e0 0000000C
3C80809C 80841E38
80840000 808401CC
80641918 8084191C
54842036 7C83202E
80630264 80A30068
80C3006C 8065000C
3BC00002 7C632050
28031A28 40A10010
8066000C 3BC00001
7C632050 388000D8
7C632396 2C1E0002
60000000 00000000
045E237C 38600002

NTSC-J
C200B4B8 0000000F
48000015 2F47686F
7374732F 30302E72
6B670000 A07F0016
2C030031 A07F0024
40A2001C 3863CFD0
5460EFFF 38630302
41A20008 386300F6
38633030 7FE802A6
B07F0008 7C6802A6
3D808015 618CDE6C
7D8903A6 4E800421
2C03FFFF 7FE3FB78
83ED9400 809F004C
40820008 7FE3FB78
60000000 00000000
C2542160 0000000C
3C80809C 80840E98
80840000 808401CC
80641918 8084191C
54842036 7C83202E
80630264 80A30068
80C3006C 8065000C
3BC00002 7C632050
28031A28 40A10010
8066000C 3BC00001
7C632050 388000D8
7C632396 2C1E0002
60000000 00000000
045E1C58 38600002

NTSC-K
C200B640 0000000F
48000015 2F47686F
7374732F 30302E72
6B670000 A07F0016
2C030031 A07F0024
40A2001C 3863CFD0
5460EFFF 38630302
41A20008 386300F6
38633030 7FE802A6
B07F0008 7C6802A6
3D808015 618CDE6C
7D8903A6 4E800421
2C03FFFF 7FE3FB78
83ED9400 809F004C
40820008 7FE3FB78
60000000 00000000
C2530838 0000000C
3C80809B 80840478
80840000 808401CC
80641918 8084191C
54842036 7C83202E
80630264 80A30068
80C3006C 8065000C
3BC00002 7C632050
28031A28 40A10010
8066000C 3BC00001
7C632050 388000D8
7C632396 2C1E0002
60000000 00000000
045D0518 38600002

SOURCE:

NAND Variant: Load the file from the NAND:

Code:
#inject(0x8000B5B0)\n\n (PAL)
#inject(0x8000B510)\n\n (NTSC-U)
#inject(0x8000B4D4)\n\n (NTSC-J)
#inject(0x8000B65C)\n\n (NTSC-K)

.set region, 'P'
.if (region == 'P' || region == 'p') # RMCP
.set ISFSOpen, 0x8016AE5C
.set ISFSClose, 0x8016B384
.set ISFSRead, 0x8016B1FC
.elseif (region == 'E' || region == 'e') # RMCE
.set ISFSOpen, 0x8016ADBC
.set ISFSClose, 0x8016B2E4
.set ISFSRead, 0x8016B15C
.elseif (region == 'J' || region == 'j') # RMCE
.set ISFSOpen, 0x8016AD7C
.set ISFSClose, 0x8016B2A4
.set ISFSRead, 0x8016B11C
.elseif (region == 'K' || region == 'k') # RMCE
.set ISFSOpen, 0x8016AEF8
.set ISFSClose, 0x8016B420
.set ISFSRead, 0x8016B298

.else # Invalid Region
.abort
.endif

.macro Call register, function
lis \register, \function@h
ori \register, \register, \function@l
mtctr \register
bctrl
.endm

#r31 = SystemManager::StaticInstance, path of the file at 0x0
stw r3, 0x50 (r31) #Default, saves buffer pointer


bl Path
.string "/Ghosts/00.rkg"
.align 2
Path:


lbz r3, 0x16 (r31) #Get Staff Ghost type by checking the folder the normal file is in (ghost1 vs ghost2)
cmpwi r3, 0x31     #Check if easy
mflr r3            #Pointer to the path
lhz r4, 0x24 (r31) #Get saveindex ID of the normal ghost as an ASCII formatted int
bne+ NotAnEasy

subi r4, r4, 0x3030    #Weird ASCII to hex to ASCII conversion to add 32 if the ghost is normally an expert
extrwi. r0, r4, 1, 28  #Check if last digit is 8 or 9 as adding 32 changes the tens value
addi r4, r4, 0x0302    #Add 32 to the file number
beq+ Convert
addi r4, r4, 0xF6      #If last digit is 8 or 9, correction factor
Convert:
addi r4, r4, 0x3030    #Back to ASCII

NotAnEasy:
sth r4, 0x8 (r3)       #Store which custom RKG to load


li r4, 1
Call r12, ISFSOpen #ISFSOpen to open rkg, r3 already has the path
cmpwi r3, 0        #check if the file exists
blt- end
lwz r4, 0x4C (r31) #RKG buffer
mr r31, r3         #r31 no longer used, save fd
li r5, 0x2800      #RKG length, dump the whole file in the buffer
Call r12, ISFSRead #Dump RKG into the buffer
mr r3, r31         #Close the file
Call r12, ISFSClose
end:
 



DISC Variant: Load the file from the disc



Code:
#inject(0x8000B594)\n\n (PAL)
#inject(0x8000B4F4)\n\n (NTSC-U)
#inject(0x8000B4B8)\n\n (NTSC-J)
#inject(0x8000B640)\n\n (NTSC-K)

.set region, 'P'
.if (region == 'P' || region == 'p') # RMCP
    .set DVDConvertPathToEntrynum, 0x8015DF4C
    .set SysManagerR13offset, -0x6C00
.elseif (region == 'E' || region == 'e') # RMCE
    .set DVDConvertPathToEntrynum, 0x8015DEAC
    .set SysManagerR13offset, -0x6C00
.elseif (region == 'J' || region == 'j') # RMCP
    .set DVDConvertPathToEntrynum, 0x8015DE6C
    .set SysManagerR13offset, -0x6C00
.elseif (region == 'K' || region == 'k') # RMCE
    .set DVDConvertPathToEntrynum, 0x8015DFC4
    .set SysManagerR13offset, -0x6BE0
.else # Invalid Region
.abort
.endif

.macro Call register, function
lis \register, \function@h
ori \register, \register, \function@l
mtctr \register
bctrl
.endm

#r31 = SystemManager::StaticInstance, path of the file at 0x0, the game loads the file from the disc right after this hook


bl Path
.string "/Ghosts/00.rkg"
.align 2
Path:

lhz r3, 0x16 (r31) #Get Staff Ghost type by checking the folder the normal file is in (ghost1 vs ghost2)
cmpwi r3, 0x31     #Check if easy
lhz r3, 0x24 (r31) #Get saveindex ID as a formatted ASCII int
bne+ NotAnEasy     #Get saveindex ID of the normal ghost as an ASCII formatted int

subi r3, r3, 0x3030   #Weird ASCII to hex to ASCII conversion to add 32 if the ghost is normally an expert
extrwi. r0, r3, 1, 28 #Check if last digit is 8 or 9 as adding 32 changes the tens value
addi r3, r3, 0x0302   #Add 32 to the file number
beq+ Convert
addi r3, r3, 0xF6     #If last digit is 8 or 9, correction factor
Convert:
addi r3, r3, 0x3030   #Back to ASCII
NotAnEasy:
mflr r31              #Pointer to the path in r31, will be needed later
sth r3, 0x8 (r31)
mflr r3               #Pointer to the path


Call r12, DVDConvertPathToEntrynum #Check if the file exists
cmpwi r3, -1
mr r3, r31                         #r3 (the path arg for the upcoming function call) is changed to the custom path
lwz r31, SysManagerR13offset (r13) #Get r31 back to its initial value, SystemManager::StaticInstance
lwz r4, 0x4C (r31)                 #RKG buffer, default instruction
bne- Exists
mr r3, r31                         #r3 will point to the normal path as the file does not exit
Exists:

COMMON CODE to both variants, corrects the savegameID the game uses to load the ghost in game, as well as the ghost type. This essentially is the code that makes sure the correct ghost is loaded and allows users to have multiple ghosts per track without caring about the name.

Code:
#inject(0x805427e0)\n\n (PAL)
#inject(0x8053D2A4)\n\n (NTSC-U)
#inject(0x80542160)\n\n (NTSC-J)
#inject(0x80530838)\n\n (NTSC-K)

.set region, 'P' #Must set region value, or else source will not compile
.if (region == 'P' || region == 'p') # RMCP
    .set MENUDATA, 0x809c1e38 
.elseif (region == 'E' || region == 'e') # RMCE
    .set MENUDATA, 0x809bd508
.elseif (region == 'J' || region == 'j') # RMCE
    .set MENUDATA, 0x809c0e98
.elseif (region == 'K' || region == 'k') # RMCE
    .set MENUDATA, 0x809B0478
.else
  .err
.endif

.macro Call register, function
    lis \register, \function@h
    ori \register, \register, \function@l
    mtctr \register
    bctrl
.endm


lis r4, MENUDATA@ha
lwz r4, MENUDATA@l (r4)
lwz r4, 0 (r4)        #Current Scene

lwz r4, 0x1CC (r4)    #screens[0x71] which is the selectGhost screen
lwz r3, 0x1918 (r4)   #Pointer to RKG Header list for the selected track ghosts, ordered by button
lwz r4, 0x191C (r4)   #Which button was pressed from 1 to amount of ghosts on selected track
slwi r4, r4, 4
lwzx r4, r3, r4       #Get the RKG Header corresponding to the button pressed from the list


/* This part is needed for people who have not unlocked every expert staff ghost. This determines whether the custom ghost loaded replaces an expert or an easy, and then gets the corresponding save index ID by determining which entry it is in the list of all 32 easy/expert ghosts*/

lwz r3, 0x264 (r3)   #Screen 0xA7, Ghost List Manager
lwz r5, 0x68 (r3)    #Pointer to substruct related to Easy Staff Ghosts
lwz r6, 0x6C (r3)    #Pointer to substruct related to Expert Staff Ghosts


lwz r3, 0xC (r5)     #RKG Header list of all the easy staff ghosts   
li r30, 0x2          #Set GhostType to Easy


sub r3, r4, r3       #Calculates offset from the beginning of the easy ghosts list
cmplwi r3, 0x1A28    #This is 0xD8 * 31 since there are 32 entries and each is 0xD8 bytes long
ble+ Divide          #If we selected an easy, go straight to the division
lwz r3, 0xC (r6)     #RKG Header list of all the expert staff ghosts    
li r30, 0x1          #Set GhostType to Expert
sub r3, r4, r3       #Calculates offset from the beginning of the expert ghosts list

Divide:
li r4, 0xD8          #Each ghost entry is 0xD8 bytes long
divwu r3, r3, r4     #Offset to beginning of the list/Size of each entry = position of the selected ghost = the savegame ID used in the path
cmpwi r30, 0x2       #Default instruction, leads to different path being used depending on the ghost type.


Code creator: Melg
Reply
#2
Bumping this because I needed to watch Sosis's new 612 rSL WR (and nobody has still uploaded a vid of it yet), and was concerned I would have to do the old school silly ISO edits. So I've searched "rkg" on the forums and totally forgot about this code.

Needless to say, this code works flawlessly.
Reply


Forum Jump:


Users browsing this thread: 3 Guest(s)