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

In Progress Subaction Event Scheduler

Punkline

Dr. Frankenstack
Joined
May 15, 2015
Messages
423
So here’s a big project I’m working on.

Project goals range from many things, but largely revolve around code-based subaction event manipulation. It is a long work in progress, and will inevitably involve many concept codes and refinements as I try different things.

SAES v0.02 -- Subaction Event Scheduler
Last updated 2/9/2016

Append, replace, or skip subaction event data via a special data syntax that you may write to an "input table" included in the code. See UPDATE: 0 for more information.

Experimental and subject to change~

C2 code, with all of the data tables and all of the poorly written utilities stuffed into one:

$SAeventSchedulerv002 [Punkline]
C205C038 0000007A
48000014 4E800020
4E800020 4BFFFFF8
4BFFFFF8 38000000
480000DD 7F0802A6
3B18000C 4800018C
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000

03000000 4E800020
4E800020 2C000000
40820024 7C000026
3821FFDC D8010000
D8210008 D8410010
90010018 9061001C
90810020 3821FFF0
92A10000 92C10004
92E10008 9301000C
4E800020 2C000000
82A10000 82C10004
82E10008 8301000C
38210010 40820020
C8010000 C8210008
C8410010 80010018
8061001C 80810020
38210024 4E800020
80040000 3884FFFC
90010000 3821FFFC
3863FFFF 2C030000
4181FFE8 4E800020
80010000 38210004
90040000 38840004
3863FFFF 2C030000
4181FFE8 4E800020
7F17C378 3AA00000
88170000 2C000000
418201F4 2C000003
418201F4 C01D0000
C0228874 FC000840
88770001 40810014
5460039D 4082000C
3AA00002 48000050
546003DF 5463041A
98770001 4182000C
3AA00001 48000038
88170000 2C000002
4182FFD8 809D0008
2C040000 4082000C
38800000 4800000C
88840000 5484063A
88770001 7C032000
4082FFB0 38600080
38800028 3AC00004
88170002 7C600039
40820018 70600001
40820070 5463F83E
38840004 4BFFFFE4
3AD60004 38000001
4BFFFE85 7C751B78
7C962378 4BFFFE79
7C6802A6 7C632214
7C6803A6 4E800021
38000001 4BFFFEA1
7EA3AB78 7EC4B378
4BFFFE95 4BFFFFB0
4BFFFE44 4BFFFE40
4BFFFE3C 4BFFFE38
4BFFFE34 4BFFFE30
4BFFFE2C 4BFFFE2C
3AC00004 2C150002
7EC3B378 82D70000
7EE3BA14 418200CC
56C3E73F 4182009C
809D0008 3884FFFC
4BFFFE81 38840004
7C350B78 7EE1BB78
56C3E73E 4BFFFE8D
7EA1AB78 3821FFFC
56C3F6BA 7C832050
909D0008 88840000
5484F6BE 2C04000A
5484103E 4080001C
3C60803C 606367C0
7C632214 7C6803A6
7FA3EB78 4800001C
3804FFD8 7C7F0214
80630000 7C6803A6
7FA4EB78 387DFBBC
4E800021 38210008
56C3F6BA 809D0008
7C832050 5463F03E
4BFFFE19 3821FFFC
56C316BB 41820024
809D0008 7C832214
909D0008 88840000
5484063A 56C3863A
7C032000 4182FE0C
56C3F6BA 7EE3BA14
4BFFFE04 3AF70004
4BFFFDFC 38000000
4BFFFD75 7C0FF120
801D0008 00000000

Injection Mods with considerably more length in the input field:

Code:
    -==-


SAevent Scheduler v0.02 (0x100)
*CONCEPT CODE*
Use an input field (~0x100 bytes in length) to modify subaction events as they are read.
FF EE BB IS
FF = function ID (00 skip, 01 input, 02 fail, 03 terminate parse)
EE = trigger event
BB = (bitfield, used to extend the code)
I = length of event to insert (include actual event data after input line)
S = length of trigger event, to skip
[Punkline]
Version -- DOL Offset ------ Hex to Replace ---------- ASM Code
1.02 ----- 0x800732AC --- 801D0008 -> Branch

48000014 4E800020
4E800020 4BFFFFF8
4BFFFFF8 38000000
4800011D 7F0802A6
3B18000C 480001CC
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
03000000 4E800020
4E800020 2C000000
40820024 7C000026
3821FFDC D8010000
D8210008 D8410010
90010018 9061001C
90810020 3821FFF0
92A10000 92C10004
92E10008 9301000C
4E800020 2C000000
82A10000 82C10004
82E10008 8301000C
38210010 40820020
C8010000 C8210008
C8410010 80010018
8061001C 80810020
38210024 4E800020
80040000 3884FFFC
90010000 3821FFFC
3863FFFF 2C030000
4181FFE8 4E800020
80010000 38210004
90040000 38840004
3863FFFF 2C030000
4181FFE8 4E800020
7F17C378 3AA00000
88170000 2C000000
418201F4 2C000003
418201F4 C01D0000
C0228874 FC000840
88770001 40810014
5460039D 4082000C
3AA00002 48000050
546003DF 5463041A
98770001 4182000C
3AA00001 48000038
88170000 2C000002
4182FFD8 809D0008
2C040000 4082000C
38800000 4800000C
88840000 5484063A
88770001 7C032000
4082FFB0 38600080
38800028 3AC00004
88170002 7C600039
40820018 70600001
40820070 5463F83E
38840004 4BFFFFE4
3AD60004 38000001
4BFFFE85 7C751B78
7C962378 4BFFFE79
7C6802A6 7C632214
7C6803A6 4E800021
38000001 4BFFFEA1
7EA3AB78 7EC4B378
4BFFFE95 4BFFFFB0
4BFFFE44 4BFFFE40
4BFFFE3C 4BFFFE38
4BFFFE34 4BFFFE30
4BFFFE2C 4BFFFE2C
3AC00004 2C150002
7EC3B378 82D70000
7EE3BA14 418200CC
56C3E73F 4182009C
809D0008 3884FFFC
4BFFFE81 38840004
7C350B78 7EE1BB78
56C3E73E 4BFFFE8D
7EA1AB78 3821FFFC
56C3F6BA 7C832050
909D0008 88840000
5484F6BE 2C04000A
5484103E 4080001C
3C60803C 606367C0
7C632214 7C6803A6
7FA3EB78 4800001C
3804FFD8 7C7F0214
80630000 7C6803A6
7FA4EB78 387DFBBC
4E800021 38210008
56C3F6BA 809D0008
7C832050 5463F03E
4BFFFE19 3821FFFC
56C316BB 41820024
809D0008 7C832214
909D0008 88840000
5484063A 56C3863A
7C032000 4182FE0C
56C3F6BA 7EE3BA14
4BFFFE04 3AF70004
4BFFFDFC 38000000
4BFFFD75 7C0FF120
801D0008 00000000



    -==-


SAevent Scheduler v0.02 (0x300)
*CONCEPT CODE*
Use an input field (~0x300 bytes in length) to modify subaction events as they are read.
FF EE BB IS
FF = function ID (00 skip, 01 input, 02 fail, 03 terminate parse)
EE = trigger event
BB = (bitfield, used to extend the code)
I = length of event to insert (include actual event data after input line)
S = length of trigger event, to skip
[Punkline]
Version -- DOL Offset ------ Hex to Replace ---------- ASM Code
1.02 ----- 0x800732AC --- 801D0008 -> Branch

48000014 4E800020
4E800020 4BFFFFF8
4BFFFFF8 38000000
4800031D 7F0802A6
3B18000C 480003CC
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
03000000 4E800020
4E800020 2C000000
40820024 7C000026
3821FFDC D8010000
D8210008 D8410010
90010018 9061001C
90810020 3821FFF0
92A10000 92C10004
92E10008 9301000C
4E800020 2C000000
82A10000 82C10004
82E10008 8301000C
38210010 40820020
C8010000 C8210008
C8410010 80010018
8061001C 80810020
38210024 4E800020
80040000 3884FFFC
90010000 3821FFFC
3863FFFF 2C030000
4181FFE8 4E800020
80010000 38210004
90040000 38840004
3863FFFF 2C030000
4181FFE8 4E800020
7F17C378 3AA00000
88170000 2C000000
418201F4 2C000003
418201F4 C01D0000
C0228874 FC000840
88770001 40810014
5460039D 4082000C
3AA00002 48000050
546003DF 5463041A
98770001 4182000C
3AA00001 48000038
88170000 2C000002
4182FFD8 809D0008
2C040000 4082000C
38800000 4800000C
88840000 5484063A
88770001 7C032000
4082FFB0 38600080
38800028 3AC00004
88170002 7C600039
40820018 70600001
40820070 5463F83E
38840004 4BFFFFE4
3AD60004 38000001
4BFFFE85 7C751B78
7C962378 4BFFFE79
7C6802A6 7C632214
7C6803A6 4E800021
38000001 4BFFFEA1
7EA3AB78 7EC4B378
4BFFFE95 4BFFFFB0
4BFFFE44 4BFFFE40
4BFFFE3C 4BFFFE38
4BFFFE34 4BFFFE30
4BFFFE2C 4BFFFE2C
3AC00004 2C150002
7EC3B378 82D70000
7EE3BA14 418200CC
56C3E73F 4182009C
809D0008 3884FFFC
4BFFFE81 38840004
7C350B78 7EE1BB78
56C3E73E 4BFFFE8D
7EA1AB78 3821FFFC
56C3F6BA 7C832050
909D0008 88840000
5484F6BE 2C04000A
5484103E 4080001C
3C60803C 606367C0
7C632214 7C6803A6
7FA3EB78 4800001C
3804FFD8 7C7F0214
80630000 7C6803A6
7FA4EB78 387DFBBC
4E800021 38210008
56C3F6BA 809D0008
7C832050 5463F03E
4BFFFE19 3821FFFC
56C316BB 41820024
809D0008 7C832214
909D0008 88840000
5484063A 56C3863A
7C032000 4182FE0C
56C3F6BA 7EE3BA14
4BFFFE04 3AF70004
4BFFFDFC 38000000
4BFFFD75 7C0FF120
801D0008 00000000


And the ASM. Not proud of this mess… I should explain that this was at around the time I was discovering how the stack could be used, and so I did a lot of really stupid things in here. It’s actually kind of amazing that it runs, in retrospect:

Code:
#@800732AC: lwz r0, 0x8(r29)    (load SAevent pointer for read loop)
#context:
#LR is safe
#cr0 is safe, but cr2 may be important to context
#r0 == safe as GPR
#r31 == non-control event pointer
#r29 == player.0x444

        b dataP        #branch past the subroutine data to the data pointer constructor

#---data1----------------------------------
#---subroutine functions
ss04:    blr        #<placeholder for unwritten subroutines>

ss00:    #SUBROUTINE: do nothing (useful for disabling a subroutine)
    blr

#---subroutine branch table (TOC)
    b ss04
ssTOC:    b ss00         # << subroutine branches go here -0x18(rP)
#---data1 end------------------------------

dataP:  li r0, 0        #load 0 to tell backup function to back up lots of registers
        bl backup
        mflr r24        #take lr and create a pointer sandwiched between these two data tables
    addi r24, r24, 0xC    #located here, this shouldn’t ever need to be adjusted
    b start

#---data2----------------------------------
#---inputs
#Enter any subaction modifications below (number of inputs can be arbitrary)
# FF = function ID, EE = event ID search, BB = bits toggle inline condition checks,
# I = Insert event length (in 4byte,) S = Skip event length (in 4byte)
        #     0xFFEEBBIS
            .long 0x00000000
            .long 0x00000000
            .long 0x00000000
            .long 0x00000000
            .long 0x00000000
            .long 0x00000000
            .long 0x00000000
            .long 0x00000000
            .long 0x00000000
            .long 0x00000000
#add as many lines as you want here (don't add lines to the machine code, this gets branched over!)
            .long 0x03000000    #DO NOT REMOVE, use “0x03000000” to finish input data!

#---BB line functions
#These functions use the BB byte to select extra lines that are appended to the Insert code
bb1:
bb2:
bb3:
bb4:
bb5:
bb6:
bb7:    blr    #<placeholder for unwritten inline condition functions>

bb8:    #FUNCTION: specify a subroutine, and any memory lines for it (or other subroutines) to use
    blr

#---other (reusable) functions
backup:    #FUNCTION: uses r0 to determine how many registers to back up to the stack
    #    the purpose is to give some cheap reusability to these lines for subroutines and stuff
    #    r0 == (not 0) means only r21, r22, r23, and r24 are backed up
    #    r0 == 0 means the above are backed up, along with f0, f1, f2, CR (as r0,) r3, and r4.
    cmpwi r0, 0
    bne bckp1              # if r0 == 0, then back up ALL of the following
    mfcr r0                  #back up CR as r0
    subi sp, sp, 0x24    #move stack for 3 floats and 3 ints
    stfd f0, 0x0(sp)
    stfd f1, 0x8(sp)
    stfd f2, 0x10(sp)
    stw r0, 0x18(sp)
    stw r3, 0x1C(sp)
    stw r4, 0x20(sp)    #if was != 0, then only back up these 4 ints
bckp1:    subi sp, sp, 0x10    #move stack for 4 ints
    stw r21, 0x0(sp)
    stw r22, 0x4(sp)
    stw r23, 0x8(sp)
    stw r24, 0xC(sp)
    blr

restore: #FUNCTION: loads backed up values from the stack with the same options as the above function
    cmpwi r0, 0
    lwz r21, 0x0(sp)
    lwz r22, 0x4(sp)
    lwz r23, 0x8(sp)
    lwz r24, 0xC(sp)
    addi sp, sp, 0x10
    bne rstr1        # skip the extra backup if r0 was != 0
    lfd f0, 0x0(sp)
    lfd f1, 0x8(sp)
    lfd f2, 0x10(sp)
    lwz r0, 0x18(sp)    #note that this still holds old CR
    lwz r3, 0x1C(sp)
    lwz r4, 0x20(sp)
    addi sp, sp, 0x24
rstr1:    blr

x2stack: #FUNCTION: takes x lines (r3) from r4 (bottom to top) and puts them on the stack
    lwz r0, 0(r4)        #if the sp is backed up in another register
    subi r4, r4, 4        #then it’s also possible to put "y" in the sp and use this to move "x2y"
    stw r0, 0(sp)
    subi sp, sp, 4
    subi r3, r3, 1
    cmpwi r3, 0
    bgt x2stack        #loop until x is exhausted
    blr

stack2x: #FUNCTION: reverse of the above, and writes lines from the stack to x
    lwz r0, 0(sp)
    addi sp, sp, 4
    stw r0, 0(r4)
    addi r4, r4, 4
    subi r3, r3, 1
    cmpwi r3, 0
    bgt stack2x
    blr
#---data2 end------------------------------

#--CODE START--
#The code is basically just a loop that determines whether
#    or not to run any specified Inserts, Skips, or Subroutines. <<still working on subroutines
#    FF EE BB IS    is the format of the input line, which specifies most of this information

#r24 = rP = data Pointer (used to reference any subroutines and start checking inputs)
#r23 = rI = current input line being read
#r22 = rBB = count of extra length for this input (selected by the BB byte)
#r21 = rR = condition result for current input line (subroutine/insert/skip check)
#    result 0 = still checking, 1 = forced, 2 = failed

#Note that the loop is not aware of the length of each input, or the total number of inputs until they're checked
#    Each line is responsible for containing information that informs the loop each iteration
#    BBloop determines the extra length added by any programmable BB lines


start:    mr r23, r24        #create rI for loopIn (input loop)

loopIn:    li r21, 0        #start of input loop, result = 0 (checking)
    lbz r0, 0(r23)        #load FF ID to check for a skip function (ignore current line and read next one)
    cmpwi r0, 0
    beq nxtIn        #if FF == 0 (skip) then don't check this line and move on to the next one
    cmpwi r0, 3
    beq endIn        #if FF == 3 (end) then finish the input loop

intrpt:    lfs f0, 0(r29)        #load in subaction timer (float)
    lfs f1, -0x778C(rtoc)    # 0.0
    fcmpo cr0, f0, f1    #
    lbz r3, 0x1(r23)    #load EE in r3
    ble force
    rlwinm. r0, r3, 0, 14, 14    #check for interrupt flag, if subaction timer is still ticking
    bne force            #continue as normal if flagged

iFAIL1:    li r21, 2            #else, mark Condition Result as FAILED (loop continues to check line lengths)
    b BBlines            #skip to BBlines, to find length for skipping to next input

force:    rlwinm. r0, r3, 0, 15, 15    #check for forced bit
    rlwinm r3, r3, 0, 16, 13    #unflag both bits (they are only to stay on for one frame at a time)
    stb r3, 0x1(r23)            #update flags
    beq EEcheck
    li r21, 1            #mark result as FORCED (can't fail conditions)
    b BBlines

EEcheck:lbz r0, 0(r23)
    cmpwi r0, 2        #check if FF ID is == 2 (currently unused, fails the input)
    beq iFAIL1
    lwz r4, 0x8(r29)
    cmpwi r4, 0
    bne point
    li r4, 0
    b nopoint

point:    lbz r4, 0(r4)
    rlwinm r4, r4, 0, 24, 29    #r0 = 6-bit event ID
nopoint:lbz r3, 0x1(r23)        #r3 = 6-bit EE search
    cmpw r3, r4
    bne iFAIL1        #fail condition result if not a match

BBlines: #this byte contains bits that toggle extra lines for conditions and subroutines
    #    checking these are critical to the operation of the loop because
    #    they contain information about how long each line is.
    li r3, 0x80        #r3 = "loop bit"
    li r4, 0x28        #r4 = "BB func offset"
    li r22, 4        #reset "BBline count"

BBloop:    lbz r0, 0x2(r23)    #load BB byte from input
    and. r0, r3, r0        #compare BB byte to "loop bit"
    bne BBcheck        #if bit is toggled, then account for it in BBcheck

BBnext:    andi. r0,r3,1        #check if loop is finished
    bne BBend
    rlwinm r3, r3, 31, 0, 31    #if not, then shift loop bit for next iteration
    addi r4, r4, 4        #and increment BB func offset
    b BBloop        #loop iterate

BBcheck: #when BBlines finds a BBline, it counts it here, and runs its function
    addi r22, r22, 4    #count BB line--note that more lines can be specified by each
    li r0, 1        #    bb function, and so those must be counted inside the function
    bl backup
    mr r21, r3
    mr r22, r4
    bl backup        #make room in the registers for fancy BB functions

    mflr r3
    add r3, r3, r4        #construct pointer to appropriate BBfunc
    mtlr r3
    blrl            #call function

    li r0, 1
    bl restore
    mr r3, r21
    mr r4, r22
    bl restore        #all registers are restored, and BB function is finished
    b BBnext        #return to loop

bbTOC:    b bb1            #(these get selected by the pointer constructed for BLRL)
    b bb2
    b bb3
    b bb4
    b bb5
    b bb6
    b bb7
    b bb8

BBend:    #once BB lines are all checked, the Result Condition in r21 is tested before insert/skip
    li r22, 4        #reset "BBline count"

    cmpwi r21, 2        #r21 is now free
    mr r3, r22        #r22 is now free
    lwz r22, 0(r23)        #otherwise, get ready to handle the SAevent insert (if applicable)
    add r23, r3, r23    #update rI to point after BBlines (r22 now holds Input line)
                #r23 == Insert Event (or next Input line, when no insert)
                #r22 == FF EE BB IS of this event
    beq nxtInI        #finish loop iteration if result was == FAIL

Icheck:    rlwinm. r3, r22, 28, 28, 31    #load/check "I" 4-bit value (word length)
    beq Scheck        #skip Icheck if I <= 0 (no insert event to read)

Insert:    lwz r4, 0x8(r29)    #load event pointer for x as x2stack
    subi r4, r4, 4        #correct stack...
                #r3 holds input length
    bl x2stack        #back up (real) SAevent lines in stack
    addi r4, r4, 4        #correct stack...
    mr r21, sp        #back up the stack pointer so we can use stack2x in a funky way
    mr sp, r23        #use input lines as "stack" (input for function)
    rlwinm r3, r22, 28, 28, 31    #load "I" 4-bit value (as word length
    bl stack2x         #this should move input lines to the current SAevent lines
    mr sp, r21        #restore stack pointer
    subi sp, sp, 4        #correct stack...

IreadE:    rlwinm r3, r22, 30, 26, 29    #load "I" 4-bit value (as byte length)
    subf r4, r3, r4        #offset event pointer by I
    stw r4, 0x8(r29)    #update event pointer
    lbz r4, 0(r4)
    rlwinm r4, r4, 30, 26, 31    #load 6-bit Event id
    cmpwi r4, 10        #we're ready at this point to run our fake Event
    rlwinm r4, r4, 2, 0, 31    #unrotate for offset
    bge event        #if ID is < 10, then it is a control event, not a normal one

cEvent:    lis r3, 0x803c
    ori r3, r3, 0x67c0    #so construct pointer to control SAevent functions
    add r3, r3, r4        #
    mtlr r3            #
    mr r3, r29        #
    b readI

event:    subi r0, r4, 40        #else, r0 = 6-bit event ID - 10 (non-control event)
    add r3, r31, r0        #r31 holds pointer to control event functions (hook context)
    lwz r3, 0(r3)
    mtlr r3
    mr r4, r29        #move player.0x444 to r4 instead of r3
    subi r3, r29, 0x444    #creates pointer to player.0x0 in r3, for event functions

readI:    blrl            #read inserted subaction event
    addi sp, sp, 8        #correct stack...
    rlwinm r3, r22, 30, 26, 29    #load "I" 4-bit value (as byte length)
    lwz r4, 0x8(r29)
    subf r4, r3, r4
    rlwinm r3, r3, 30, 0, 31        #shift r3 for stack2x
    bl stack2x        #repair the lines we clobbered by loading stored SAevent from the stack
    subi sp, sp, 4        #correct stack...
                #the inserted subaction event has been read, and memory should now appear untouched

Scheck:    rlwinm. r3, r22, 2, 26, 29    #load/check "S" 4-bit value
    beq nxtInI        #finish loop iteration if nothing specified for "S" skip
    lwz r4, 0x8(r29)
    add r4, r3, r4        #otherwise, add S to the event pointer
    stw r4, 0x8(r29)    #and store it
    lbz r4, 0(r4)
    rlwinm r4, r4, 0, 24, 29     #check to see if new event also matches EE
    rlwinm r3, r22, 16, 24, 29
    cmpw r3, r4
    beq start            #if they are, then start over from the beginning of the input loop

nxtInI:    rlwinm r3, r22, 30, 26, 29    #load "I" 4-bit value (as byte length)
    add r23, r3, r23
    b loopIn
nxtIn:    addi r23, r23, 4
    b loopIn

endIn:    li r0, 0        #finish loop when "0x03" FF ID is read
    bl restore
    mtcr r0
    lwz r0, 0x8(r29)    #original instruction

I have a lot of plans for this concept. It’s bound to change over time quite a bit, and is going to be a massive learning experience for me. It’d be really great if everything turns out the way I want it to, but even if it doesn’t I hope to at least demonstrate the power we have over subaction events from the RAM.


---

PASI v0.02 -- Player Subaction Injections
Last updated 3/31/2016

Inject subaction event data non-destructively from specified Plxx.dat offsets. Use/create options in the parse for specifying conditions for triggering injected data.

Experimental and subject to change~

So, here’s the injection mod package that includes the functions, tables, and parse instructions I’ve written. Just paste it into a new/existing text file inside of the Mods Library folder of your MCM directory:

Injection Mod -- add this to MCM:

Code:
-==-
Player Subaction Injections v0.02
A library of reusable standalone functions.
Injection mod demonstrates subaction injections via any table added to the PSAIgetPackageTable function.

Tables included this way are parsed in order to conditionally inject subactions.
[Punkline]
Version -- DOL Offset ------ Hex to Replace ---------- ASM Code

1.02 ----- 0x800732ec --- 7FA3EB78 -> Branch

7C0802A6 90010004
9421FFF8
bl <PSAIgetPackageTable>
7C6802A6 7F64DB78
bl <PSAIparsePackage>
809D0008 38210008
80010004 7C0803A6
00000000


<PSAIgetPackageTable>
4E800021    #any tables of data to be injected must be included here
b <PSAIgetData00>
00000000

<PSAIgetData00>
4E800021 #start of default data table

#this is your default table
#you may optionally use this instead of creating a new table

00000000    #end of data table


<PSAIgetOptionsTable>
4E800021
b <PSAIgetOption0Table>
00000000


<PSAIgetOption0Table>
4E800021
b <PSAIoption01short>
00000000


<UTILgetPSAoffsetReference>
4E800021 3B844108
41C447D4 7D44418C
42CC3CA8 4300433C
3CEC1BD0 3E4442E0
3C743F24 3ED03AC0
46404028 45343CC0
41AC3D04 443441B8
49C00C38 0B7C3360
318C40CC


<PSAIparsePackage>
7C0802A6 90010004
9421FFF0 BFA10008
7C9F2378 7C7E1B78
3BA00000 7C7EE82E
2C030000 41820034
7C7EEA14 7C6803A6
4E800021 7C6802A6
80030000 2C000000
41820010 7FE4FB78
bl <PSAIparseDatum>
4BFFFFEC 3BBD0004
4BFFFFC8 BBA10008
38210010 80010004
7C0803A6 4E800020


<PSAIparseDatum>
7C0802A6 90010004
9421FFB8 BE010008
7C9F2378 7C7E1B78
807F0064 889E0000
7C032000 408200E4
7FE4FB78 A07E0002
bl <UTILtranslatePSAoffset>
809F044C 7C032000
408200CC 887E0005
3A9E0008 7EA3A214
7EB6AB78 56C007BF
41820008 3AD60002
7C14A800 40800074
A0740000 5464A77E
5470053E
bl <PSAIgetOptionsTable>
7C6802A6 7C632214
7C6803A6 4E800021
7E6802A6 3A400800
3A200000 7E009039
41820024 7C719A14
7C6803A6 7EC3B378
7FE4FB78 4E800021
2C030000 41820058
7C761B78 2C120001
41820010 3A310004
5652F87E 4BFFFFC8
3A940002 4BFFFF8C
887E0004 7C63F214
7FE4FB78 88BE0001
54A00631 4182001C
54A5C00E 7CA500D0
54A5463E 7C0500D0
80BF044C 7CA50214
bl <UTILinjectPSAdata>
887E0004 A09E0006
7C632214 7C63F214
38630004 BA010008
38210048 80010004
7C0803A6 4E800020


<UTILinjectPSAdata>
90C1FFF8 90E1FFF4
9101FFF0 2C050000
41820038 80C40450
80E4044C 39060001
54C0103A 91040450
7CC72A14 39040444
7D080214 74A08000
4182000C 90A80010
48000008 90C80010
9064044C 80C1FFF8
80E1FFF4 8101FFF0
4E800020


<UTILtranslatePSAoffset>
90A1FFF8 90C1FFFC
7C0802A6
bl <UTILgetPSAoffsetReference>
7CA802A6 7C0803A6
80C40064 54C6083C
7CA5322E 80C40084
80C6000C 7CC53050
7C661A14 80C1FFFC
80A1FFF8 4E800020


<UTILreturnStaticPlayerBlock>
8863006C 1C630E90
38633080 3C808045
7C831B78 4E800020


<PSAIoption01short>
93E1FFF0 93C1FFF4
90A1FFF8 7C9F2378
7C7E1B78 887E0000
889F0679 7C652030
54A00631 418200E8
889F067B 7C652030
54A0077B 418200D8
7FE3FB78 7C0802A6
bl <UTILreturnStaticPlayerBlock>
7C0803A6 80830008
887E0001 7C652030
54A00631 418200B4
5465073F 41820018
2C050009 41810010
889F1AFB 7C042800
40820098 887E0002
2C0300FF 41820018
801F19D4 8000002C
80800010 7C032000
40820078 807F06BC
5464A636 A0BF008C
2C053F80 41820014
5485FE72 50850E30
508506B6 7CA42B78
5465C7BE 5460052B
41820008 68A50008
54600675 41820008
68A50004 887E0003
706000F0 2C0000F0
4182000C 7C002000
40820020 7060000F
2C00000F 4182000C
7C002800 4082000C
387E0004 48000008
38600000 80A1FFF8
83C1FFF4 83E1FFF0
4E800020
Syntax Highlighted ASM (as PDF)

ASM as text:
Code:
/*-- DESCRIPTION BRIEF
Player Subaction Injections--v0.02 [Punkline]

Library of functions that may be used to conditionally inject subaction data.
See comments for more details.
*/

###
### CODE INJECTION
###

/*The code here is designed to work from the player subaction event parsing loop.
It needs to be able to intercept new events as they come in in order to accurately make procedural injections.

#@800732ec: lwz    r4, 0x0008 (r29)    (loading next player event)
#context:
#LR is safe
#cr0 is safe, but cr2 may be important to context
#r0 == safe as GPR
#r31 == non-control event functions pointer
#r29 == player.0x444
#r27 == player.0x0
*/
mflr r0
stw r0, 0x4(sp)
stwu sp, -0x8(sp)
bl <PSAIgetPackageTable>    #grab data
mflr r3                #pass as r3
mr r4, r27            #pass player as r4
bl <PSAIparsePackage>        #tell code to parse it
lwz r4, 0x8 (r29)    #original instruction
addi sp, sp, 0x8
lwz r0, 0x4(sp)
mtlr r0
.long 0x00000000






###
### DATA
###

#PSAI tables--------
#this is where all the subaction data is written

/*-- PSAIgetPackageTable
This table is used by the code in order to reach and parse all other included data tables.
Multiple injections may be included per table; so this master table is mostly just for organizing packages.
Remember to terminate all tables with an extra "00000000" line at the end!
*/
blrl
bl <PSAIgetData00>    #this table is the default table. It is blank, until you put subaction data in it.
            #you can create new tables and append them to this list.
.long 0x00000000

/*-- PSAIgetData00
The default table. Create a header and include subaction data to be injected below it.
These tables can be extended to (virtually) any length. Add as many injections to the table as needed.
Remember to terminate all tables with an extra "00000000" line at the end!
*/
blrl
.long 0x00000000






#parse tables--------
#these are used by parsing functions

/*-- PSAIgetOptionsTable
This table is used to organize the option parses made in the header of each PSAI datum.
It's essentially a lookup table of other tables that contain lists of parsing function pointers.
*/
blrl
b <PSAIgetOption0Table>
.long 0x00000000

/*-- PSAIgetOption0Table
This table holds pointers to each parse function for page "0" of the options bitfield.
*/
blrl
b <PSAIoption01short>
.long 0x00000000

/*-- UTILgetPSAoffsetReference
This lookup table uses the internal character ID at player.0x64 to map the file data offsets of PSA "Wall Damage"
The address pointed to in player.0x84 -> 0xC will match this file data offset, providing a key for translation.
*/
blrl
.hword 0x3B84    #00 Mario
.hword 0x4108    #01 Fox
.hword 0x41C4    #02 Cfalcon
.hword 0x47D4    #03 DK
.hword 0x7D44    #04 Kirby
.hword 0x418C    #05 Bowser
.hword 0x42CC    #06 Link
.hword 0x3CA8    #07 Shiek
.hword 0x4300    #08 Ness
.hword 0x433C    #09 Peach
.hword 0x3CEC    #0A Popo
.hword 0x1BD0    #0B Nana
.hword 0x3E44    #0C Pikachu
.hword 0x42E0    #0D Samus
.hword 0x3C74    #0E Yoshi
.hword 0x3F24    #0F Jpuff
.hword 0x3ED0    #10 Mewtwo
.hword 0x3AC0    #11 Luigi
.hword 0x4640    #12 Marth
.hword 0x4028    #13 Zelda
.hword 0x4534    #14 YLink
.hword 0x3CC0    #15 Doc
.hword 0x41AC    #16 Falco
.hword 0x3D04    #17 Pichu
.hword 0x4434    #18 GaW
.hword 0x41B8    #19 Ganondorf
.hword 0x49C0    #1A Roy
.hword 0x0C38    #1B Master Hand? the following are untested
.hword 0x0B7C    #1C Crazy Hand?
.hword 0x3360    #1D Wire Male?
.hword 0x318C    #1E Wire Female?
.hword 0x40CC    #1F Giga Bowser?




###
### CODE
###




/*-- PSAIparsePackage
r3 = pointer to PSAIpackageTable (list of all data tables containing subaction data)
r4 = player.0x0
This function loops through every entry on the provided PSAI package table.
It then runs PSAIdatumParse for each entry on each table it finds.
Datum entry lengths are defined in their corresponding headers.
*/
mflr r0
stw r0, 0x4(sp)
stwu sp, -0x10(sp)
stmw r29, 0x8(sp)
mr r31, r4        #save "player.0x0"
mr r30, r3        #save package table pointer
li r29, 0        #Table loop increment, used as key for package table

tableLoop:
lwzx r3, r30, r29    #find package table entry
cmpwi r3, 0        #make sure it isn't blank
beq return        #terminate if it is (0 is a termination code)
add r3, r30, r29    #find package table entry
mtlr r3
blrl
mflr r3            #r3 now holds pointer to data inside of a data table that needs to be parsed

dataLoop:
lwz r0, 0(r3)        #load data value
cmpwi r0, 0        #make sure data isn't blank
beq nextTable        #stop reading this table if it is
mr r4, r31
bl <PSAIparseDatum>    #r3 = location of datum start, r4 = player.0x0
b dataLoop        #returns with r3 = location of next datum

nextTable:
addi r29, r29, 0x4    #increment r20 so that next table is grabbed
b tableLoop

return:
lmw r29, 0x8(sp)
addi sp, sp, 0x10
lwz r0, 0x4(sp)
mtlr r0
blr


/*-- PSAIparseDatum
r3 = location of datum start
r4 = player.0x0
Returns r3 = location of subsequent address (after entire datum)
Parses datum header. If header parse is successful, player subaction data is injected.
Upon return, the next datum is located via the saved datum start pointer, header length, and data length.
*/
mflr r0                #giant stack frame
stw r0, 0x4(sp)            #how to use stmw?
stwu sp, -0x48(sp)
stmw r16, 0x8(sp)        #like this? Neat as heck.

mr r31, r4            #save player.0x0
mr r30, r3            #save datum start location

#Character match check
lwz r3, 0x64(r31)        #load internal character ID from player
lbz r4, 0(r30)            #load internal character ID from parse data
cmpw r3, r4
bne return            #if no match, skip injection and return

#Event match check
mr r4, r31
lhz r3, 0x2(r30)        #r3 = Plxx.dat offset, r4 = player.0x0
bl <UTILtranslatePSAoffset>
lwz r4, 0x44C(r31)
cmpw r3, r4            #if resulting pointer doesn't match current event pointer
bne return            #then skip injection and return

#options parse setup
lbz r3, 0x5(r30)
addi r20, r30, 0x8        #r20 = (location of) current options bitfield page being parsed
add r21, r3, r20        #r21 = end of options bitfield pages
mr r22, r21
rlwinm. r0, r22, 0,30,31    #check word alignment
beq optionsPageLoop
addi r22, r22, 0x2        #r22 = current location of next option parse

optionsPageLoop:
cmpw r20, r21            #check to see if we're done parsing options
bge inject            #if we've reached end, then we're finally ready to inject the data
lhz r3, 0(r20)            #else, we're still checking options. So load the options bitfield to be parsed
rlwinm r4, r3, 20, 29, 31    #r4 = bitfield page number
rlwinm r16, r3, 0, 20, 31    #r16 = bitfield -- each bit in this field represents a whole set of parsing instructions
bl <PSAIgetOptionsTable>    #each set of instructions is handled by a seperate parsing function, organized as a heirarchical table
mflr r3                #r3 = options "page" table. "Pages" are just the parent tables used to hold lists of functions.
add r3, r3, r4            #r3 = matching page table function
mtlr r3
blrl                #calls (and returns a pointer of) appropriate page of parsing functions; mapped to bitfield sequence
mflr r19            #r19 will be our saved option page table. This holds a list of the page's parsing function pointers.
li r18, 0x800            #r18 will be our bitkey. This will get rightshifted each iteration of the loop in order to check all 12 bits
li r17, 0            #r17 will be our actual index key. This will be used to load a corresponding parsing function from r19

optionsParseLoop:        #---this is a nested loop
and. r0, r16, r18        #key in r18 will check each individual bit of r16 on different increments
beq nextBit            #if key doesn't match this bit, then check next bit
add r3, r17, r19        #else, use current key in r17 to offset the page table for branch to appropriate function
mtlr r3
mr r3, r22
mr r4, r31            #pass r3 = option parse data start, r4 = player.0x0
blrl
cmpwi r3, 0            #returns r3 = next datum parse, or 0 if option parse conditions failed
beq return            #if parse failed, then skip injection and return
mr r22, r3            #else, save the new loaction of the current option data to be parsed next iteration

nextBit:
cmpwi r18, 1
beq nextOptions            #if we're done with this bitfield, then we're ready to check if there's a new one
addi r17, r17, 0x4        #else, continue checking bits in this bitfield, and matching them to the parse function table
srwi r18, r18, 1        #sets up next iteration of loop
b optionsParseLoop

nextOptions:
addi r20, r20, 0x2        #increment hword bitfield
b optionsPageLoop        #new iteration. check for termination is at beginning of loop

inject:                #loop structure terminates here if parse is successful
lbz r3, 0x4(r30)        #load length of header
add r3, r3, r30            #r3 = start of injection data
mr r4, r31            #r4 = player.0x0
lbz r5, 0x1(r30)        #r5 = return offset
rlwinm. r0, r5,0,24,24        #check signed 8-bit for signed bit
beq callInject            #just pass the offset if it's positive
slwi r5, r5, 24            #else, create a negative 32-bit value out of this 8-bit one...
neg r5, r5
srwi r5, r5, 24
neg r0, r5
lwz r5, 0x44C(r31)        #function doesn't accept a negative value, so we pass it a calculated pointer
add r5, r5, r0

callInject:
bl <UTILinjectPSAdata>

return:                #loop terminates here if parse failed; handles return value in r3 so injection runs this too
lbz r3, 0x4(r30)        #header length
lhz r4, 0x6(r30)        #data length
add r3, r3, r4
add r3, r3, r30
addi r3, r3, 0x4        #r3 = location of next datum

lmw r16, 0x8(sp)
addi sp, sp, 0x48
lwz r0, 0x4(sp)
mtlr r0
blr

/*-- UTILinjectPSAdata
r3 = injection destination
r4 = player.0x0
r5 = return pointer/offset
Subactions can terminate themselves with a 00xxxxxx event line.
This makes returns from the subaction injection optional.
If your injection doesn't return, then specify "0" for r5.
Otherwise, the length (in bytes) of the value in r5 is used to offset the current event pointer on return.
If a full pointer is passed in r5, it will be recognized and used as an absolute pointer.
*/
stw r6, -0x8(sp)    #leaf
stw r7, -0xC(sp)
stw r8, -0x10(sp)
cmpwi r5, 0             #if return offset is 0, then skip the return write (just inject the data)
beq inject
lwz r6, 0x450(r4)       #this next part is practically identical to control event 14--which writes a return in player memory
lwz r7, 0x44C(r4)
addi r8, r6, 0x1
rlwinm r0, r6, 2, 0, 29
stw r8, 0x450(r4)
add r6, r7, r5          #this is where the passed return offset (r5) is added
addi r8, r4, 0x444
add r8, r8, r0
andis. r0, r5, 0x8000   #we want to check if r5 is actually an absolute pointer
beq relative
stw r5, 0x10(r8)
b inject
relative:
stw r6, 0x10(r8)
inject:
stw r3, 0x44C(r4)       #set player event pointer to PSA injection data
lwz r6, -0x8(sp)
lwz r7, -0xC(sp)
lwz r8, -0x10(sp)
blr



/*-- UTILtranslatePSAoffset
r3 = PSA offset (player subaction data located in Plxx.dat)
r4 = player.0x0
Returns r3 = pointer to matching subaction event in RAM, r4 = player.0x0 (unmodified)
An included lookup table is used here to create an effective BA for converting Plxx.dat subaction offsets into RAM addresses
*/
stw r5, -0x8(sp)    #leaf
stw r6, -0x4(sp)
mflr r0            #we need LR to grab a pointer
bl <UTILgetPSAoffsetReference>
mflr r5            #r5 = pointer to reference table
mtlr r0            #LR is restored
lwz r6, 0x64(r4)    #load character ID
slwi r6, r6, 1        #hword alignment
lhzx r5, r5, r6        #load appropriate reference offset
lwz r6, 0x84(r4)    #load Player Subaction table
lwz r6, 0xC(r6)        #this loads pointer to beginning of "Wall Damage" PSA in RAM
subf r6, r5, r6        #Subtract offset from RAM address to create an effective BA
add r3, r6, r3        #add input address to effective BA to create final pointer
lwz r6, -0x4(sp)
lwz r5, -0x8(sp)
blr



/*-- UTILreturnStaticPlayerBlock
r3 = player.0x0
Returns r3 = static player block
This simply matches a (dynamic) player entity to a cooresponding static player block.
Useful for grabbing static data values from the context of a player function.
--Note this clobbers r4. I usually try to preserve these if necessary, but it's not necessary here...
*/
lbz r3, 0x6C(r3)        #load player slot
mulli r3, r3, 0xe90        #multiply player slot by static block length
addi r3, r3, 0x3080
lis r4, 0x8045
or r3, r4, r3
blr



/*-- PSAIoption01short
r3 = location of parse start (passed from PSAIoptionsParse)
r4 = player.0x0
Returns r3 = pointer to next option parse, or 0 if fail

This parse is meant to summarize some common conditions.
Multiple entries for one condition type are ORed together. The resulting condition types are ANDed together.
IDs represented as bitfields progress from left to right.
The line is only 32-bits, and is comprised like this:
CT PL II NN
C - 5-bit Costume ID field
T - 3-bit Team ID field
P - 4-bit player slot type field (8 = HMN, 4 = CPU, 2 = Demo, 1 = N/A) (these last 2 bits are open for change)
L - 4-bit CPU level integer
II - 8-bit Held Item ID integer
S - 4-bit Controller analog stick field (8 = left, 4 = right, 2 = down, 1 = up)
B - 4-bit Controller button field (8 = X/Y, 4 = heavy L/R, 2 = A, 1 = B)
*/
stw r31, -0x10(sp)    #fake leaf (makes a single, simple call)
stw r30, -0xC(sp)
stw r5, -0x8(sp)
mr r31, r4
mr r30, r3
lbz r3, 0(r30)        #load CT byte
lbz r4, 0x679(r31)    #load player costume ID
slw r5, r3, r4        #shift by amount == to costume ID
rlwinm. r0, r5, 0,24,24    #check if bit is in parse
beq return0        #if not in parse, then return with 0 for fail
lbz r4, 0x67B(r31)    #load player team ID
slw r5, r3, r4
rlwinm. r0, r5, 0,29,29    #check if bit is in the parse
beq return0        #return with 0 if not

mr r3, r31
mflr r0
bl <UTILreturnStaticPlayerBlock>
mtlr r0
lwz r4, 0x8(r3)        #load slot type from static player block
lbz r3, 0x1(r30)    #load PL parse values
slw r5, r3, r4
rlwinm. r0, r5, 0,24,24    #check if slot type is in parse
beq return0
rlwinm. r5, r3, 0,28,31    #extract 0-based cpu lvl parse value
beq itemcheck        #if level parse is set to 0, then skip it
cmpwi r5, 9
bgt itemcheck        #similarly, skip if level is set above "9"
lbz r4, 0x1AFB(r31)    #load player CPU level
cmpw r4, r5
bne return0        #return with 0 if cpu level is specified, and not a match

itemcheck:
lbz r3, 0x2(r30)    #load II parse value
cmpwi r3, 0xFF
beq controllercheck    #if II value uses "FF" code, then skip it
lwz r0, 0x19D4(r31)    #load pointer to entity struct of currently held item
lwz r0, 0x2C(r0)    #load main data of currently held item
lwz r4, 0x10(r0)    #load Item ID of currently held item
cmpw r3, r4
bne return0        #if item ID is specified in parse, but doesn't match, then return with 0

controllercheck:
lwz r3, 0x6BC(r31)    #load basic digital controller inputs from player
rlwinm r4, r3, 20,24,27    #extract control stick directional bits and align them to parse check
lhz r5, 0x8C(r31)    #load facing direction. This is a float, but it's always 1 or -1
cmpwi r5, 0x3F80    #check if == "1"
beq btnsCheck        #if facing left, then directions are already mapped correctly
rlwinm r5, r4, 31,25,25
rlwimi r5, r4, 1,24,24
rlwimi r5, r4, 0,26,27    #this just swaps the bits for left and right in our extraction
mr r4, r5        #use swapped direction bits when facing the wrong way

btnsCheck:
rlwinm r5, r3, 24,30,31    #extract and insert buttons A and B
rlwinm. r0, r3, 0,20,21    #check if either x or y is pressed (we will summarize both as one bit)
beq heavyLRcheck    #skip xor if not being pressed
xori r5, r5, 0x08    #this is the XorY bit
heavyLRcheck:
rlwinm. r0, r3, 0,25,26    #check if either heavy L or R is pressed (we will summarize both as one bit)
beq controllerbits    #skip xor if not being pressed
xori r5, r5, 0x04    #this is the LorR bit

controllerbits:
lbz r3, 0x3(r30)    #load NN parse value
andi. r0, r3, 0xF0
cmpwi r0, 0xF0        #Special code for skipping analog stick input check
beq btns
cmpw r0, r4        #analog stick input must match N value
bne return0
btns:
andi. r0, r3, 0xF    #Special code for skipping button input check
cmpwi r0, 0xF
beq return1
cmpw r0, r5        #otherwise, button input must match
bne return0


return1:
addi r3, r30, 0x4    #move the option parse pointer and return it
b return
return0:
li r3, 0
return:
lwz r5, -0x8(sp)
lwz r30, -0xC(sp)
lwz r31, -0x10(sp)
blr
 
Last edited:

Punkline

Dr. Frankenstack
Joined
May 15, 2015
Messages
423
Update: 0 - Concept

Alright, so I’ve been pulling my hair out a little bit for a month trying to get this code out of my head.

I want to create a really flexible tool that does non-destructive edits to subactions by mutating incoming events as they are read; while simultaneously providing a platform for scheduling ASM subroutines inline with these events from within the subaction event parsing loop.

The tool would work from an input field with a length allocated from code space in the form of blank variables (where “input lines” could then be written to--before or after assembly.) These input lines would trigger parsing functions that read them and do various things at the frame>player>event detail.

The ASM subroutines would just be macros for a number of reusable utility functions that would be included; the most versatile of which would simply read straight machine code from the input field. The rest would be designed to simplify common tasks.

I’d also like to experiment with writing “procedural injections.” These would be like functions that take in a hook address as an argument and construct a simple flag or context retrieval of some kind; storing some data from another function to memory for the parsing functions to see.

---

I had an argument with a text file long enough to make this silly post that sort of vaguely detailed a concept code that does the kind of subaction event mutations I want to utilize.

I haven’t improved on this really at all. In the middle of designing it I discovered the “standalone function” in the code manager, and have since sort of gone into reading mode. I’ve still got a big fog of war going with this ASM business, so this was good...

I’m excited to try using standalone functions, but this isn’t reflected in the concept code I’m releasing today. When I get around to writing this from scratch, I can see the massive potential standalone functions have to break my otherwise limited concept into something usable from any context via designed prefunctions (thunks? My terminology isn’t very strong)

It would be the start of a library that I anticipate would split up a few times into lighter versions that could then be included in a DOL; giving a vanilla copy of melee special functions that constitute all of the dripping features I have planned. I’ll make a couple followup posts later on about details, as I’ve already concepted a few of them and would like to see what people think.

---

Anyway, in comparison to my plans the current code is rather limited, but still useful as a proof of concept. (I like concepts.)

It also provides a convenient interface for experimenting with subaction event inputs/removals/additions en mass, which opens a window for some easy methods of experimentation. I’ll be making a followup post about this, and my desire to document every event in detail Ampers Ampers @Tater:


If you’ve seen Itaru’s fascinating Melee Syntax School thread, you might already be familiar with this event syntax and the power it has when free of limitations.

I’m not (currently) sure of how, but at some point the data from the .dat files gets loaded into RAM. Player event data is all parsed by this function--which is the platform for my concept code.

I have an interest in keeping this code non-destructive--even to the data already loaded into memory. I think I will make a followup post showing how to use Itaru’s methods in the RAM to make use of space in the DOL though, because it seems like it’s entirely possible! Thank you @Itaru for documenting this so well!

I’m fairly sure it would not be an issue to develop a “subaction injection” that allows for space in the input field to be read off directly by the parsing loop! It would be very similar to how C2 codes work, actually; especially considering that “goto” subaction events use calculated absolute addresses in RAM once they’re loaded.

---

With that said, I probably should make it clear that--while this is the same data syntax and all--my current code does NOT use these same methods and is quite a bit different from editing the .dat files.

Here is how to use my concept code (SAevent Scheduler v0.02: )



Custom subaction events written by @achilles1515 work in such a way that this parsing loop is able to see them; which means nothing extra needs to be done to make them compatible with this code.


This video is just condensed footage of me goofing around with projectiles for a while with cheat engine. I’ll provide some similar examples of Achilles’ latest custom event soon:





Input data structure (what the input field is parsed for: )
FF EE BB IS
  • FF - parse function byte
    • xxxx xx - currently unused, but planned out
    • ff - 2-bit function ID, with the following function options:
      • 0 - skip to next line (allows for blank allocation, and whitespace)
      • 1 - parse for an event in the normal event index
      • 2 - reserved for parsing an extended custom index syntax
      • 3 - terminate parsing loop
    • In a future version, this index will be extended to 4-bit (0-15,) with room for custom parsing functions.
      • Each will use the second 4-bit F for custom options, and will define a parsing structure that can be completely different from the one described here.
      • Every bit of the following is just for ID “1”
  • EE - event
    • eeee ee - 6-bit event ID that prefixes every subaction event
    • i - interrupt flag, allows the input to ignore the subaction event timer
    • f - force flag, allows this input to ignore the result condition
  • BB - parse option toggles (bitfield)
    • xxxx xxxx - currently unused, but these will be the means of extending the length of this input line to include all of the extra parse lines I’ve been designing.
      • These lines will be of variable length, and so any core to the library will be encapsulated as a single option--leaving the majority of them free to extend.
  • I - insert digit (nibble, half-byte)
    • iiii - 4-bit unsigned integer, specifies length of event to be inserted in a unit of 4-byte words.
      • In a followup post, I’ll be detailing my interest in fully documenting the event index and creating a lookup table of these lengths; allowing this to instead specify a number of events to insert. Same goes for skip.
  • S - skip digit
    • ssss - 4-bit unsigned integer, specifies length of event to be skipped in a unit of 4-byte words



Using the input line:
  • Skip S lines of every EE event
    • EE == event to skip (by 6-bit ID or “command byte”)
    • S == length of event to skip (unit of 4-byte words)
  • Insert the following I lines in front of every EE event
    • EE == event to be prefixed (by 6-bit ID or “command byte”)
    • I == length of insert event (included in-between this and the next input line)
  • Replace EE (regardless of length) by doing both of the above in tandem
    • EE == the event to be replaced
    • S == length of event to skip
    • I == length of insert event

That’s it for now :(

Here’s a sneaky spoiler though with a synopsis of my plans for “BB”:
The “BB” field is for extending the input line. Many input lines will have a BB-like field that allows more options to emerge from parsing the current line--making input lines arbitrary in length.

The goal is to leave as many of these bits available as possible for custom parsing options; so only 2 are planned for usage by the library:

  • 02 - inline condition checks
    • a shorthand for creating filters for common things represented in the “result condition”:
      • character IDs, move IDs, AS IDs, animation IDs, controller flags, character flags, etc
        • I’m considering assigning most of these to lookup tables that can then be selected from a master index, allowing their individual IDs to be further indexable by 24-bit bitfields; allowing multiple concurrent selections to be written in very few lines.
    • a shorthand for adding manual comparisons with boolean logic:
      • as a lookup for values from an absolute address
      • as an immediate
      • as an offset from a base address specified from an extendable master index of common base addresses
        • and with an option to declare the target as a pointer, providing another input line to specify an offset from there
  • 01 - predefined subroutine calls and memory allocation
    • a shorthand for setting up ASM subroutine calls that can use a piece of the input field for memory in order to store variables globally
      • subroutine 01 would be used to call library subroutines, and the rest of the index would be extendable for use with custom subroutines included with the dol (written by anyone and everyone.)
      • library core subroutines:
        • read inline ASM from memory
          • strait up ASM that gets read based on any or all of the various result conditions
          • 0 - passed
          • 1 - forced
          • 2 - failed
        • toggle C2 injection from memory
          • like a C2 code you can turn on and off in-game
        • convert/interpolate/extrapolate float/int
          • convert between ints/float
          • specify one value of arbitrary type to interpolate a series of other values output to memory (of arbitrary type)
        • variable rlwinm/rlwimi
          • lookup/extract bits
          • place them in memory specified by BA index
            • includes allocated memory for subroutines
            • include event data inserts
            • includes areas in each player block
        • other utilities--make suggestions, yeah?

I don’t think I’ll be porting these “concepts” mid-development, but if I hit any big landmarks I’ll be sure to release versions for Melee v1.0, v1.1, and PAL. The code manager makes it easy though, so if anyone has a request along development for a specific version, I’ll probably be able to do it without problems.

C2 code, with all of the data tables and all of the poorly written utilities stuffed into one:

$SAeventSchedulerv002 [Punkline]
C205C038 0000007A
48000014 4E800020
4E800020 4BFFFFF8
4BFFFFF8 38000000
480000DD 7F0802A6
3B18000C 4800018C
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000

03000000 4E800020
4E800020 2C000000
40820024 7C000026
3821FFDC D8010000
D8210008 D8410010
90010018 9061001C
90810020 3821FFF0
92A10000 92C10004
92E10008 9301000C
4E800020 2C000000
82A10000 82C10004
82E10008 8301000C
38210010 40820020
C8010000 C8210008
C8410010 80010018
8061001C 80810020
38210024 4E800020
80040000 3884FFFC
90010000 3821FFFC
3863FFFF 2C030000
4181FFE8 4E800020
80010000 38210004
90040000 38840004
3863FFFF 2C030000
4181FFE8 4E800020
7F17C378 3AA00000
88170000 2C000000
418201F4 2C000003
418201F4 C01D0000
C0228874 FC000840
88770001 40810014
5460039D 4082000C
3AA00002 48000050
546003DF 5463041A
98770001 4182000C
3AA00001 48000038
88170000 2C000002
4182FFD8 809D0008
2C040000 4082000C
38800000 4800000C
88840000 5484063A
88770001 7C032000
4082FFB0 38600080
38800028 3AC00004
88170002 7C600039
40820018 70600001
40820070 5463F83E
38840004 4BFFFFE4
3AD60004 38000001
4BFFFE85 7C751B78
7C962378 4BFFFE79
7C6802A6 7C632214
7C6803A6 4E800021
38000001 4BFFFEA1
7EA3AB78 7EC4B378
4BFFFE95 4BFFFFB0
4BFFFE44 4BFFFE40
4BFFFE3C 4BFFFE38
4BFFFE34 4BFFFE30
4BFFFE2C 4BFFFE2C
3AC00004 2C150002
7EC3B378 82D70000
7EE3BA14 418200CC
56C3E73F 4182009C
809D0008 3884FFFC
4BFFFE81 38840004
7C350B78 7EE1BB78
56C3E73E 4BFFFE8D
7EA1AB78 3821FFFC
56C3F6BA 7C832050
909D0008 88840000
5484F6BE 2C04000A
5484103E 4080001C
3C60803C 606367C0
7C632214 7C6803A6
7FA3EB78 4800001C
3804FFD8 7C7F0214
80630000 7C6803A6
7FA4EB78 387DFBBC
4E800021 38210008
56C3F6BA 809D0008
7C832050 5463F03E
4BFFFE19 3821FFFC
56C316BB 41820024
809D0008 7C832214
909D0008 88840000
5484063A 56C3863A
7C032000 4182FE0C
56C3F6BA 7EE3BA14
4BFFFE04 3AF70004
4BFFFDFC 38000000
4BFFFD75 7C0FF120
801D0008 00000000

Injection Mods with considerably more length in the input field:

Code:
    -==-


SAevent Scheduler v0.02 (0x100)
*CONCEPT CODE*
Use an input field (~0x100 bytes in length) to modify subaction events as they are read.
FF EE BB IS
FF = function ID (00 skip, 01 input, 02 fail, 03 terminate parse)
EE = trigger event
BB = (bitfield, used to extend the code)
I = length of event to insert (include actual event data after input line)
S = length of trigger event, to skip
[Punkline]
Version -- DOL Offset ------ Hex to Replace ---------- ASM Code
1.02 ----- 0x800732AC --- 801D0008 -> Branch

48000014 4E800020
4E800020 4BFFFFF8
4BFFFFF8 38000000
4800011D 7F0802A6
3B18000C 480001CC
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
03000000 4E800020
4E800020 2C000000
40820024 7C000026
3821FFDC D8010000
D8210008 D8410010
90010018 9061001C
90810020 3821FFF0
92A10000 92C10004
92E10008 9301000C
4E800020 2C000000
82A10000 82C10004
82E10008 8301000C
38210010 40820020
C8010000 C8210008
C8410010 80010018
8061001C 80810020
38210024 4E800020
80040000 3884FFFC
90010000 3821FFFC
3863FFFF 2C030000
4181FFE8 4E800020
80010000 38210004
90040000 38840004
3863FFFF 2C030000
4181FFE8 4E800020
7F17C378 3AA00000
88170000 2C000000
418201F4 2C000003
418201F4 C01D0000
C0228874 FC000840
88770001 40810014
5460039D 4082000C
3AA00002 48000050
546003DF 5463041A
98770001 4182000C
3AA00001 48000038
88170000 2C000002
4182FFD8 809D0008
2C040000 4082000C
38800000 4800000C
88840000 5484063A
88770001 7C032000
4082FFB0 38600080
38800028 3AC00004
88170002 7C600039
40820018 70600001
40820070 5463F83E
38840004 4BFFFFE4
3AD60004 38000001
4BFFFE85 7C751B78
7C962378 4BFFFE79
7C6802A6 7C632214
7C6803A6 4E800021
38000001 4BFFFEA1
7EA3AB78 7EC4B378
4BFFFE95 4BFFFFB0
4BFFFE44 4BFFFE40
4BFFFE3C 4BFFFE38
4BFFFE34 4BFFFE30
4BFFFE2C 4BFFFE2C
3AC00004 2C150002
7EC3B378 82D70000
7EE3BA14 418200CC
56C3E73F 4182009C
809D0008 3884FFFC
4BFFFE81 38840004
7C350B78 7EE1BB78
56C3E73E 4BFFFE8D
7EA1AB78 3821FFFC
56C3F6BA 7C832050
909D0008 88840000
5484F6BE 2C04000A
5484103E 4080001C
3C60803C 606367C0
7C632214 7C6803A6
7FA3EB78 4800001C
3804FFD8 7C7F0214
80630000 7C6803A6
7FA4EB78 387DFBBC
4E800021 38210008
56C3F6BA 809D0008
7C832050 5463F03E
4BFFFE19 3821FFFC
56C316BB 41820024
809D0008 7C832214
909D0008 88840000
5484063A 56C3863A
7C032000 4182FE0C
56C3F6BA 7EE3BA14
4BFFFE04 3AF70004
4BFFFDFC 38000000
4BFFFD75 7C0FF120
801D0008 00000000



    -==-


SAevent Scheduler v0.02 (0x300)
*CONCEPT CODE*
Use an input field (~0x300 bytes in length) to modify subaction events as they are read.
FF EE BB IS
FF = function ID (00 skip, 01 input, 02 fail, 03 terminate parse)
EE = trigger event
BB = (bitfield, used to extend the code)
I = length of event to insert (include actual event data after input line)
S = length of trigger event, to skip
[Punkline]
Version -- DOL Offset ------ Hex to Replace ---------- ASM Code
1.02 ----- 0x800732AC --- 801D0008 -> Branch

48000014 4E800020
4E800020 4BFFFFF8
4BFFFFF8 38000000
4800031D 7F0802A6
3B18000C 480003CC
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
00000000 00000000
03000000 4E800020
4E800020 2C000000
40820024 7C000026
3821FFDC D8010000
D8210008 D8410010
90010018 9061001C
90810020 3821FFF0
92A10000 92C10004
92E10008 9301000C
4E800020 2C000000
82A10000 82C10004
82E10008 8301000C
38210010 40820020
C8010000 C8210008
C8410010 80010018
8061001C 80810020
38210024 4E800020
80040000 3884FFFC
90010000 3821FFFC
3863FFFF 2C030000
4181FFE8 4E800020
80010000 38210004
90040000 38840004
3863FFFF 2C030000
4181FFE8 4E800020
7F17C378 3AA00000
88170000 2C000000
418201F4 2C000003
418201F4 C01D0000
C0228874 FC000840
88770001 40810014
5460039D 4082000C
3AA00002 48000050
546003DF 5463041A
98770001 4182000C
3AA00001 48000038
88170000 2C000002
4182FFD8 809D0008
2C040000 4082000C
38800000 4800000C
88840000 5484063A
88770001 7C032000
4082FFB0 38600080
38800028 3AC00004
88170002 7C600039
40820018 70600001
40820070 5463F83E
38840004 4BFFFFE4
3AD60004 38000001
4BFFFE85 7C751B78
7C962378 4BFFFE79
7C6802A6 7C632214
7C6803A6 4E800021
38000001 4BFFFEA1
7EA3AB78 7EC4B378
4BFFFE95 4BFFFFB0
4BFFFE44 4BFFFE40
4BFFFE3C 4BFFFE38
4BFFFE34 4BFFFE30
4BFFFE2C 4BFFFE2C
3AC00004 2C150002
7EC3B378 82D70000
7EE3BA14 418200CC
56C3E73F 4182009C
809D0008 3884FFFC
4BFFFE81 38840004
7C350B78 7EE1BB78
56C3E73E 4BFFFE8D
7EA1AB78 3821FFFC
56C3F6BA 7C832050
909D0008 88840000
5484F6BE 2C04000A
5484103E 4080001C
3C60803C 606367C0
7C632214 7C6803A6
7FA3EB78 4800001C
3804FFD8 7C7F0214
80630000 7C6803A6
7FA4EB78 387DFBBC
4E800021 38210008
56C3F6BA 809D0008
7C832050 5463F03E
4BFFFE19 3821FFFC
56C316BB 41820024
809D0008 7C832214
909D0008 88840000
5484063A 56C3863A
7C032000 4182FE0C
56C3F6BA 7EE3BA14
4BFFFE04 3AF70004
4BFFFDFC 38000000
4BFFFD75 7C0FF120
801D0008 00000000


And the ASM. Not proud of this mess… I should explain that this was at around the time I was discovering how the stack could be used, and so I did a lot of really stupid things in here. It’s actually kind of amazing that it runs, in retrospect:

Code:
#@800732AC: lwz r0, 0x8(r29)    (load SAevent pointer for read loop)
#context:
#LR is safe
#cr0 is safe, but cr2 may be important to context
#r0 == safe as GPR
#r31 == non-control event pointer
#r29 == player.0x444

        b dataP        #branch past the subroutine data to the data pointer constructor

#---data1----------------------------------
#---subroutine functions
ss04:    blr        #<placeholder for unwritten subroutines>

ss00:    #SUBROUTINE: do nothing (useful for disabling a subroutine)
    blr

#---subroutine branch table (TOC)
    b ss04
ssTOC:    b ss00         # << subroutine branches go here -0x18(rP)
#---data1 end------------------------------

dataP:  li r0, 0        #load 0 to tell backup function to back up lots of registers
        bl backup
        mflr r24        #take lr and create a pointer sandwiched between these two data tables
    addi r24, r24, 0xC    #located here, this shouldn’t ever need to be adjusted
    b start

#---data2----------------------------------
#---inputs
#Enter any subaction modifications below (number of inputs can be arbitrary)
# FF = function ID, EE = event ID search, BB = bits toggle inline condition checks,
# I = Insert event length (in 4byte,) S = Skip event length (in 4byte)
        #     0xFFEEBBIS
            .long 0x00000000
            .long 0x00000000
            .long 0x00000000
            .long 0x00000000
            .long 0x00000000
            .long 0x00000000
            .long 0x00000000
            .long 0x00000000
            .long 0x00000000
            .long 0x00000000
#add as many lines as you want here (don't add lines to the machine code, this gets branched over!)
            .long 0x03000000    #DO NOT REMOVE, use “0x03000000” to finish input data!

#---BB line functions
#These functions use the BB byte to select extra lines that are appended to the Insert code
bb1:
bb2:
bb3:
bb4:
bb5:
bb6:
bb7:    blr    #<placeholder for unwritten inline condition functions>

bb8:    #FUNCTION: specify a subroutine, and any memory lines for it (or other subroutines) to use
    blr

#---other (reusable) functions
backup:    #FUNCTION: uses r0 to determine how many registers to back up to the stack
    #    the purpose is to give some cheap reusability to these lines for subroutines and stuff
    #    r0 == (not 0) means only r21, r22, r23, and r24 are backed up
    #    r0 == 0 means the above are backed up, along with f0, f1, f2, CR (as r0,) r3, and r4.
    cmpwi r0, 0
    bne bckp1              # if r0 == 0, then back up ALL of the following
    mfcr r0                  #back up CR as r0
    subi sp, sp, 0x24    #move stack for 3 floats and 3 ints
    stfd f0, 0x0(sp)
    stfd f1, 0x8(sp)
    stfd f2, 0x10(sp)
    stw r0, 0x18(sp)
    stw r3, 0x1C(sp)
    stw r4, 0x20(sp)    #if was != 0, then only back up these 4 ints
bckp1:    subi sp, sp, 0x10    #move stack for 4 ints
    stw r21, 0x0(sp)
    stw r22, 0x4(sp)
    stw r23, 0x8(sp)
    stw r24, 0xC(sp)
    blr

restore: #FUNCTION: loads backed up values from the stack with the same options as the above function
    cmpwi r0, 0
    lwz r21, 0x0(sp)
    lwz r22, 0x4(sp)
    lwz r23, 0x8(sp)
    lwz r24, 0xC(sp)
    addi sp, sp, 0x10
    bne rstr1        # skip the extra backup if r0 was != 0
    lfd f0, 0x0(sp)
    lfd f1, 0x8(sp)
    lfd f2, 0x10(sp)
    lwz r0, 0x18(sp)    #note that this still holds old CR
    lwz r3, 0x1C(sp)
    lwz r4, 0x20(sp)
    addi sp, sp, 0x24
rstr1:    blr

x2stack: #FUNCTION: takes x lines (r3) from r4 (bottom to top) and puts them on the stack
    lwz r0, 0(r4)        #if the sp is backed up in another register
    subi r4, r4, 4        #then it’s also possible to put "y" in the sp and use this to move "x2y"
    stw r0, 0(sp)
    subi sp, sp, 4
    subi r3, r3, 1
    cmpwi r3, 0
    bgt x2stack        #loop until x is exhausted
    blr

stack2x: #FUNCTION: reverse of the above, and writes lines from the stack to x
    lwz r0, 0(sp)
    addi sp, sp, 4
    stw r0, 0(r4)
    addi r4, r4, 4
    subi r3, r3, 1
    cmpwi r3, 0
    bgt stack2x
    blr
#---data2 end------------------------------

#--CODE START--
#The code is basically just a loop that determines whether
#    or not to run any specified Inserts, Skips, or Subroutines. <<still working on subroutines
#    FF EE BB IS    is the format of the input line, which specifies most of this information

#r24 = rP = data Pointer (used to reference any subroutines and start checking inputs)
#r23 = rI = current input line being read
#r22 = rBB = count of extra length for this input (selected by the BB byte)
#r21 = rR = condition result for current input line (subroutine/insert/skip check)
#    result 0 = still checking, 1 = forced, 2 = failed

#Note that the loop is not aware of the length of each input, or the total number of inputs until they're checked
#    Each line is responsible for containing information that informs the loop each iteration
#    BBloop determines the extra length added by any programmable BB lines


start:    mr r23, r24        #create rI for loopIn (input loop)

loopIn:    li r21, 0        #start of input loop, result = 0 (checking)
    lbz r0, 0(r23)        #load FF ID to check for a skip function (ignore current line and read next one)
    cmpwi r0, 0
    beq nxtIn        #if FF == 0 (skip) then don't check this line and move on to the next one
    cmpwi r0, 3
    beq endIn        #if FF == 3 (end) then finish the input loop

intrpt:    lfs f0, 0(r29)        #load in subaction timer (float)
    lfs f1, -0x778C(rtoc)    # 0.0
    fcmpo cr0, f0, f1    #
    lbz r3, 0x1(r23)    #load EE in r3
    ble force
    rlwinm. r0, r3, 0, 14, 14    #check for interrupt flag, if subaction timer is still ticking
    bne force            #continue as normal if flagged

iFAIL1:    li r21, 2            #else, mark Condition Result as FAILED (loop continues to check line lengths)
    b BBlines            #skip to BBlines, to find length for skipping to next input

force:    rlwinm. r0, r3, 0, 15, 15    #check for forced bit
    rlwinm r3, r3, 0, 16, 13    #unflag both bits (they are only to stay on for one frame at a time)
    stb r3, 0x1(r23)            #update flags
    beq EEcheck
    li r21, 1            #mark result as FORCED (can't fail conditions)
    b BBlines

EEcheck:lbz r0, 0(r23)
    cmpwi r0, 2        #check if FF ID is == 2 (currently unused, fails the input)
    beq iFAIL1
    lwz r4, 0x8(r29)
    cmpwi r4, 0
    bne point
    li r4, 0
    b nopoint

point:    lbz r4, 0(r4)
    rlwinm r4, r4, 0, 24, 29    #r0 = 6-bit event ID
nopoint:lbz r3, 0x1(r23)        #r3 = 6-bit EE search
    cmpw r3, r4
    bne iFAIL1        #fail condition result if not a match

BBlines: #this byte contains bits that toggle extra lines for conditions and subroutines
    #    checking these are critical to the operation of the loop because
    #    they contain information about how long each line is.
    li r3, 0x80        #r3 = "loop bit"
    li r4, 0x28        #r4 = "BB func offset"
    li r22, 4        #reset "BBline count"

BBloop:    lbz r0, 0x2(r23)    #load BB byte from input
    and. r0, r3, r0        #compare BB byte to "loop bit"
    bne BBcheck        #if bit is toggled, then account for it in BBcheck

BBnext:    andi. r0,r3,1        #check if loop is finished
    bne BBend
    rlwinm r3, r3, 31, 0, 31    #if not, then shift loop bit for next iteration
    addi r4, r4, 4        #and increment BB func offset
    b BBloop        #loop iterate

BBcheck: #when BBlines finds a BBline, it counts it here, and runs its function
    addi r22, r22, 4    #count BB line--note that more lines can be specified by each
    li r0, 1        #    bb function, and so those must be counted inside the function
    bl backup
    mr r21, r3
    mr r22, r4
    bl backup        #make room in the registers for fancy BB functions

    mflr r3
    add r3, r3, r4        #construct pointer to appropriate BBfunc
    mtlr r3
    blrl            #call function

    li r0, 1
    bl restore
    mr r3, r21
    mr r4, r22
    bl restore        #all registers are restored, and BB function is finished
    b BBnext        #return to loop

bbTOC:    b bb1            #(these get selected by the pointer constructed for BLRL)
    b bb2
    b bb3
    b bb4
    b bb5
    b bb6
    b bb7
    b bb8

BBend:    #once BB lines are all checked, the Result Condition in r21 is tested before insert/skip
    li r22, 4        #reset "BBline count"

    cmpwi r21, 2        #r21 is now free
    mr r3, r22        #r22 is now free
    lwz r22, 0(r23)        #otherwise, get ready to handle the SAevent insert (if applicable)
    add r23, r3, r23    #update rI to point after BBlines (r22 now holds Input line)
                #r23 == Insert Event (or next Input line, when no insert)
                #r22 == FF EE BB IS of this event
    beq nxtInI        #finish loop iteration if result was == FAIL

Icheck:    rlwinm. r3, r22, 28, 28, 31    #load/check "I" 4-bit value (word length)
    beq Scheck        #skip Icheck if I <= 0 (no insert event to read)

Insert:    lwz r4, 0x8(r29)    #load event pointer for x as x2stack
    subi r4, r4, 4        #correct stack...
                #r3 holds input length
    bl x2stack        #back up (real) SAevent lines in stack
    addi r4, r4, 4        #correct stack...
    mr r21, sp        #back up the stack pointer so we can use stack2x in a funky way
    mr sp, r23        #use input lines as "stack" (input for function)
    rlwinm r3, r22, 28, 28, 31    #load "I" 4-bit value (as word length
    bl stack2x         #this should move input lines to the current SAevent lines
    mr sp, r21        #restore stack pointer
    subi sp, sp, 4        #correct stack...

IreadE:    rlwinm r3, r22, 30, 26, 29    #load "I" 4-bit value (as byte length)
    subf r4, r3, r4        #offset event pointer by I
    stw r4, 0x8(r29)    #update event pointer
    lbz r4, 0(r4)
    rlwinm r4, r4, 30, 26, 31    #load 6-bit Event id
    cmpwi r4, 10        #we're ready at this point to run our fake Event
    rlwinm r4, r4, 2, 0, 31    #unrotate for offset
    bge event        #if ID is < 10, then it is a control event, not a normal one

cEvent:    lis r3, 0x803c
    ori r3, r3, 0x67c0    #so construct pointer to control SAevent functions
    add r3, r3, r4        #
    mtlr r3            #
    mr r3, r29        #
    b readI

event:    subi r0, r4, 40        #else, r0 = 6-bit event ID - 10 (non-control event)
    add r3, r31, r0        #r31 holds pointer to control event functions (hook context)
    lwz r3, 0(r3)
    mtlr r3
    mr r4, r29        #move player.0x444 to r4 instead of r3
    subi r3, r29, 0x444    #creates pointer to player.0x0 in r3, for event functions

readI:    blrl            #read inserted subaction event
    addi sp, sp, 8        #correct stack...
    rlwinm r3, r22, 30, 26, 29    #load "I" 4-bit value (as byte length)
    lwz r4, 0x8(r29)
    subf r4, r3, r4
    rlwinm r3, r3, 30, 0, 31        #shift r3 for stack2x
    bl stack2x        #repair the lines we clobbered by loading stored SAevent from the stack
    subi sp, sp, 4        #correct stack...
                #the inserted subaction event has been read, and memory should now appear untouched

Scheck:    rlwinm. r3, r22, 2, 26, 29    #load/check "S" 4-bit value
    beq nxtInI        #finish loop iteration if nothing specified for "S" skip
    lwz r4, 0x8(r29)
    add r4, r3, r4        #otherwise, add S to the event pointer
    stw r4, 0x8(r29)    #and store it
    lbz r4, 0(r4)
    rlwinm r4, r4, 0, 24, 29     #check to see if new event also matches EE
    rlwinm r3, r22, 16, 24, 29
    cmpw r3, r4
    beq start            #if they are, then start over from the beginning of the input loop

nxtInI:    rlwinm r3, r22, 30, 26, 29    #load "I" 4-bit value (as byte length)
    add r23, r3, r23
    b loopIn
nxtIn:    addi r23, r23, 4
    b loopIn

endIn:    li r0, 0        #finish loop when "0x03" FF ID is read
    bl restore
    mtcr r0
    lwz r0, 0x8(r29)    #original instruction

I have a lot of plans for this concept. It’s bound to change over time quite a bit, and is going to be a massive learning experience for me. It’d be really great if everything turns out the way I want it to, but even if it doesn’t I hope to at least demonstrate the power we have over subaction events from the RAM.
 
Last edited:

Ampers

Smash Journeyman
Joined
Feb 2, 2015
Messages
237
Location
St. Louis, MO
Dude this looks like it's gonna be pretty awesome once you get have more of it complete and polished up. I'll be following this project for sure.
 

Punkline

Dr. Frankenstack
Joined
May 15, 2015
Messages
423
Excellent! This project’s all about the R&D, so hopefully it leads to some useful discoveries. As I learn more about this game, I’m sure this thread will accumulate a lot of information that you guys can use, Ampers Ampers and @Tater.

Ampers, I’m curious if you’ve made any progress with projectile hitboxes. I followed your lead after a conversation that almost happened about finding them in RAM (but fell short after nobody really had any info) and tried looking into it.

(These are straight memory edits. No codes yet.)


I found a lot of really interesting things that I think you and @SinsOfApathy might be interested in as well; considering his recent contributions to the CSM:


(edits: just compartmentalized a bunch of things)
First of all--I’m not really clear on the difference between articles, entities or projectiles. I’m just going under the assumption that items, projectiles, and body attachments are different types of articles… hopefully this nomenclature doesn’t confuse anyone.

I'm focusing more on a differentiation between player data/events, article data/events, and their common control data/events.


---


Anyway--articles appear to have their own subaction event parsing loop.


The normal player SAevent parsing loop (which I’ve simply been referring to as “SAevent_frame” all this time... I should be calling SAevent_playerFrame or something) calculates a pointer in the link register based off of a lookup table that designates all events 28-and above.

Pointers calculated like this can vary at runtime (they variably link to each type of event) so they aren’t easily mapped without looking at the lookup table they use.

The control events get called by a separate little function that works BLRLs in a similar manner, but with a different lookup table than what’s used for player events.

These 3 functions call every known event:
Control - 0x802799E4; Player - 0x80073240; Article - 0x802799E4

The lookup tables they use are located here:
Control - 0x80005B64; Player - 0x803C06E8; Article - 0x803F22A8

Thing is, this separate BLRL function first gets a straight call--so the callstack/symbol tools in Dolphin Debug allow you to see what else calls it (any of which presumably use the SAevent System; with the bottom of the contextual event index consisting of control events.)


---


It was a little while ago, so I don’t exactly remember my train of discovery; but it ended in deja-vu.

Here’s a dump of the Article counterpart to the Player SAevent_frame. I’ve included the player and control functions for comparison.

It basically uses the same loop iteration for parsing event data as the player event loop; except that the non-control index is yet another separate set of parsing functions from player events and apply to an article data pointer rather than a player data pointer.

@SinsOfApathy, I think you’ve seen these article data pointers a lot recently--looking at these functions you’ve gathered up. The pointer is passed as r3 to the SAevent_articleFrame function, so you can monitor every article from there pretty easily. You can also see what events they use from inside the parsing loop, which is the later half of the function:

From what I’ve seen, it looks like these have a common table structure (complete with a header that resembles the beginning of player data tables.)

Because the control events are common between event types; the “event control” table (located at player.0x444) has an analogous counterpart in the article data at article.0x2C->0x524 (I have to double check this. Will edit if necessary.) Each of them look like this:

0x00 - event timer
0x04 - subaction frame
0x08 - event pointer < (all the fun)
0x0C - loop count
0x10 - return pointer

For more details on this specifically, see my post here.


---


The article data seems to have a familiar header that looks very similar to the player data table. It has a series of flags at 0x0 (of which I believe the first Hword is used to identify the type of data table it is--6 being an article, and 4 being a player) as well as a pointer at 0x2C that points to the main data. I don’t know what all else is in this article data structure, but I bet it’s juicy @Absolome

The article data seems to get constructed and destroyed as the articles spawn and despawn. Because of this, SAevent_articleFrame will run once per article, per frame; and a hook/breakpoint inside the parsing loop will catch every event of every article.

As such, it’s possible to edit article event data in the same way it’s possible to edit player event data. Ampers Ampers , I believe this is what you were experimenting with in RAM. It’s a lot of fun:

(edit: I guess spoilers hate gfycat. Moved video to top of post)​


---


Here are some interesting pointer addresses that appear to be static and seem to keep track of articles on the stage:

0x80BDA4A4 - first article
0x80BDA5C4 - latest article
0x80BDA6D8 - first article (duplicate)
0x80BDA818 - latest article (duplicate)

If you look at any of these, watch carefully after an article has despawned. If you follow the pointer in 0x2C of the same header for anything else that appears, you will find other kinds of data tables too… I think I found one for a “texture covered mesh” like the kind that I disabled textures for in my Unobscured Hitboxes code. They’re identified with an “8” Hword at 0x0. I’ll try to look into this more later.


---


What’s more, a couple of memory searches revealed that the pointers for most of the character-related projectiles are referenced from within the player data tables. Strangely, they all appear in different offsets based on character--but within the same general area.

Example character data offsets that store active player projectiles pointers:

0x23E4 - Fox’s Illusion/”shadow”
0x228C - Fox’s blaster
0x19E4 - Link’s boomerang (holding)
0x2294 - Link’s boomerang
0x22A0 - Link’s Bow
0x22A0 - YLink’s bow (clones all seem to use matching locations)
0x22A4 - YLink’s milk

Note that these locations are all similar to the one I mentioned in this post.


---


Finally, one more thing--this table at player.0x21FC matches the pointers available in Crazy Hand’s move logic tables according to the current player/move:

0x00 - IASA function pointer
0x04 - Animation Interrupt pointer
0x08 - Action Physics pointer
0x0C - Collision Interrupt pointer
0x10 - Camera Behaviour pointer


---


I have some loose notes about other functions I was looking at, each of which might hold some more answers should you guys be curious:

(0x80272460) - article_hitboxDamageCalc -- hitboxes use this to stale and scale damage (to model size)
(0x80268e5c) - article_behaviour? -- either an initialization, or an “adjustment” function. I believe it’s the former.
(0x802694cc) - article_body_init -- every non-held body attachment executes this.
(0x80269528) - article_update? -- a really big function that runs once for every article. Does stuff.
 
Last edited:

Punkline

Dr. Frankenstack
Joined
May 15, 2015
Messages
423
So, not long ago I started chatting with @DRGN about some bugs in MCM that seemed to be preventing the use of multiple standalone functions in a single injection mod. They’ve all since been fixed in a recent update!

I already sort of mentioned this to you DRGN, but I just wanted to reiterate that the effort you’ve poured into this community is some really inspiring stuff. I know I’m not the only one around here who feels like your contributions are invaluable. Thank you again for somehow finding the time to address these subtle issues on top of doing everything that you do~


---

Standalone functions are rad. Now that I’ve got my hands on these things, I think this project will start picking up some pace.

For those who might not be aware; a standalone function is an advanced feature in MCM that allows you to create floating functions that can be included with your code.

This brings with it a design philosophy in code writing that’s more akin to writing pure ASM; like when writing a DOL mod. The big difference is that all of the injection site mess is handled by an automated parse when saving in MCM; allowing you to design without prerequisite knowledge of the locations of your codes/functions in the DOL.


---

Here’s an example of a standalone function:

I’ve called it “UTILinjectPSAdata” because it’s a function that redirects a player subaction event pointer to point at a new location; allowing you to inject subaction data in a manner similar to how codes are injected. It can be called in MCM by any code with the following special syntax:

bl <UTILinjectPSAdata>


The function behaves like so:

<UTILinjectPSAdata>
r3 = injection data start location
r4 = player entity pointer
r5 = return pointer/offset (displaces and stores current event location for “return” events to use)
This function redirects a player event pointer to the location specified in r3, and (optionally) writes a return pointer in the same manner that event 14 does for the 18 “return” event. By specifying 0 for the return pointer, this behaves like a 1C event and doesn’t write a return.

<UTILinjectPSAdata>
90C1FFF8 90E1FFF4
9101FFF0 2C050000
41820038 80C40450
80E4044C 39060001
54C0103A 91040450
7CC72A14 39040444
7D080214 74A08000
4182000C 90A80010
48000008 90C80010
9064044C 80C1FFF8
80E1FFF4 8101FFF0
4E800020



Syntax Highlighted ASM

ASM as text:
Code:
/*-- UTILinjectPSAdata
r3 = injection destination
r4 = player.0x0
r5 = return pointer/offset
Subactions can terminate themselves with a 00xxxxxx event line.
This makes returns from the subaction injection optional.
If your injection doesn't return, then specify "0" for r5.
Otherwise, the length (in bytes) of the value in r5 is used to offset the current event pointer on return.
If a full pointer is passed in r5, it will be recognized and used as an absolute pointer.
*/
stw r6, -0x8(sp)    #leaf
stw r7, -0xC(sp)
stw r8, -0x10(sp)
cmpwi r5, 0             #if return offset is 0, then skip the return write (just inject the data)
beq inject
lwz r6, 0x450(r4)       #this next part is practically identical to control event 14--which writes a return in player memory
lwz r7, 0x44C(r4)
addi r8, r6, 0x1
rlwinm r0, r6, 2, 0, 29
stw r8, 0x450(r4)
add r6, r7, r5          #this is where the passed return offset (r5) is added
addi r8, r4, 0x444
add r8, r8, r0
andis. r0, r5, 0x8000   #we want to check if r5 is actually an absolute pointer
beq relative
stw r5, 0x10(r8)
b inject
relative:
stw r6, 0x10(r8)
inject:
stw r3, 0x44C(r4)       #set player event pointer to PSA injection data
lwz r6, -0x8(sp)
lwz r7, -0xC(sp)
lwz r8, -0x10(sp)
blr

You can use this function to create subaction injections via any code written in MCM. It requires no modification to the original data, so default subaction events may be preserved Ampers Ampers @Tater

It requires the context of a player pointer, an optional return offset/pointer, and the location of the data to be injected (into the current subaction context).

You could involve the stack here and probably do some really tricky stuff with calls to the player event parsing loop. The concept code in my Update: 0 post sort of explores this idea in a very conservative way. I’ve got some ideas about how to rewrite it that I’d like to explore in future updates.


---

Standalone functions are also extremely useful for creating floating data tables that can be easily referenced by any code in MCM.

To make one, simply make the first line of a standalone function a “blrl” instruction. This will cause any function that calls it to immediately return with a pointer to anything below it.

Here’s an example of a structure that’s very useful in tandem with the above function example:

<UTILgetPSAoffsetReference>
Any function that calls this will immediately return with a pointer to the data included.
The included data is an hword-aligned lookup table of each character’s (effective) Plxx.dat offset for the start of “wall damage” subaction events when translating a PSA offset. It is keyed to the internal character id index.

<UTILtranslatePSAoffset>
r3 = Plxx.dat offset of a player subaction event
r4 = player entity pointer, with a character that matches the specified offset
Returns: r3 = pointer to event location currently loaded in RAM
This function takes the file data offset (of a player subaction event) provided in r3 and matches it to the character ID of the player entity in r4. It spits out a pointer to the location in RAM; allowing Plxx.dat offsets to be used as a means of referencing subaction events mid-game.

<UTILgetPSAoffsetReference>
4E800021 3B844108
41C447D4 7D44418C
42CC3CA8 4300433C
3CEC1BD0 3E4442E0
3C743F24 3ED03AC0
46404028 45343CC0
41AC3D04 443441B8
49C00C38 0B7C3360
318C40CC

<UTILtranslatePSAoffset>
90A1FFF8 90C1FFFC
7C0802A6
bl <UTILgetPSAoffsetReference>
7CA802A6 7C0803A6
80C40064 54C6083C
7CA5322E 80C40084
80C6000C 7CC53050
7C661A14 80C1FFFC
80A1FFF8 4E800020



Syntax Highlighted ASM

ASM as text:
Code:
/*-- UTILgetPSAoffsetReference
This lookup table uses the internal character ID at player.0x64 to map the file data offsets of PSA "Wall Damage"
The address pointed to in player.0x84 -> 0xC will match this file data offset, providing a key for translation.
*/
blrl
.hword 0x3B84    #00 Mario
.hword 0x4108    #01 Fox
.hword 0x41C4    #02 Cfalcon
.hword 0x47D4    #03 DK
.hword 0x7D44    #04 Kirby
.hword 0x418C    #05 Bowser
.hword 0x42CC    #06 Link
.hword 0x3CA8    #07 Shiek
.hword 0x4300    #08 Ness
.hword 0x433C    #09 Peach
.hword 0x3CEC    #0A Popo
.hword 0x1BD0    #0B Nana
.hword 0x3E44    #0C Pikachu
.hword 0x42E0    #0D Samus
.hword 0x3C74    #0E Yoshi
.hword 0x3F24    #0F Jpuff
.hword 0x3ED0    #10 Mewtwo
.hword 0x3AC0    #11 Luigi
.hword 0x4640    #12 Marth
.hword 0x4028    #13 Zelda
.hword 0x4534    #14 YLink
.hword 0x3CC0    #15 Doc
.hword 0x41AC    #16 Falco
.hword 0x3D04    #17 Pichu
.hword 0x4434    #18 GaW
.hword 0x41B8    #19 Ganondorf
.hword 0x49C0    #1A Roy
.hword 0x0C38    #1B Master Hand? the following are untested
.hword 0x0B7C    #1C Crazy Hand?
.hword 0x3360    #1D Wire Male?
.hword 0x318C    #1E Wire Female?
.hword 0x40CC    #1F Giga Bowser?


/*-- UTILtranslatePSAoffset
r3 = PSA offset (player subaction data located in Plxx.dat)
r4 = player.0x0
Returns r3 = pointer to matching subaction event in RAM, r4 = player.0x0 (unmodified)
An included lookup table is used here to create an effective BA for converting Plxx.dat subaction offsets into RAM addresses
*/
stw r5, -0x8(sp)    #leaf
stw r6, -0x4(sp)
mflr r0            #we need LR to grab a pointer
bl <UTILgetPSAoffsetReference>
mflr r5            #r5 = pointer to reference table
mtlr r0            #LR is restored
lwz r6, 0x64(r4)    #load character ID
slwi r6, r6, 1        #hword alignment
lhzx r5, r5, r6        #load appropriate reference offset
lwz r6, 0x84(r4)    #load Player Subaction table
lwz r6, 0xC(r6)        #this loads pointer to beginning of "Wall Damage" PSA in RAM
subf r6, r5, r6        #Subtract offset from RAM address to create an effective BA
add r3, r6, r3        #add input address to effective BA to create final pointer
lwz r6, -0x4(sp)
lwz r5, -0x8(sp)
blr

Note that the data pointer doesn’t need to be passed to the function that uses it. This is because the function can simply use the special branching syntax to reference the data as if we knew the location it were stored deep in RAM.

--

With these pieces, it’s possible to set up a parsing function inside the SAevent parsing loop that reads off an included data table that provides the following in the form of a header for each injection entry:

  • Character ID (internal)
  • Plxx.dat Offset(as an injection point)
  • Injection length (so multiple entries can be made per table)

Those just are bare bones. We’re working with ASM here, so there’s a lot of extra power we can piece together into a pipeline that fits in with this parse.

Here’s an experimental parsing structure that will likely be adapted in some form as a major part of the next update for this project:

ChRtPlxx HdOpData (Pfff... ) (OOOOOOOO) (...) <subaction data to be injected>

  • Ch - Character ID
  • Rt - Return displacement (signed 8-bit, 0 for “no return” write option)
  • Plxx - Plxx.dat Offset used for data injection point
  • Hd - Header Length (this includes options, as well as any padding)
  • Ob - Options bitfield length (the length of hword array that selects option parses)
  • Data - Injection Data Length (the length of the subaction event data you've included)

This information helps the parse quickly sort out the length of the following (optional) structure:

Start of options selection (hword array)
  • P - Options bitfield Page
    • fff - Options bitfield Flags - each denotes a unique parsing function
  • (Ends in hword padding if necessary for 32-bit alignment)
    • (this padding is implied, and should not be specified in “Ob”)

Start of options parse data (32-bit array) at end of (aligned) options length.
  • OOOOOOOO - All applicable option parses selected by options bitfield pages. These may vary in length, but will all be 32-bit aligned.
<start of injection data at end of specified header length>


---

This “options” system in the parse is extremely flexible:

For simple injections, options may be omitted entirely by formatting the first part of the header like so:

ChRtPlxx 0800Data <subaction injection> -- (no options to parse in this header)

With 00 option page length to parse, the header length will always be a static 08 bytes long; so you just need to know the character ID, the return displacement that you want to use (if any), the Plxx.dat offset that you want to use to start your injection, and the length of your injected data.

By specifying an hword-aligned length in place of 00, you will tell the parse to expect an appropriate number of options pages to follow. Any bits in the provided options page bitfields will each denote a special parsing option that must be parsed after the bitfields.

Parsing functions can be written for these parsing options and simply pointed to by the included "UTILgetOptionsTable" table in order to be assigned to 1 fff bitflag on a specific P page.

Currently, I’ve only written one option for testing. It’s a “shorthand” conditions parse (on page 0, bit 0x800.) It checks a few common things, and follows this syntax for OOOOOOOO:

CTPLIISB
C - 5-bit Costume ID field
T - 3-bit Team ID field
P - 4-bit player slot type field (8 = HMN, 4 = CPU, 2 = Demo, 1 = N/A) (these last 2 bits are open for change)
L - 4-bit CPU level integer
II - 8-bit Held Item ID integer
S - 4-bit Controller analog stick field (8 = forward, 4 = back, 2 = down, 1 = up)
B - 4-bit Controller button field (8 = X/Y, 4 = heavy L/R, 2 = A, 1 = B)

Note that each of these "ID fields" are bitfields with each bit representing one place on the index. This allows you to specify multiple IDs in a single parse.

Also, every option in the parse has been designed to effectively "skip" itself when specified as "F" or "FF."


---

What this all means is that by specifying the correct information in the header, you can create conditions that determine whether or not the procedurally injected subaction will be used.

This is an early example, but with it you can use costume IDs, human/cpu slot type, cpu level, held item ID, or basic controller inputs as conditions for executing an injected subaction. If the condition fails, then the unaltered default subaction data is used instead.

This is very much in the spirit of the Subaction Event Scheduler concept I posted here months ago.


---

So, here’s the injection mod package that includes the functions, tables, and parse instructions I’ve written about above. Just paste it into a new/existing text file inside of the Mods Library folder of your MCM directory:

Injection Mod -- add this to MCM:

Code:
-==-
Player Subaction Injections v0.02
A library of reusable standalone functions.
Injection mod demonstrates subaction injections via any table added to the PSAIgetPackageTable function.

Tables included this way are parsed in order to conditionally inject subactions.
[Punkline]
Version -- DOL Offset ------ Hex to Replace ---------- ASM Code

1.02 ----- 0x800732ec --- 7FA3EB78 -> Branch

7C0802A6 90010004
9421FFF8
bl <PSAIgetPackageTable>
7C6802A6 7F64DB78
bl <PSAIparsePackage>
809D0008 38210008
80010004 7C0803A6
00000000


<PSAIgetPackageTable>
4E800021    #any tables of data to be injected must be included here
b <PSAIgetData00>
00000000

<PSAIgetData00>
4E800021 #start of default data table

#this is your default table
#you may optionally use this instead of creating a new table

00000000    #end of data table


<PSAIgetOptionsTable>
4E800021
b <PSAIgetOption0Table>
00000000


<PSAIgetOption0Table>
4E800021
b <PSAIoption01short>
00000000


<UTILgetPSAoffsetReference>
4E800021 3B844108
41C447D4 7D44418C
42CC3CA8 4300433C
3CEC1BD0 3E4442E0
3C743F24 3ED03AC0
46404028 45343CC0
41AC3D04 443441B8
49C00C38 0B7C3360
318C40CC


<PSAIparsePackage>
7C0802A6 90010004
9421FFF0 BFA10008
7C9F2378 7C7E1B78
3BA00000 7C7EE82E
2C030000 41820034
7C7EEA14 7C6803A6
4E800021 7C6802A6
80030000 2C000000
41820010 7FE4FB78
bl <PSAIparseDatum>
4BFFFFEC 3BBD0004
4BFFFFC8 BBA10008
38210010 80010004
7C0803A6 4E800020


<PSAIparseDatum>
7C0802A6 90010004
9421FFB8 BE010008
7C9F2378 7C7E1B78
807F0064 889E0000
7C032000 408200E4
7FE4FB78 A07E0002
bl <UTILtranslatePSAoffset>
809F044C 7C032000
408200CC 887E0005
3A9E0008 7EA3A214
7EB6AB78 56C007BF
41820008 3AD60002
7C14A800 40800074
A0740000 5464A77E
5470053E
bl <PSAIgetOptionsTable>
7C6802A6 7C632214
7C6803A6 4E800021
7E6802A6 3A400800
3A200000 7E009039
41820024 7C719A14
7C6803A6 7EC3B378
7FE4FB78 4E800021
2C030000 41820058
7C761B78 2C120001
41820010 3A310004
5652F87E 4BFFFFC8
3A940002 4BFFFF8C
887E0004 7C63F214
7FE4FB78 88BE0001
54A00631 4182001C
54A5C00E 7CA500D0
54A5463E 7C0500D0
80BF044C 7CA50214
bl <UTILinjectPSAdata>
887E0004 A09E0006
7C632214 7C63F214
38630004 BA010008
38210048 80010004
7C0803A6 4E800020


<UTILinjectPSAdata>
90C1FFF8 90E1FFF4
9101FFF0 2C050000
41820038 80C40450
80E4044C 39060001
54C0103A 91040450
7CC72A14 39040444
7D080214 74A08000
4182000C 90A80010
48000008 90C80010
9064044C 80C1FFF8
80E1FFF4 8101FFF0
4E800020


<UTILtranslatePSAoffset>
90A1FFF8 90C1FFFC
7C0802A6
bl <UTILgetPSAoffsetReference>
7CA802A6 7C0803A6
80C40064 54C6083C
7CA5322E 80C40084
80C6000C 7CC53050
7C661A14 80C1FFFC
80A1FFF8 4E800020


<UTILreturnStaticPlayerBlock>
8863006C 1C630E90
38633080 3C808045
7C831B78 4E800020


<PSAIoption01short>
93E1FFF0 93C1FFF4
90A1FFF8 7C9F2378
7C7E1B78 887E0000
889F0679 7C652030
54A00631 418200E8
889F067B 7C652030
54A0077B 418200D8
7FE3FB78 7C0802A6
bl <UTILreturnStaticPlayerBlock>
7C0803A6 80830008
887E0001 7C652030
54A00631 418200B4
5465073F 41820018
2C050009 41810010
889F1AFB 7C042800
40820098 887E0002
2C0300FF 41820018
801F19D4 8000002C
80800010 7C032000
40820078 807F06BC
5464A636 A0BF008C
2C053F80 41820014
5485FE72 50850E30
508506B6 7CA42B78
5465C7BE 5460052B
41820008 68A50008
54600675 41820008
68A50004 887E0003
706000F0 2C0000F0
4182000C 7C002000
40820020 7060000F
2C00000F 4182000C
7C002800 4082000C
387E0004 48000008
38600000 80A1FFF8
83C1FFF4 83E1FFF0
4E800020
Syntax Highlighted ASM (as PDF)

ASM as text:
Code:
/*-- DESCRIPTION BRIEF
Player Subaction Injections--v0.02 [Punkline]

Library of functions that may be used to conditionally inject subaction data.
See comments for more details.
*/

###
### CODE INJECTION
###

/*The code here is designed to work from the player subaction event parsing loop.
It needs to be able to intercept new events as they come in in order to accurately make procedural injections.

#@800732ec: lwz    r4, 0x0008 (r29)    (loading next player event)
#context:
#LR is safe
#cr0 is safe, but cr2 may be important to context
#r0 == safe as GPR
#r31 == non-control event functions pointer
#r29 == player.0x444
#r27 == player.0x0
*/
mflr r0
stw r0, 0x4(sp)
stwu sp, -0x8(sp)
bl <PSAIgetPackageTable>    #grab data
mflr r3                #pass as r3
mr r4, r27            #pass player as r4
bl <PSAIparsePackage>        #tell code to parse it
lwz r4, 0x8 (r29)    #original instruction
addi sp, sp, 0x8
lwz r0, 0x4(sp)
mtlr r0
.long 0x00000000






###
### DATA
###

#PSAI tables--------
#this is where all the subaction data is written

/*-- PSAIgetPackageTable
This table is used by the code in order to reach and parse all other included data tables.
Multiple injections may be included per table; so this master table is mostly just for organizing packages.
Remember to terminate all tables with an extra "00000000" line at the end!
*/
blrl
b <PSAIgetData00>    #this table is the default table. It is blank, until you put subaction data in it.
            #you can create new tables and append them to this list.
.long 0x00000000

/*-- PSAIgetData00
The default table. Create a header and include subaction data to be injected below it.
These tables can be extended to (virtually) any length. Add as many injections to the table as needed.
Remember to terminate all tables with an extra "00000000" line at the end!
*/
blrl
.long 0x00000000






#parse tables--------
#these are used by parsing functions

/*-- PSAIgetOptionsTable
This table is used to organize the option parses made in the header of each PSAI datum.
It's essentially a lookup table of other tables that contain lists of parsing function pointers.
*/
blrl
b <PSAIgetOption0Table>
.long 0x00000000

/*-- PSAIgetOption0Table
This table holds pointers to each parse function for page "0" of the options bitfield.
*/
blrl
b <PSAIoption01short>
.long 0x00000000

/*-- UTILgetPSAoffsetReference
This lookup table uses the internal character ID at player.0x64 to map the file data offsets of PSA "Wall Damage"
The address pointed to in player.0x84 -> 0xC will match this file data offset, providing a key for translation.
*/
blrl
.hword 0x3B84    #00 Mario
.hword 0x4108    #01 Fox
.hword 0x41C4    #02 Cfalcon
.hword 0x47D4    #03 DK
.hword 0x7D44    #04 Kirby
.hword 0x418C    #05 Bowser
.hword 0x42CC    #06 Link
.hword 0x3CA8    #07 Shiek
.hword 0x4300    #08 Ness
.hword 0x433C    #09 Peach
.hword 0x3CEC    #0A Popo
.hword 0x1BD0    #0B Nana
.hword 0x3E44    #0C Pikachu
.hword 0x42E0    #0D Samus
.hword 0x3C74    #0E Yoshi
.hword 0x3F24    #0F Jpuff
.hword 0x3ED0    #10 Mewtwo
.hword 0x3AC0    #11 Luigi
.hword 0x4640    #12 Marth
.hword 0x4028    #13 Zelda
.hword 0x4534    #14 YLink
.hword 0x3CC0    #15 Doc
.hword 0x41AC    #16 Falco
.hword 0x3D04    #17 Pichu
.hword 0x4434    #18 GaW
.hword 0x41B8    #19 Ganondorf
.hword 0x49C0    #1A Roy
.hword 0x0C38    #1B Master Hand? the following are untested
.hword 0x0B7C    #1C Crazy Hand?
.hword 0x3360    #1D Wire Male?
.hword 0x318C    #1E Wire Female?
.hword 0x40CC    #1F Giga Bowser?




###
### CODE
###




/*-- PSAIparsePackage
r3 = pointer to PSAIpackageTable (list of all data tables containing subaction data)
r4 = player.0x0
This function loops through every entry on the provided PSAI package table.
It then runs PSAIdatumParse for each entry on each table it finds.
Datum entry lengths are defined in their corresponding headers.
*/
mflr r0
stw r0, 0x4(sp)
stwu sp, -0x10(sp)
stmw r29, 0x8(sp)
mr r31, r4        #save "player.0x0"
mr r30, r3        #save package table pointer
li r29, 0        #Table loop increment, used as key for package table

tableLoop:
lwzx r3, r30, r29    #find package table entry
cmpwi r3, 0        #make sure it isn't blank
beq return        #terminate if it is (0 is a termination code)
add r3, r30, r29    #find package table entry
mtlr r3
blrl
mflr r3            #r3 now holds pointer to data inside of a data table that needs to be parsed

dataLoop:
lwz r0, 0(r3)        #load data value
cmpwi r0, 0        #make sure data isn't blank
beq nextTable        #stop reading this table if it is
mr r4, r31
bl <PSAIparseDatum>    #r3 = location of datum start, r4 = player.0x0
b dataLoop        #returns with r3 = location of next datum

nextTable:
addi r29, r29, 0x4    #increment r20 so that next table is grabbed
b tableLoop

return:
lmw r29, 0x8(sp)
addi sp, sp, 0x10
lwz r0, 0x4(sp)
mtlr r0
blr


/*-- PSAIparseDatum
r3 = location of datum start
r4 = player.0x0
Returns r3 = location of subsequent address (after entire datum)
Parses datum header. If header parse is successful, player subaction data is injected.
Upon return, the next datum is located via the saved datum start pointer, header length, and data length.
*/
mflr r0                #giant stack frame
stw r0, 0x4(sp)            #how to use stmw?
stwu sp, -0x48(sp)
stmw r16, 0x8(sp)        #like this? Neat as heck.

mr r31, r4            #save player.0x0
mr r30, r3            #save datum start location

#Character match check
lwz r3, 0x64(r31)        #load internal character ID from player
lbz r4, 0(r30)            #load internal character ID from parse data
cmpw r3, r4
bne return            #if no match, skip injection and return

#Event match check
mr r4, r31
lhz r3, 0x2(r30)        #r3 = Plxx.dat offset, r4 = player.0x0
bl <UTILtranslatePSAoffset>
lwz r4, 0x44C(r31)
cmpw r3, r4            #if resulting pointer doesn't match current event pointer
bne return            #then skip injection and return

#options parse setup
lbz r3, 0x5(r30)
addi r20, r30, 0x8        #r20 = (location of) current options bitfield page being parsed
add r21, r3, r20        #r21 = end of options bitfield pages
mr r22, r21
rlwinm. r0, r22, 0,30,31    #check word alignment
beq optionsPageLoop
addi r22, r22, 0x2        #r22 = current location of next option parse

optionsPageLoop:
cmpw r20, r21            #check to see if we're done parsing options
bge inject            #if we've reached end, then we're finally ready to inject the data
lhz r3, 0(r20)            #else, we're still checking options. So load the options bitfield to be parsed
rlwinm r4, r3, 20, 29, 31    #r4 = bitfield page number
rlwinm r16, r3, 0, 20, 31    #r16 = bitfield -- each bit in this field represents a whole set of parsing instructions
bl <PSAIgetOptionsTable>    #each set of instructions is handled by a seperate parsing function, organized as a heirarchical table
mflr r3                #r3 = options "page" table. "Pages" are just the parent tables used to hold lists of functions.
add r3, r3, r4            #r3 = matching page table function
mtlr r3
blrl                #calls (and returns a pointer of) appropriate page of parsing functions; mapped to bitfield sequence
mflr r19            #r19 will be our saved option page table. This holds a list of the page's parsing function pointers.
li r18, 0x800            #r18 will be our bitkey. This will get rightshifted each iteration of the loop in order to check all 12 bits
li r17, 0            #r17 will be our actual index key. This will be used to load a corresponding parsing function from r19

optionsParseLoop:        #---this is a nested loop
and. r0, r16, r18        #key in r18 will check each individual bit of r16 on different increments
beq nextBit            #if key doesn't match this bit, then check next bit
add r3, r17, r19        #else, use current key in r17 to offset the page table for branch to appropriate function
mtlr r3
mr r3, r22
mr r4, r31            #pass r3 = option parse data start, r4 = player.0x0
blrl
cmpwi r3, 0            #returns r3 = next datum parse, or 0 if option parse conditions failed
beq return            #if parse failed, then skip injection and return
mr r22, r3            #else, save the new loaction of the current option data to be parsed next iteration

nextBit:
cmpwi r18, 1
beq nextOptions            #if we're done with this bitfield, then we're ready to check if there's a new one
addi r17, r17, 0x4        #else, continue checking bits in this bitfield, and matching them to the parse function table
srwi r18, r18, 1        #sets up next iteration of loop
b optionsParseLoop

nextOptions:
addi r20, r20, 0x2        #increment hword bitfield
b optionsPageLoop        #new iteration. check for termination is at beginning of loop

inject:                #loop structure terminates here if parse is successful
lbz r3, 0x4(r30)        #load length of header
add r3, r3, r30            #r3 = start of injection data
mr r4, r31            #r4 = player.0x0
lbz r5, 0x1(r30)        #r5 = return offset
rlwinm. r0, r5,0,24,24        #check signed 8-bit for signed bit
beq callInject            #just pass the offset if it's positive
slwi r5, r5, 24            #else, create a negative 32-bit value out of this 8-bit one...
neg r5, r5
srwi r5, r5, 24
neg r0, r5
lwz r5, 0x44C(r31)        #function doesn't accept a negative value, so we pass it a calculated pointer
add r5, r5, r0

callInject:
bl <UTILinjectPSAdata>

return:                #loop terminates here if parse failed; handles return value in r3 so injection runs this too
lbz r3, 0x4(r30)        #header length
lhz r4, 0x6(r30)        #data length
add r3, r3, r4
add r3, r3, r30
addi r3, r3, 0x4        #r3 = location of next datum

lmw r16, 0x8(sp)
addi sp, sp, 0x48
lwz r0, 0x4(sp)
mtlr r0
blr

/*-- UTILinjectPSAdata
r3 = injection destination
r4 = player.0x0
r5 = return pointer/offset
Subactions can terminate themselves with a 00xxxxxx event line.
This makes returns from the subaction injection optional.
If your injection doesn't return, then specify "0" for r5.
Otherwise, the length (in bytes) of the value in r5 is used to offset the current event pointer on return.
If a full pointer is passed in r5, it will be recognized and used as an absolute pointer.
*/
stw r6, -0x8(sp)    #leaf
stw r7, -0xC(sp)
stw r8, -0x10(sp)
cmpwi r5, 0             #if return offset is 0, then skip the return write (just inject the data)
beq inject
lwz r6, 0x450(r4)       #this next part is practically identical to control event 14--which writes a return in player memory
lwz r7, 0x44C(r4)
addi r8, r6, 0x1
rlwinm r0, r6, 2, 0, 29
stw r8, 0x450(r4)
add r6, r7, r5          #this is where the passed return offset (r5) is added
addi r8, r4, 0x444
add r8, r8, r0
andis. r0, r5, 0x8000   #we want to check if r5 is actually an absolute pointer
beq relative
stw r5, 0x10(r8)
b inject
relative:
stw r6, 0x10(r8)
inject:
stw r3, 0x44C(r4)       #set player event pointer to PSA injection data
lwz r6, -0x8(sp)
lwz r7, -0xC(sp)
lwz r8, -0x10(sp)
blr



/*-- UTILtranslatePSAoffset
r3 = PSA offset (player subaction data located in Plxx.dat)
r4 = player.0x0
Returns r3 = pointer to matching subaction event in RAM, r4 = player.0x0 (unmodified)
An included lookup table is used here to create an effective BA for converting Plxx.dat subaction offsets into RAM addresses
*/
stw r5, -0x8(sp)    #leaf
stw r6, -0x4(sp)
mflr r0            #we need LR to grab a pointer
bl <UTILgetPSAoffsetReference>
mflr r5            #r5 = pointer to reference table
mtlr r0            #LR is restored
lwz r6, 0x64(r4)    #load character ID
slwi r6, r6, 1        #hword alignment
lhzx r5, r5, r6        #load appropriate reference offset
lwz r6, 0x84(r4)    #load Player Subaction table
lwz r6, 0xC(r6)        #this loads pointer to beginning of "Wall Damage" PSA in RAM
subf r6, r5, r6        #Subtract offset from RAM address to create an effective BA
add r3, r6, r3        #add input address to effective BA to create final pointer
lwz r6, -0x4(sp)
lwz r5, -0x8(sp)
blr



/*-- UTILreturnStaticPlayerBlock
r3 = player.0x0
Returns r3 = static player block
This simply matches a (dynamic) player entity to a cooresponding static player block.
Useful for grabbing static data values from the context of a player function.
--Note this clobbers r4. I usually try to preserve these if necessary, but it's not necessary here...
*/
lbz r3, 0x6C(r3)        #load player slot
mulli r3, r3, 0xe90        #multiply player slot by static block length
addi r3, r3, 0x3080
lis r4, 0x8045
or r3, r4, r3
blr



/*-- PSAIoption01short
r3 = location of parse start (passed from PSAIoptionsParse)
r4 = player.0x0
Returns r3 = pointer to next option parse, or 0 if fail

This parse is meant to summarize some common conditions.
Multiple entries for one condition type are ORed together. The resulting condition types are ANDed together.
IDs represented as bitfields progress from left to right.
The line is only 32-bits, and is comprised like this:
CT PL II NN
C - 5-bit Costume ID field
T - 3-bit Team ID field
P - 4-bit player slot type field (8 = HMN, 4 = CPU, 2 = Demo, 1 = N/A) (these last 2 bits are open for change)
L - 4-bit CPU level integer
II - 8-bit Held Item ID integer
S - 4-bit Controller analog stick field (8 = left, 4 = right, 2 = down, 1 = up)
B - 4-bit Controller button field (8 = X/Y, 4 = heavy L/R, 2 = A, 1 = B)
*/
stw r31, -0x10(sp)    #fake leaf (makes a single, simple call)
stw r30, -0xC(sp)
stw r5, -0x8(sp)
mr r31, r4
mr r30, r3
lbz r3, 0(r30)        #load CT byte
lbz r4, 0x679(r31)    #load player costume ID
slw r5, r3, r4        #shift by amount == to costume ID
rlwinm. r0, r5, 0,24,24    #check if bit is in parse
beq return0        #if not in parse, then return with 0 for fail
lbz r4, 0x67B(r31)    #load player team ID
slw r5, r3, r4
rlwinm. r0, r5, 0,29,29    #check if bit is in the parse
beq return0        #return with 0 if not

mr r3, r31
mflr r0
bl <UTILreturnStaticPlayerBlock>
mtlr r0
lwz r4, 0x8(r3)        #load slot type from static player block
lbz r3, 0x1(r30)    #load PL parse values
slw r5, r3, r4
rlwinm. r0, r5, 0,24,24    #check if slot type is in parse
beq return0
rlwinm. r5, r3, 0,28,31    #extract 0-based cpu lvl parse value
beq itemcheck        #if level parse is set to 0, then skip it
cmpwi r5, 9
bgt itemcheck        #similarly, skip if level is set above "9"
lbz r4, 0x1AFB(r31)    #load player CPU level
cmpw r4, r5
bne return0        #return with 0 if cpu level is specified, and not a match

itemcheck:
lbz r3, 0x2(r30)    #load II parse value
cmpwi r3, 0xFF
beq controllercheck    #if II value uses "FF" code, then skip it
lwz r0, 0x19D4(r31)    #load pointer to entity struct of currently held item
lwz r0, 0x2C(r0)    #load main data of currently held item
lwz r4, 0x10(r0)    #load Item ID of currently held item
cmpw r3, r4
bne return0        #if item ID is specified in parse, but doesn't match, then return with 0

controllercheck:
lwz r3, 0x6BC(r31)    #load basic digital controller inputs from player
rlwinm r4, r3, 20,24,27    #extract control stick directional bits and align them to parse check
lhz r5, 0x8C(r31)    #load facing direction. This is a float, but it's always 1 or -1
cmpwi r5, 0x3F80    #check if == "1"
beq btnsCheck        #if facing left, then directions are already mapped correctly
rlwinm r5, r4, 31,25,25
rlwimi r5, r4, 1,24,24
rlwimi r5, r4, 0,26,27    #this just swaps the bits for left and right in our extraction
mr r4, r5        #use swapped direction bits when facing the wrong way

btnsCheck:
rlwinm r5, r3, 24,30,31    #extract and insert buttons A and B
rlwinm. r0, r3, 0,20,21    #check if either x or y is pressed (we will summarize both as one bit)
beq heavyLRcheck    #skip xor if not being pressed
xori r5, r5, 0x08    #this is the XorY bit
heavyLRcheck:
rlwinm. r0, r3, 0,25,26    #check if either heavy L or R is pressed (we will summarize both as one bit)
beq controllerbits    #skip xor if not being pressed
xori r5, r5, 0x04    #this is the LorR bit

controllerbits:
lbz r3, 0x3(r30)    #load NN parse value
andi. r0, r3, 0xF0
cmpwi r0, 0xF0        #Special code for skipping analog stick input check
beq btns
cmpw r0, r4        #analog stick input must match N value
bne return0
btns:
andi. r0, r3, 0xF    #Special code for skipping button input check
cmpwi r0, 0xF
beq return1
cmpw r0, r5        #otherwise, button input must match
bne return0


return1:
addi r3, r30, 0x4    #move the option parse pointer and return it
b return
return0:
li r3, 0
return:
lwz r5, -0x8(sp)
lwz r30, -0xC(sp)
lwz r31, -0x10(sp)
blr


To quickly add some subaction data to the code for injections, modify the “PSAIgetData00” table in the mod package.

Alternatively, you can create new tables and name them yourself for categorization. If you do this, be sure to include a branch to any new data in the package table.



---

Here are a few examples I came up with for Ness while testing this code. They require Achilles' custom Character Data subaction event.

They create conditions for each of his aerial normals that provide a unique movement option when holding an appropriate direction on the control stick before hitboxes come out:

Code:
<PSAIgetNessMovingAirs>
4E800021 #start of example data table
#I've exploded the headers so that they could be easily commented
#They don't need to be written all spaced out like this.

08         #Ness (b-air)
04         #return offset
4F E8     #Plxx.dat offset
0C         #header length
02         #options pages length
00 14    #data length
08 00    #options page (0)
00 00
FF        #costume/team ID flagfield (FF = any)
FF        #Human/CPU type field, CPU level (FF = any)
FF        #required held Item ID    (FF = any)
8        #required directional input field (f,b,u,d)
F        #required button input field (x/y,l/r,A,B)
F8 FF 00 0C 03 80 00 80 40 08 00 00 4C 00 00 01 18 00 00 00

08         #Ness (f-air)
04         #return offset
4F 18     #Plxx.dat offset
0C         #header length
02         #options pages length
00 24    #data length
08 00    #options page (0)
00 00
FF        #costume/team ID flagfield (FF = any)
FF        #Human/CPU type field, CPU level (FF = any)
FF        #required held Item ID    (FF = any)
4        #required directional input field (f,b,u,d)
F        #required button input field (x/y,l/r,A,B)
F8 FF 00 1C 03 83 00 80 BF 70 00 00 03 00 00 84 3F A0 00 00 02 01 22 18 FF FF F7 FF 4C 00 00 01 18 00 00 00

08         #Ness (d-air)
04         #return offset
50 E8     #Plxx.dat offset
0C         #header length
02         #options pages length
00 1C    #data length
08 00    #options page (0)
00 00
FF        #costume/team ID flagfield (FF = any)
FF        #Human/CPU type field, CPU level (FF = any)
FF        #required held Item ID    (FF = any)
1        #required directional input field (f,b,u,d)
F        #required button input field (x/y,l/r,A,B)
F8 FF 00 14 03 00 00 84 40 00 00 00 02 01 22 18 FF FF F7 FF 4C 00 00 01 18 00 00 00

08         #Ness (u-air)
04         #return offset
50 84     #Plxx.dat offset
0C         #header length
02         #options pages length
00 14    #data length
08 00    #options page (0)
00 00
FF        #costume/team ID flagfield (FF = any)
FF        #Human/CPU type field, CPU level (FF = any)
FF        #required held Item ID    (FF = any)
2        #required directional input field (f,b,u,d)
F        #required button input field (x/y,l/r,A,B)
F8 FF 00 0C 02 02 22 18 00 00 08 00 4C 00 00 01 18 00 00 00

08         #Ness (n-air)
04         #return offset
4E 68     #Plxx.dat offset
08         #header length
00         #options pages length
00 24    #data length
F8 FF 00 1C 03 83 00 80 3F 00 00 00 03 00 00 84 3F 40 00 00 02 01 22 18 FF FF F7 FF 4C 00 00 01 18 00 00 00
00000000    #end of data table


 
Last edited:
Top Bottom