• Welcome to Smashboards, the world's largest Super Smash Brothers community! Over 250,000 Smash Bros. fans from around the world have come to discuss these great games in over 19 million posts!

    You are currently viewing our boards as a visitor. Click here to sign up right now and start on your path in the Smash community!

Request Yet another Ledge Invincibility Staling mechanic

DTsm

Smash Rookie
Joined
Feb 21, 2020
Messages
2
Hello.

I have an idea for yet another Ledge Invincibility Staling mechanic, that sounds simple enough to implement, albeit somewhat laborious, except for a key detail: I would like to add a few variables to the player character structure to keep track of some ledgegrab state.

I have some experience programming at asm-level, but know next to nothing about hacking a live program other than the concept of injecting branches to new code.
As such, I know what I want to do, but I have no idea where to do it, nor I know how hard it actually is to implement.
The most modding I've actually done on Melee is customizing a few characters with Crazy Hand.

The idea is to add ledge staling in 4 levels, which reduces ledge invincibility from full to only a quarter at worst, covering pretty much only the ledgegrab portion.
The staling level increases when the player regrabs the ledge 2, 6 (2+4), and 14 (2+4+8) times respectively, and decreases by touching the ground after ledgegrabbing and staying off the ledge for 15 seconds (900 frames) (effectively reducing the ledgegrab count from 14+ to 14, 6+ to 6, 2+ to 2, and 0+ to 0 otherwise)
As such, it needs to add onto the player character structure two bitflags, one for when the timer has to run and one for when the character has ledgegrabbed and hasn't yet touched ground, and two variables for the timer and (resetable) ledgegrab count.
In a pinch, all of this data could fit in 24 bits of space, perhaps reducing the two bitflags to only one, but a variable has to be added nonetheless.

The problems I have with it are two:
1. I have no idea how to add variables to the player structure.
What I suppose would be needed, if there is no exploitable free space in the already existing structure, and assuming that player character structures are dynamically allocated, is to locate and modify the allocation of the structure to allocate the space needed for the additional variables.
This is trivial enough given the source code, but if other places in the game assume the size of the structure, then those are to be manually modified as well, and if it moves around data that shouldn't be moved, then I have no idea how to recover from that.

2. I don't know where to find the functions I want to inject my code into.
This stems from my inexperience with Melee's structure, so definitely a fault on my part, but a roadblock nonetheless! I know of the

My question thus is as follows:
Is it doable? How hard would it be to add variables for each player? Would another approach work better to achieve the same?

Spoilered below is a more detailed description/half pseudocode of what I would want to achieve.
Player Variables to be added:
LGP: A variable set when ledgegrabbing, unset when landing.
LGCnt: A modifiable ledgegrab counter.
LGTmOn = 0-1;
LGTm = 0-900: Two variables for the ledge unstaling timer.


Optional variables, calculated by functions:
LGInvK = the default ledge invincibility duration taken from PlCo.dat.

Ledge staling level: LGStL = 0-3; LGStL = case LGCnt: if <2: 0; if <6: 1; if <14: 2; else: 3
Ledge invincibility: LGInv = LGInvK * (4 - LGStL) / 4


Functions:
"reset timer" = set LGTmOn to 0, LGTm to 0.
"start timer" = set LGTmOn to 1.

"reset ledgegrab count" = Set LGCnt to 0, LGP to 0.
"reduce staling level" = LGCnt = case LGCnt: if <2: 0; if <6: 2; if <14: 6; else: 14


Code to be injected:
somewhere in the player loop: Increase LGTm if LGTmOn is set, if it is >= 900 reduce staling level and set it to 0.
on Spawn/Death (where it is more appropriate): reset ledgegrab count, reset timer.
on LedgeGrab: Increase LGCnt, set LGP, reset timer, set invincibility for LGInv frames.
on Landing (where DJ is restored): check whether LGP is set, unset it and start timer if it is.

Thanks for your time if you read all of this.
 
Last edited:

Punkline

Dr. Frankenstack
Joined
May 15, 2015
Messages
423
Hey, welcome!

If you’re familiar with ASM but aren’t familiar with studying live programs, I suggest going through Dan’s Wii Game Modding tutorial to get a primer on how to use Dolphin’s debug features -- memory breakpoints in particular. You should also pick up the Community Symbols Map to get some named functions in Dolphin while you’re poking and peeking around.

If you check out the SSBM Data Sheet, you can get some information about known data structures in the game. There’s one specifically for Player GObjs in a tab called “Char Data Offsets” that will be helpful to you when making memory-check breakpoints to find functions related to ledge grabbing. The section titled START of Character Data Offsets is what the player GObj data table is made up of. I also recommend doing a search in the codes section here for any codes related to "Ledge Grab" or "Cliff Catch" (the name of the action state) to get an idea of what functions other people have modified to get similar results.

---

Here’s a brief synopsis of the way Melee handles player instances:
  • Players are represented as entities using a class of object called a GObj.
    • GObjs are little structures that point to data/callbacks that can be used to create a generic object with properties/behaviors that are easy to plug into a “Scene” context in Melee.
      • The data within a GObj data allocation can be used to describe a more nuanced object structure, with its own attribute system that can be updated or read by GProc behaviors, external functions, or codes. Players are an example of this; with a rich action state machine described through several structures combined into a properties table -- over 0x2000 bytes in size -- allocated for each player GObj.
    • GObjs can be linked together and listed in groups that are used by GProcs, and a “GX” callback for optionally delivering model information or other vertex data to the GX using a specific camera in the scene when rendering the frame.
    • Each GObj may optionally create a property table allocation in scene-persistent dynamic memory for its GProcs to make use of when processing the object each frame. This data table is given a destructor callback pointer to handle it in the case that a GObj needs to be destroyed before the scene ends.

Allocating new property variables for each player is a common problem and an annoying obstacle when designing codes for Melee. The trouble with changing the size of this allocation to accommodate more variables is that -- while certainly possible -- your code will end up being the only extension to this table. Any other code that does the same will end up conflicting with yours unless you share some kind of prerequisite protocol common between each code for reading/writing to these variables.

There are several ways to make a solution to this that require fewer or no prerequisite constructs:

---

Static Memory

One of the easiest and most compartmentalized methods is to just make static variables that are allocated within your code. This involves leaving blank space for the size of the table you’d like to create inside of your code somewhere, branching over it if needed, and then referencing it with instructions.

You can exploit the link register and the blrl PPC instruction to reference any table that you can reach with a branch link; like this:

Rich (BB code):
.macro mydata,d,a,b=.+4;
  .ifb \d;blrl;_mydata.blrl=.;.exitm;.endif; # if no arguments, then gen blrl location
  .ifb \a;bl _mydata.blrl-4;mflr \d;.exitm;  # if \a is blank, set reg \d=address of blrl
  .else;addi \d,\a,\b-_mydata.blrl;.endif    # else calculate an offset from blrl address
.endm; # --- create and reference inline data tables  
  
# Example macro usage:
b _data_end       # branch over inline data...
mydata     # <-- this location becomes the base address for inline data
           .asciz "Hello World"; .align 2
_goodbye:  .asciz "Goodbye";     .align 2
  
_data_end:
mydata r3                # <-- r3 now has address of inline data start
mydata r4, r3            # <-- r4 now has address of this instruction
mydata r5, r3, _goodbye  # <-- r5 now has address of the _goodbye label

-- or you can reference a static table by putting it into a standalone function container in Melee Code Manager, and use its given container name to build the location through immediates:

Rich (BB code):
## ... from any MCM code ...  
lis r3, <<myDataTableName>>@h
ori r3, r3, <<myDataTableName>>@l
# r3 now = address of wherever the container <myDataTableName> was compiled to in start.dol
  


<myDataTableName> NTSC 1.02
## this is a mod container for the MCM Mods Library
.zero 16*4  # make this many zeroed out bytes for static <myDataTableName>

If you use the former, then you can create a solution that can be used in gecko codes, but can only reach data that can be branched to. (there are ways around this, but they take some extra steps and are limited in the number of times they can be used)

If you use the latter however, you can reference the data from separately assembled ASM programs that are inside of Melee Code Manager. This is useful if you are working with multiple injection mods for a single code, and need them all to reach the same data table.


The downside to this is that you will have to organize some way of handling multiple players as memory slots in this table, since you can’t make one “for each player” like you could if you were extending the player variables directly. However, this is actually easy to do by creating a variable in each slot that holds a pointer to a specifying player GObj identifier; and then limiting the maximum number of affectable players to the number of memory slots that you make. This way, you can update the slots by nullifying the GObj pointer when a slot is no longer needed -- freeing it for use as part of a memory pool.

---

Since GObjs create a 2-way linked list with both their GX_Link and P_Link groups, you can also just follow the previous P_Link pointer (offset 0xC the GObj structure) in a Player GObj to find a null link in the first player (counting its place in the list along the way) and then use that as an index value for locating a slot in your static array. This is actually a very reliable way to handle this problem in most cases if you make at least 12 slots to handle a worst-vanilla-case of player allocations in a match; but it’s worth noting that if the index of CPU players ever changes because they have been added/removed to the scene, then it may cause mismatching to happen. These are rare and predictable enough to still make this a useful solution, however.

I’ll have an example of this last method available soon in the form of a code I’m in the middle of writing. The code requires extensions to player variables to create a jab lock counter, which creates a similar problem to the one you’re facing in this code.

---

New Dynamic Memory

Another approach is to just make a new allocation in the scene; separate from the player variables.

The easiest way to do this is to use one of the higher-level allocation functions used by things like GObj and HSD object constructors to make yourself an allocation that will be released automatically at the end of the scene. If you do this however, you will have to make a pointer to it somewhere; meaning that you will need to rely on some static memory -- or someplace that your code can reliably reach -- to create a root pointer in a linked list, or to a toc that reaches this allocation. This will also mean that your static pointer variable may end up pointing to expired data at some point, so you (may) have to account for that in your code as well by making a protocol for re-allocation.

Try any of these functions:

Rich (BB code):
# --- high level alloc/dealloc:
hsdAllocMemPiece = 0x80381fa8
# r3 = alloc size in bytes
# - returns r3 = allocation address or null
hsdFreeMemPiece = 0x8038216c
# r3 = allocation address; r4 = size of allocation
HSD_MemAlloc = 0x8037F1E4
# r3 = size
HSD_Free = 0x8037f1b0
# r3 = allocation address
  
# --- formatting functions:
Zero_AreaLength = 0x8000c160
# r3 = address; r4 = number of bytes to fill with 00
fill_mem = 0x80003130
# r3 = address;  r4 = byte to fill with;  r5 = number of bytes to fill
memcpy = 0x800031f4
# r3 = copy to; r4 = copy from; r5 = copy size

I have an MCM function module here that experiments with doing this in a way that also generates a configurable metadata format for creating abstract data types. It lets you describe a variety of ways of configuring node links, and stuffs metadata pointers into a negative index from the base of each node in a shared allocation. I have not experimented with it much, but looking over the ASM might give you some ideas.

---

The most stable solution is to use a whole GObj of your own.

Since GObjs can create their own destructor pointers, and even maintain them with GProcs; it’s possible to make these as flexible as you like. However, they’re comparatively more cumbersome to set up and use than other solutions. You have to call a function with setup arguments for every attribute of the GObj.

I *think* this is the gist of what you’ll need to call in order to construct a new GObj with a data table and an optional GProc that runs each frame, with some structure notes at the bottom:

Rich (BB code):
AllocSize = 0x20
# - the size of the data table to create for this GObj  
GObjClassID = -2  
# - optional 16-bit GObj name; game uses small positive numbers to identify vanilla types
PLink = 16   
# - select from up to 64 process lists to create a priority for this new GObj
# - some PLink lists are dedicated to specific entity types in a scene
PPriority = 0
# - set a priority for the ordering of this GObj in its PLink list -- with 0 being the highest
# - will be placed after other GObjs of <= PPriority
SPriority = 0
# - set a priority for the GProc order in the SLink priority list
  
rGObj = 31  # saved register, for address of GObj
rData = 30  # saved register, for address of new data allocation
  
li r3, GObjClassID
li r4, PLink
li r5, PPriority
bla 0x803901F0
# GObj_Create = 0x803901F0
# - returns r3 = new GObj
  
mr rGObj, r3
li r3, AllocSize   
bla 0x8037F1E4
# HSD_MemAlloc = 0x8037F1E4
# - returns r3 = new data allocation
  
mr rData, r3
li r4, AllocSize
bla 0x8000c160
# Zero_AreaLength = 0x8000c160
# - allocation has now been zeroed out
  
mr r3, rGObj
lis r0, 0x8037f1b0@h
li r4, -2  # data type, not useful here unless you use -1 to create a "null" type
ori r5, r0, 0x8037f1b0@l # HSD_Free = 0x8037f1b0  (destructor callback)
mr r6, rData
bla 0x80390B68
# GObj_InitKindData = 0x80390B68
# - GObj now has registered the allocation as an attribute it can handle on destruction
  
lis r0, <<myGProcCallback>>@h
mr r3, rGObj
ori r4, r0, <<myGProcCallback>>@l
li r5, SPriority
bla 0x8038FD54
# GObj_CreateProcWithCallback = 0x8038FD54
# - GObj will now be used by the given GProc Callback once per frame
# - the GProc will be given argument r3 = GObj
  
# GObj structure:
#     0x00  (class ID)
#     0x02  p_link
#     0x03  gx_link
#     0x04  p_link Priority
#     0x05  gx_link Priority
#     0x06  obj_type
#     0x07  data_type
# --- 0x08  Next GObj in p_link chain
# --- 0x0C  Previous GObj in p_link chain
#     0x10  Next GObj in gx_link chain
#     0x14  Previous GObj in gx_link chain
#     0x18  to root GProc node
#     0x1C  gx_link callback
#     0x20  camera gx_link bools (high 32)
#     0x24  camera gx_link bools (low 32)
#     0x28  to Root HSD Object
# --- 0x2C  Pointer to Data Allocation
#     0x30  Pointer to Destructor Function

If you want to pass a callback function to the GProc constructor, then you can actually write it locally to the ASM program that you're making the call in, and then wrap it into a table made with the blrl exploit mentioned earlier in the static memory solutions. You can also pass standalone functions created in Melee Code Manager.

---

Exploit Existing Memory

By far the easiest, but most problematic method is to exploit the valuable padding available in some parts of the player structure. This is a method used by popular mods like 20XX, and will often result in creating incompatibilities with other codes.

If that isn’t a concern of yours however, then I might suggest studying the structure of hurtbox allocations for each player starting at offset 0x11A0 of the player data structure. The allocation in this table for hurtboxes is an array of 15 0x4C-byte elements that is made regardless of how many hurtboxes a character uses. It’s worth noting that only like 1 or 2 characters use all of the hurtboxes so most characters have an unused hurtbox allocation laying around in their data. (offset 0x119C holds the number of hurtboxes in use by the character.)

However, something that applies to ALL characters is the fact that only 1 of 4 bytes allocated for the boolean flags appear to be used in hurtbox logic; leaving 24 bits of padding starting at offset 0x25 in each of the 15 hurtbox allocations.

If you were to use these bytes, my recommendations for minimizing conflicts would be to only use 1 of the 3 available bytes present in the padding because -- if other codes use these bytes -- it’s a good vector for making a “for each hurtbox” type of code. You will probably reduce the chance of colliding with partial use of this padding in other codes if you can separate your variables into byte-size or smaller and scatter them across multiple hurtboxes.
 
Last edited:

DTsm

Smash Rookie
Joined
Feb 21, 2020
Messages
2
Wow, this is a very informative post, thank you a lot for all this valuable information!
Sorry for the late reply, but for various reasons I had no time to look into it before.

I see, making new dynamic allocations while keeping player pointers is better for compatibility with other mods. I definitely didn't think of that.
It is funny that there's exactly 24 bits of usable padding, but you convinced me that it is better to use a more scalable approach.

Very interesting to see that the game lets you register new procedures that are called per-frame. I will definitely read more about GObjs and GProcs, thank you for pointing me in that direction!


I've been playing around with a few tools, among which MCM, and I've been peeking around the memory with Dolphin's Debug mode. To get used to reading the Data Sheet, I've tried implementing a much easier mod around shield values (which is just patching at runtime two global values in PlCo.dat).
To do that, I thought of looking at address where the pointer to PlCo.dat is stored (0x804D6554 says the Data Sheet), and add to said pointer the index to get and modify the values I want, but I can't seem to match addresses from the Data Sheet with running melee memory: even MCM seems to say that the address 0x804D6554 is out of bounds, and it seems not mapped under any symbol in the Community Symbols Map (which perhaps only names function addresses, not data?).
What are those addresses relative to? How can I match them with live memory? I'm sorry if this is answered in the video series you linked, as I've not had the time to watch it yet.
Perhaps this isn't the right place to ask these questions.

So far I've been able to find the AS procedures for LedgeGrabbing and Landing, finding the one for Respawn won't be difficult, and the per-frame GProc should take care of my "player loop" procedure, which takes care of all the code injection I needed. Variables are taken care of by the memory allocation techniques you explained to me. I should now have everything to make the code possible except practice and experience! I doubt I will be able to make it soon, but I will post again when I'll succeed.
With more practice, I should be able to learn from other people's codes as you suggested as well.

Thank you a lot for your help!
 

Punkline

Dr. Frankenstack
Joined
May 15, 2015
Messages
423
(Meant to respond to this earlier, got a bit distracted, sorry)

The symbols map only has function names, yes. The functions become very useful for making sense of data once you know how to make use of memory-check breakpoints though. These allow you to see what processes use the data that you’re checking, and can be a very useful research tool when trying to learn more about how the game does stuff.

It's been a long time since I've gone through Dan's video tutorial, but I recall learning all about breakpoints from there -- if you want to skim it for that in particular.


In the file start.dol (the main executable on disk that gets loaded into RAM and creates the game runtime) there are sections of data that become static in their allocation, since they load with the program near boot. There are a couple parts of these data sections that get used to create special base addresses in registers r2 (rtoc) and r13 (data?). For Melee: the addresses in these registers never change and the game uses negative offsets from them to reach globals from any point in the runtime. They are dedicated registers.

From what I understand, rtoc (804df9e0) is read-only data (mostly floating point values, since they can’t be loaded as immediates). r13 (804db6a0) is used for read-write variables. Both have values that can be initialized from data in start.dol, but I have on occasion found the MCM will consider them OOB in some codes I’ve attempted to port between Melee versions, in the past. It may be that some of the read-write variables are allocated separately, and aren’t a part of start.dol -- I’m not actually sure though.

You said the absolute address of the PlCo.dat pointer you're using is 0x804D6554 -- which is -0x514C(r13). So, at any point in the game program, you can use lwz r3, -0x514C(r13) to access this global PlCo pointer allocation in r3, or a register of your choice. Not all globals can be accessed like this, but all addresses within -0x8000 bytes of r13 or rtoc can.

If you want to study what uses this pointer, then you can set a memory check breakpoint (+MBP) from the “Breakpoints” panel in debug dolphin. If you set a range for the memory breakpoint to include the address of this pointer (804d6554), then the game will slow down a bit when you run it, and will interpret the execution for any uses of that pointer by instructions in the runtime; pausing (breaking) once it is detected.

If you do this, you may find the processes that use and maintain this pointer, and you might see that the game uses either the above stated r13 offset, or constructs an absolute address out of immediates to reach it.


... Speaking of which; if you ever want to load a known address that's outside of rtoc or r13 into a register, you can just use these 2 instructions:
Rich (BB code):
lis r3, 0x804D6554@h
ori r3, r3, 0x804D6554@l
The @h and @l symbols stand for “high” and “low” 16-bit. They can be used in these instructions for the purpose of constructing absolute addresses from 16-bit signed immediate values -- allowing you to reference static data without needing some relative address.
 
Top Bottom