In Progress RT Stack Snacks

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
405
#1
RT Stack Snacks is a module for MCM that enables a unique method of allocating globally accessible variables using a series of files kept in the MCM directory. These allocations may be thought of as static, but do not take up space in the DOL or in the dynamic memory heap -- and are initially zeroed out. They may be used to house data variables as needed for large code projects in a way that makes them very easy to define and use from multiple Melee codes.

Clone or Download the latest version here - https://github.com/Punkline/MCM-RTStackSnacks.git


When installed, the code uses .include to tap into a highly customizable file called RTStackSnacks.s. Additional modules and the contents of the 4 “.RTStackSnacks\*.txt” files may be bundled for certain project configurations. You may configure RTStackSnacks.s to create variables by following the comments, or see below for more details.

To install, copy everything in the ‘MCM’ folder to your MCM root directory. Then, move the text file to your Mods Library folder.

To load the module for use in other codes, use the line: .include "RTStackSnacks.s"

---


How to configure:

You may modify the contents of RTStackSnacks.s or the files contained in the “.RTStackSnacks” subdirectory. The comments briefly explain how to use the module, and what it is comprised of:

Rich (BB code):
# --- RTStackSnacks.s - v0.1 
# .include this module in your code to enable snacks. 
#    use the line:  .include "RTStackSnacks.s" 
  
# --- to find the snacks - use one of these methods: 
#    getsnacks rSnacks          # get variable allocations 
#    getsnacks.predef rPredef   # get predefined data table 
#   - or: 
#    lwz rSnacks, -0x2018(rtoc) 
#    lwz rPredef, -0x2014(rtoc) 
  
  
# --- to get a snack - use a snack allocation symbol: 
#    lwz rSnack, xMySnack(rSnacks) 
#   - or: 
#    addi rSnack, rSnacks, xMySnack 
# - these symbols are generated below, in Snack Allocations 
# - they may also be generated in .RTStackSnacks\Snacks.txt 
  
  
# 'Snacks' are variable allocations. 
# Snack data is effectively static, but initially blank. 
# - snacks take up no space in the static DOL file 
# - snacks take up no space in the dynamic memory heap 
# - snacks are not volatile, and persist between scenes 
# - snacks all derive from base address 804eebb0 - size 
#   - you may access this from a pointer in -0x2018(rtoc) 
#     - shortcut macro:  getsnacks rSnacks 
  
# 'Predef' data is just regular static data. 
# - Predef data takes up space in the DOL 
# - Predef data can be used to define default values 
# - Predef data is static, but loc isn't known by assembler 
#   - you may access this from a pointer in -0x2014(rtoc) 
#     - shortcut macro:  getsnacks.predef rPredef 
  
# 'Setup symbols' can be used to initialize GAS objects 
# - all codes that .include this file will inherit these 
# - symbols for Predef structures may be added here 
# - utility macros may also be added here 
  
# 'Setup instructions' can be used to initialize Snacks 
# - setup instructions are executed at start of the game 
# - they may be used to initialize snack variables 
#   - Predef may be used to copy defaults if needed 


“Snack” allocations are made by generating a runtime stack frame before the Melee scene loop begins. This is done exactly once at the beginning of the game, which makes this frame permanent, and effectively static; with allocations starting at address 804eebb0 (using a negative offset). If the frame is made too large, it may impose unintended limits on the runtime stack's capacity that may cause problems. If you keep your allocations snack-sized however, then tens of thousands of bytes can be afforded without issue.
(These allocations will survive purges of the dynamic memory heap, as though they were static.)

In addition to the snacks made out of the bogus stackframe, a centralized container for static data in the DOL is also made available as a separate customizable table -- for convenience. Data can be defined in here at assembly time, so it is useful for making default values that can be copied to the snack variables when a default is required, or at the start of the game.

Both tables are accessible from pointers stored in rtoc offsets -0x2014 and -0x2018 through the use of mytoc block 335.
These have been given special macros called getsnacks and getsnacks.predef -- which may be used like so to retrieve data:



Any code may reach these snack variables or predefined data tables by using a shared set of symbols that are loaded by the module. This means that a code that is written to use these variables does not need to reference specific numerical offsets to reach the allocation it wants from the table -- only a unique keyword. Because of this, the order of installation does not matter, and if they have a specific name, then offsets for a specific code can be stacked into the allocation table at any position.

In short, this means that codes made this way can be ported between different configurations of the module, and easily combined for large code projects.

Additional GAS modules may be bundled with this module to create symbols and macros that every code using it will have access to. Some default modules are bundled to create core features.

Finally, an injection event is created inside of the initialization injection code that is put into the beginning of the game, just after snacks have been allocated. Developers may attach code to this for the sake of initializing variables or launching other initialization processes. This literally inserts the instructions into the injection code container rather than make a call to a function, so calls to any initialization functions must be made with bl or blrl instructions.


The bare version of this code should be compatible with the Gecko Code Table generation feature in MCM, and can even facilitate defining static branch-in lists for supporting specially written custom functions that can be called (by keyword name) using just 4 instructions by any gecko code included in an installation with this module. UnclePunch UnclePunch

---

A special core module has been included to enable the ‘enum’ macro -- which can be used to enumerate named offset values meant to be used from a common base address. The macro counts the total bytes enumerated, and the code uses it to calculate a total for the new stack frame size used to support snack allocations.

To create new snack variables, use the ‘enum’ macro to define new offset names in the following section:

Rich (BB code):
# --- Example Snack Allocations: 
  
# You may use 'enum' to list exact offset names 
#   enum [base], [size], [name1, name2, ...] 
enum 0, 4, example.xMyVar, example.xMySecond, example.xMyThird 
  
# You may use a blank [base] value to continue enumeration with another group of names 
enum  , 4, example.namespace.xMyVar 
  
# You may use 'enumf' to format names with a common prefix 
#   enumf [base], [size], [prefix], [suffix], [name1, name2, ...] 
enumf, 4, example.namespace.,, xMySecond, xMyThird, xMyFourth 
  
# You may use (expressions) in parentheses to change [size] inline with the arguments 
enumf, 4, example.,, xMySingle, (8),xMyDouble, (2),xMyHWord, (1),xByteA, xByteB, (4),xMyWord 
  
# You may use the property 'enum' to reference the current [base] memory 
example.size = enum - example.xMyVar 
# - the last enum value is used to calculate total size in a way similar to this 
#   - the enum value must therfore remain intact while building the snacks allocation table 
#   - you may store the value of enum in another variable identifier temporarily, if needed 
  
  
# --- Create Snack Allocations here: - start at base of 0 
# - names must be unique, so using a parent namespace is recommended 
enum 0, 4, 
  
  
  
  
  
# --- end of Snack Allocations 
.include ".RTStackSnacks\\Snacks.txt" 
# -- (you may also include additional offsets in this file) 
  
RTStackSnacks.xSnackRack = enum 
RTStackSnacks.bogusframesize = (enum + RTStackSnacks.SnackRack.size + 0x80) 
# - this helps define the stack frame size using any enumerations that were defined 


These snack variable allocations can be accessed at any time using a pointer stored in -0x2018(rtoc), or by loading the static address (0x804eebb0 - (RTStackSnacks.bogusframesize - 0x80)). Alternatively, you may just use the getsnacks macro to assign the base address to a register of your choice.

For example:

Rich (BB code):
<example.MyVar.interface> 
# - standalone function may be called in MCM by any code 
  
.include "RTStackSnacks.s"            # load module 
enum r3, 1, rWrite, rRead, rSnacks    # name registers 
getSnacks rSnacks                     # load base address 
  
lwz rRead, example.xMyVar(rSnacks)   
stw rWrite, example.xMySecond(rSnacks) 
blr  # returns:  r4=MyVar;  r5=base of snacks table   


To create data that the module puts in the DOL to boot up with, you can edit the ‘Predef’ section:

Rich (BB code):
.macro RTStackSnacks.PredefDataEmitter 
# This container is for real data, not symbols 
# You may access it from the getsnacks.predef macro 
# - these definitions will take up space in the DOL 
## # Examples: 
##   .long 0, 1, 2, 3 
##   .asciz "Hello World" 
##   mflr r0 
# --- Initialize Predef Data here: 
  
  
  
  

# --- end of Predef Data 
.include ".RTStackSnacks\\PredefData.txt" 
# -- (you may also include data in this file) 
# -- (you may use the .incbin directive to include binary files) 
.endm 


This data can be accessed at any time using a pointer stored in -0x2014(rtoc). Alternatively, you may just use the getsnacks.predef macro to assign the base address to a register of your choice.


If you want to make enumerations for the offsets of some of these predefined data values, you may use the following section to declare additional symbol or macro objects:

Rich (BB code):
# --- Bundled Modules: 
.include "enum.s"   # - enables useful enumeration macros and register/cr symbols 
.include "blaba.s"  # - enables bla and ba instructions as long-form blrl and bctr branches 
  
  
# --- Setup Symbols and Macros here: 
# - non-snack symbols or macros can be defined here for every code that uses the this module 
  
  
  
  
  
# --- end of Setup Symbols and Macros 
.include ".RTStackSnacks\\SetupSymbols.txt" 
# -- (you may also include stuff in this file) 


It’s important to separate this section from the ‘Predef’ data section because it is invoked each time the module is loaded -- while the data is only emitted once via macro call.


If you want to inject extra code into the game-start event, you can do so by adding instructions or calls to functions in this section:

Rich (BB code):
.macro RTStackSnacks.SetupInstructions 
# This last container is for any instructions you want 
#   to include at the very beginning of the game. 
# They will be executed before Melee is fully initialized. 
# --- Extra Setup Instructions go here: 
  

  
  
  
# --- end of Setup Instructions 
.include ".RTStackSnacks\\SetupInstructions.txt" 
# -- (you may also include stuff in this file) 
.endm 


For each of these sections, you may instead use the mentioned file to append the contents instead of directly editing the module file. These are kept in the subdirectory included with the module, called ".RTStackSnacks". If you modify these txt files, you can create configurations that may be plugged into a project.

For example, you can place ‘enum’ calls in the file “.RTStackSnacks\Snacks.txt” to isolate a configuration of variables to a separate file.
You may further explode this abstraction by using .include directives in these file to reference multiple other files; making a collection of variable groups.
 
Last edited:

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
405
#2
Updated with a fix for a backchain artifact that was appearing in the callstack when debugging, and removed the overwrites in rtoc for zeroing out mytoc block 335 so that gecko 04 ops wouldn’t be writing null pointers every frame.

Also, here’s an example code experiment that uses this module to create snack variables:


RTStackSnacks - Jab Lock/Reset in Melee (concept 2)
To make this a part of a custom RTStackSnacks configuration...


Install the RTStackSnacks module by following the instructions in the main post.

Put these variable definitions at the end of your .RTStackSnacks\Snacks.txt file:

Rich (BB code):
enum, (16*4), jabLockMemory.xArray

Then put this code at the end of a text file in your Mods Library to make the code installable:

Rich (BB code):
-==-
  
RTStackSnacks - Jab Lock/Reset (concept 2)
A limited jab lock that resets after n consecutive hits
- n=2 by default; modify with "MaxLock" setting in first injection
- requires "RTStackSnacks.s" module in MCM root directory
# follow error instructions to copy variable definitions
[Punkline]
NTSC 1.02 --- 8009f1b4 ---- 7c040378 -> Branch
# on successful weak attack
  
MaxLock = 2
# change this to adjust number of locks allowed before reset kicks in
  
.include "RTStackSnacks.s"
.ifndef jabLockMemory.xArray
  .error "\nCopy and paste the following line(s) into .RTStackSnacks\\Snacks.txt:\n\n enum, (16*4), jabLockMemory.xArray\n\n"
.endif
enum r3, 1, rGObj, rASID, rIndex, rThis, rNextASTID, rASTID, rCounter, rSnacks;  rPlayer=r31
# register names
  
enum 0, 2, xCounter, xASTID
xLastASTID=0x206C; xNextASTID=-0x5228; xPrevGObj=0xC
# other syombols:
#  ASID  = Action State ID (specific move in state machine)
#  ASTID = Action State Transition ID (pseudo-unique 16-bit counter)
  
mr rASID, r0
mr rThis, rGObj
li rIndex, -4
# rThis is first used to find the value of rIndex
  
_find_null:
  addi rIndex, rIndex, 4
  lwz rThis, xPrevGObj(rThis)
  cmpwi rThis, 0
  # build rIndex value for each non-null GObj in player chain
   
blt+ _find_null
cmpwi rIndex, 16*4
# we don't attempt to overflow memory, and use a 16 player limit just in case
  
bge- _return
  addi rIndex, rIndex, jabLockMemory.xArray
  getsnacks rSnacks
  add rThis, rSnacks, rIndex
  # rThis is now used to select an element from the jabLockMemory array
  # - array element has xCounter and xASTID offsets allocated as variable hwords for us to update
   
  lhz rASTID, xASTID(rThis)
  lhz r0, xLastASTID(rPlayer)
  cmpw rASTID, r0
  li rCounter, 0
  # if trigger ASTID != last player ASTID, then reset counter
   
  bne- 0f
    lhz rCounter, xCounter(rThis)
    # else, increment counter
     
  0:
  lhz rNextASTID, xNextASTID(r13)
  addi rCounter, rCounter, 1
  sth rNextASTID, xASTID(rThis)
  sth rCounter, xCounter(rThis)
  # increment counter - non-0 values create a check on bounce landing
  # update ASTID trigger - non matching triggers on bounce landing reset counter
  # - this allows resets to not reset the counter until combo is broken
   
  cmpwi rCounter, MaxLock
  # if lock counter > maximum, then do a reset instead of a lock
   
  bgt- _return
    subi rASID, rASID, 2
    # else, continue lock ...
    # - reach up or down facing bounce animation from rASID subtraction
     
_return:
.long 0
  
------------- 80097de4 ---- 4800061d -> Branch
# on bounce landing
  
MinWake = 1
# change this to adjust number of locks required to trigger the wakeup on landing
  
.include "RTStackSnacks.s"
enum r3, 1, rGObj, rASID, rIndex, rThis, rASTID, rCounter, rSnacks, rPlayer
# register names
  
enum 0, 2, xCounter, xASTID;  xNextASTID=-0x5228; xPrevGObj=0xC
# misc offsets
  
xLastASTID=0x206C; xASID=0x10;
# player offsets
  
DownUDThreshold=190; DownWake=186
# ASID symbols
  
mr rThis, rGObj
li rCounter, 0
li rIndex, -4
# rThis is first used to find the value of rIndex
  
_find_null:
  addi rIndex, rIndex, 4
  lwz rThis, xPrevGObj(rThis)
  cmpwi rThis, 0
  # build rIndex value for each non-null GObj in player chain
   
blt+ _find_null
cmpwi rIndex, 16*4
# we don't attempt to overflow memory, and use a 16 player limit just in case
  
bge- _decideInterrupt
  addi rIndex, rIndex, jabLockMemory.xArray
  getsnacks rSnacks
  add rThis, rSnacks, rIndex
  # rThis is now used to select an element from the jabLockMemory array
  # - array element has xCounter and xASTID offsets allocated as variable hwords
   
  lwz rPlayer, 0x2C(rGObj)
  lhz rASTID, xASTID(rThis)
  lhz r0, xLastASTID(rPlayer)
  cmpw rASTID, r0
  beq- 0f
    sth rCounter, xCounter(rThis)
    b _decideInterrupt
    # if trigger ASTID != last player ASTID, then reset counter and skip trigger update
    # - this will allow the downed state to continue the counter
     
  0:
  lhz rASTID, xNextASTID(r13)
  sth rASTID, xASTID(rThis)
  # else update trigger
   
_decideInterrupt:
lhz rCounter, xCounter(rThis)
cmpwi rCounter, MinWake
blt+ _lay_on_ground
  lwz r0, xASID(rPlayer)
  cmpwi r0, DownUDThreshold
  li rASID, DownWake
  blt+ _wake
    addi rASID, rASID, 8
    # select up or down facing wakup animation
     
  _wake:
  bla r12, 0x80098160
  li r3, 1
  b _return
  # wakeup in appropriate animation if recovering from lock >= MinWake
   
_lay_on_ground:
bla r12, 0x80098400
# else revert to default behavior
  
_return:
.long 0

Attempting to assemble this code with missing definitions in Snacks.txt will trigger an error message that reminds you:



Pre-assembled Standalone Gecko Code:
This may be installed without setup if no other RTStackSnacks assemblies are installed...

Edit the highlighted bytes to change the values for MaxLock and MinWake:
Rich (BB code):
$RTStackSnacks - Jab Lock/Reset (concept 2) [Punkline]
C209F1B4 0000000D
7C040378 7C661B78
38A0FFFC 38A50004
80C6000C 2C060000
4180FFF4 2C050040
40800044 38A50008
8142DFE8 7CCA2A14
A1060002 A01F206C
7C080000 39200000
40820008 A1260000
A0EDADD8 39290001
B0E60002 B1260000
2C090002 41810008
3884FFFE 00000000
C2097DE4 00000014
7C661B78 39000000
38A0FFFC 38A50004
80C6000C 2C060000
4180FFF4 2C050040
40800034 38A50008
8122DFE8 7CC92A14
8143002C A0E60002
A00A206C 7C070000
4182000C B1060000
4800000C A0EDADD8
B0E60002 A1060000
2C080001 41A00030
800A0010 2C0000BE
388000BA 41A00008
38840008 3D808009
618C8160 7D8803A6
4E800021 38600001
48000014 3D808009
618C8400 7D8803A6
4E800021 00000000
C21A4518 0000000D
3C60FFFF 60637F38
7C21196E 20630004
7C01192E 3883FFC0
38610020 9062DFE8
3C008000 600CC160
7D8803A6 4E800021
48000029 7D8802A6
9182DFEC 8062DFEC
8082DFE8 38040048
90040000 80030000
90040004 4800000C
4E800021 00000069
3C608048 00000000
041A451C 60000000
042F1B88 C82280A0
042F1B34 C82280A0
042F1AF4 C82280A0
042F1A88 C82280A0
042F1A48 C82280A0
042F1950 C84280A0

Multiple pre-assembled codes that use RTStackSnacks (like this gecko code) cannot be installed simultaneously. Use the ASM and module to configure your own assembly in MCM if you want to combine multiple codes.

---


This code is just a proof of concept that uses a vanilla mechanic that I didn’t know much about until recently to create logic for a Jab Lock mechanic in Melee.

An action state counter that is available as a global hword variable in -0x5228(r13) is used by the game to assign a pseudo-unique 16-bit action state transition ID to every character that goes through an action state change; stored at player data offset 0x206C. Previously I’d thought this counted the number of actions the player had made, but it actually counts actions between ALL players -- and is thus a unique identifier in addition to a counter.

It is used in this concept code to detect the continuation of a lock sequence in a way that requires no record of the action states used by the character -- relying instead on the uniqueness of the transition ID before action changes to continue the lock counter trigger for both the missed-tech bounce and the downed action transitions. If an action is performed other than the interrupts for those two actions as caused by the lock mechanic, then the counter will expire and reset its memory.


The code uses this resetting counter to inform a new lock and reset mechanic created for Melee: the MaxLock limit, and the MinWake threshold:



The MinWake threshold determines how many locks are required before landing on the ground will trigger a reset wakeup animation. The default has been set to 1, causing all locks to result in wakeups if the player touches the ground after being hit with a weak attack.

This can be turned off by setting MinWake>MaxLock. Try setting it to 3, like in the above gif.

The MaxLock limit determines how many times a weak attack will trigger the missed tech bounce instead of a regular jab reset. Once the limit has been reached, additional weak hits will result in the vanilla reset flinch, which is highly susceptible to knockback values and SDI for escaping. The default has been set to 2, causing the limit to mimic that of Smash Ultimate.

Set it to 3 to mimic Smash 4, or to a high number like 9999 (or anything lower than 65,535) to create virtually infinite locks in a way similar to Brawl. You can also set it to 0 to completely disable the code, and mimic Melee.


The defaults MaxLock=2; MinWake=1 create very punishable circumstances for missed techs, and expand the possibilities for follow-up attacks through the use of weak attacks that control wakeup timing to prolong combos:



---


Stormghetti Stormghetti -- I used your request to test this module a little bit, but the final code won’t be using it. You can use this gecko code to test this second proof of concept without needing to set up anything special. Any 2 gecko codes made with this module however will not be compatible -- so if you want to install multiple codes made like this one, you will then need to set the module up in MCM first, install the ASM, and use the “generate GCT” feature to compile a configuration of selected codes. Currently this is the only example of such a code, but there may be more in the future.

I’ll be making a new concept later that relies on a similar mechanic for creating the lock counter, but a different method of creating variables; and possibly much more control over the action state transitions; like what animation is used, vulnerability types, animation speed, added visual/audable body aura cues, etc.
 
Last edited:
Top Bottom