Customizing Hide-and-Seek-Mod (General Assembly related Questions)
#1
Hi,

some friends of mine and I have been playing the Hide And Seek mod by CLF78 (https://wiki.tockdom.com/wiki/Hide_and_Seek) and I'm currently trying do adapt the mod to our needs.

I have so far accomplished to change minor things and I want to add another behaviour to the mod. By default the minimap is hidden throughout the rounds - I want the minimap to be seen in the last 20 seconds.

In the source files you will find these lines (src\payload\main\hudedit.S)

Code:
HideHUD:
rlwinm r3, r3, 0, 29, 25
# Show score + Kill Info Text
ori r31, r3, 0x1400

Code:
directWriteBranch(HUDEditsHook, HideHUD, true);

Code:
/* Disable Various HUD Elements */
HUDEditsHook = 0x808562B8;

I'm quite new to assembly and this is what I have though of so far:
The HideHUD is hooked in an adress responsible for changing HUD elements. What I think HideHUD does is taking the value of r3, modifying it and storing the modified value in r31

What I'm not really sure of: is HideHUD then executed once at the beginning or each frame? I have assumed it's executed each frame so what I though of is storing the original value of r3 and later on - when 30 seconds are left to play - , restoring the original r3, adapting the modifications it's been done to and storing it in r31

I have not yet found out the value how to show the minimap only as I wanted to focus on Assembly and logic first. This is what I got so far: (it's not the final code - just some playing around)

Code:
# Turn off position, lap and minimap
HideHUD:
rlwinm r3, r3, 0, 29, 25

# check if register has been backed up
lis r5, HUDBackupSaved@ha
lbz r5, HUDBackupSaved@l(r5)
cmpwi r5, 1
beq+ SkipBackup

# backup register
lis r5, HUDBackup@ha
stw r3, HUDBackup@l(r5)

# backupSaved = 1
li r6, 1
lis r5, HUDBackupSaved@ha
stw r6, HUDBackupSaved@l(r5)

SkipBackup:
# if more then 20 seconds remaining
lis r12, Raceinfo@ha
lwz r12, Raceinfo@l(r12)
lwz r12, 0x20(r12)
cmpwi r12, 1200
bgt+ WithoutMap

# if less than 1 frame remaining
lis r12, Raceinfo@ha
lwz r12, Raceinfo@l(r12)
lwz r12, 0x20(r12)
cmpwi r12, 1
blt+ WithoutMap

WithMap:
# Score + Kill Info + LIVE
lis r5, HUDBackup@ha
lbz r3, HUDBackup@l(r5)
ori r31, r3, 0x1600
blr

WithoutMap:
# Score + Kill Info
ori r31, r3, 0x1400
blr

Would be great if you could give me any tips what to change so the minimap will be visible in the last 20 seconds

Just in case HideHUD is executed only once at the beginning, how could I unhide the minimap then? I have thought about executing the Assembly command from C code when 20 seconds are left to play - is it possible to execute Assembly commands from C?
Reply
#2
This will not work. The HUD hide hook alters a configuration flag to disable certain HUD elements on track load. What you want to do is turn on the minimap by enabling the correct bit in this hook, then modify the "visible" property in the minimap screen element itself.
Reply
#3
(01-25-2023, 10:33 PM)CLF78 Wrote: This will not work. The HUD hide hook alters a configuration flag to disable certain HUD elements on track load. What you want to do is turn on the minimap by enabling the correct bit in this hook, then modify the "visible" property in the minimap screen element itself.

Thank you for your quick answer

From what I understood:
1) finding the right bit to enable the minimap
2) modifying the correct property at start (visible = false)
3) turn the property to true when less than 20s

Is that what you meant?

I'm trying to make sense out of this and convert it into the actual code lines.
1) Would you mind handing me references to the minimap property you were talking about?
2) I have thought about adding some lines in MainTimerUpdate (timer.c). Can I change the property from within C? If not, how can I do that?
Reply
#4
He's saying the mini map has to be turned on first regardless. This does not mean its actually visible. You will then have to modify the visibility property of the mini map, which I assumed can be done at any frame/time.

Regarding your Assembly questions about r3, r31, and what not~

r3 thru r10 are the parameter (volatile) registers. They are used as inputs/arguments for functions. They are also used as return values given back by functions to let you know how a function went (failed, succeeded, denied, etc). The Caller (whatever code responsible for actually calling a function) is responsible for setting up the args. The Callee (function itself) will return output(s) in the parameter registers back to the Caller.

For 99.9% of functions, r3 will contain the return value/output. For float based functions, return value is in f1. There are a handful of functions that will need to use r4+ or f2+ for extra return values, but this is really rare. For a majority a functions, a negative value indicates an error.

r31 is a non-volatile register, aka global variable register (GVR for short). GVRs are r14 thru r31. These are saved thru out function calls. A function may save args given by the Caller into the GVRs so a later child function(s) can use or modify them. GVRs that need to be preserved the longest are kept in the lower range of r14 thru r31, while GVRs that need to be used/modified/etc in a relatively short manner are in the higher range of r14 thru r31.

Example of using GVRs to save items throughout function calls. Just made up a template with fake args and functions for demonstration. Take note of the two instructions that have comments attached

li r3, 1
li r4, 0
bl function_a
mr. r31, r3 ####Do a cmpwi r31, 0 to see if initial r3 was less than zero, place r3 into r31 GVR
blt- error
lis r4, 0x8085
ori r4, r4, 0xC000
bl function_b
cmpwi r3, 0x1000
bne+ error
mr r3, r31 ###Return value of function_a is arg for function_c
bl function_c

Regarding your specific source, keep in mind that you can implement conditional branches to the LR/CTR 

Example:
cmplwi r3, 1200
bgtlr+

For bge/bgt/ble/blt branches after a comparison, you should be using logical comparisons (i.e. cmplwi). Unless... you are expecting the possibility of values in the 0x80000000 thru 0xFFFFFFFF range to be present and you want said values to be treated as negative (signed).

You also can use other CRs (cr5, cr6, cr7) for doing consecutive compares.

Example:
cmplwi r3, 1200
cmplwi cr7, r3, 1
...
...
bgt+ somewhere
blt- cr7, somewhere_else

The Broadway Manual and PPC Compiler Writer's Guide are the 2 best docs to read up on btw

They are provided in this download - https://mariokartwii.com/downloads/PPCManuals.zip
Reply
#5
(01-26-2023, 12:06 AM)Vega Wrote: He's saying the mini map has to be turned on first regardless. This does not mean its actually visible. You will then have to modify the visibility property of the mini map, which I assumed can be done at any frame/time.

Regarding your Assembly questions about r3, r31, and what not~

r3 thru r10 are the parameter (volatile) registers. They are used as inputs/arguments for functions. They are also used as return values given back by functions to let you know how a function went (failed, succeeded, denied, etc). The Caller (whatever code responsible for actually calling a function) is responsible for setting up the args. The Callee (function itself) will return output(s) in the parameter registers back to the Caller.

For 99.9% of functions, r3 will contain the return value/output. For float based functions, return value is in f1. There are a handful of functions that will need to use r4+ or f2+ for extra return values, but this is really rare. For a majority a functions, a negative value indicates an error.

r31 is a non-volatile register, aka global variable register (GVR for short). GVRs are r14 thru r31. These are saved thru out function calls. A function may save args given by the Caller into the GVRs so a later child function(s) can use or modify them. GVRs that need to be preserved the longest are kept in the lower range of r14 thru r31, while GVRs that need to be used/modified/etc in a relatively short manner are in the higher range of r14 thru r31.

Example of using GVRs to save items throughout function calls. Just made up a template with fake args and functions for demonstration. Take note of the two instructions that have comments attached

li r3, 1
li r4, 0
bl function_a
mr. r31, r3 ####Do a cmpwi r31, 0 to see if initial r3 was less than zero, place r3 into r31 GVR
blt- error
lis r4, 0x8085
ori r4, r4, 0xC000
bl function_b
cmpwi r3, 0x1000
bne+ error
mr r3, r31 ###Return value of function_a is arg for function_c
bl function_c

Regarding your specific source, keep in mind that you can implement conditional branches to the LR/CTR 

Example:
cmplwi r3, 1200
bgtlr+

For bge/bgt/ble/blt branches after a comparison, you should be using logical comparisons (i.e. cmplwi). Unless... you are expecting the possibility of values in the 0x80000000 thru 0xFFFFFFFF range to be present and you want said values to be treated as negative (signed).

You also can use other CRs (cr5, cr6, cr7) for doing consecutive compares.

Example:
cmplwi r3, 1200
cmplwi cr7, r3, 1
...
...
bgt+ somewhere
blt- cr7, somewhere_else

The Broadway Manual and PPC Compiler Writer's Guide are the 2 best docs to read up on btw

They are provided in this download - https://mariokartwii.com/downloads/PPCManuals.zip

Thank you for your in depth explanation. This cleared up some bits.

I haven't worked that much with ASM before and some tutorials use a lot of technical terms and words without any examples. At times, this can be quite frustrating for a beginner.

What I understood for my issue is, that I need to find out the bit of the map and then manually use the logic in c to call a procedure in ASM to change the visibility of the map.

I have found this thread by CLF78: https://mariokartwii.com/showthread.php?...ht=minimap

So

Code:
047EA498 38000000

Would completely hide the map and

Code:
047EA498 38000064

restore it to its original opacity.

So it's writing 38000064 at address 0x847EA498. What I haven't figure out yet: How could I translate that into ASM?
Reply
#6
Just to get this out of the way, if you haven't seen this "Index" yet, here it is - https://mariokartwii.com/showthread.php?tid=1114

It's a full guide from Beginner to Pro for PPC ASM

To address your question at the very bottom of your reply, well it depends...

Regarding Gecko Codes, you can...

1. Use a C2 Code hooked to an address that occurs every frame in a race and only in a race.
2. Use a C0 code which would require some check to verify you are in a race (to prevent DSI exceptions and what not)

Option 1 is preferred.

C2 Gecko codes work via the Code Handler placing a backwards branch on the address in question. The original instruction at the address is overwritten. The backwards branch jumps to a reserved spot in memory where the C2 Code's specific instructions reside at. C2 Code gets executed, and then it will branch back to where it came from. Therefore, in most C2 codes, you need to supply the Hook Address's original instruction.

Regarding C0 codes, here's a nice tutorial - https://mariokartwii.com/showthread.php?tid=1156

Option 1 (C2 code) can be broken into 2 sub options, in regards to something such as CLF's code...

1. Change the 04 RAM Write code to be a C2 Code.
2. Use an unrelated address and write source to modify the instruction residing at CLF78's address (this would require some cache instructions as you will be doing what is known as self modifying code). This is essentially the same mechanism that would be done on a C0 code (rewrite executable instruction at address)

sub option 1 is obviously the best choice. You could then just write out basic source to check the time left in match and then preform said actions to modify mini map's opacity

---

Unfortunately, the Address on CLF78's code only executes once at the start of a race/battle. Meaning once said race/battle starts, the opacity cannot be changed until next race/battle.

There's probably a complex workaround to this. There's also a possibility of modifying the entire visibility of the HUD as a whole in which that is "allowed" to be done at anytime. Of course the issue is you are effecting the HUD as a whole unit.
Reply
#7
(01-26-2023, 02:06 AM)Vega Wrote: Just to get this out of the way, if you haven't seen this "Index" yet, here it is - https://mariokartwii.com/showthread.php?tid=1114

It's a full guide from Beginner to Pro for PPC ASM

To address your question at the very bottom of your reply, well it depends...

Regarding Gecko Codes, you can...

1. Use a C2 Code hooked to an address that occurs every frame in a race and only in a race.
2. Use a C0 code which would require some check to verify you are in a race (to prevent DSI exceptions and what not)

Option 1 is preferred.

C2 Gecko codes work via the Code Handler placing a backwards branch on the address in question. The original instruction at the address is overwritten. The backwards branch jumps to a reserved spot in memory where the C2 Code's specific instructions reside at. C2 Code gets executed, and then it will branch back to where it came from. Therefore, in most C2 codes, you need to supply the Hook Address's original instruction.

Regarding C0 codes, here's a nice tutorial - https://mariokartwii.com/showthread.php?tid=1156

Option 1 (C2 code) can be broken into 2 sub options, in regards to something such as CLF's code...

1. Change the 04 RAM Write code to be a C2 Code.
2. Use an unrelated address and write source to modify the instruction residing at CLF78's address (this would require some cache instructions as you will be doing what is known as self modifying code). This is essentially the same mechanism that would be done on a C0 code (rewrite executable instruction at address)

sub option 1 is obviously the best choice. You could then just write out basic source to check the time left in match and then preform said actions to modify mini map's opacity

---

Unfortunately, the Address on CLF78's code only executes once at the start of a race/battle. Meaning once said race/battle starts, the opacity cannot be changed until next race/battle.

There's probably a complex workaround to this. There's also a possibility of modifying the entire visibility of the HUD as a whole in which that is "allowed" to be done at anytime. Of course the issue is you are effecting the HUD as a whole unit.

Thank you for the guides; I haven't seen them yet as I have registered an account just a few hours ago.

This actually seems a lot do understand, learn and analyze in order to implement this feature - I may come back to this when I'm willing to invest that time and energy in the future.
Reply
#8
(01-25-2023, 10:48 PM)tefo7 Wrote: From what I understood:
1) finding the right bit to enable the minimap
2) modifying the correct property at start (visible = false)
3) turn the property to true when less than 20s

Is that what you meant?

Yes, correct.

(01-25-2023, 10:48 PM)tefo7 Wrote: Would you mind handing me references to the minimap property you were talking about?

What the HideHUD code is doing is very simple: the original instruction simply moves the contents of r3 to r31 for reuse later in the original function. Therefore, i hijacked this spot to disable some bit flags and enable others.

The flags are as follows (you can check the function using them at 0x80857CC0 PAL):
Code:
0x2 = Timer
0x4 = Countdown text
0x8 = Minimap
0x10 = Position
0x20 = Lap
0x40 = Item roulette
0x80 = Battle score
0x100 = Ghost race time difference
0x200 = Live view text
0x400 = Mission mode score
0x800 = Item alert
0x1000 = Ghost cannot be saved text (repurposed for Kill Information in HNS)
0x2000 = Another battle score related screen element

So, the rlwinm instruction is turning off the bits for the minimap, the position and the lap, while the ori instruction is turning on the score and the kill info text. Therefore all you have to do is modify the rlwinm instruction to suit your needs. I ported this to C to aid you in editing, so feel free to mess with it: https://godbolt.org/z/73j9EEjsG (just don't forget the result of the rlwinm+ori must go in r31)

Final note: this does not simply hide the elements, it literally prevents their creation, so they don't exist at all if their flag is turned off.

(01-25-2023, 10:48 PM)tefo7 Wrote: I have thought about adding some lines in MainTimerUpdate (timer.c). Can I change the property from within C? If not, how can I do that?

Yes, you should be able to. Unfortunately this is not easy, because the minimap will not be at the same place every time, nor there's a static pointer to it, therefore it has to be saved somewhere or obtained through weird means.

A few hints:
- The minimap is constructed at 0x808581A0 PAL (the pointer to it is being moved from r3 to r14 at this address, so it's a good candidate for a hook)
- The visible attribute is at offset 0x80, is 1 byte big and obviously takes a boolean value (0 = visible, 1 = hidden)

I'll leave the rest for you to figure out Wink
Reply
#9
(01-26-2023, 11:55 AM)CLF78 Wrote:
(01-25-2023, 10:48 PM)tefo7 Wrote: From what I understood:
1) finding the right bit to enable the minimap
2) modifying the correct property at start (visible = false)
3) turn the property to true when less than 20s

Is that what you meant?

Yes, correct.

(01-25-2023, 10:48 PM)tefo7 Wrote: Would you mind handing me references to the minimap property you were talking about?

What the HideHUD code is doing is very simple: the original instruction simply moves the contents of r3 to r31 for reuse later in the original function. Therefore, i hijacked this spot to disable some bit flags and enable others.

The flags are as follows (you can check the function using them at 0x80857CC0 PAL):
Code:
0x2 = Timer
0x4 = Countdown text
0x8 = Minimap
0x10 = Position
0x20 = Lap
0x40 = Item roulette
0x80 = Battle score
0x100 = Ghost race time difference
0x200 = Live view text
0x400 = Mission mode score
0x800 = Item alert
0x1000 = Ghost cannot be saved text (repurposed for Kill Information in HNS)
0x2000 = Another battle score related screen element

So, the rlwinm instruction is turning off the bits for the minimap, the position and the lap, while the ori instruction is turning on the score and the kill info text. Therefore all you have to do is modify the rlwinm instruction to suit your needs. I ported this to C to aid you in editing, so feel free to mess with it: https://godbolt.org/z/73j9EEjsG (just don't forget the result of the rlwinm+ori must go in r31)

Final note: this does not simply hide the elements, it literally prevents their creation, so they don't exist at all if their flag is turned off.

(01-25-2023, 10:48 PM)tefo7 Wrote: I have thought about adding some lines in MainTimerUpdate (timer.c). Can I change the property from within C? If not, how can I do that?

Yes, you should be able to. Unfortunately this is not easy, because the minimap will not be at the same place every time, nor there's a static pointer to it, therefore it has to be saved somewhere or obtained through weird means.

A few hints:
- The minimap is constructed at 0x808581A0 PAL (the pointer to it is being moved from r3 to r14 at this address, so it's a good candidate for a hook)
- The visible attribute is at offset 0x80, is 1 byte big and obviously takes a boolean value (0 = visible, 1 = hidden)

I'll leave the rest for you to figure out Wink

Thank you for the great explanation - it makes now much more sense to me, what the code does and how the mods modifies the original instructions.

You have posted an overview of all the bits concerning element creation. Did you look that up somewhere or did you reverse engineered it yourself? If there's a documentation for the bits, would you mind posting a link to them?

I will try out the approch in the next few days - did you find out the attribute being at 0x80 yourself or did you look that up somewhere? Do you know the offset for the opacity so I could slowly fade in the minimap instead of instantly going from 0 to 1.

You defined a procedure called
Code:
directWriteBranch

What exactly does the last boolean do? For example
Code:
directWriteBranch(HUDEditsHook, HideHUD, true)

EDIT:

This is what I got so far:
Code:
# Turn off position and lap (Minimap = 0x8)
HideHUD:
rlwinm r3, r3, 0, 28, 25
# Enable score + kill info text
ori r31, r3, 0x1400
blr

StoreMinimap:
# original instruction
or r14, r3, r3
# hide minimap
li r4, 1
stb r4, 0x80(r14)

Code:
MinimapHook = 0x808581A0;

Code:
// Store Minimap for enabling later on
directWriteBranch(MinimapHook, StoreMinimap, true);


I was just testing the injected code; Minimap is created and shown at start. I was trying to set the bit at offset 0x80 to 1 but the minimap is still visible. Are the ASM lines correct?
Reply
#10
(01-27-2023, 12:45 AM)tefo7 Wrote: You have posted an overview of all the bits concerning element creation. Did you look that up somewhere or did you reverse engineered it yourself? If there's a documentation for the bits, would you mind posting a link to them?
I already listed all the bits for you. As for where i got them, i just found the function using Ghidra (it was already documented by another person before me).

(01-27-2023, 12:45 AM)tefo7 Wrote: I will try out the approch in the next few days - did you find out the attribute being at 0x80 yourself or did you look that up somewhere?
Also found in Ghidra and not documented by me.

(01-27-2023, 12:45 AM)tefo7 Wrote: Do you know the offset for the opacity so I could slowly fade in the minimap instead of instantly going from 0 to 1.
This is the base structure for all the game's UI components: https://github.com/mkwcat/mkw-tournament...trol.h#L59

There are 4 opacity values, but you'll want the 4th one since it's calculated starting from the others. If you can read C++ code, you should be able to figure out the offset and size of the opacity field yourself.

(01-27-2023, 12:45 AM)tefo7 Wrote: You defined a procedure called
Code:
directWriteBranch

What exactly does the last boolean do?
It defines whether the branch instruction should be a simple branch (b) or a branch and link (bl).

(01-27-2023, 12:45 AM)tefo7 Wrote:
Code:
StoreMinimap:
# original instruction
or r14, r3, r3
# hide minimap
li r4, 1
stb r4, 0x80(r14)
You are missing a blr instruction to return to the game's code, unless it was omitted here.

(01-27-2023, 12:45 AM)tefo7 Wrote: I was just testing the injected code; Minimap is created and shown at start. I was trying to set the bit at offset 0x80 to 1 but the minimap is still visible. Are the ASM lines correct?
The lines are (almost) correct, but the hook you used is too early to be effective. You need to wait for the object to be initialized before modifying the property. Since it seems like you are lacking any sort of documentation, i highly suggest you to get familiar with Ghidra in order to be able to conduct reverse engineering on your own. Here's some info to get you started https://github.com/stblr/mkw-sp#resources
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)