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~
---
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~
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:
Syntax Highlighted ASM (as PDF)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
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: