• 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!

Completed PDT Stacks Module 1.1

Punkline

Dr. Frankenstack
Joined
May 15, 2015
Messages
423
This code adds space to the end of each player data table as it is allocated in the heap, and uses a DTag Stack container to manage the space in a way that promotes compatibility between any codes that make use of it.

It works by utilizing MCM standalone functions in order to create a set of specialized (and very small) functions that make use of the DTag Stacks module as a foundation.

https://i.imgur.com/4V5djVW.png


!IMPORTANT! - Make sure the DTag Stacks module is installed before this code to avoid dependency issues.


Edit 9/21/2017 - Fixed typo in DOL mod

Download version 1.10 here:

Code:
    -==-

PDT Stacks 1.10
- Requires DTag Stacks Module
Pad in-game player data table allocations by a 0x3800 bytes, and use DTag Stack container to allocate tagged pushes in padding. Allows player codes to be written in a way that saves persistant data.
[Punkline]
1.02 ----- 0x800679bc --- 388023ec -> branch
bl <pdtGlobals>
7C8802A6 80840014
00000000

1.02 ----- 0x80068eec --- 3c808046 -> Branch
7C7E1B78
bl <pdtGlobals>
7C8802A6 38A00000
7FC6F378 80E40010
81040014 8064000C
7C63F214
bl <dtagInit>
3C808046 7FC3F378
00000000

<pdtGlobals> All
4E800021
b <dtagSeek>
b <dtagPush>
b <dtagSeekPush>
00002400 00002800
00006000

<pdtDispatch> All
7C0802A6 90010004
9421FFF8
bl <pdtGlobals>
7CE802A6 8167000C
7C635A14 7D4A3A14
7D4803A6 4E800021
38210008 80010004
7C0803A6 4E800020

<pdtSeek> All
39400000
b  <pdtDispatch>

<pdtPush> All
39400004
b  <pdtDispatch>

<pdtSeekPush> All
39400008
b  <pdtDispatch>

<pdtSafeSeek> All
38C0FFFF
b  <pdtSeek>

<pdtQuickSeek>All
38C00000
b  <pdtSeek>

<pdtSafeSeekPush> All
38C0FFFF
b  <pdtSeekPush>

<pdtQuickSeekPush> All
38C00000
b  <pdtSeekPush>
Code:
    -==-

PDT Stacks 1.10
- Requires DTag Stacks Module
Pad in-game player data table allocations by a 0x3800 bytes, and use DTag Stack container to allocate tagged pushes in padding. Allows player codes to be written in a way that saves persistant data.
[Punkline]
1.02 ----- 0x800679bc --- 388023ec -> branch
# 800679bc : li    r4, 9196
# is executed before players are allocated,
# loading hardcoded immidate 0x23EC
# -- 0x23EC normally becomes size player data table
# -- we set it to whatever is specified in <pdtGlobal>
.set xEnd,   0x14
bl <pdtGlobals>
mflr r4           # pad the end of player data by (size)
lwz r4, xEnd(r4)  # r4 = specified size in globals
.long 0

1.02 ----- 0x80068eec --- 3c808046 -> Branch
# 80068eec : lis r4, 0x8046
# this executes just after each player's HSD_MemAlloc
# -- we use the opportunity to initialize the stack and
# zero out the padded space
# r3 contains fresh player data table start address
# it will be saved to r30, so we store it there for now
.set xHead,  0x0C  # these are offsets;
.set xStart, 0x10  # see <pdtGlobals> for params
.set xEnd,   0x14  # can be edited procedurally
mr r30, r3
bl <pdtGlobals>  # call hits blrl and returns immediately
mflr r4          # pdt globals address becomes init tag
li r5, 0         # ordered, sized, read/write stack
mr r6, r30       # base address == player data start
lwz r7, xStart(r4) # start offset will become address
lwz r8, xEnd  (r4) # end offset will become address
lwz r3, xHead (r4)
add r3, r3, r30    # calculate header address
bl <dtagInit>      # initialize stack in player data

lis r4, 0x8046  # original instruction
mr r3, r30      # restore original context in r3
.long 0


<pdtGlobals> All
blrl
# 0x0
b <dtagSeek>     # dispatch will use these when setting
b <dtagPush>     # up player header for all pdt funcs
b <dtagSeekPush> # (this saves a few lines of code)
# 0xC
.long 0x2400  # 0x0C - header start offset
.long 0x2800  # 0x10 - container start offset
.long 0x6000  # 0x14 - container ending offset
# start is {inclusive} ; end is {}exclusive

# PDT - player data tags
# --  a player data version of the dtag stack (type 0)

<pdtDispatch> All
# handle quick operation that uses globals to specify
# location of header from a player data start address.

# dispatch removes need for multiple runtime (sp) frames
# r3 = player data start address
# r4 = tag for op
# r5 = size for op
# r6 = dupeArg, if applicable
# r10 = dispatch offset from blrl in pdtGlobals
.set rHead, 3
.set rInit, 7
mflr r0
stw  r0,  0x4(sp)
stwu sp, -0x8(sp)  # runtime stack activation

bl <pdtGlobals>    # get globals table
mflr rInit         # pdt uses table address as init tag
lwz  r11, 0xC(rInit)   # load header offset from
add  rHead, rHead, r11 # relocate offset using r3 address
add  r10, r10, rInit   # load branch-in func blrl call
mtlr r10
blrl # dispatch

_return:
addi sp, sp, 0x8   # runtime stack deactivation
lwz  r0, 0x4(sp)
mtlr r0
blr

# cute little functions~
<pdtSeek> All
li r10, 0
b  <pdtDispatch>

<pdtPush> All
li r10, 4
b  <pdtDispatch>

<pdtSeekPush> All
li r10, 8
b  <pdtDispatch>

<pdtSafeSeek> All
li r6, -1
b  <pdtSeek>

<pdtQuickSeek>All
li r6, 0
b  <pdtSeek>

<pdtSafeSeekPush> All
li r6, -1
b  <pdtSeekPush>

<pdtQuickSeekPush> All
li r6, 0
b  <pdtSeekPush>

---

The default player padding initialization extends the region from 0x2400 bytes to 0x6000 bytes; which may be modified through <pdtGlobals> in the DOL mod.

New player data offsets, by default <pdtGlobals> params:
0x2450 - disjointed allocation stack header, 0x30 bytes in length
0x2480 - start of unused padding area, 0x380 bytes in length.
0x2800 - start of allocation stack container, 0x3800 bytes in length
0x6000 - end of player data and stack container

The unused padded area can be directly written to if desired. This is not recommended for the sake of sharing; as it will not be compatible with other codes that do so. It may still serve as useful for personal mods and experiments, however.

Rather than use these offsets directly, the code gives you a set of functions that can manage the stack without any knowledge of table’s displacement offset. For the most part, these are the only 3 things that you will need:

r3 = player data start address
r4 = 32-bit allocation tag (used in search identity and/or push definitions)
r5 = size of allocation in bytes (used in search identity and/or push definitions)

The DTag Stacks module was designed to use these 3 arguments in common with all included stack operation functions. These essentially just comprise of Seek, Push, and combinations/permutations of the two.

An example using the included <pdtSafeSeekPush> function:

Code:
# r31 is assumed to hold external player entity address

lwz    r3, 0x2C(r31)      # load internal player data table
lis    r4, 0x8028         # specify a tag
li     r5, 0x70           # specify a size
bl     <pdtSafeSeekPush>  # call SeekPush
cmpwi  r3, 0              # check for null
beq-   _return            # return if null

_code:
# code functionality goes here

_return:
blr
Gonna use Zelda magic to explain this.

Farore’s Wind Interface :
SeekPush functions can initialize and recall a waypoint from the same input arguments. These two functionalities are both accessed from a single spell set of calling instructions.

Zelda Sheik @DRGN had a great idea for using ocarina melodies ascii strings as a way of providing a key that confirms a valid location for codes intending to read structured data. This inspired the container module used to create this interface; limiting the keys to 32-bit numbers for efficient scans.

This interface may be used to cover the step of setting up an allocation, and later the step of recalling it. No extra written code is required to cover both cases.


Din’s Fire Allocation :
If the specified tag doesn’t exist in this player’s dictionary, then -- provided that there’s room left in the stack for a push of the specified <alloc size> + 8-byte tag -- the code will carve out a chunk of the padding left in the player data table, and designate a new symbol for it in the tag dictionary

The number of bytes specified for the allocation will be used to push the decrementing allocation stack frame inside the container if it is not set to 0. Since the container is push-only, the tag’s order in the dictionary can optionally be used to later recover its Push size during Seek operations.


Nayru’s Love Logic :
A byte number provided in r5 will cause the allocation size of a push symbol to become part of the scan identity.

This somewhat reduces the chance of running into compatibility conflicts that would normally be caused by multiple codes using the same 32-bit tag identifier. Scans will skip this distinction if r5 is provided as ‘0’ -- checking only for 32-bit tag identifiers.


Code:
if:           then: FALSE  ,  TRUE
r4 = ‘tag_’         ‘tag_’ , ‘tag_’
r5 = 0x70            0x48  ,  0x70

if:           then:  TRUE  ,  TRUE
r4 = ‘tag_’         ‘tag_’ , ‘tag_’
r5 = 0               0x48  ,  0x70
With SafeSeekPush, these cases where more than one identical tag/size instances are found will cause the code to detect a null return instead of a valid address; resulting in the code disabling itself.

This measure protects the game from potential complications that might otherwise cause a crash. A number of other predictable error cases will recycle this null mechanic, allowing each to be checked (simultaneously) with a single logical comparison after return. The type of error can be derived from return value in r6, if necessary.


---

So let’s break down what those 6 call instructions are doing.

The bl <pdtSeekPush> line is a special branch syntax in MCM. The lines above it specify our r3, r4, and r5 arguments.



Our inputs for the call in this example are:
r3 = ( player data start address )
r4 = 0x80280000 ( a “name” )
r5 = 0x70 ( number of bytes we want for our allocation )


The returned values are:
r3 = Address of tagged allocation, (or Null for Error)
r4 = Allocation Tag
r5 = Allocation Size
r6 = Player Data Offset of Allocation start, ( or Error Code )
r7 = 1-based duplicate instance ID ( or Null for new Push )
r8 = 1-based duplicate instance count
r9 = 0-based dictionary ID ( dictionary element index )


If the specified r3, r4, and r5 inputs can’t provide a valid match, and the DUPE_LIMIT was not exceeded; then the function will push a new tag/allocation matching those specifications and return the new tag instead; if possible.

In all future calls using the same specifications -- so long as the player has remained allocated in the heap consistently between calls and no other codes have registered an identical tag -- the return values will consistently reference a symbol of the same push in the stack.

So if this example call were to run each frame, the first call would create the allocation and use that as the return value. Any following calls will cause the allocation pushed in the first call to be recalled -- save for any errors.



The cmpwi r3, 0 at the end is a check for null. It’s possible for the stack to be full when attempting a push, a seek to return 0 matches, too many matches, etc. In these null cases, we branch to a handle that aborts the execution of our code. This disables the code in cases where an address could not be returned for any reason.

In cases where the returned r3 address is null, the returned r6 value will also be <= 0 as a way of representing an error code.

As of DTag Stacks 0.27, these are:
0 - NO_MATCH_ERROR (Seek operations)
-1 - DUPE_LIMIT_ERROR (Seek operations)
-2 - VALIDATION_ERROR (Seek and Push operations)
-3 - NO_POINTER_ALERT (Seek and Push operations)
-4 - PUSH_LIMIT_ERROR (Push operations)
-5 - READ_ONLY_ERROR (Push operations)


These don’t need to be checked for, but may optionally be used to handle specific cases.

For instance, the “SeekPush” operations included in this module are just “Seek” operations that handle a NO_MATCH_ERROR with a “Push” operation.

In Seek operations, r6 is an additional argument ( 1-based ID ) that can make a scan a bit more specific. In plain Seek and SeekPush functions, this ID can be used to specify a place in an index of “duplicate” instances as an additional match condition.

In PDT containers, this means specifying which of two or more identically named/sized symbol instances to return. Only one tag is returned from the Seek operation, but the 1-based duplicate ID is returned as r7 -- with r8 as the total duplicate count.

By specifying 0 as r6, Seek operations will skip the process of looking for duplicates and use the first matching instance as the only return condition.

The “Quick” versions of Seek and SeekPush merely branch into the plain versions of the function with 0 as a default value for r6 -- invoking the above behavior without the need to specify it.

By specifying a negative number in r6, its absolute value will be treated like a positive ID -- however if any additional instances are found beyond this matching ID, it will throw a DUPE_LIMIT_ERROR instead. This is meant to allow codes to be safely handled in cases where there may be a conflict of tag identities between codes.

The “Safe” versions of Seek and SeekPush branch into the plain versions of the functions with -1 as a default value for r6.

---

Some further documentation, for those intending to develop with these functions:

<pdtPush>
input:
r3 = internal player data start address
r4 = 32-bit tag
r5 = allocation size (or 0)


the container dictionary is pushed by 8 bytes to make room for a new tag
the 32-bit tag is written at offset 0x0 of the dictionary element

if r5 is set to a number greater than 0,
then the container pushes the stack frame to make room for that many bytes.
the address of the new push is written at offset 0x4 of the dictionary element
the frame address is returned in r3

else, if r5 == 0
then the unallocated dictionary element address is returned in r3
r6 provides an error code to indicate that pushed tag contains a null pointer

output:
r3 = returned address, or null for error
r4 = 32-bit tag
r5 = allocation size
r6 = player data offset, or error code
r9 = dictionary ID
<pdtSeek>
input:
r3 = internal player data start address
r4 = 32-bit tag
r5 = allocation size (or 0)
r6 = duplicate instance count/limit


for each tag in this player’s dictionary:
if the tag matches r4,
-- if the size matches r5; OR if r5 == 0
---- if r6 == 0, then return match
---- else, if abs(r6) == current match count, then save record of current tag
------ if r6 is negative AND match has already been found,
------ then throw DUPE_LIMIT_ERROR

if no matches were found, throw NO_MATCH_ERROR

When returning a match, if the tag pointer is blank, the tag address is returned in r3.

Otherwise, the allocation pointed to by the tag is returned in r3.

output:
r3 = returned address, or null for error
r4 = 32-bit tag
r5 = allocation size
r6 = player data offset, or error code
r7 = matching duplicate instance ID
r8 = total duplicates counted
r9 = dictionary ID
<pdtSafeSeek>
input:
r3 = internal player data start address
r4 = 32-bit tag
r5 = allocation size (or 0)


Perform a Seek operation with a strict limit on the number of duplicate instance matches allowed in results.

If SafeSeek finds more than one instance of the specified symbol, it will return DUPE_LIMIT_ERROR in place of a symbol.

output:
r3 = returned address, or null for error
r4 = 32-bit tag
r5 = allocation size
r6 = player data offset, or error code
r7 = matching duplicate instance ID
r8 = total duplicates counted
r9 = dictionary ID
<pdtQuickSeek>
r3 = internal player data start address
r4 = 32-bit tag
r5 = allocation size (or 0)


Perform a Seek operation without accounting for duplicate matches.

Returns after first matching instance is found -- making the scan faster than a complete Seek operation.

output:
r3 = returned address, or null for error
r4 = 32-bit tag
r5 = allocation size
r6 = player data offset, or error code
r7 = matching duplicate instance ID
r8 = total duplicates counted
r9 = dictionary ID
<pdtSeekPush>
input:
r3 = internal player data start address
r4 = 32-bit tag
r5 = allocation size (or 0)
r6 = duplicate instance count/limit


Perform a Seek operation.

If the Seek operation returns a NO_MATCHES_ERROR then perform a Push operation.

After push has been performed, set returned r7 to Null 0 to signify a push was made.

output:
r3 = returned address, or null for error
r4 = 32-bit tag
r5 = allocation size
r6 = player data offset, or error code
r7 = matching duplicate instance ID or null for push
r8 = total duplicates counted
r9 = dictionary ID
<pdtSafeSeekPush>
input:
r3 = internal player data start address
r4 = 32-bit tag
r5 = allocation size (or 0)


Perform a SeekPush operation, but with SafeSeek parameters.

If Seek operation returns DUPE_LIMIT_ERROR in place of a NO_MATCHES_ERROR -- then no push is made.

output:
r3 = returned address, or null for error
r4 = 32-bit tag
r5 = allocation size
r6 = player data offset, or error code
r7 = matching duplicate instance ID or null for push
r8 = total duplicates counted
r9 = dictionary ID
<pdtQuickSeekPush>
input:
r3 = internal player data start address
r4 = 32-bit tag
r5 = allocation size (or 0)


Perform a SeekPush operation, but with QuickSeek parameters.

If Seek operation finds any matches, it will return the result immediately and forgo pushing.

output:
r3 = returned address, or null for error
r4 = 32-bit tag
r5 = allocation size
r6 = player data offset, or error code
r7 = matching duplicate instance ID or null for push
r8 = total duplicates counted
r9 = dictionary ID

d
 
Last edited:

V_D_X

Smash Cadet
Joined
May 16, 2015
Messages
29
I feel like this and the DTags stack module codes could potentially do a lot of crazy stuff, but I'm having a hard time thinking of specific situations for which they could be used. What are some other things you could do with them, besides the collision ghosts model code you've already shown?
 

UnclePunch

Smash Ace
Joined
Nov 9, 2014
Messages
673
I feel like this and the DTags stack module codes could potentially do a lot of crazy stuff, but I'm having a hard time thinking of specific situations for which they could be used. What are some other things you could do with them, besides the collision ghosts model code you've already shown?
Anything that needs space to store info, so a lot of stuff. I'm going to convert my turbo mode code to use this space rather than whats being used now (variable that stores when kirby's hammer was used midair).
 

Punkline

Dr. Frankenstack
Joined
May 15, 2015
Messages
423
I feel like this and the DTags stack module codes could potentially do a lot of crazy stuff, but I'm having a hard time thinking of specific situations for which they could be used. What are some other things you could do with them, besides the collision ghosts model code you've already shown?
Basically, the core module just describes a blueprint for a special kind of container object, and this PDT module sets up one of these containers while the game allocates space in RAM for player entities while they’re being initialized.

The result is a sort of pile that developers can throw their data in without having to worry too much about where it ends up in RAM. When the data needs to be recalled, you make your code whistle and call the data’s name rather than look in a specific place.

---

This is useful because in order for code writers to include seemingly simple features like “timers” or “states” -- codes need to memorize data for longer than they are being executed.

It’s possible to achieve this in a number of ways, but they are methods that are incompatible with other codes that do the same, and/or are wasteful uses of precious code space -- a relatively limited resource compared to heap space.

This method avoids compatibility issues and uses unallocated space in RAM just as a loaded file would.

---

My apologies for being slow to provide examples. It’s a pretty abstract thing without them.

I’m actually really eager to put PDT to use, but am currently focusing on a second module that allows codes to interface with any dat file for the purpose of reading in custom data. It uses the core module to “seek” read-only stacks of tags included at the end of a dat file--which can then point anywhere else in the file via file data offset.

This would let codes access anything a person throws in their file data, which I think would really complement this code well for designing character mods.

I don't currently have much in the way of examples right now, but here are a few things I have planned with all of the modules together:

Data Visualization experiments
  • ECB logger -- Experiment from early POC. Edit - sorry, no attached ASM at the moment. This will be rewritten soon to work with the new PDT module.
  • HSD exploration with binary visualization -- This is footage from an old cheat engine experiment I recorded, where I fed sound data into various image types after they'd already been declared as active HSD instances in the heap. It's possible to use PDT to direct pointers that might be used for purposes like this.
    • MissingNo. !
Player subaction event syntax patch
  • Goto and subroutine relocations no longer require use of DAT relocation table
  • A selection of suitable vanilla syntaxes are modified to support thousands of additional subaction event parsing functions (tying standalone functions to parsable move data syntaxes usable in crazy hand)
  • A standalone function that can be used to evoke event data parses in a way that ignores timers -- allowing it to be used for writing procedurally written move-data
  • A section of player data files that can be used to extend subaction moveset data through a dedicated 1-line event that acts as a sort of portal.
  • A set of registers that store persistent--but volatile (and perhaps savable)--information for making basic expressions used for conditional gotos -- turning the event data system into a language capable of creating logical flow.
Attribute coefficients
  • Allow for N amount of optionally timed/conditionally reset coefficients that copy the original value of player data offset X and stores them alongside some data that defines its data type, its timer, its reset condition, and its current deviation from 1.00
    • original value would be interpolated according to the coefficient, and written to its original offset once per frame
    • upon reset, the value would be restored from memory
  • Would allow for transient modifications to character attributes in a way that is not permanent, or disregarding of file-data specifications
  • Would have a corresponding subaction event data syntax
Dev Widgets
  • Using the same exploit used to inflate player entities, create a new module that makes a stack inside of the player bonuses developer mode text buffer used on screen when pressing B + dleft
  • create a jump table that inserts itself at the end of the cycling index of "display modes" available for this developer mode hotkey.
    • On each new state, collapse the stack and run an initialization function mapped via the included jump table
  • -- would allow for standalone functions to create new developer mode displays that could easily display things like targeted file data offsets using large amounts of formatted text.
Taunt Menu
  • attempting to smash charge a taunt puts player in continuous smash charge while holding A button
  • in this state, a standalone function uses a provided jump table to map hotkeys to different event callbacks written as other standalone functions
  • Only default included hotkey is A + start -- which puts player in an in-game menu state that creates a small debugmode text box on top of their damage percentage graphic.
  • (a new jump table could then be used to plug custom functionality into said menu state)
Sword Trails for everyone
  • would actually be as simple as allowing for the same data format used by marth/roy and link/ylink in extended padding area.
  • complications caused by fact that involved JObjs are designed to be sword hilts; and applying to other characters has strange effects on the trail transformations over time.
 
Last edited:
Top Bottom