• 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 DTag Stacks Module 0.27

Punkline

Dr. Frankenstack
Joined
May 15, 2015
Messages
423
Data Tag Stacks Module -- create modular container objects for storing persistent data and/or data symbols. Defined symbol pointers may be recalled without needing to directly reference a location in RAM.

!IMPORTANT! - Other codes making use of this code will REQUIRE it to be installed as a prerequisite. When installing codes that use Dtag Stacks in MCM, make sure they come AFTER their dependencies; like so:


Get version 0.27 here:
uses mytoc block 277

Code:
    -==-

DTag Stacks 0.27
Provides a set of functions for creating and maintaining container objects that pair pointer addresses with "tags" entered into a dictionary index. Tags may be scanned for in order to recall their associated pointers in a way that does not reference any direct offsets from a table, and pushing new tags may optionally include a number of bytes allocated as extra write-space.
[Punkline]
1.02 ----- 0x8028AFF8 --- C842D040 -> C84280A0
<dtagInit> All
54E0842C 28008000
41820008 7CE73214
5500842C 28008000
41820008 7D083214
7D274050 7C8A2378
39830018 70A00002
41A20034 7CAC85AA
39830004 39430018
856C0004 7C0C0000
40800080 5560842C
28008000 41A2FFEC
396B0006 916C0000
4BFFFFE0 7CACC5AA
38000000 9001FFF0
552AF0BE 7D4903A6
3947FFFC 940A0004
4200FFFC 70A00004
7D0B4378 7D0C4378
4082001C 39000008
3947FFF8 7D495378
70A00001 4182001C
48000010 3900FFF8
392AFFF8 7D0A4378
39600000 39800000
38E00000 7CE3C5AA
4E800020

<dtagPush> All
8103002C 7C083800
41A20010 38C0FFFE
38600000 4800007C
7CC3E4AA 71800002
41A20018 38C0FFFB
4BFFFFE8 71800005
41820008 38A00000
81830028 398CFFF8
7D856050 280C0000
40A0000C 38C0FFFC
4BFFFFC0 91830028
38C60001 7D083A14
7D455050 7CC3A5AA
90880000 91480004
8163001C 7CC93378
7D435379 40A20010
7D034378 38C0FFFD
48000008 7CCB5050
4E800020

<dtagSeek> All
8003002C 7C003800
41A20010 38C0FFFE
38600000 48000120
80E30018 70E00001
41820008 38A00000
80030000 2C000000
40A20010 39000000
38C00000 4BFFFFD4
7C0903A6 81430014
8183000C 81630004
39000000 3920FFFF
9121FFF0 9121FFEC
39200000 7D2C586E
7C092000 40A20084
80EC0004 54E0842C
28008000 41A2000C
8123001C 7CE74A14
2C050000 41820010
7C075050 7C002800
40820058 39080001
7C0902A6 81230000
7C004850 7CC93379
40A20014 38E0FFFF
3900FFFF 7C090378
48000044 41A1001C
8121FFEC 2C09FFFF
41A2000C 38C0FFFF
4BFFFF38 7D2600D0
7C084800 40A2000C
9101FFEC 9001FFF0
7D2B6050 81490004
4200FF6C 80E1FFEC
8121FFF0 55201839
41A0FF2C 8183000C
8143001C 7D8C5A14
7C6C0214 80030004
2C000000 38C0FFFD
4182000C 7C030378
7CCA1850 4E800020

<dtagSeekPush> All
7C0802A6 90010004
9421FFC0 90E10010
90610014
bl    <dtagSeek>
2C030000 40A20028
2C060000 40820020
39080001 91010018
80E10010 80610014
bl    <dtagPush>
81010018 38E00000
38210040 80010004
7C0803A6 4E800020

<dtagSafeSeek> All
38C0FFFF
b <dtagSeek>

<dtagQuickSeek> All
38C00000
b <dtagSeek>

<dtagSafeSeekPush> All
38C0FFFF
b <dtagSeek>

<dtagQuickSeekPush> All
38C00000
b <dtagSeek>
Code:
    -==-

DTag Stacks 0.27
Provides a set of functions for creating and maintaining container objects that pair pointer addresses with "tags" entered into a dictionary index. Tags may be scanned for in order to recall their associated pointers in a way that does not reference any direct offsets from a table, and pushing new tags may optionally include a number of bytes allocated as extra write-space.
[Punkline]
Version -- DOL Offset ------ Hex to Replace ---------- ASM Code
1.02 ----- 0x8028AFF8 --- C842D040 -> C84280A0
# lfd -0x2FC0(rtoc) becomes lfd -0x7F60(rtoc) -- both use 4330000080000000.
# mytoc implementation frees up -0x2FC0(rtoc) for use to store globally recallabled pointers to the functions in this code.
# this single overwrite provides 8 bytes of space for us to make injections that will run like function calls.

<dtagInit> All
# low level leaf function

# Input args build a new header using provided init tag
# calculated params are used when navigating in scan/push
# 0x30 bytes are stored to create/initialize header

# See header structure detailed in symbol definitions

# flags essentially provide 8 different container types
# incrementing dictionary:
#   0 - push/seek (incr dict+decr alloc); scans tag+size
#     -- uniquely, this type provides space for allocs
#   1 - push/seek incr dict; scans tag-only identity
#   2 - seek-only incr dict; scans tag+size identity
#   3 - seek-only incr dict; scans tag-only identity

# decrementing dictionary:
#   4 - push/seek decr dict; scans tag+size identity
#   5 - push/seek decr dict; scans tag-only identity
#   6 - seek-only decr dict; scans tag+size identity
#   7 - seek-only decr dict; scans tag-only identity

# If the tags are incrementally ordered, they may be used
# to determine the size of a push.

# The dictionary describes the location of each push
# - dictionary pairs pointers with a 32-bit tag:
#   0x0 - 32-bit tag identifier
#   0x4 - point to "tagged" space

# ---

# Input arguments:
# r3 = rHead, r4 = rInit, r5 = rFlags, r6 = rBase,
# r7 = rStart, r8 = rEnd

# calculate rBytes from rEnd - rStart arguments

# if flags specify that stack is pushable:
#   zero out specified region between rStart-rEnd
#   # rStart is {inclusive} -- where rEnd is {}exclusive
#   DCount is set as 0
# Else:
#   stack is considered READ_ONLY
#   pushes will be illegal
#   region is not zeroed out
#   DCount is preserved

# if rStart/rEnd are not valid RAM addresses:
#   then assume they are offsets from rBase;
#   relocate them before proceeding with initialization

# store header params as string 1 (0x18-0x2C)
# # these are values, but are stored like a string

# if flag INVERTED == TRUE:
#   then dictionary is decrementing
#   pushing arbitrary number of bytes is disabled
#   # only blank dictionary tags may be pushed
#   DIter  = -8                     |____|____| ^
#   DFrame = End                    |____|____| |
#   DStart = End           frame    |____|____| |
#                        & start -> |xxxx|xxxx| <- OOB

# else, if flag INVERTED == FALSE:
#   then dictionary is incrementing
#   if flag IGNORE_SIZE == FALSE:
#     both dictionary and allocation frames may be pushed;
#     rAFrame, rAStart = rEnd
#   else:
#     no allocation frame pushes
#     no size identity in scans
#     tag pointers may point anywhere
#     rAFrame, rAStart = 0  start ->|xxxx|xxxx| <- OOB
#   rDIter  = +8          & frame   |tag_|addr||
#   rDStart = start - 8             |____|____|v
#   rDFrame = start                 |____|____|^
#                                   |____|____||
#                  optional alloc ->|xxxx|xxxx| <- OOB

# starting OOB will cause first push to be in-bounds

# store remaining header params as string 2

# Output return values:
# r3 = rHead, r4 = Init, r5 = Flags, r6 = rBase,
# r7 = STR2, r12 = end of STR2
# input argument names:
.set rHead,    3 # arg r3 = address of write start
.set rInit,    4 # arg r4 = 32-bit init tag -- validation
.set rFlags,   5 # arg r5 = header flags -- see symbols
.set rBase,    6 # arg r6 = base for return index offsets
.set rStart,   7 # arg r7 = start of struct {inclusive}
.set rEnd,     8 # arg r8 = end of struct   {}exclusive
# other GPRs:
.set rBytes,   9 # calculated from rEnd - rStart
.set rTag,    10 # copied rInit tag - end of string 1
.set rDCount,  7 # calculated dict tag count
.set rDIter,   8 # calculated dict iteration const
.set rDFrame,  9 # dictionary frame pointer
.set rDStart, 10 # address of OOB index -1 dict tag
.set rAFrame, 11 # allocation frame pointer
.set rAStart, 12 # address of OOB index -1 alloc start
# macros:
.macro check_if_valid_address, rVAddr
rlwinm r0, \rVAddr, 16, 0xFE00
cmplwi  r0, 0x8000 # mask 0xFE00 should == 0x8000
.endm
# stored as string 2:  (offsets mostly just illustrative)
.set xDCount,     0x00 # preserved on READ_ONLY
.set xDIter,      0x04 #
.set xDFrame,     0x08 #
.set xDStart,     0x0C #
.set xAFrame,     0x10 #
.set xAStart,     0x14 #
# stored as string 1:  (offsets mostly just illustrative)
.set xFlags,      0x18 # stored rFlags arg
.set xBase,       0x1C # stored rBase  arg
.set xStart,      0x20 # stored rStart arg
.set xEnd,        0x24 # stored rEnd   arg
.set xBytes,      0x28 # number of bytes remaining
.set xInit,       0x2C # stored rInit  arg
# other constants:
.set xDCountSave, -0x10 # redzone stack offset, leaf-only
.set INVERTED,    04 # flag signifies decrementing dict
.set READ_ONLY,   02 # flag prohibits pushes, dirty init
.set IGNORE_SIZE, 01 # flag ignores size in pushes/scans
.set STR1size,    xInit   - xFlags  + 4 # size of strings
.set STR2size,    xAStart - xDCount + 4 # for stswi ops

_code_start:

# first we relocate offsets that should be RAM addresses
# either RAM addresses or offsets from given Base work
_check_rStart: # this makes it easier for read-only inits
check_if_valid_address rStart
beq _check_rEnd
add rStart, rStart, rBase  # rStart = RAM address
_check_rEnd:
check_if_valid_address rEnd
beq _calc_rBytes
add rEnd, rEnd, rBase      # rEnd = RAM address

_calc_rBytes:
sub   rBytes, rEnd, rStart

_prepare_string_1:
mr    rTag, rInit           # move rInit for string
addi  r12, rHead, xFlags    # r12 = string start addr

_check_read_only:
andi. r0, rFlags, READ_ONLY
beq+  _not_read_only

# it is assumed that read-only initializations have
# headers already written; using offsets from rBase
_read_only_handle:               # update part of STR1
stswi rFlags, r12, STR1size - 8  # skip rBytes and rTag
addi  r12, rHead, xDFrame -4 # setup lwzu update loop
addi  r10, rHead, xAStart +4 # r10 = end of loop

_relocate_loop:
lwzu  r11, 0x4(r12)          # load pointer
cmpw  r12, r0                # if address == end of loop
bge- _return                 # then return from init
check_if_valid_address r11   # if address isn't valid
beq- _relocate_loop          # then relocate using rBase
addi  r11, r11, rBase
stw   r11, 0x0(r12)          # and update it
b _relocate_loop

# when not read-only, initialization calculates the rest
_not_read_only:
stswi rFlags, r12, STR1size # store STR1
# stored 6 params in head as string 1:
# offsets xFlags, xBase, xStart, xEnd, xBytes, xInit
_zero_out:  # zero out start-end region
li    r0, 0          # r0 = 0
stw   r0, xDCountSave(sp) # r0 still == 0
srwi  r10, rBytes, 2 # create word count N from bytes
mtctr r10            # store in ctr for bdnz loop
subi  r10, rStart, 4 # r10 = iterating address
_zero_loop:
stwu  r0, 0x4(r10)
bdnz+ _zero_loop     # stwu r0 N times, covering region

_check_invert:              # cr0 holds bool, cmp to 0
andi. r0, rFlags, INVERTED  # (alloc only used if decr)
mr    rAFrame,  rEnd        # allocs = (struct end, OOB)
mr    rAStart,  rEnd        # these are in place of 0
bne-  _setup_decr_dict

_setup_incr_dict:           # if incrementing dictionary:
li    rDIter, 8             # rDStart = (start -8, OOB)
addi  rDStart, rStart, -8   # rDFrame = (start -8, OOB)
mr    rDFrame, rDStart      # rDIter  = positive incr
_setup_check_size_and_incr:
andi. r0, rFlags, IGNORE_SIZE
beq-  _store_header_2       # skip alloc null if no flag
b     _null_alloc

_setup_decr_dict:           # if decrementing dictionary:
li    rDIter, -8            # rDStart = (struct end, OOB)
addi  rDFrame, rDStart, -8  # rDFrame = (struct end, OOB)
mr    rDStart, rEnd         # rDIter  = negative decr
_null_alloc:
li    rAFrame, 0            # alloc pointers only used
li    rAStart, 0            # in limited circumstances

_store_header_2:
# at this point, params reflect stack orientation
li    rDCount, 0
stswi rDCount, rHead, STR2size
# stored 6 params in head as string 2:
# DCount, DIter, DFrame, DStart, AFrame, AStart

_return:
blr


<dtagPush> All
# low level leaf function
# pushes successfully initialized dtag stack if possible
# type of push may be defined using header and arguments

# Input arguments:
# r3 = rHead, r4 = rTag, r5 = rSize,
# r7 = rInit

# if the stack header can't be verified:
#   then return VALIDATION_ERROR

# if the stack is READ_ONLY:
#   then return READ_ONLY_ERROR; push is write operation

# if header specifies IGNORE_SIZE, or INVERTED flags:
#   then set Push Size argument (rSize) to 0
#   # this makes the push return new blank dictionary tag

# if number of Bytes Remaining < Push Size + Tag Size
#   then return PUSH_LIMIT_ERROR

# push Allocation Frame by Push Size (0 or param)
# load 'DIter' and use it to iterate the Dictionary Frame
#  # (+/- 8 ; 2 words, incrementing or decrementing)

# store updated frame pointers
# store Tag argument and AFrame pointer in dictionary

# # AFrame pointer is set to 0 on init, so 0 - 0 = 0
# if AFrame == 0:
#   then return Address of DFrame push (with blank tag)
# else:
#   return Address of AFrame push (with blank writespace)

# Output return values:
# r3 = rAddr, r4 = rTag, r5 = rSize, r6 = rIndex/Error
# r9 = rDictID
# if r3 is null, then r6 is provided as Error Code
# if r3 is address of tag, r6 is provided as alert Code
# else r3 address is of tag allocation start

# arguments/params:
.set rHead,       3  # r3 = address of dtag header
.set rTag,        4  # r4 = 32-bit tag for push
.set rSize,       5  # r5 = size of alloc push
.set rInit,       7  # r7 = 32-bit header tag
# return values:
.set rAddr,       3  # r3 returns alloc address
           # returns dictionary element alloc inhibit
           # returns null if error occured
                     # r4 returns argument
                     # r5 returns argument or null
.set rIndex,      6  # r6 returns offset from base
.set rDictID,     9
# other GPRs:
.set rDCount,     6
.set rDIter,      7
.set rDFrame,     8
.set rAFrame,     10
.set rFlags,      12
# header offsets
.set xDCount,     0x00
.set xDIter,      0x04
.set xDFrame,     0x08
.set xAFrame,     0x10
.set xFlags,      0x18
.set xBase,       0x1C
.set xBytes,      0x28
.set xInit,       0x2C
# return error codes:
.set VALIDATION_ERROR, -2  # if header isn't valid
.set NO_POINTER_ALERT, -3  # naked tag has no allocation
.set PUSH_LIMIT_ERROR, -4  # if too few bytes for push
.set READ_ONLY_ERROR,  -5  # if params prevent push
# other constants:
.set INVERTED,    4  # flag signifies decrementing dict
.set READ_ONLY,   2  # flag prohibits pushes, dirty init
.set IGNORE_SIZE, 1  # flag ignores size in pushes/scans
.set NO_SIZE,     0

_code_start:     # before pushing, we check for errors
lwz   r8, xInit(rHead)
cmpw  r8, rInit
beq+  _valid     # if rInit doesn't match header tag

_err_validation:        # throw error
li    rIndex, VALIDATION_ERROR
_return_null:
li    rAddr, 0
b     _return

_valid:  # header assumed safe to use at this point
lswi  r6, r3, 0x1C
andi. r0, rFlags, READ_ONLY
beq+  _check_bytes      # if stack is read-only

_err_read_only:         # throw error
li    rIndex, READ_ONLY_ERROR
b     _return_null

_set_push_size_setting:
andi. r0, rFlags, IGNORE_SIZE + INVERTED # flags
beq _check_bytes        # if header flags:
li    rSize, NO_SIZE # set size arg to 0 for push param

_check_bytes:
lwz    r12, xBytes(rHead)  # r12 is now free to use
subi   r12, r12, 8
sub    r12, r12, rSize
cmplwi r12, 0
bge+   _commit_push  # make sure there's room for push

_err_push_limit:
li    rIndex, PUSH_LIMIT_ERROR
b     _return_null

# at this point, all errors have been accounted for
_commit_push:
stw   r12, xBytes(rHead)
addi  rDCount, rDCount, 1
add   rDFrame, rDFrame, rDIter
sub   rAFrame, rAFrame, rSize
stswi r6, r3, 0x14           # update header information
stw   rTag,    0x0(rDFrame)
stw   rAFrame, 0x4(rDFrame)  # create dictionary tag

lwz   r11, xBase(rHead) # load base address while
mr    rDictID, rDCount  # header is still in r3
mr.   rAddr, rAFrame    # r9 = dictID of push
bne+  _return_alloc     # is rAFrame 0?

_return_tag:
# if return would include blank allocation from rAFrame,
# instead return the address of the tag in dictionary
# this allows the allocation pointer to be set manually
mr    rAddr, rDFrame
li    rIndex, NO_POINTER_ALERT
b _return

_return_alloc:
# otherwise return address of the data that tag points to
# and calculate an index from base stored in header
sub   rIndex, rAFrame, r11  # r11 holds base
_return:
# r3 = tag allocation, tag address, or null
# r4 = 32-bit tag
# r5 = size of allocation, or null
# r6 = index of address from base, or ERROR code
# r7 = ID of result in index of like instance identities
# r8 = total number of like instance identities
# r9 = dictionary tag ID (index of place in dictionary)

# if r3 is null, then r6 is provided as ERROR code
# if r3 is address of tag, r6 is provided as handle code
# else r3 address is tag allocation pointer
blr


<dtagSeek> All
# leaf function
# Arguments describe a scan operation performed on
# properly intialized dtag stack (from header in r3)

# In:
# r3 = rHead, r4 = rTag, r5 = rSize,
# r6 = rDupeArg, r7 = rInit

# if the stack header can't be verified using rInit tag:
#   then return VALIDATION_ERROR

# if dictionary tag count is 0:
#   then return NO_MATCH_ERROR

# for each tag in dictionary:
#   if tag != rTag:
#     then no match; iterate next tag in scan loop
#   if rSize != 0, and header IGNORE_SIZE flag == FALSE:
#     X = size of tag alloc, from ordered decr addr
#     if X is an offset:
#       then it becomes index from base address
#     if rSize != X:
#       then no match; iterate next tag in scan loop

#   rDupeTotal += 1

#   if rDupeArg == 0:
#     then return matching tag identity immediately

#   if rDupeArg < 0:
#     if rDupeID was already written as match result:
#       return DUPE_LIMIT_ERROR

#   if abs(rDupeArg) == rDupeTotal:
#     then store rDupeTotal as returnned rDupeID
# iterate next tag in scan loop

# load result rDupeID and rDictID from redzone

# if no matches after scan:
#   return NO_MATCH_ERROR

# if return tag contains unspecified allocation (blank):
#   return tag address in r3, with NO_POINTER_ALERT
# else,
#   return tag pointer in r3

# Output return values:
# r3 = rAddr, r4 = rTag, r5 = rSize, r6 = rIndex/Error
# r7 = rDupeID, r8 = rDupeTotal, r9 = rDictID

# rTag is unmodified from provided rTag argument

# arguments/params:
.set rHead,             3  # r3 = address of dtag header
.set rTag,              4  # r4 = 32-bit tag for push
.set rSize,             5  # r5 = size of alloc push
.set rDupeArg,          6  # r6 = duplicate instance ID
.set rInit,             7  # r7 = 32-bit header tag
# return values:
.set rAddr,             3  # r3 returns alloc address
# returns dictionary element alloc inhibit
# returns null if error occured
                           # r4 returns argument
                           # r5 returns argument or null
.set rIndex,            6  # r6 returns offset from base
.set rDupeID,           7  # r7 returns dupe instance ID
.set rDupeTotal,        8  # r8 returns dupe count total
.set rDictID,           9  # r9 returns index of tag
# other GPR names:
.set rFlags,            7
.set rThisAlloc,        7
.set rPrevAlloc,       10
.set rDIter,           11
.set rDAddr,           12
# macros:
.macro check_if_valid_address, rVAddr
rlwinm r0, \rVAddr, 16, 0xFE00
cmplwi  r0, 0x8000 # mask 0xFE00 should == 0x8000
.endm
# offsets/displacements
.set xDCount,     0x00 # from header
.set xDIter,      0x04
.set xDStart,     0x0C
.set xAStart,     0x14
.set xFlags,      0x18
.set xBase,       0x1C
.set xInit,       0x2C
.set xMatchingDict, -0x10 # from sp (redzone, leaf-only)
.set xMatchingDupe, -0x14 # from sp (redzone, leaf-only)
# return error codes:
# no_match prioritized as 0 for easy cr0 '.' comparisons
.set   NO_MATCH_ERROR,  0  # if finished with no match
.set DUPE_LIMIT_ERROR, -1  # if rDupeID limit is exceeded
.set VALIDATION_ERROR, -2  # if header isn't valid
.set NO_POINTER_ALERT, -3  # naked tag has no allocation
# (negative rDupeArg ID specifies an rDupeID limit)
# other constants:
.set IGNORE_SIZE, 01  # flag - ignore size param in scan
.set NO_SIZE,      0  # r5 option inhibits alloc

_code_start:

_validation_check:
# compare given rInit to xInit stored in header
lwz   r0, xInit(rHead)
cmpw  r0, rInit
beq+  _set_scan_size_setting
# if header tag doesn't match, throw VALIDATION_ERROR
_err_validation:
li    rIndex, VALIDATION_ERROR
_return_null:  # errors re-use this _return_null label
li    rAddr,  0
b     _return

_set_scan_size_setting:
lwz   rFlags, xFlags(rHead)
andi. r0, rFlags, IGNORE_SIZE      # flags
beq   _check_dictionary_tag_count  # if header flags:
li    rSize, NO_SIZE  # set size arg to 0 for scan param
# at this point, rSize (r5 param) determines scan setting

_check_dictionary_tag_count:
lwz   r0, xDCount(rHead)
cmpwi r0, 0
bne+  _setup_scan
# if dictionary has no tags, just throw NO_MATCH_ERROR
_err_no_match:
li    r8, 0
li    rIndex, NO_MATCH_ERROR
b _return_null

_setup_scan:  # r9 is free
mtctr r0
lwz   rPrevAlloc, xAStart(rHead)
lwz   rDAddr,     xDStart(rHead)
lwz   rDIter,     xDIter (rHead)
li    rDupeTotal, 0   # count of total instance matches

# on match, DictID and DupeID are recorded in redzone
li    r9, -1  # 0 = null
stw   r9, xMatchingDict(sp)  # store null as defaults
stw   r9, xMatchingDupe(sp)  # leaf-only redzone stores
# these are overwritten when match finds dupeArg in count
li    r9, 0

_scan_loop:
lwzux r9, rDAddr, rDIter        # zux it up

_check_tag:
cmpw  r9, rTag                  # 0x0 - tag
bne+  _iterate_loop             # 0x4 - point/offset

_matching_tag:                  # r9 is free again
lwz   rThisAlloc, 0x4(rDAddr)   # load adjacent pointer
check_if_valid_address rThisAlloc
beq+  _valid_alloc_address

_relocate_alloc:  # read-only addresses may be offsets
lwz   r9, xBase(rHead)            # In case of offset,
add   rThisAlloc, rThisAlloc, r9  # add base address.

_valid_alloc_address:  # rThisAlloc holds RAM address
cmpwi rSize, NO_SIZE            # check rSize param
beq-  _matching_instance_identity

_check_size:                    # if not 0, check size
sub   r0, rPrevAlloc, rThisAlloc
cmpw  r0, rSize                 # size arg is input r5
bne- _iterate_loop

_matching_instance_identity:
# at this point, we've found a matching identity
# but we still need to check if it's the right instance.

# Duplicate instance identities are counted in rDupeTotal
# on return, Seek provides a DupeID and DictID for match

# if rDupeArg == 0 then scan returns first instance found
# (this is used to provide a fast version of the scan)

# if rDupeArg  < 0 then abs(ID) becomes dupe limit
# (returns matching ID, but throws error if exceeded)

# else, scan returns matching > 0 ID (if found)
# (if ID is exceeded, scan continues to count duplicates)
# (only specified ID is returned as a match)

# In all successful scans, a DictID is returned;
# but quickscans will return -1 for DupeID and DupeTotal
_count_instance:
addi   rDupeTotal, rDupeTotal, 1
mfctr  r0
lwz    r9, xDCount(rHead)
sub    r0, r9, r0        # construct DictID in r0
mr.     r9, rDupeArg     # r9 becomes absolute rDupeArg
bne+   _no_quickscan     # rDupeArg == 0 is quickscan

_quickscan_match:
li rDupeID, -1           # -1 denotes "partial scan"
li rDupeTotal, -1        # r0 contains DictID
mr rDictID, r0           # r7, r8, r9 return values
b  _load_results

_no_quickscan:
bgt+   _check_instance   # check if instance is positive

_limit_ID_negate:        # if negative,
lwz    r9, xMatchingDupe(sp) #dupe record indicates limit
cmpwi  r9, -1             # so check if match is recorded
beq+   _limit_check

_err_dupe_limit:         # if so, dupe is above limit
li     rIndex, DUPE_LIMIT_ERROR
b      _return_null      # exceeding limit produces error

_limit_check:            # else get abs ID in order to
neg    r9, rDupeArg      # check if we've hit the limit
# limit cases act like normal scans if limit
# is reached, but not exceed.

# quickscan and limit cases have been handled by now
_check_instance:         # check for dupeID match
cmpw   rDupeTotal, r9
bne+   _iterate_loop     # if not reached, continue scan

_final_match:            # else, record final match
stw    rDupeTotal, xMatchingDupe(sp)
stw    r0,         xMatchingDict(sp)
# return values are retrieved from xMatchingDict
# loop is still exhausted to count instance duplicates
# if limit has been reached, next match will throw error

_iterate_loop:
sub    r9, rDAddr, rDIter
lwz    rPrevAlloc, 0x4(r9)
bdnz+  _scan_loop

_exhausted_loop:
lwz    rDupeID, xMatchingDupe(sp)
lwz    rDictID, xMatchingDict(sp)

_load_results:
# r7, r8, r9 return values are ready:
# rDupeID, rDupeTotal, and rDictID
slwi.  r0, rDictID, 3  # turn DictID into index of tag
blt-   _err_no_match   # if still at default value, error
lwz    rDAddr, xDStart(rHead)
lwz    r10, xBase(rHead)
add    rDAddr, rDAddr, rDIter
add    rAddr,  rDAddr, r0
# at this point, rAddr contains address of tag
# if tag pointer is 0,
# then  tag address is returned for rAddr with code in r6
lwz    r0, 0x4(rAddr)
cmpwi  r0, 0
li     rIndex, NO_POINTER_ALERT
beq-   _return

_return_alloc:
# else, tag pointer is returned for rAddr like normal
# and rIndex reflects an offset from Base address in r10
mr     rAddr, r0
sub    rIndex, rAddr, r10

_return:
# r3 = tag allocation, tag address, or null
# r4 = 32-bit tag
# r5 = size of allocation, or null
# r6 = index of address from base, or ERROR code
# r7 = ID of result in index of like instance identities
# r8 = total number of like instance identities
# r9 = dictionary tag ID (index of place in dictionary)
blr


<dtagSeekPush> All
# function makes calls, creates stack frame
# Seek function calls Push to handle a NO_MATCH case
# DUPE_LIMIT specified by negative rDupeArg are not pushed
# Input arguments:
# r3 = rHead, r4 = rTag, r5 = rSize,
# r6 = rDupeArg, r7 = rInit

# save r7 argument

# call <dtagSeek>

# if r3 is not Null:
#   return results

# else, if r6 == NO_MATCH_ERROR:
#   then save rDupeTotal += 1
#   call <dtagPush> using saved r7 init
#   set rDupeID to 0, denoting new push
#   return results

# Output return values:
# r3 = rAddr/null, r4 = rTag, r5 = rSize, r6 = rIndex
# r7 = rDupeID/null, r8 = rDupeTotal, r9 = rDictID

.set rHead,      3
.set rAddr,      3
.set rInit,      7
.set rError,     6
.set rDupeID,    7
.set rDupeTotal, 8
.set rDictID,    9
.set NO_MATCH_ERROR,  0

mflr r0
stw  r0, 0x4(sp)
stwu sp, -0x40(sp)

# args:
# r3 = rHead
# r4 = rTag
# r5 = rSize
# r6 = rDupeArg
# r7 = rInit
_Seek:  # in case Seek requires push, we store some args
stw   rInit, 0x10(sp)
stw   rHead, 0x14(sp)
bl    <dtagSeek>
cmpwi rAddr, 0
bne+ _return  # Seek produced a result
cmpwi rError, NO_MATCH_ERROR
bne- _return  # Seek produced unexpected error

_Push:  # to handle a NO_MATCH_ERROR, push is made
addi  rDupeTotal, rDupeTotal, 1 # adapt new rDupeTotal
stw   rDupeTotal, 0x18(sp) # store it for return
lwz   rInit, 0x10(sp)
lwz   rHead, 0x14(sp)      # restore args for push
bl    <dtagPush>
lwz   rDupeTotal, 0x18(sp) # restore rDupeTotal
li    rDupeID, 0  # set rDupeID to 0 to denote new push

_return:
# values:
# r3 = rAddr/null   -- null = error
# r4 = rTag
# r5 = rSize
# r6 = rIndex/Error -- <= 0 = error/alert description
# r7 = rDupeID/null -- null = new push
# r8 = rDupeTotal
# r9 = rDictID
addi sp, sp, 0x40
lwz  r0, 0x4(sp)
mtlr r0
blr

# now, MUCH smaller functions can abstract away the
# more complex specificity:

<dtagSafeSeek> All
# Seek returns first match ONLY if it is unique
li r6, -1    # DUPE_LIMIT set to 1
b <dtagSeek> # return handled via branch-in func


<dtagQuickSeek> All
# Return first match without checking for duplicates
li r6, 0     # partial scan option
b <dtagSeek> # return handled via branch-in func


<dtagSafeSeekPush> All
li r6, -1
b <dtagSeek> # return handled via branch-in func


<dtagQuickSeekPush> All
li r6, 0
b <dtagSeek> # return handled via branch-in func

---

This code was made for developers. It may be used to create and navigate custom (and potentially unrelated) data tables in a way that avoids the need for referencing data offsets directly.

Example codes: (coming soon)

I'm posting this as a work in progress for now. More information will become available after I’ve been able to thoroughly test all of the features in this module with some example codes I have planned. Source ASM comments provide some documentation in the meantime.

version 0.27:

<dtagInit>
https://i.imgur.com/SflCUA9.png


<dtagPush>
https://i.imgur.com/zwVxAxh.png


<dtagSeek>
https://i.imgur.com/FCQZ5R7.png


<dtagSeekPush>
https://i.imgur.com/cuxZX48.png


<dtagQuickSeek>
https://i.imgur.com/5YaD6OX.png


<dtagSafeSeek>
https://i.imgur.com/HlTjkWZ.png


<dtagQuickSeekPush>
https://i.imgur.com/7GCN0zW.png


<dtagSafeSeekPush>
https://i.imgur.com/ZGrsasT.png
 
Last edited:
Top Bottom