Completed Primitive Drawing Module

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
292
#1
This MCM module recycles parts of the sword trail drawing function using a couple of projection mods. The resulting functions have been used to create a light-weight library that allows developers to draw various primitive types.

To make use of the functions in this library, install the DOL mod with the latest version of Melee Code Manager.

Code:
-==-

Primitive Drawing Module
Use included functions to send GX FIFO stack vertex information for drawing primitives.
See ( https://smashboards.com/threads/primitive-drawing-module.454232/ ) for more info.
[Punkline]
1.02 ----- 0x800c2684 --- 386000013880000438a0000538c00005 -> 57E3A7BE 57E4C77E 57E5E77E 57E6073E
1.02 ----- 0x800c26b0 --- 386000013880000338a00000 ->57C3A7FE 57C4C77E 57C5AFFE
1.02 ----- 0x800c26c0 --- 38600000 -> 57C39FFE
1.02 ----- 0x800c272c --- 38600000 -> 57C317BE
1.02 ----- 0x800c2da0 --- 38b80001 -> branch
b <projection_return>
00000000
1.02 ----- 0x800c2674 --- 38600001 -> branch
bl <prim_default_swordtrails>
7C8802A6 7C6444AA
bl <prim_setup>
881C2101
b 0x800c2738
60000000
1.02 ----- 0x800c2734 --- 881c2101 -> branch
BBC10010
b <projection_return>
60000000
1.02 ----- 0x800c2d3c --- 4827968d -> branch
bl <prim_mtx_setup>
38B80001
b 0x800c2da4
60000000
<prim_setup> 1.02
7C0802A6 90010004 9421C000 BFC10010 7C7E1B78 7C9F2378 38600001
b 0x800c2678
<prim_mtx_setup> 1.02
7C0802A6 90010004 9421C000
bl 0x8033C3C8
b  0x800c2d40
<projection_return> All
38214000 80010004 7C0803A6 4E800020
<GXBegin> 1.02
b 0x8033D0DC
<GXSetLineWidth> 1.02
b 0x8033D240
<GXSetPointSize> 1.02
b 0x8033d298
<prim_close> 1.02
3860FFFF
b 0x80361fc4
<prim_main> All
7C0802A6 90010004 9421FFF8
bl <prim_defaults>
7D0802A6 80A80000 7C842B78 80A80004 3CC00010 60C61300 38E01455 7CC845AA
bl <prim_main_parametric>
38210008 80010004 7C0803A6 4E800020
<prim_main_parametric> All
7C0802A6 90010004 9421FFC0 BFA10010 7C7E1B78 7C9F2378 7C832378 7CA42B78
bl <prim_setup>
bl <prim_mtx_setup>
57E3063E 2C030005 41A00024 2C030007 4181001C 57E385BE 38800005 4182000C
bl <GXSetLineWidth>
48000008
bl <GXSetPointSize>
57E31E38 38630080 38800000 57C5043E
bl     <GXBegin>
3C60CC01 38638000 BBA10010 38210040 80010004 7C0803A6 4E800020
<prim_quads> All
38800000
b <prim_main>
<prim_triangles> All
38800002
b <prim_main>
<prim_trianglestrip> All
38800003
b <prim_main>
<prim_trianglefan> All
38800004
b <prim_main>
<prim_lines> All
38800005
b <prim_main>
<prim_linestrip> All
38800006
b <prim_main>
<prim_points> All
38800007
b <prim_main>
<prim_defaults>
4E800021 00101300 00001455
<prim_default_swordtrails>
4E800021 00101300 00001455
<prim_toggle_z>
7C0802A6 90010004 9421FFF8
bl <prim_defaults>
7C6802A6 80830000 68841000 90830000 38210008 80010004 7C0803A6 4E800020
Code:
-==-
!
ASM - Primitive Drawing Module
Use included functions to send GX FIFO stack vertex information for drawing primitives.
See ( https://smashboards.com/threads/primitive-drawing-module.454232/ ) for more info.
[Punkline]
1.02 ----- 0x800c2684 --- 386000013880000438a0000538c00005 -> 57E3A7BE 57E4C77E 57E5E77E 57E6073E
1.02 ----- 0x800c26b0 --- 386000013880000338a00000 ->57C3A7FE 57C4C77E 57C5AFFE
1.02 ----- 0x800c26c0 --- 38600000 -> 57C39FFE
1.02 ----- 0x800c272c --- 38600000 -> 57C317BE
# these overwrites convert loaded immediates into rlwinms
# -- this allows the primitives to be drawn somewhat parametrically


1.02 ----- 0x800c2da0 --- 38b80001 -> branch
# projection end <prim_mtx_setup>
b <projection_return>
.long 0


1.02 ----- 0x800c2674 --- 38600001 -> branch
# -- projection start for <prim_setup>
# External prolog fabricates an interface for the
# purpose of creating parameters in the setup process:
#    r3       r4
# C0000000 00000000 = r3 for GXSetCullMode
# 03FF0000 00000000 =  (line width, or point size)
# 00002000 00000000 = r3 for GXSetCompLoc
# 00001000 00000000 = r3 for GXSetZMode
# 00000800 00000000 = r5 for GXSetZMode
# 00000700 00000000 = r4 for GXSetZMode
# 000000FF 00000000 =  (primitive type)
# 00000000 00003000 = r3 for GXSetBlendMode
# 00000000 00000700 = r4 for GXSetBlendMode
# 00000000 00000070 = r5 for GXSetBlendMode
# 00000000 0000000F = r6 for GXSetBlendMode

# 00101303 00001455 = defaults

# each paraam is parsed by an rlwinm (see static overwrites)
# (line width and primite type are not parsed here)

bl <prim_default_swordtrails>
mflr r4
lswi r3, r4, 0x8
# load sword trail parameters for natural trail drawing
# modifying these params will change the way sword trails are drawn

bl <prim_setup>
lbz r0, 0x2101 (r28) # repair INJ2 hook instruction externally
b 0x800c2738
nop


1.02 ----- 0x800c2734 --- 881c2101 -> branch
# projection end <prim_setup>
# lbz r0, 0x2101 (r28)
lmw r30, 0x10(sp)
b <projection_return>
nop


1.02 ----- 0x800c2d3c --- 4827968d -> branch
# projection start <prim_mtx_setup>
bl <prim_mtx_setup>
addi r5, r24, 1
b 0x800c2da4
nop


<prim_setup> 1.02
# projection handle
# r3 = params 1 = C WWW ZZ PP  (W is unused here)
# r4 = params 2 = ---- BBBB
# r30 and r31 are safe to use for projection duration
mflr r0
stw  r0, 0x4(sp)
stwu sp, -0x4000(sp)
stmw r30, 0x10(sp)

mr r30, r3  # Static overwrite strings will create rlwinms that extract from r30 and r31.
mr r31, r4  # These will replace some hardcoded arguments made for GX function calls

li r3, 1    # original instruction; arg for GXSetColorUpdate
b 0x800c2678


<prim_mtx_setup> 1.02
# projection handle
mflr r0
stw  r0, 0x4(sp)
stwu sp, -0x4000(sp)

bl 0x8033C3C8    # GXClearVtxDesc -- original instruction
b  0x800c2d40


<projection_return> All
# reusable return function
addi  sp, sp, 0x4000
lwz  r0, 0x4(sp)
mtlr r0
blr


<GXBegin> 1.02
b 0x8033D0DC # portable function call


<GXSetLineWidth> 1.02
b 0x8033D240


<GXSetPointSize> 1.02
b 0x8033d298


<prim_close> 1.02
li r3, -1
b 0x80361fc4


<prim_main> All
# r3 = vert count
# r4 = primitive type
mflr r0
stw  r0, 0x4(sp)
stwu sp, -0x8(sp)
bl <prim_defaults>
mflr r8
lwz r5, 0(r8)
or r4, r4, r5
lwz r5, 4(r8)
# load params

lis r6,      0x0010
ori r6, r6,  0x1300
li   r7,     0x1455
stswi r6, r8, 0x8
# reset params to default for next draw

bl <prim_main_parametric>

_return:
addi sp, sp, 8
lwz r0, 0x4(sp)
mtlr r0
blr



<prim_main_parametric> All
# r3 = vert count
#    r4        r5
# C0000000  00000000 = Cull Mode
#  +8 = cull backface
#  +4 = cull frontface

# 03FF0000  00000000 = Line Width/Point Size
#   1/16 pixel increments; 0x2A8 maximum width

# 00002000  00000000 = Compare Location
#  +1 = z buffer compares before texturing

# 00001000  00000000 = Z Buffer Compare
#  +1 = z buffer enables compare

# 00000800  00000000 = Z Buffer Update
#  +1 = z buffer enables update

# 00000700  00000000 = Z Buffer Comparison Logic
#  +4 = greater than
#  +2 = equal to
#  +1 = less than

# 000000FF  00000000 = Primitive Type
#   0 = quads
#   1 = --
#   2 = triangles
#   3 = trianglestrip
#   4 = trianglefan
#   5 = lines
#   6 = linestrip
#   7 = points

# 00000000  00003000 = Blend Type
#   0 = None 
#   1 = Blend -- blend using blending equation
#   2 = Logic -- blend using bitwise operation
#   3 = Subtract -- input subtracts from existing pixel

# 00000000  00000700 = Blend Source
#   0 = zero -- 0.0
#   1 = one  -- 1.0
#   2 = source color
#   3 = inverted source color
#   4 = source alpha
#   5 = inverted source alpha
#   6 = destination alpha
#   7 = inverted destination alpha

# 00000000  00000070 = Blend Dest
#   (see above)

# 00000000  0000000F = Blend Logic
#   0 -- CLEAR;   dst = 0
#   1 -- AND;     dst = src & dst
#   2 -- REVAND;  dst = src & ~dst
#   3 -- COPY;    dst = src
#   4 -- INVAND;  dst = ~src & dst
#   5 -- NOOP;    dst = dst
#   6 -- XOR;     dst = src ^ dst
#   7 -- OR;      dst = src | dst
#   8 -- NOR;     dst = ~(src | dst)
#   9 -- EQUIV;   dst = ~(src ^ dst)
#   A -- INV;     dst = ~dst
#   B -- REVOR;   dst = src | ~dst
#   C -- INVCOPY; dst = ~src
#   D -- INVOR;   dst = ~src | dst
#   E -- NAND;    dst = ~(src & dst)
#   F -- SET;     dst = 1

_macros:
.macro countShift  m, c
  # countShift = recursive macro updates symbol "maskShift"
  # maskShift  = number of 0 bits padding first mask bit (from right to left)

  # <- termination condition allows us to escape loop
  .if \m&1               # if (mask & 0x00000001) is not == 0
    .set maskShift, \c     # maskshift = count   -- termination

    # <- recursive macro call allows us to iterate loop
    .else                  # else
    .set maskShift, \c+1   # maskshift = count++ -- incr counter
    countShift \m>>1, \c+1 # call self

  .endif # close if block
.endm  # close macro block

# macro "countShift" can be used to count the zero bits
# resulting symbol "maskShift" can be used to imply shift amount in rlwinm and rlwimi syntax

.macro mext  rD, rS, m
# mext : masked extract
  countShift \m, 0    # m = 32-bit mask
  rlwinm \rD, \rS, (32-maskShift)%32, \m>>maskShift
.endm

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

mr r30, r3 # save r3 and r4 for after projection calls
mr r31, r4 #
mr r3,  r4 # r3 = params 1
mr r4,  r5 # r4 = params 2

bl <prim_setup>     # set up params
bl <prim_mtx_setup> # set up mtx

mext   r3, r31, 0x000000FF  # r3 = primitive ID
cmpwi  r3, 5                 # if it's larger than
blt+ _end_width_handle
  cmpwi  r3, 7
  bgt- _end_width_handle
    mext   r3, r31, 0x03FF0000
    li     r4, 5
    beq- _point_handle

    _width_handle: # exclusively for lines and linestrips
    bl <GXSetLineWidth>
    b _end_width_handle

    _point_handle: # exclusively for points
    bl <GXSetPointSize>

_end_width_handle:

rlwinm  r3, r31, 3, 0x000000F8
addi   r3, r3, 0x80  # r3 is now a primitive ID that can be fed to GXBegin
li     r4, 0         # use vtx format 0
mext   r5, r30, 0x0000FFFF # use N number of vertices
bl     <GXBegin>

lis  r3, 0xCC01
addi r3, r3, -0x8000 # r3 = address of hardware FIFO stack
# write vertex(X, Y, Z, C) to address in r3
# each write must be to the exact same address

lmw  r29, 0x10(sp)
addi sp, sp, 0x40
lwz  r0, 0x4(sp)
mtlr r0
blr


# convenience functions:
<prim_quads> All
li r4, 0
b <prim_main>


<prim_triangles> All
li r4, 2
b <prim_main>


<prim_trianglestrip> All
li r4, 3
b <prim_main>


<prim_trianglefan> All
li r4, 4
b <prim_main>


<prim_lines> All
li r4, 5
b <prim_main>


<prim_linestrip> All
li r4, 6
b <prim_main>


<prim_points> All
li r4, 7
b <prim_main>

<prim_defaults>
blrl
.long 0x00101300
.long 0x00001455

<prim_default_swordtrails>
blrl
.long 0x00101300
.long 0x00001455

<prim_toggle_z>
# this function simply toggle the z buffer comparison in the Z buffer options
# it's meant to make it easy for people who just want to draw on top of things
mflr r0
stw  r0, 0x4(sp)
stwu sp, -0x8(sp)
bl <prim_defaults>
mflr r3
lwz r4, 0(r3)
xori r4, r4, 0x1000 # toggle z comparison bit
stw r4, 0(r3)
addi sp, sp, 8
lwz r0, 4(sp)
mtlr r0
blr

---

Example codes:

- Nibble Printer POC 0.1
- ECB Anchor Test 2
- ECB Anchor Test 1
- Player JObj tracer
- Colored Player Skeletons

---

With this module installed, developers can make use of the following functions for the purpose of drawing to the EFB:

bl <prim_quads>
# draw a series of individual quadrilateral 4-point polygons

bl <prim_triangles>
# draw a series of individual triangle 3-point polygons

bl <prim_trianglestrip>
# draw a contiguous strip of connecting 3-point triangles that share an edge for each connection in the sequence.

bl <prim_trianglefan>
# draw a triangle strip where one side of the strip circles around a central vertex

bl <prim_lines>
# draw a series of individual lines using 2 points per line. Line width is the same for each line in the series.

bl <prim_linestrip>
# draw a contiguous strip of connecting 2-point lines

bl <prim_points>
# draw a series of individual points, using single vertices. Point size is the same for each point in the series.

Each of the above functions only require the argument:
r3 = number of vertices

When the function returns, you may use the address returned in r3 to store vertices.
note - when storing vertex information, all data goes to the exact same address; 0(r3)

To describe a vertex, store the following values to 0(r3) in a sequence:
1 - floating point single = vertex X
2 - floating point single = vertex Y
3 - floating point single = vertex Z
4 - 32-bit UINT = RGBA color

---

The following two functions can be called without arguments:

bl <prim_close>
# use this at the end of your drawing to prevent params from corrupting the next GFX drawing in the game

bl <prim_toggle_z>
# use this before starting a primitive to toggle the zbuffer comparison on or off. If off, primitives will be drawn on top of other geometry. On by default.

---

The above primitive functions all use the following functions with a set of default parameters. These functions require you to specify more parameters, but give you much more power over how each polygon is drawn.

Advanced Functions:
bl <prim_main>
# r3 = vert count
# r4 = primitive type -- used to provide all of the above basic functions
# this call creates parameters similar to the basic sword trail drawings by default
# <prim_defaults> can be modified to change the params for the next drawn primitive
# primitive types for r4 argument
# 0 - quads
# 1 - (quads2)
# 2 - triangles
# 3 - trianglestrip
# 4 - trianglefan
# 5 - lines
# 6 - linestrip
# 7 - points
bl <prim_defaults>
# this blrl table can be accessed with an mflr instruction upon return.
# it may be modified in order to change the parameters of the next <prim_main> call
# parameters will be reverted after call to <prim_main>
# see the following function for details about params structure
bl <prim_main_parametric>
# this version of the function lets you specify the parameters directly using immediates built in r4 and r5
# r3 = vert count
# r4 = params 1
# r5 = params 2

# the parameters are encoded like so:

# r4 r5
# C0000000 00000000 = Cull Mode
# +8 = cull backface
# +4 = cull frontface

# 03FF0000 00000000 = Line Width/Point Size
# 1/16 pixel increments; 0x2A8 maximum width
# only used for points and lines; otherwise ignored

# 00002000 00000000 = Compare Location
# +2 = z buffer compares before texturing
# 00001000 00000000 = Z Buffer Compare
# +1 = z buffer enables compare

# 00000800 00000000 = Z Buffer Update
# +8 = z buffer enables update

# 00000700 00000000 = Z Buffer Comparison Logic
# +4 = greater than
# +2 = equal to
# +1 = less than

# 000000FF 00000000 = Primitive Type
# 0 = quads
# 1 = --
# 2 = triangles
# 3 = trianglestrip
# 4 = trianglefan
# 5 = lines
# 6 = linestrip
# 7 = points

# 00000000 00003000 = Blend Type
# 0 = None -- write input directly to EFB
# 1 = Blend -- blend using blending equation
# 2 = Logic -- blend using bitwise operation
# 3 = Subtract -- input subtracts from existing pixel

# 00000000 00000700 = Blend Source
# 0 = zero -- 0.0
# 1 = one -- 1.0
# 2 = source color
# 3 = inverted source color
# 4 = source alpha
# 5 = inverted source alpha
# 6 = destination alpha
# 7 = inverted destination alpha

# 00000000 00000070 = Blend Dest
# (see above)

# 00000000 0000000F = Blend Logic
# 0 -- CLEAR; dst = 0
# 1 -- AND; dst = src & dst
# 2 -- REVAND; dst = src & ~dst
# 3 -- COPY; dst = src
# 4 -- INVAND; dst = ~src & dst
# 5 -- NOOP; dst = dst
# 6 -- XOR; dst = src ^ dst
# 7 -- OR; dst = src | dst
# 8 -- NOR; dst = ~(src | dst)
# 9 -- EQUIV; dst = ~(src ^ dst)
# A -- INV; dst = ~dst
# B -- REVOR; dst = src | ~dst
# C -- INVCOPY; dst = ~src
# D -- INVOR; dst = ~src | dst
# E -- NAND; dst = ~(src & dst)
# F -- SET; dst = 1

---

I will post some demonstrations soon, and link them in the example codes section.
 
Last edited:

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
292
#2
To demonstrate, I’ll be making a series of example codes. I have a few things in mind, so it’ll take me some time to come up with it all.

To start, here are a few codes that use the drawing module to trace JObjs with line and dot primitives.

---

ECB Anchor Test 1

Code:
-==-

ECB Anchor Visualization
primitive drawing test traces ECB anchor joints on players
[Punkline]
1.02 ----- 80081104 --- bb61008c -> branch
2C1D0002 40A2004C
bl <prim_toggle_z>
38600007
bl <prim_linestrip>
809C002C 38000007 7C0903A6 3800FFFF 80A407F8 C0050050 D0030000 C0050060 D0030000 C0050070 D0030000 90030000 38840004 4200FFDC
bl <prim_close>
BB61008C 00000000
Code:
-==-
!
ECB Anchor Visualization
primitive drawing test traces ECB anchor joints on players
[Punkline]
1.02 ----- 80081104 --- bb61008c -> branch
# this injection is in the middle of a drawing loop used by players
# it has the active camera set to the main camera, so drawing will appear on screen
# injection placement ensures it draws after the player
# r28 = player entity
# r29 = z loop iteration (out of 2)
cmpwi r29, 2
bne+ _return
# with this, our drawing will only happen after everything on the player is drawn

bl <prim_toggle_z>
# we want to make our line drawing appear on top of the player
# toggling z buffer comparison makes it so that it ignores intersection with player

li r3, 7  # r3 argument = number of vertices to draw
bl <prim_linestrip>
# returns r3 = fifo pipe address
# writing to this will let us create vertices
# we need to write to 0(r3) for every piece of information given to GX

lwz r4, 0x2C(r28)
li r0, 7
mtctr r0
# load player data, and set up a bdnz loop for 7 iterations
# this will catch each JObj we want to trace

li r0, -1
# r0 = 0xFFFFFFFF -- or the RGBA color white

_loop:
lwz  r5, 0x7F8(r4)
lfs  f0, 0x50(r5) # load X vert translation
stfs f0, 0(r3)    # write to GX FIFO
lfs  f0, 0x60(r5) # load Y
stfs f0, 0(r3)    # write Y
lfs  f0, 0x70(r5) # load Z
stfs f0, 0(r3)    # write Z
stw  r0, 0(r3)    # write RGBA color
# X, Y, Z, and color create a vertex

addi r4, r4, 4   # move to next JObj
bdnz+ _loop      # iterate loop
# loop will finish after 7 vertices have been drawn

bl <prim_close>
# finish the primitive with this

_return:
lmw    r27, 0x008C (sp)  # original instruction
.long 0

This code simply draws lines between the bones that anchor a player’s ECB quad. It’s useful for seeing how the ECB are influenced by the player skeleton. DRGN DRGN

Player data offset 0x7F8 is the start of a series of JObj pointers, which stand for “joint” objects. Joints carry information about where they are in 3D space using offsets 0x50, 0x60, and 0x70 to store an absolute translation coordinate as part of a transformation matrix.

By accessing the 7 joint pointers in a player using a loop, it’s possible to use these coordinates to draw the location of the bone. In this code, a simple default line primitive is created using <prim_linestrip> and 7 vertices are drawn using the X Y and Z translation coordinates of the corresponding JObjs:



By calling <prim_linestrip> with 7 in r3, we tell the GX that we’re ready to draw 7 vertices for a linestrip primitive. The default parameters are set up to be just like sword trails, with lines taking up 1 native pixel in rasterized width:



As you can see, the lines illustrate where the ECB joints are located, starting at the bottom with TopN. The lines are visible through the player because we called <prim_toggle_z> before <prim_linestrip>



---

Player JObj Tracer

Code:
-==-

Player JObj Tracer
This primitive drawing test includes a function that traces JObj skeletons.
By default, it's used to illustrate player joints.
[Punkline]
1.02 ----- 800c2660 --- 8383002c -> branch
8383002C 807C05E8 80630050
bl <illustrate_JObj_skeleton>
00000000
<illustrate_JObj_skeleton>
7C0802A6 90010004 9421FFE0 BFC10008 7C7F1B78 807F0010 2C030000 4180FFE5 807F0008 2C030000 4180FFD9
bl <prim_toggle_z>
38600002 38800005 3BC0FFFF 48000018
bl <prim_toggle_z>
38600001 38800007 3FC0FF00 63DE00FF
bl <prim_main>
C01F0050 D0030000 C01F0060 D0030000 C01F0070 D0030000 93C30000 2C1EFFFF 40820028 83FF000C C01F0050 D0030000 C01F0060 D0030000 C01F0070 D0030000 93C30000 4BFFFFA4
bl <prim_close>
BBC10008 38210020 80010004 7C0803A6 4E800020
Code:
-==-
!
ASM - Player JObj Tracer
This primitive drawing test includes a function that traces JObj skeletons.
By default, it's used to illustrate player joints.
[Punkline]
1.02 ----- 800c2660 --- 8383002c -> branch
lwz    r28, 0x002C (r3) # original instruction

lwz r3, 0x5E8(r28)
lwz r3, 0x50(r3) # load 5th bone in skeleton (to skip TopN, TransN, XRotN, YRotN)
bl <illustrate_JObj_skeleton>
.long 0



<illustrate_JObj_skeleton>
# r3 = THIS JObj

_recursion:
# recursive function will call itself for each remaining JObj in the family
mflr r0
stw  r0, 0x4(sp)
stwu sp, -0x20(sp)
stmw  r30, 0x8(sp)
mr r31, r3

lwz r3, 0x10(r31)
# r3 = child
cmpwi r3, 0
bltl _recursion
# if  child exists, recursively call self

lwz r3, 0x8(r31)
# r3 = sibling
cmpwi r3, 0
bltl _recursion
# if sibling exists, recursively call self

_line:
bl <prim_toggle_z>
li r3, 2   # 2 verts per line
li r4, 5   # lines
li r30, -1 # white
# lines on first pass
# note these are not linestrips, because we can't predict the number of verts we need
b _draw

_point:
bl <prim_toggle_z>
li r3, 1         # 1 vert per point
li r4, 7         # points
lis r30, 0xFF00  # red
ori r30, r30, 0xFF
# the saved register makes it so we can check for second pass conclusion

_draw:
# r3 = vert count
# r4 = primitive type
#     type 5 = lines
#     type 7 = points
bl <prim_main>
# r3 = GX FIFO hardware address


lfs f0, 0x50(r31)
stfs f0, 0(r3)
lfs f0, 0x60(r31)
stfs f0, 0(r3)
lfs f0, 0x70(r31)
stfs f0, 0(r3)
stw r30, 0(r3)
# first JOBj vertex has been drawn

cmpwi r30, -1
bne _close
# if on second pass, don't draw second vert

lwz r31, 0xC(r31)
# load JObj parent

lfs f0, 0x50(r31)
stfs f0, 0(r3)
lfs f0, 0x60(r31)
stfs f0, 0(r3)
lfs f0, 0x70(r31)
stfs f0, 0(r3)
stw r30, 0(r3)
# second vertex has been draw, completing the line
# line should appear in the EFB for the next frame

b _point
# this branch is only made on the first pass

_close:
bl <prim_close>

_return:
lmw  r30, 0x8(sp)
addi sp, sp, 0x20
lwz  r0, 0x4(sp)
mtlr r0
blr

This code uses recursion to iterate through all bones in a player skeleton, and draws the child->parent relationships. Instead of just using lines, this code makes a second pass and creates tiny red dots where a player bone is located using point primitives.

The recursion is made possible by this structure:



There is a child <-> parent and sibling -> sibling link in each skeletal JObj. By creating a function that recursively checks for each child and sibling from a root joint, it’s possible to create something that acts on them all:



I was very surprised by the nature of player JObjs after observing this code. It seems that several bones in different player skeletons are not included in some animations -- causing them to stop being transformed and get left behind without the rest of the skeleton. This is really apparent in bowser’s jump animations, or any teleport move.


For some, untransformed bones are just for like, a falcon punch graphic, or cape model for captain falcon and mario respectively; but for others it seems to be ragdoll joint ends, and some other special joints. If they haven’t been included in ANY animations, the joints will appear at coordinates 0,0,0 on the stage:


For the drawing, the function <prim_main> is used to create a primitive type that’s specified by r4, allowing both lines and dots to be created from the same function:



---

Colored Player Skeletons

Code:
-==-

Colored Player Skeletons
This is a test of the primitive drawing module.
It draws lines between player joints that are moving, and dots where joints have stopped moving.
Many characters have joints that stop being animated after a move, or that never move from the beginning of a match.
[Punkline]
1.02 ----- 80373804 --- 39000088 -> 390000A0
1.02 ----- 800737e8 --- 39000088 -> 390000A0
1.02 ----- 80073718 --- 39000088 -> 390000A0
1.02 ----- 800304c8 --- 48338141 -> branch
806DC18C 83E30020 2C1F0000 418200A4 38800001 80FF0028 80C7009C 811F002C 806805E8 80A80894 90A7009C 7C062800 40A20014 80A80010 2C05000B 41820008 38800000 38841000 80630050 38A0FF00 88E8000C 2C070004 4080004C 38C004E1 1D070003 7CC64430 54C9C1CE 50C97BDE 50C935EE 1CA900FF 3D008045 61083130 1C070E90 7CC8002E 7C06F800 41A20018 55203032 7CA50278 3C003F3F 60003F00 7CA50278
bl <trace_JObj_skeleton>
83FF0010 4BFFFF5C
bl 0x80368608
00000000
<trace_JObj_skeleton> All
7C0802A6 90010004 9421FFD0 BF810008 7C7F1B78 7C9E2378 7CBC2B78 807F0010 2C030000 4180FFDD 7FC4F378 7F85E378 807F0008 2C030000 4180FFC9 395F0094 7CCA64AA 893F0093 807F0050 809F0060 80BF0070 73C00001 41820008 7C6A65AA 7D3CE378 57C5421E 38800005 7F033000 7F843800 4CC73202 7F854000 4CC73202 2F8900FF 7FA00026 41820030 419A0018 39690040 2C0B00FF 4081001C 396000FF 48000014 3969FFFE 2C0B0000 40800008 39600000 997F0093 38600002 41BE0018 40BA0014 38600001 57C5421E 3CA50018 38800007 7CA42378 60840300 38A01455
bl <prim_main_parametric>
C01F0050 D0030000 C01F0060 D0030000 C01F0070 D0030000 93830000 7FAFF120 419E0008 419A0024 83FF000C C01F0050 D0030000 C01F0060 D0030000 C01F0070 D0030000 93830000
bl <prim_close>
BB810008 38210030 80010004 7C0803A6 4E800020
Code:
-==-
!
ASM - Colored Player Skeletons
This is a test of the primitive drawing module.
It draws lines between player joints that are moving, and dots where joints have stopped moving.
Many characters have joints that stop being animated after a move, or that never move from the beginning of a match.
[Punkline]
1.02 ----- 80373804 --- 39000088 -> 390000A0
1.02 ----- 800737e8 --- 39000088 -> 390000A0
1.02 ----- 80073718 --- 39000088 -> 390000A0
# JObj padding, 0x18 bytes are zeroed after JObj structure allocation
# this does not change the resulting allocation size, since it aligns to 32 bytes


1.02 ----- 800304c8 --- 48338141 -> branch
# bl ->0x80368608 --  HSD_CObjEndCurrent
# This is the end of a general match camera display function.
# It's the same function that hosts displaying ECB quads for players and items.
# The current HSD CObj = main camera entity CObj
# The original instruction dismisses the current CObj
# -- we fit in our drawing function before that.
lwz r3, -0x3e74(r13)
lwz r31,0x20(r3)
# r31 = THIS player

_for_each_player:
cmpwi r31, 0
beq- _return
# termination condition

li r4, 1
lwz r7, 0x28(r31)
lwz r6, 0x9C(r7)
lwz r8, 0x2C(r31)
lwz r3, 0x5E8(r8)
# r3 = player skeleton base index
# r4 = argument for updating JObj delta in draw iteration
# r6 = recorded action state frame (stored in root JObj padding)
# r7 = root JObj
# r8 = player data

lwz r5, 0x894(r8)
stw r5, 0x9C(r7)
# update recorded action state frame with new data from player entity

cmpw r6, r5
bne+ _draw_setup
# if action frame is different from record, then update XYZ pos for delta calc
# delta calc is true or false, and simply determines whether to draw a line or a dot
# dots will slowly disappear with a fading alpha

  lwz r5, 0x10(r8)
  cmpwi r5, 0xB
  beq- _draw_setup
  # if character is "sleeping" we'll force the update so the skeleton dots disappear

    li r4, 0
    # else, we leave the records alone for things like hitlag, or slowmo frames

_draw_setup:
addi r4, r4, 0x1000

lwz r3, 0x50(r3)
# r3 = load 5th bone in skeleton (to skip drawing TopN, TransN, XRotN, YRotN)

li r5, -256
# r27 = RGBA color; defaults to white
# FF FF FF 00 (alpha gets ORed in)

lbz r7, 0xC(r8)
cmpwi r7, 4
bge- _draw
# if player slot is not in range of controller ports, just use white color
# else, generate red, blue, yellow, or green according to player slot

li r6, 0b010011100001

# 3-bit BGR color array, indexed according to player colors

mulli r8, r7, 3
srw r6, r6, r8
# r6 = unmasked color

rlwinm r9, r6, 24, 7, 7    # 01 00 00 00  <-  01
rlwimi r9, r6, 15, 15, 15  # 00 01 00 00  <-  02
rlwimi r9, r6, 6, 23, 23   # 00 00 01 00  <-  04
mulli  r5, r9, 0xFF       #  R  G  B 00  *=  FF
# 8-bit channels, RGBA

lis r8, 0x8045
ori r8, r8, 0x3130
mulli r0, r7, 0xE90
lwzx r6, r8, r0
cmpw r6, r31
beq+ _draw
# if player doesn't match player slot, then character is a sub-player like nana
# we'll darken the color for this case

_sub_player:
slwi r0, r9, 6
xor r5, r5, r0
# mix 25% black to color

lis r0, 0x3f3f
ori r0, r0, 0x3f00
xor r5, r5, r0
# mix ~25% mid-gray to color

_draw:
# r3 = bone to start on
# r4 = condition for updating delta records
# r5 = color without a blank alpha channel
bl <trace_JObj_skeleton>

_iterate:
lwz r31, 0x10(r31)
b _for_each_player

_return:
bl 0x80368608
.long 0




<trace_JObj_skeleton> All
# r3 = THIS JObj
# r4 = params
# 0x00000001 = bool for updating delta record
# 0x0000FF00 = line width base
# r5 = color with no alpha channel
_recursion:
mflr r0
stw  r0, 0x4(sp)
stwu sp, -0x30(sp)
stmw r28, 0x8(sp)
mr   r31, r3
mr   r30, r4
mr   r28, r5

lwz r3, 0x10(r31)
cmpwi r3, 0
bltl _recursion
# if  child exists, recursively call self

mr  r4, r30
mr  r5, r28
lwz r3, 0x8(r31)
cmpwi r3, 0
bltl _recursion
# if sibling exists, recursively call self

# done with recursion
# before each stack frame collapses, the involved joints are drawn

addi r10, r31, 0x94
lswi r6, r10, 0xC
lbz r9, 0x93(r31)
lwz r3, 0x50(r31)
lwz r4, 0x60(r31)
lwz r5, 0x70(r31)
# r3...r5 = XYZ absolute mtx transposition
# r6...r7 = XYZ records from previous check

_update:
andi. r0, r30, 1
beq- _draw
# if saved r4 argument is false, then don't update the records in JObj padding

  stswi r3, r10, 0xC
  # else, update XYZ positions for next delta check
  # memory in registers are used for this frame's check

_draw:
or r28, r9, r28
# color gets an alpha value that reflects the time it has not been moving.
# a byte is stored alongside each record of XYZ to be ORed into given color.

rlwinm r5, r30, 8, 8, 15
li r4, 5
# line primitive params

cmpw cr6, r3, r6   # X vs X
cmpw cr7, r4, r7   # Y vs Y
crand cr6, cr7, cr6
cmpw cr7, r5, r8   # Z vs Z
crand cr6, cr7, cr6
cmpwi cr7, r9, 0xFF # has the alpha been dropping?
mfcr r29

# cr0 = comparison of r4 argument
# cr6 = comparison of XYZ delta
# cr7 = comparison of alpha to (almost) full
# r29 holds saved comparisons
beq cr0, _end_update

  beq cr6, _alpha_decr

    _alpha_incr:
    addi r11, r9, 0x40  # 4 frames to go from 0% -> 100%
    cmpwi r11, 0xFF
    ble- _alpha_store

      _cap_FF:
      li r11, 0xFF
      b _alpha_store

    _alpha_decr:
    addi r11, r9, -2    # 128 frames to go from 100% -> 0%
    cmpwi r11, 0
    bge- _alpha_store

      _cap_00:
      li r11, 0

    _alpha_store:
    stb r11, 0x93(r31)
_end_update:

li r3, 2
beq+ cr7, _setup
# if alpha is almost full, draw line regardless of delta check result
# we want to only resort to drawing disappearing dots for bones that are holding still
# this small frame buffer helps prevent an erroneous trigger of dots on hitlag

  bne+ cr6, _setup
  # else, if alpha is not full, we check the delta comparison

  # if XYZ delta == 0; then draw dot
  # if XYZ delta != 0; then draw line

    _dot_setup:
    li  r3, 1
    rlwinm r5, r30, 8, 8, 15
    addis r5, r5, 0x18
    li r4, 7
    # dot primitives params take place of line primitives
    # dot radius is half a native pixel larger than input width

_setup:
or r4, r5, r4
ori r4, r4, 0x0300  # default primitive settings, but ignores the Z buffer
# with this setting, lines will be drawn on top of anything they come after in the draw order

li r5, 0x1455 # default primitive settings for params string 2
bl <prim_main_parametric>

# r3 now = GX FIFO hardware address
# all information stored to this address goes into the drawing pipe
# all information is stored to the same address 0(r3)

_draw_child:
lfs f0, 0x50(r31)
stfs f0, 0(r3)
lfs f0, 0x60(r31)
stfs f0, 0(r3)
lfs f0, 0x70(r31)
stfs f0, 0(r3)
stw r28, 0(r3)
# vertex stored

mtcr r29
beq- cr7, _draw_parent
beq- cr6, _end_draw
# check saved delta; if 0, then draw only a point at the child vertex
# else, draw a line between parent <- child

_draw_parent:
lwz r31, 0xC(r31)
# load JObj parent

lfs f0, 0x50(r31)
stfs f0, 0(r3)
lfs f0, 0x60(r31)
stfs f0, 0(r3)
lfs f0, 0x70(r31)
stfs f0, 0(r3)
stw r28, 0(r3)
# second vertex stored

_end_draw:
bl <prim_close>

_return:
lmw r28, 0x8(sp)
addi sp, sp, 0x30
lwz  r0, 0x4(sp)
mtlr r0
blr

This last code (for now) is just a fancy version of the previous demonstration. It uses some padding in JObj structures to store X Y and Z translation values of the last measurement, so it can create a delta check each time it draws a frame.


This translation delta is checked against 0 to determine whether to draw a line OR a point, but not both. When a joint’s position in space is different from the last frame, it will be drawn as a line colored by the player slot. Sub-character players are drawn with a hue mixed with black and gray to make a desaturated color. For player slots that are higher than the controller port maximum are drawn with a base hue of white.

When a joint stops translating for more than a frame, its drawing turns into a point, and its alpha slowly decreases over ~ 2 seconds worth of action frame data.

Since it only records translation coords for the delta check, it isn’t perfect -- but this cuts away most of the unanimated joints from the display, causing a visualization that (for the most part) only illustrates actively animated joints.

This code uses calls to <prim_main_parametric>, which may be used to specify all of the available drawing parameters as immediates. r4 and r5 contain 8 bytes (6, really) that describe the following parameters:

Code:
#    r4        r5
# C0000000  00000000 = Cull Mode
#  +8 = cull backface
#  +4 = cull frontface

# 03FF0000  00000000 = Line Width/Point Size
#   1/16 pixel increments; 0x2A8 maximum width

# 00002000  00000000 = Compare Location
#  +1 = z buffer compares before texturing

# 00001000  00000000 = Z Buffer Compare
#  +1 = z buffer enables compare

# 00000800  00000000 = Z Buffer Update
#  +1 = z buffer enables update

# 00000700  00000000 = Z Buffer Comparison Logic
#  +4 = greater than
#  +2 = equal to
#  +1 = less than

# 000000FF  00000000 = Primitive Type
#   0 = quads
#   1 = --
#   2 = triangles
#   3 = trianglestrip
#   4 = trianglefan
#   5 = lines
#   6 = linestrip
#   7 = points

# 00000000  00003000 = Blend Type
#   0 = None  -- write input directly to EFB
#   1 = Blend -- blend using blending equation
#   2 = Logic -- blend using bitwise operation
#   3 = Subtract -- input subtracts from existing pixel

# 00000000  00000700 = Blend Source
#   0 = zero -- 0.0
#   1 = one  -- 1.0
#   2 = source color
#   3 = inverted source color
#   4 = source alpha
#   5 = inverted source alpha
#   6 = destination alpha
#   7 = inverted destination alpha

# 00000000  00000070 = Blend Dest
#   (see above)

# 00000000  0000000F = Blend Logic
#   0 -- CLEAR;   dst = 0
#   1 -- AND;     dst = src & dst
#   2 -- REVAND;  dst = src & ~dst
#   3 -- COPY;    dst = src
#   4 -- INVAND;  dst = ~src & dst
#   5 -- NOOP;    dst = dst
#   6 -- XOR;     dst = src ^ dst
#   7 -- OR;      dst = src | dst
#   8 -- NOR;     dst = ~(src | dst)
#   9 -- EQUIV;   dst = ~(src ^ dst)
#   A -- INV;     dst = ~dst
#   B -- REVOR;   dst = src | ~dst
#   C -- INVCOPY; dst = ~src
#   D -- INVOR;   dst = ~src | dst
#   E -- NAND;    dst = ~(src & dst)
#   F -- SET;     dst = 1
---

I’ll have more examples soon. In the meantime, feel free to make your own.
UnclePunch UnclePunch T tauKhan @rmn
 
Last edited:

tauKhan

Smash Lord
Joined
Feb 9, 2014
Messages
1,337
#3
Oh wow, this is just the library I wanted :) . Amazing stuff, will definitively put this into use.

Also almost did a code similar to ECB Anchor earlier. Sidenote: I'd remove the first ECB node (0x7F8) from the anchor draw. It just points to TopN and isn't used for determining ECB vertices. In fact it looks like it's probably not read anywhere. Having the TopN in the anchor also distracts from the fact that some characters have TopN as another node, actually used for calculation.
 

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
292
#5
Oh wow, this is just the library I wanted :) . Amazing stuff, will definitively put this into use.
I’m glad to hear that! I have some other libraries that I’ll be releasing soon that might also interest you. They should make it easier to create draw events that can be shared by multiple codes, as well as store vertex information in a buffer that persists beyond the time it was drawn.

Also almost did a code similar to ECB Anchor earlier.
Don’t let me step on your toes! These demos are barely a step beyond “hello world” scripts meant to test the library and explore some structures.

I’ve only ever looked into how ecb quads were drawn; you seem to be more acquainted with how they’re updated. I was extremely impressed with your ECB wall collision raycast code. If you can use this library to make a demonstrative visualization of how the ECB anchor joints influence the ECB shape, I’ll be sure to add it to the example codes list.

The same goes for anyone else interested in this library; if you would like your codes listed in the example codes list -- tag me in your post. I’ll add a link to it in the OP, appending newest codes to the top of the index.

I'd remove the first ECB node (0x7F8) from the anchor draw. It just points to TopN and isn't used for determining ECB vertices. In fact it looks like it's probably not read anywhere. Having the TopN in the anchor also distracts from the fact that some characters have TopN as another node, actually used for calculation.
I agree. I excluded TopN, TransN, XRotN, and YRotN from the player skeleton codes because they were distracting, so it makes sense to do the same here. TopN is an informative joint for other reasons, but not so much in this case.

---

Here’s a second version of the code that uses thick points instead of lines, and removes TopN from the display. The points are each given a distinct hue, which I think makes a much clearer identity for the joints:


ECB Anchor Test 2

Code:
-==-

ECB Anchor Test 2
this version of the code only draws points, and skips the TopN joint
[Punkline]
1.02 ----- 80081104 --- bb61008c -> branch
2C1D0002 40A20068 38600006 3C800038 60840307 38A01455
bl <prim_main_parametric>
809C002C 38000006 7C0903A6 7CC902A6 38E00001 50C7C1CE 50C77BDE 50C735EE 1C0700FF 80A407FC C0050050 D0030000 C0050060 D0030000 C0050070 D0030000 90030000 38840004 4200FFC4
bl <prim_close>
BB61008C 00000000
Code:
-==-
!
ASM - ECB Anchor Test 2
this version of the code only draws points, and skips the TopN joint
[Punkline]
1.02 ----- 80081104 --- bb61008c -> branch
# this injection is in the middle of a drawing loop used by players
# it has the active camera set to the main camera, so drawing will appear on screen
# injection placement ensures it draws after the player
# r28 = player entity
# r29 = z loop iteration (out of 2)
cmpwi r29, 2
bne+ _return
# with this, our drawing will only happen after everything on the player is drawn

.macro toChannel, rD, rS, chan, mask, pad
rlwimi \rD, \rS, 32-(\chan<<3)-\pad, \mask<<(32-(\chan<<3))
.endm
.set chRd, 1
.set chGr, 2
.set chBl, 3
# macro helps us create color channels from binary

li r3, 6  # r3 argument = number of vertices to draw
lis r4, 0x38
ori r4, r4, 0x0307
li  r5, 0x1455
bl <prim_main_parametric>
# returns r3 = fifo pipe address
# writing to this will let us create vertices
# we need to write to 0(r3) for every piece of information given to GX

lwz r4, 0x2C(r28)
li r0, 6
mtctr r0
# load player data, and set up a bdnz loop for 6 iterations
# this will catch each JObj we want to trace

_loop:
mfctr r6
li r7, 1
toChannel r7, r6, chRd, 1, 0
toChannel r7, r6, chGr, 1, 1
toChannel r7, r6, chBl, 1, 2
mulli r0, r7, 0xFF
# r0 = RGBA color generated from ctr number
# 0x7FC = yellow
# 0x800 = magenta
# 0x804 = red
# 0x808 = cyan
# 0x80C = green
# 0x810 = blue

lwz  r5, 0x7FC(r4)
lfs  f0, 0x50(r5) # load X vert translation
stfs f0, 0(r3)    # write to GX FIFO
lfs  f0, 0x60(r5) # load Y
stfs f0, 0(r3)    # write Y
lfs  f0, 0x70(r5) # load Z
stfs f0, 0(r3)    # write Z
stw  r0, 0(r3)    # write RGBA color
# X, Y, Z, and color create a vertex

addi r4, r4, 4   # move to next JObj
bdnz+ _loop      # iterate loop
# loop will finish after 6 vertices have been drawn

bl <prim_close>
# finish the primitive with this

_return:
lmw    r27, 0x008C (sp)  # original instruction
.long 0

The linestrip in the previous test code was meant to illustrate the order of the bone index. You can still sort of do that in this new one because the colors are built from a CTR countdown of the number 6...1. The loop that draws the bones uses rlwimi instructions to convert the last 3 bits of an input number into a color.

Edit: updated with corrections to index order

0x7FC - cyan
0x800 - magenta
0x804 - blue
0x808 - yellow
0x80C - green
0x810 - red

---

Very cool!

This could be really useful when working on creating new animations.
Thanks. I think I’ll be using this library to create quite a few code experiments that prod at joint-related stuff, so hopefully there’s more to come.

I got caught up in some more example codes I wanted to share for a little while, but I ultimately started focusing on my other libraries because they’ll help me do some things I can’t do at the moment.

I did manage to finish this however. It’s a sort of general purpose JObj relationship tracing function for MCM. It’s a lot fancier than the other functions in my previous test codes:

<draw_JObj_relation>
Code:
<draw_JObj_relation>
7C0802A6 90010004 9421FFC0 BF410008 7F400026 3B610020 7C7B85AA BB9B0000 54C6801E 7CC10120 408E0010 807C0010 2C030000 41A0FFCD 408D0010 807C0008 2C030000 4180FFBD 408C0020 807C0080 2C030000 40A00014 80630008 2C030000 60C60006 4180FF9D 2C050000 4181005C 41820090 48000019 7C6802A6 54A5103A 38630005 7C6903A6 4E800420 4E800021 48000030 48000024 48000018 807C0080 2C030000 40A0005C 80630008 48000020 807C0010 48000018 807C0008 48000010 807C000C 48000008 7CA32B78 2C030000 40800030 7C7E1B78 57E4609E 60840305 38A01455 38600002
bl <prim_main_parametric>
7FC3F378 7FA4EB78
bl <draw_JObj_as_vertex>
7F83E378
bl <draw_JObj_as_vertex>
408F0024 57E4609E 60840307 38A01455 38600001
bl <prim_main_parametric>
7F83E378 7FA4EB78
bl <draw_JObj_as_vertex>
7C7B84AA 7F4FF120 BB410008 38210040 80010004 7C0803A6 4E800020

<draw_JObj_as_vertex>
3CA0CC01 C0030050 D0058000 C0030060 D0058000 C0030070 D0058000 90858000 4E800020
Code:
<draw_JObj_relation>
# r3 = THIS JObj
# r4 = color
# r5 = relation type
#      0 - draw no line
#      1 - draw line to parent
#      2 - draw line to sibling
#      3 - draw line to child
#      4 - draw line to RObj ref
#      address = draw line to second JObj (address)
# r6 = draw bools and line width
#      +1 = draw point on self
#      +2 = draw for each child
#      +4 = draw for each sibling
#      +8 = draw attached RObj families
#      0xFFF0 - mask for draw width of lines/points
_recursion:
mflr r0
stw r0, 0x4(sp)
stwu sp, -0x40(sp)
stmw r26, 0x8(sp)
mfcr r26 # cr is backed up and restored before returning
addi r27, sp, 0x20
stswi r3, r27, 0x10
lmw   r28, 0(r27)
slwi  r6, r6, 16
mtcrf 0b00010000, r6
# r28...r31 = r3...r6
# r28 = THIS JObj
# r29 = color
# r30 = relation type
# r31 = line width
# cr4 holds saved bools
# since we've backed up cr, this will persist through recursive calls

.set bSelf,    15 # draw point for self
.set bChild,   14 # recursion options
.set bSibling, 13
.set bRObj,    12
# these bits can be checked from cr4

_check_child:
bf bChild, _check_sibling
  lwz r3, 0x10(r28)
  cmpwi r3, 0
  bltl- _recursion
  # call self recursively for each child, if draw option specifies

_check_sibling:
bf bSibling, _check_robj
  lwz r3, 0x8(r28)
  cmpwi r3, 0
  bltl+ _recursion
  # call self recursively for each sibling, if draw option specifies

_check_robj:
bf bRObj, _end_of_recursion_checks
  lwz r3, 0x80(r28)
  cmpwi r3, 0
  bge+ _end_of_recursion_checks
    lwz r3, 0x8(r3)
    cmpwi r3, 0
    ori r6, r6, 0x6  # ensure RObj family is drawn entirely
    bltl+ _recursion

_end_of_recursion_checks:
cmpwi r5, 0
bgt- _address_line
beq- _draw_point
bl _get_branch_index
mflr r3
slwi r5, r5, 2
addi r3, r3, r5
mtctr r3
bctr
# bctr puts PC at branch according to r5 option

_get_branch_index:
blrl
b _parent
b _sibling
b _child

_RObj:
lwz r3, 0x80(r28)
cmpwi r3, 0
bge+ _draw_point
lwz r3, 0x8(r3)
b _draw_line
# RObj relation only drawn if RObj exists

_child:
lwz r3, 0x10(r28)
b _draw_line

_sibling:
lwz r3, 0x8(r28)
b _draw_line

_parent:
lwz r3, 0xC(r28)
b _draw_line

_address_line:
mr r3, r5

_draw_line:
# r3 = joint to draw line to from THIS JObj
# r3 may possibly be null, so we check
cmpwi r3, 0
bge _draw_point
# skip line drawing if no second joint to draw to

mr r30, r3
rlwinm r4, r31, 12, 0x3FFF0000
ori r4, r4, 0x0305
li  r5, 0x1455
li r3, 2
bl <prim_main_parametric>
mr r3, r30
mr r4, r29
bl <draw_JObj_as_vertex>
mr r3, r28
bl <draw_JObj_as_vertex>
# line is drawn

_draw_point:
bf bSelf, _return
  rlwinm r4, r31, 12, 0x3FFF0000
  ori r4, r4, 0x0307
  li  r5, 0x1455
  li r3, 1
  bl <prim_main_parametric>
  mr r3, r28
  mr r4, r29
  bl <draw_JObj_as_vertex>
  # point is drawn only if option is checked in bools

_return:
lswi r3, r27, 0x10
# each return restores r3...r6 for recursion arguments
mtcr r26
# cr is restored so that we don't have to move bits into CR more than once
lmw r26, 0x8(sp)
addi sp, sp, 0x40
lwz r0, 0x4(sp)
mtlr r0
blr



<draw_JObj_as_vertex>
lis r5, 0xCC01
lfs f0, 0x50(r3)
stfs f0, -0x8000(r5)
lfs f0, 0x60(r3)
stfs f0, -0x8000(r5)
lfs f0, 0x70(r3)
stfs f0, -0x8000(r5)
stw r4, -0x8000(r5)
blr


r3 = root JObj
  • This will act as the parent of the drawing pass.
    • Since the function is recursive, the parent is actually drawn last.
  • If specified, the root JObj may act as a single JObj with no recursive drawing.
r4 = RGBA color of this drawing pass
r5 = joint relationship
  • 0 = no relationship; just THIS joint
  • 1 = LINE between THIS and PARENT
  • 2 = LINE between THIS and SIBLING
  • 3 = LINE between THIS and CHILD
  • 4 = LINE between THIS and ROBJ link
  • else = interpret r5 as a second JObj address
    • create LINE between THIS and argument JOBJ
r6 = line width and skeleton options, encoded as integers and bools in an hword:
  • 0x1F00 = line width, in native pixels
  • 0x00F0 = line width, in 1/16 pixels
  • 0x0001 = include point at location of THIS joint
  • 0x0002 = draw for each sibling in skeleton
  • 0x0004 = draw for each child in skeleton
  • 0x0008 = draw for each RObj link in skeleton
The function has only been lightly tested.

I’m curious if this could be used to draw out some unknown entity skeletons in the various global entity pointer tables that exist in r13. The offset 0x28 in each entity type is like some kind of “root hsd object” pointer, or something. It’s usually a root JObj, but I’ve also seen them point to CObjs in certain entity types. The HSD info table at the start of the object can be used to identify JObj class types, so it’d probably just be a matter of testing for that.
 
Last edited:

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
292
#6
I made this last week while looking into some undocumented flags in the action state change function. It’s a proof of concept for a library that prints out data in an entirely visual format -- making it easy to watch flag fields without needing to understand the finer details of hexadecimal.


The BASE CODE below comes with the Primitive Drawing Module. It does nothing beside enable an interface for 4 printer structures shown in the above screenshot. On its own, no noticeable change is made to the game.

Each of the 4 printers is limited to storing only 32 bits of data, but it will do so indefinitely throughout runtime. The 32-bit value in each structure may be used to either store a value or point to a word in RAM.

---

Get the BASE CODE here:
Code:
-==-

Nibble Printer POC 0.1
- MASTER CODE -
Draw nibbles from RAM using colored quads.
4 global watch structures are created at:
top-left : 804D7A48; AKA -0x7F98(rtoc)
bot-left : 804D7B50; AKA -0x7E90(rtoc)
top-right: 804D7BC8; AKA -0x7E18(rtoc)
bot-right: 804D7C58; AKA -0x7D88(rtoc)

Each watch structure is 0x8 bytes:
0x0 - value/pointer to read
0x4 - watch type; 1= value, 2= pointer

see url for more info - https://smashboards.com/threads/primitive-drawing-module.454232/post-22251187
[Punkline]# these are mytoc slots 1, 2, 3, and 5
1.02 ----- 0x80008D44 --- C8228068 -> C8228000
1.02 ----- 0x804D7A48 --- 4330000000000000 -> 0000000000000000
# mytoc 1: 804D7A48 -0x7F98(rtoc)

1.02 ----- 0x8000EA28 --- C8628170 -> C86280A0
1.02 ----- 0x804D7B50 --- 4330000080000000 -> 0000000000000000
# mytoc 2: 804D7B50 -0x7E90(rtoc)

1.02 ----- 0x80010220 --- CBC281E8 -> CBC280A0
1.02 ----- 0x804D7BC8 --- 4330000080000000 -> 0000000000000000
# mytoc 3: 804D7BC8 -0x7E18(rtoc)

1.02 ----- 0x80013F84 --- C8228278 -> C82280A0
1.02 ----- 0x80013C3C --- C8228278 -> C82280A0
1.02 ----- 0x804D7C58 --- 4330000080000000 -> 0000000000000000
# mytoc 5: 804D7C58 -0x7D88(rtoc)

1.02 --- 8039102c ----- 80010014 -> branch
38628068 3C8000AF 60840002 38A00402
bl <nibPrint_nibPrint_draw_nibbles_from_input>
38628170 3C8000AF 6084001A 38A00402
bl <nibPrint_nibPrint_draw_nibbles_from_input>
386281E8 3C800141 60840002 38A00402
bl <nibPrint_nibPrint_draw_nibbles_from_input>
38628278 3C800141 6084001A 38A00402
bl <nibPrint_nibPrint_draw_nibbles_from_input>
80010014 00000000

<nibPrint_nibPrint_draw_nibbles_from_input>
2C030000 4C800020 80030004 2C000001 41820010 2C000002 80630000 4CA20020
b <nibPrint_nibPrint_draw_nibbles>

<nibPrint_nibPrint_draw_nibbles>
7C0802A6 90010004 9421FFD0 BF610010 7C9D2378 54BC063E 54BB421E 7C7F1B79 4080004C 806DB780 2C030000 40800040
bl 0x80368458
386000A0
bl <prim_quads>
3BC00008 7FA4EB78 7F85E378 83FF0000 57FF203E 7FE3FB78
bl <nibPrint_draw_nibble>
7C84DA14 37DEFFFF 4082FFEC
bl <prim_close>
bl 0x80368608
BB610010 38210030 80010004 7C0803A6 4E800020

<nibPrint_draw_nibble>
7C0802A6 90010004 9421FFC0 BF210010 7C7F1B78 7CBE2B78 1FBE0002 1F9E0003 1F7E0007 3B4000FF 394000AA 57E0A9CE 7D8A01D6 57E073DE 7D2A01D6 7D8C4A14 57E03DEE 7D2A01D6 7D8C4A14 3D405555 614A5500 546007FE 7D2A01D6 7F2C4A14
7F65DB78 7F43D378
bl <nibPrint_draw_square>
7C84F214 57C0801E 7C840214 7FA5EB78 57E6EFFE 1CC600FF 7F233378
bl <nibPrint_draw_square>
7C84E214 57E6FFFE 1CC600FF 7F233378
bl <nibPrint_draw_square>
5780801E 7C840214 57E607FE 1CC600FF 7F233378
bl <nibPrint_draw_square>
7C9C2050 57E6F7FE 1CC600FF 7F233378
bl <nibPrint_draw_square>
57A0801E 7C840214 7C9E2050 7FC5F378 BB210010 38210040 80010004 7C0803A6 4E800020

<nibPrint_draw_square>
B0A1FFF0 9081FFF4 E0612FF0 E0213FF4 10410CE0 10031CE0 3CE0CC01 D0278000 D0478000 D0678000 90678000 EC42002A D0278000 D0478000 D0678000 90678000 EC21002A D0278000 D0478000 D0678000 90678000 EC420028 D0278000 D0478000 D0678000 90678000 EC210028 4E800020

# includes primitive drawing module:
1.02 ----- 0x800c2684 --- 386000013880000438a0000538c00005 -> 57E3A7BE 57E4C77E 57E5E77E 57E6073E
1.02 ----- 0x800c26b0 --- 386000013880000338a00000 ->57C3A7FE 57C4C77E 57C5AFFE
1.02 ----- 0x800c26c0 --- 38600000 -> 57C39FFE
1.02 ----- 0x800c272c --- 38600000 -> 57C317BE
1.02 ----- 0x800c2da0 --- 38b80001 -> branch
b <projection_return>
00000000
1.02 ----- 0x800c2674 --- 38600001 -> branch
bl <prim_default_swordtrails>
7C8802A6 7C6444AA
bl <prim_setup>
881C2101
b 0x800c2738
60000000
1.02 ----- 0x800c2734 --- 881c2101 -> branch
BBC10010
b <projection_return>
60000000
1.02 ----- 0x800c2d3c --- 4827968d -> branch
bl <prim_mtx_setup>
38B80001
b 0x800c2da4
60000000
<prim_setup> 1.02
7C0802A6 90010004 9421C000 BFC10010 7C7E1B78 7C9F2378 38600001
b 0x800c2678
<prim_mtx_setup> 1.02
7C0802A6 90010004 9421C000
bl 0x8033C3C8
b  0x800c2d40
<projection_return> All
38214000 80010004 7C0803A6 4E800020
<GXBegin> 1.02
b 0x8033D0DC
<GXSetLineWidth> 1.02
b 0x8033D240
<GXSetPointSize> 1.02
b 0x8033d298
<prim_close> 1.02
3860FFFF
b 0x80361fc4
<prim_main> All
7C0802A6 90010004 9421FFF8
bl <prim_defaults>
7D0802A6 80A80000 7C842B78 80A80004 3CC00010 60C61300 38E01455 7CC845AA
bl <prim_main_parametric>
38210008 80010004 7C0803A6 4E800020
<prim_main_parametric> All
7C0802A6 90010004 9421FFC0 BFA10010 7C7E1B78 7C9F2378 7C832378 7CA42B78
bl <prim_setup>
bl <prim_mtx_setup>
57E3063E 2C030005 41A00024 2C030007 4181001C 57E385BE 38800005 4182000C
bl <GXSetLineWidth>
48000008
bl <GXSetPointSize>
57E31E38 38630080 38800000 57C5043E
bl     <GXBegin>
3C60CC01 38638000 BBA10010 38210040 80010004 7C0803A6 4E800020
<prim_quads> All
38800000
b <prim_main>
<prim_triangles> All
38800002
b <prim_main>
<prim_trianglestrip> All
38800003
b <prim_main>
<prim_trianglefan> All
38800004
b <prim_main>
<prim_lines> All
38800005
b <prim_main>
<prim_linestrip> All
38800006
b <prim_main>
<prim_points> All
38800007
b <prim_main>
<prim_defaults>
4E800021 00101300 00001455
<prim_default_swordtrails>
4E800021 00101300 00001455
<prim_toggle_z>
7C0802A6 90010004 9421FFF8
bl <prim_defaults>
7C6802A6 80830000 68841000 90830000 38210008 80010004 7C0803A6 4E800020
Code:
-==-
!
ASM - Nibble Printer POC 0.1
- MASTER CODE -
Draw nibbles from RAM using colored quads.
4 global watch structures are created at:
top-left : 804D7A48; AKA -0x7F98(rtoc)
bot-left : 804D7B50; AKA -0x7E90(rtoc)
top-right: 804D7BC8; AKA -0x7E18(rtoc)
bot-right: 804D7C58; AKA -0x7D88(rtoc)

Each watch structure is 0x8 bytes:
0x0 - value/pointer to read
0x4 - watch type; 1= value, 2= pointer

see url for more info - https://smashboards.com/threads/primitive-drawing-module.454232/post-22251187
[Punkline]# these are mytoc slots 1, 2, 3, and 5
1.02 ----- 0x80008D44 --- C8228068 -> C8228000
1.02 ----- 0x804D7A48 --- 4330000000000000 -> 0000000000000000
# mytoc 1: 804D7A48 -0x7F98(rtoc)

1.02 ----- 0x8000EA28 --- C8628170 -> C86280A0
1.02 ----- 0x804D7B50 --- 4330000080000000 -> 0000000000000000
# mytoc 2: 804D7B50 -0x7E90(rtoc)

1.02 ----- 0x80010220 --- CBC281E8 -> CBC280A0
1.02 ----- 0x804D7BC8 --- 4330000080000000 -> 0000000000000000
# mytoc 3: 804D7BC8 -0x7E18(rtoc)

1.02 ----- 0x80013F84 --- C8228278 -> C82280A0
1.02 ----- 0x80013C3C --- C8228278 -> C82280A0
1.02 ----- 0x804D7C58 --- 4330000080000000 -> 0000000000000000
# mytoc 5: 804D7C58 -0x7D88(rtoc)

1.02 --- 8039102c ----- 80010014 -> branch
# epilog of main draw loop


.set size, 2
.set pad, 4
# this is the unit size of each drawing, in native pixels
# each nibble is square length = size*2+padding

.set sizevar, size+(pad<<8)
.set padded, (size*7)+pad
.set width, padded*8
.set height, padded
.set clearance, 6
# these calculate where to put the drawings

addi r3, rtoc, -0x7F98
lis r4, 320-width-(clearance/4)
ori r4, r4, 2
li r5, sizevar
bl <nibPrint_nibPrint_draw_nibbles_from_input>

addi r3, rtoc, -0x7E90
lis r4, 320-width-(clearance/4)
ori r4, r4, 2+height+clearance
li r5, sizevar
bl <nibPrint_nibPrint_draw_nibbles_from_input>

addi r3, rtoc, -0x7E18
lis r4, 320+(clearance/4)
ori r4, r4, 2
li r5, sizevar
bl <nibPrint_nibPrint_draw_nibbles_from_input>

addi r3, rtoc, -0x7D88
lis r4, 320+(clearance/4)
ori r4, r4, 2+height+clearance
li r5, sizevar
bl <nibPrint_nibPrint_draw_nibbles_from_input>

lwz    r0, 0x0014 (sp)
.long 0

<nibPrint_nibPrint_draw_nibbles_from_input>
# r3 = address of 2-word data structure:
#  0x0 = value for input
#  0x4 = ID:
#       - 0 = disabled
#       - 1 = draw data in 0x0
#       - 2 = point to data in 0x0

# r4 = origin X and Y (hwords)
# r5 = size, in native pixels

cmpwi r3, 0
bgelr-
lwz r0, 0x4(r3)
cmpwi r0, 1
beq- _draw_from_input
cmpwi r0, 2
lwz r3, 0x0(r3)
bnelr+

_draw_from_input:
b <nibPrint_nibPrint_draw_nibbles>



<nibPrint_nibPrint_draw_nibbles>
# r3 = address of data
# r4 = origin X and Y (hwords)
# r5 000000FF = size, in native pixels
# r5 0000FF00 = pad size, in native pixels

_code_start:
mflr r0
stw r0, 0x4(sp)
stwu sp, -0x30(sp)
stmw r27, 0x10(sp)

mr r29, r4
rlwinm r28, r5, 0, 0xFF     # r28 = size
rlwinm r27, r5, 8, 0xFF0000 # r27 = pad

_validate_address:
mr. r31, r3
bge- _return

_set_CObj:
lwz r3, -0x4880(r13)
cmpwi r3, 0
bge- _return
bl 0x80368458
# HSD_CObjSetCurrent
# 0, 0, 0 = top left corner of window frame

_start_quads:
li r3, 4*5*8
bl <prim_quads>

li r30, 8
mr r4, r29
mr r5, r28
lwz r31, 0(r31)

_for_each_nibble:
rlwinm r31, r31, 4, -1
mr r3, r31
bl <nibPrint_draw_nibble>

add r4, r4, r27
addic. r30, r30, -1
bne+ _for_each_nibble

bl <prim_close>

_end_CObj:
bl 0x80368608
# HSD_CObjEndCurrent

_return:
lmw r27, 0x10(sp)
addi sp, sp, 0x30
lwz r0, 0x4(sp)
mtlr r0
blr



<nibPrint_draw_nibble>
# r3 = nibble to draw
# r4 = origin X and Y (hwords)
# r5 = size, in native pixels
mflr r0
stw  r0, 0x4(sp)
stwu sp, -0x40(sp)
stmw r25, 0x10(sp)

_setup:
.set rCoords, 4;
.set rNibble, 31; mr rNibble , r3
.set rUnit,   30; mr rUnit, r5
.set rU2,     29; mulli rU2, rUnit, 2
.set rU3,     28; mulli rU3, rUnit, 3
.set rU7,     27; mulli rU7, rUnit, 7
.set rBlack,  26; li rBlack, 0xFF
.set rColor,  25
# r5 becomes a quantum unit size for working with integers
# rUnit and rU* will be used to build a 2x2 boardered quad motif

_build_channels:
li r10, 0xAA
rlwinm r0 , r31, 21, 0x01000000
mullw  r12 , r10, r0
# 8 = 66% red

rlwinm r0 , r31, 14, 0x00010000
mullw  r9 , r10, r0
# 4 += 66% green

add    r12, r12, r9
rlwinm r0 , r31,  7, 0x00000100
mullw  r9 , r10, r0
# 2 += 66% blue

add    r12, r12, r9
lis r10, 0x5555
ori r10, r10, 0x5500
rlwinm r0, r3, 0,  0x00000001
mullw r9, r10, r0
add rColor, r12, r9
mr r5, rU7
# 1 += 33% white
# rColor has been compiled with a null alpha

_draw_bg:
# 0, 0; square size = 7x7
mr r3, rBlack
bl <nibPrint_draw_square>
# background has been drawn

add rCoords, rCoords, rUnit
slwi r0, rUnit, 16
add rCoords, rCoords, r0
# move brush to inside of border

mr r5, rU2
# set brush size

_draw_8:
# 1, 1; square size = 2x2
rlwinm r6, rNibble, 29, 1
mulli  r6, r6, 0xFF
or     r3, rColor, r6
bl <nibPrint_draw_square>
# +8 bit has been drawn
# alpha in each drawn bit reflects value of TRUE/FALSE

add rCoords, rCoords, rU3
# move brush down 3 units

_draw_2:
# 1, 4; square size = 2x2
rlwinm r6, rNibble, 31, 1
mulli  r6, r6, 0xFF
or     r3, rColor, r6
bl <nibPrint_draw_square>
# +2 bit has been drawn

slwi r0, rU3, 16
add rCoords, rCoords, r0
# move brush right 3 units

_draw_1:
rlwinm r6, rNibble, 0, 1
mulli  r6, r6, 0xFF
or     r3, rColor, r6
bl <nibPrint_draw_square>
# +1 bit has been drawn

sub rCoords, rCoords, rU3
# move brush up 3 units

_draw_4:
rlwinm r6, rNibble, 30, 1
mulli r6, r6, 0xFF
or     r3, rColor, r6
bl <nibPrint_draw_square>
# all bits have been drawn in nibble

slwi r0, rU2, 16
add rCoords, rCoords, r0
sub rCoords, rCoords, rUnit
# move brush
# 7, 0 -- for next nibble in iteration

# resize brush
mr r5, rUnit

_return:
lmw  r25, 0x10(sp)
addi sp, sp, 0x40
lwz  r0, 0x4(sp)
mtlr r0
blr



<nibPrint_draw_square>
# r3 = color
# r4 = origin X and Y (hwords)
# r5 = size, in native pixels
# leaf draws an opaque square in the current GX context
# CObj is assumed to be set to frame overlay mtx

# returns:
# r3, r4, r5 are unchanged
# f0 = size
# f1 = x position
# f2 = y position
# f3 = 0.0

# PSQA format:
.macro PSQAform, op, rD, A, W, i, d
.long \op<<(31-5)+\rD<<(31-10)+\A<<(31-15)+\W<<(31-16)+\i<<(31-19)+(\d&0xFFF)
.endm
.macro psq_l, frD, d, rA, W, qrI
PSQAform 56, \frD, \rA, \W, \qrI, \d
.endm

# PSAB format:
.macro PSABform, op, rD, A, B, op2, f
.long \op<<(31-5)+\rD<<(31-10)+\A<<(31-15)+\B<<(31-20)+\op2<<1+\f
.endm
.macro ps_merge11, frD, frA, frB
PSABform 4, \frD, \frA, \frB, 624, 0
.endm

.macro drawVert
stfs f1, -0x8000(r7)
stfs f2, -0x8000(r7)
stfs f3, -0x8000(r7)
stw  r3, -0x8000(r7)
.endm

_code_start:
sth r5, -0x10(sp)
stw r4, -0xC(sp)
# extract fixed points from r4, and place them in redzone

_dequantize_and_format:
psq_l 3, -0x10, 1, 0, 2 # 2 = 8UINT
psq_l 1, -0xC,  1, 0, 3 # 3 = 16UINT
ps_merge11 2, 1, 1
ps_merge11 0, 3, 3
# all floats are unpacked from fixed points stored in redzone
# each float is in ps[0] -- so normal floating point singles operations will work on them

_draw_square:
lis r7, 0xCC01
drawVert
fadds f2, f2, f0
drawVert
fadds f1, f1, f0
drawVert
fsubs f2, f2, f0
drawVert
fsubs f1, f1, f0

_return:
blr

# includes primitive drawing module:
1.02 ----- 0x800c2684 --- 386000013880000438a0000538c00005 -> 57E3A7BE 57E4C77E 57E5E77E 57E6073E
1.02 ----- 0x800c26b0 --- 386000013880000338a00000 ->57C3A7FE 57C4C77E 57C5AFFE
1.02 ----- 0x800c26c0 --- 38600000 -> 57C39FFE
1.02 ----- 0x800c272c --- 38600000 -> 57C317BE
1.02 ----- 0x800c2da0 --- 38b80001 -> branch
b <projection_return>
00000000
1.02 ----- 0x800c2674 --- 38600001 -> branch
bl <prim_default_swordtrails>
7C8802A6 7C6444AA
bl <prim_setup>
881C2101
b 0x800c2738
60000000
1.02 ----- 0x800c2734 --- 881c2101 -> branch
BBC10010
b <projection_return>
60000000
1.02 ----- 0x800c2d3c --- 4827968d -> branch
bl <prim_mtx_setup>
38B80001
b 0x800c2da4
60000000
<prim_setup> 1.02
7C0802A6 90010004 9421C000 BFC10010 7C7E1B78 7C9F2378 38600001
b 0x800c2678
<prim_mtx_setup> 1.02
7C0802A6 90010004 9421C000
bl 0x8033C3C8
b  0x800c2d40
<projection_return> All
38214000 80010004 7C0803A6 4E800020
<GXBegin> 1.02
b 0x8033D0DC
<GXSetLineWidth> 1.02
b 0x8033D240
<GXSetPointSize> 1.02
b 0x8033d298
<prim_close> 1.02
3860FFFF
b 0x80361fc4
<prim_main> All
7C0802A6 90010004 9421FFF8
bl <prim_defaults>
7D0802A6 80A80000 7C842B78 80A80004 3CC00010 60C61300 38E01455 7CC845AA
bl <prim_main_parametric>
38210008 80010004 7C0803A6 4E800020
<prim_main_parametric> All
7C0802A6 90010004 9421FFC0 BFA10010 7C7E1B78 7C9F2378 7C832378 7CA42B78
bl <prim_setup>
bl <prim_mtx_setup>
57E3063E 2C030005 41A00024 2C030007 4181001C 57E385BE 38800005 4182000C
bl <GXSetLineWidth>
48000008
bl <GXSetPointSize>
57E31E38 38630080 38800000 57C5043E
bl     <GXBegin>
3C60CC01 38638000 BBA10010 38210040 80010004 7C0803A6 4E800020
<prim_quads> All
38800000
b <prim_main>
<prim_triangles> All
38800002
b <prim_main>
<prim_trianglestrip> All
38800003
b <prim_main>
<prim_trianglefan> All
38800004
b <prim_main>
<prim_lines> All
38800005
b <prim_main>
<prim_linestrip> All
38800006
b <prim_main>
<prim_points> All
38800007
b <prim_main>
<prim_defaults>
4E800021 00101300 00001455
<prim_default_swordtrails>
4E800021 00101300 00001455
<prim_toggle_z>
7C0802A6 90010004 9421FFF8
bl <prim_defaults>
7C6802A6 80830000 68841000 90830000 38210008 80010004 7C0803A6 4E800020

This code uses 4 mytoc slots to create the global interface. See the spoiler for more information about using them to create your own printed experiments using either DOL mods or Gecko codes:
Each interface uses 2 words:
0x0 - input word of bits
0x4 - input type

By setting the type to 1, the input word will be read directly, as a value.
By setting the type to 2, the input word will be read indirectly, as a pointer to a value.
Other types will disable the display. 0 is the default type, hiding each display.

The structures can be accessed at any time from the following rtoc addresses:

mytoc 1: 804D7A48 or -0x7F98(rtoc) -- top-left display
mytoc 2: 804D7B50 or -0x7E90(rtoc) -- bottom-left display
mytoc 3: 804D7BC8 or -0x7E18(rtoc) -- top-right display
mytoc 5: 804D7C58 or -0x7D88(rtoc) -- bottom-right display

(add 4 to any of those negative rtoc offsets to access the type variable. They will not display unless the type is set to 1 or 2)

If you have any questions, feel free to ask.

---

Examples:
Code:
-==-

Example 1 - Player Data Flags
Uses Nibble Printer to simply prints out 128 bits starting at player data offset 0x2210
[Punkline]
1.02 --- 8006cb7c ----- 8001001c -> branch
887F000C 2C030000 40A20030 387F2210 7CA384AA 38000001 90A28068 9002806C 90C281E8 900281EC 90E28170 90028174 91028278 9002827C 8001001C 00000000

-==-

Example 2 - Action State Change Flags
Log transition flags for last 2 action state changes in players 1 and 2
[Punkline]
1.02 --- 80069410 ----- 341a00b0 -> branch
807E002C 8883000C 7FE7FB78 39000001 2C040000 38A28068 38C28170 41A20014 2C040001 38A281E8 38C28278 40820010 7D2544AA 7D2645AA 7CE545AA 341A00B0 00000000

-==-

Example 3 - Action State Change Flags (composite)
Printed field will creates a composite record of all transition flags used in the game.
[Punkline]
1.02 --- 80069410 ----- 341a00b0 -> branch
7FE7FB78 39000001 81228068 7D273B78 38A28068 7CE545AA 341A00B0 00000000

-==-

Example 4 - Action State Change Flags (XOR filter)
The bottom-right printer will display an XOR filter between samples A and B
- Press dpad down or right to sample the transition flags
- hold dpad to clear a sample
[Punkline]
1.02 --- 80069410 ----- 341a00b0 -> branch
807E002C 8883000C 7FE7FB78 39000001 2C040000 38A28068 40A20008 7CE545AA 341A00B0 00000000

1.02 --- 8006cb7c ----- 8001001c -> branch
887F000C 2C030000 40A200B4 83DE0028 480000A5 7D6802A6 809F0668 80BF0660 50A42636 7C803120 3980002D 40B90018 898B0000 358CFFFF 40A0000C 39800000 91828170 998B0000 3980002D 40BA0018 898B0001 358CFFFF 40A0000C 39800000 918281E8 998B0001 81628068 40BD0010 81828170 7D8B5B78 91628170 40BE0010 818281E8 7D8B5B78 916281E8 81828170 816281E8 7D8C5A78 91828278 38000001 9002806C 90028174 900281EC 9002827C 4800000C 4E800021 00000000 8001001C 00000000

-==-

Example 4 - Action State Change Flags (AND filter)
The bottom-right printer will display an AND filter between samples A and B
- Press dpad down or right to sample the transition flags
- hold dpad to clear a sample
[Punkline]
1.02 --- 80069410 ----- 341a00b0 -> branch
807E002C 8883000C 7FE7FB78 39000001 2C040000 38A28068 40A20008 7CE545AA 341A00B0 00000000

1.02 --- 8006cb7c ----- 8001001c -> branch
887F000C 2C030000 40A200B4 83DE0028 480000A5 7D6802A6 809F0668 80BF0660 50A42636 7C803120 3980002D 40B90018 898B0000 358CFFFF 40A0000C 39800000 91828170 998B0000 3980002D 40BA0018 898B0001 358CFFFF 40A0000C 39800000 918281E8 998B0001 81628068 40BD0010 81828170 7D8B5B78 91628170 40BE0010 818281E8 7D8B5B78 916281E8 81828170 816281E8 7D8C5838 91828278 38000001 9002806C 90028174 900281EC 9002827C 4800000C 4E800021 00000000 8001001C 00000000

-==-

Example 5 - Player AObj Detector
Print joints in skeleton as array of true or false bits
True = joint is using AObj; else false
- Scroll skeleton array with dpad left and right
[Punkline]
1.02 --- 8006cb7c ----- 8001001c -> branch
887F000C 2C030000 40A2010C 83DE0028 480000B5 7C6802A6 3D008047 61089D60 81080000 5508063E 89230003 7C084800 418200E4 99030003 809F0668 80BF0660 50A42636 7C803120 71080003 4F821382 3980002D 40BA0018 89830007 358CFFFF 4080000C 39800000 4FDCE382 99830007 3980002D 40BB0018 89830006 358CFFFF 4080000C 39800000 4FFCE382 99830006 88A30004 88C30002 40BE0014 2C0600FF 41820030 38C60001 48000028 40BF0024 38C6FFFF 2C060001 41A10018 38C00001 48000010 4E800021 007C0100 00000000 98A30004 98C30002 7C7F1B78 7FC3F378 A09F0000 38E00000
bl <record_32_JObj_bools>
90E28170 7FC3F378 88DF0002 38C60020 38E00000
bl <record_32_JObj_bools>
90E28278 38000001 90028174 9002827C 8001001C 00000000

<record_32_JObj_bools>
7C0802A6 90010004 9421FFF0 93E10008 7C000026 9001000C 7C7F1B78 817F0010 2D0B0000 815F0008 2D8A0000 4E086382 40900038 34C6FFFF 41A10020 41A00008 38E00000 35660020 7D5F202E 5D4A2800 5D4A583E 7CE75378 807F0010 4188FFA5 807F0008 418CFF9D 8001000C 7C0FF120 83E10008 38210010 80010004 7C0803A6 4E800020



-==-

Example 6 - Player JObj Flag Detector
Print joints in skeleton as array of true or false bits
True = joint is using selected bit; else false
- Scroll skeleton array with dpad left and right
- select a bit to sample with dpad down
[Punkline]
1.02 --- 8006cb7c ----- 8001001c -> branch
887F000C 2C030000 40A2015C 83DE0028 480000D5 7C6802A6 3D008047 61089D60 81080000 5508063E 89230003 7C084800 41820134 99030003 809F0668 80BF0660 50A42636 7C803120 71080003 4F821382 3980002D 40BA0018 89830007 358CFFFF 4080000C 39800000 4FDCE382 99830007 3980002D 40BB0018 89830006 358CFFFF 4080000C 39800000 4FFCE382 99830006 3980002D 40B90018 89830005 358CFFFF 4080000C 39800000 4FBCE382 99830005 88A30004 88C30002 40BE0014 2C0600FF 4182003C 38C60001 48000034 40BF0024 38C6FFFF 2C060001 41A10024 38C00001 4800001C 4E800021 00140100 00000000 40BD000C 38A50001 54A506FE 98A30004 98C30002 7C7F1B78 7FC3F378 A09F0000 38E00000
bl <record_32_JObj_bools>
90E28170 7FC3F378 88DF0002 38C60020 38E00000
bl <record_32_JObj_bools>
90E28278 3C608000 7C632C30 90628068 7FC3F378 38800000
bl <record_composite_JObj_bools>
908281E8 38000001 90028174 9002806C 9002827C 900281EC 8001001C 00000000
<record_composite_JObj_bools>
7C0802A6 90010004 9421FFF0 93E10008 7C7F1B79 4080001C 807F0010 4BFFFFE5 807F0008 4BFFFFDD 801F0014 7C840378 83E10008 38210010 80010004 7C0803A6 4E800020
Code:
-==-
!
ASM - Example 1 - Player Data Flags
Uses Nibble Printer to simply prints out 128 bits starting at player data offset 0x2210
[Punkline]
1.02 --- 8006cb7c ----- 8001001c -> branch
# end of player think,
# r30 = player entity
# r31 = player data
lbz r3, 0xC(r31)
cmpwi r3, 0
bne+ _return

  _player_1:
  addi r3, r31, 0x2210
  lswi r5, r3, 0x10
  li r0, 1
  stw r5, -0x7F98(rtoc)
  stw r0, -0x7F94(rtoc)
  stw r6, -0x7E18(rtoc)
  stw r0, -0x7E14(rtoc)
  stw r7, -0x7E90(rtoc)
  stw r0, -0x7E8C(rtoc)
  stw r8, -0x7D88(rtoc)
  stw r0, -0x7D84(rtoc)
  # record bits

_return:
lwz    r0, 0x001C (sp)# original instruction
.long 0


-==-
!
ASM - Example 2 - Action State Change Flags
Log transition flags for last 2 action state changes in players 1 and 2
[Punkline]
1.02 --- 80069410 ----- 341a00b0 -> branch
lwz r3, 0x2C(r30)
lbz r4, 0xC(r3)
mr   r7, r31
li   r8, 1

_check_if_p1:
cmpwi r4, 0
addi r5, rtoc, -0x7F98
addi r6, rtoc, -0x7E90
beq+ _log_values

_check_if_p2:
cmpwi r4, 1
addi r5, rtoc, -0x7E18
addi r6, rtoc, -0x7D88
bne- _return

  _log_values:
  lswi r9, r5, 0x8
  stswi r9, r6, 0x8
  stswi r7, r5, 0x8

_return:
addic.    r0, r26, 176
.long 0



-==-
!
ASM - Example 3 - Action State Change Flags (composite)
Printed field will creates a composite record of all transition flags used in the game.
[Punkline]
1.02 --- 80069410 ----- 341a00b0 -> branch
mr   r7, r31
li   r8, 1

_composite_or:
lwz  r9, -0x7F98(rtoc)
or   r7, r9, r7
addi  r5, rtoc, -0x7F98
stswi r7, r5, 0x8

_return:
addic.    r0, r26, 176
.long 0


-==-
!
ASM - Example 4 - Action State Change Flags (XOR filter)
The bottom-right printer will display an XOR filter between samples A and B
- Press dpad down or right to sample the transition flags
- hold dpad to clear a sample
[Punkline]
1.02 --- 80069410 ----- 341a00b0 -> branch
lwz r3, 0x2C(r30)
lbz r4, 0xC(r3)
mr   r7, r31
li   r8, 1

_check_if_p1:
cmpwi r4, 0
addi r5, rtoc, -0x7F98
bne+ _return


  _log_values:
  stswi r7, r5, 0x8

_return:
addic.    r0, r26, 176
.long 0

1.02 --- 8006cb7c ----- 8001001c -> branch
# end of player think,
# r30 = player entity
# r31 = player data

.set bInstDRight, 30
.set bInstDDown,  29
.set bHeldDRight, 26
.set bHeldDDown,  25
# dpad bools


.set xDHeld,  0
.set xRHeld,  1
# global memory offsets

.set holdtime, 45  # number of frames to hold button before rapid input starts

lbz r3, 0xC(r31)
cmpwi r3, 0
bne+ _return

  _player_1:
  lwz r30, 0x28(r30)   # r30 = saved JObj root
  bl _get_mem
  mflr r11
  lwz r4, 0x668(r31)   # instant buttons
  lwz r5, 0x660(r31)   # held buttons
  rlwimi r4, r5, 4, 0xF0
  mtcrf  0b00000011, r4
  # dpad bits are in cr now

  _update_holding_down:
  li r12, holdtime
  bf+ bHeldDDown, _update_holding_right
    lbz r12, 0x0(r11)
    subic. r12, r12, 1
    bge+ _update_holding_right
      li r12, 0
      stw r12, -0x7E90(rtoc)
      # if holding down, clear bottom-left printer samples

  _update_holding_right:
  stb r12, 0x0(r11)
  li r12, holdtime
  bf+ bHeldDRight, _update_sample_down
    lbz r12, 0x1(r11)
    subic. r12, r12, 1
    bge+ _update_sample_down
      li r12, 0
      stw r12, -0x7E18(rtoc)
      # if holding right, clear top-right printer samples

  _update_sample_down:
  stb r12, 0x1(r11)
  lwz r11, -0x7F98(rtoc)
  bf+ bInstDDown, _update_sample_right
    lwz r12, -0x7E90(rtoc)
    or r11, r12, r11
    stw r11, -0x7E90(rtoc)

  _update_sample_right:
  bf+ bInstDRight, _update_sample_xor
    lwz r12, -0x7E18(rtoc)
    or r11, r12, r11
    stw r11, -0x7E18(rtoc)

  _update_sample_xor:
  lwz r12, -0x7E90(rtoc)
  lwz r11, -0x7E18(rtoc)
  xor r12, r12, r11
  stw r12, -0x7D88(rtoc)

  _activate_printers:
  li r0, 1
  stw r0, -0x7F94(rtoc)
  stw r0, -0x7E8C(rtoc)
  stw r0, -0x7E14(rtoc)
  stw r0, -0x7D84(rtoc)
  b _return

  _get_mem:
  blrl
  .long 0

_return:
lwz    r0, 0x001C (sp)# original instruction
.long 0



-==-
!
ASM - Example 4 - Action State Change Flags (AND filter)
The bottom-right printer will display an AND filter between samples A and B
- Press dpad down or right to sample the transition flags
- hold dpad to clear a sample
[Punkline]
1.02 --- 80069410 ----- 341a00b0 -> branch
lwz r3, 0x2C(r30)
lbz r4, 0xC(r3)
mr   r7, r31
li   r8, 1

_check_if_p1:
cmpwi r4, 0
addi r5, rtoc, -0x7F98
bne+ _return


  _log_values:
  stswi r7, r5, 0x8

_return:
addic.    r0, r26, 176
.long 0

1.02 --- 8006cb7c ----- 8001001c -> branch
# end of player think,
# r30 = player entity
# r31 = player data

.set bInstDRight, 30
.set bInstDDown,  29
.set bHeldDRight, 26
.set bHeldDDown,  25
# dpad bools


.set xDHeld,  0
.set xRHeld,  1
# global memory offsets

.set holdtime, 45  # number of frames to hold button before rapid input starts

lbz r3, 0xC(r31)
cmpwi r3, 0
bne+ _return

  _player_1:
  lwz r30, 0x28(r30)   # r30 = saved JObj root
  bl _get_mem
  mflr r11
  lwz r4, 0x668(r31)   # instant buttons
  lwz r5, 0x660(r31)   # held buttons
  rlwimi r4, r5, 4, 0xF0
  mtcrf  0b00000011, r4
  # dpad bits are in cr now

  _update_holding_down:
  li r12, holdtime
  bf+ bHeldDDown, _update_holding_right
    lbz r12, 0x0(r11)
    subic. r12, r12, 1
    bge+ _update_holding_right
      li r12, 0
      stw r12, -0x7E90(rtoc)
      # if holding down, clear bottom-left printer samples

  _update_holding_right:
  stb r12, 0x0(r11)
  li r12, holdtime
  bf+ bHeldDRight, _update_sample_down
    lbz r12, 0x1(r11)
    subic. r12, r12, 1
    bge+ _update_sample_down
      li r12, 0
      stw r12, -0x7E18(rtoc)
      # if holding right, clear top-right printer samples

  _update_sample_down:
  stb r12, 0x1(r11)
  lwz r11, -0x7F98(rtoc)
  bf+ bInstDDown, _update_sample_right
    lwz r12, -0x7E90(rtoc)
    or r11, r12, r11
    stw r11, -0x7E90(rtoc)

  _update_sample_right:
  bf+ bInstDRight, _update_sample_and
    lwz r12, -0x7E18(rtoc)
    or r11, r12, r11
    stw r11, -0x7E18(rtoc)

  _update_sample_and:
  lwz r12, -0x7E90(rtoc)
  lwz r11, -0x7E18(rtoc)
  and r12, r12, r11
  stw r12, -0x7D88(rtoc)

  _activate_printers:
  li r0, 1
  stw r0, -0x7F94(rtoc)
  stw r0, -0x7E8C(rtoc)
  stw r0, -0x7E14(rtoc)
  stw r0, -0x7D84(rtoc)
  b _return

  _get_mem:
  blrl
  .long 0

_return:
lwz    r0, 0x001C (sp)# original instruction
.long 0




-==-
!
ASM - Example 5 - Player AObj Detector
Print joints in skeleton as array of true or false bits
True = joint is using AObj; else false
- Scroll skeleton array with dpad left and right
[Punkline]
1.02 --- 8006cb7c ----- 8001001c -> branch
# end of player think,
# r30 = player entity
# r31 = player data

.set bInstDLeft,  31
.set bInstDRight, 30
.set bAuto,       28
.set bHeldDLeft,  27
.set bHeldDRight, 26
# dpad bools


.set xLookup, 0
.set xStart,  2
.set xTime,   3
.set xTarget, 4
.set xLHeld,  6
.set xRHeld,  7
# global memory offsets

.set holdtime, 45  # number of frames to hold button before rapid input starts

.macro updateHoldTimer, bool, instbool, offset
li r12, holdtime
bf+ \bool, _update_holding\@
  lbz r12, \offset(r3)
  subic. r12, r12, 1
  bge- _update_holding\@
    li r12, 0
    cror \instbool, bAuto, bAuto
_update_holding\@:
stb r12, \offset(r3)
.endm

lbz r3, 0xC(r31)
cmpwi r3, 0
bne+ _return

  _player_1:
  lwz r30, 0x28(r30)   # r30 = saved JObj root
  bl _nibble_print_memory_JObj_flag_detector
  mflr r3

  lis r8, 0x8047
  ori r8, r8, 0x9D60
  lwz r8, 0x0(r8)
  rlwinm r8, r8, 0, 0xFF
  lbz r9, xTime(r3)
  cmpw r8, r9
  beq- _return
  stb r8, xTime(r3)
  # run only on 1st player 1

  lwz r4, 0x668(r31)   # instant buttons
  lwz r5, 0x660(r31)   # held buttons
  rlwimi r4, r5, 4, 0xF0
  mtcrf  0b00000011, r4
  # dpad bits are in cr now


  andi. r8, r8, 0x3
  cror  bAuto, eq, eq
  # if r8 is 0, then the autoinput timer is 0, making held==instant input

  _update_timers_and_bools:
  updateHoldTimer bHeldDRight, bInstDRight, xRHeld
  updateHoldTimer bHeldDLeft, bInstDLeft, xLHeld
  # bInst* bools now gate input logic

  _check_DRight_event:
  lbz r5, xTarget(r3)
  lbz r6, xStart(r3)
  bf+ bInstDRight, _check_DLeft_event
    cmpwi r6, 0xFF
    beq- _update_UI_vars
    addi r6, r6, 1
    b _update_UI_vars
    # if dRight and not at max, incr starting joint

  _check_DLeft_event:
  bf+ bInstDLeft, _update_UI_vars
    subi r6, r6, 1
    cmpwi r6, 1
    bgt+ _update_UI_vars
      li r6, 1
      b _update_UI_vars
      # if dLeft and not at min, decr starting joint



      _nibble_print_memory_JObj_flag_detector:
      # this is placed here to take advantage of preceeding branch instruction placement
      blrl
      .hword 0x7C    # offset to load from JObj
      .byte  0x1, 0  # starting joint
      .byte  0       # starting target bit
      .byte  0, 0, 0 # timers for holding dpad buttons



  _update_UI_vars:
  stb r5, xTarget(r3)
  stb r6, xStart(r3)
  # update values post-event

  _record_bits:
  mr r31, r3
  mr r3, r30
  lhz r4, xLookup(r31)
  # r3 = root JObj
  # r4 = lookup offset var
  # r5 = target bit
  # r6 = starting joint id

  li r7, 0
  bl <record_32_JObj_bools>
  # r4 = lookup offset var
  # r5 = target bit
  # r7 = returned recording

  stw r7, -0x7E90(rtoc)
  # store record in bottom-left printer value

  mr r3, r30
  lbz r6, xStart(r31)
  addi r6, r6, 32
  li r7, 0
  bl <record_32_JObj_bools>
  stw r7, -0x7D88(rtoc)
  # store record of next 32 joints in bottom right printer

  li  r0, 1
  stw r0, -0x7E8C(rtoc)
  stw r0, -0x7D84(rtoc)
  # enable printers

_return:
lwz    r0, 0x001C (sp)# original instruction
.long 0


<record_32_JObj_bools>
# r3 = This JObj
# r4 = lookup index offset
# r5 = target bit
# r6 = nth joint to start from (must be >= 1)
# r7 = current OR record
.set rJObj,   31
.set rLookup, 4
.set rTarget, 5
.set rCTD,    6
.set rOR,     7

.set rAddr,   12
.set rA,      11
.set rB,      10
.set rC,      9

.set xChild,   0x10 # points to next JObj (1st dimension)
.set xSibling, 0x08 # points to next JObj (2nd dimension)

.set bValid, 16

_recursion:
mflr  r0
stw   r0, 0x4(sp)
stwu  sp, -0x10(sp)
stw   r31, 0x8(sp)
mfcr  r0
stw   r0,  0xC(sp)
mr    rJObj, r3
# JObj and CR are saved

_check_for_end:
lwz    rA, xChild(rJObj)
cmpwi  cr2, rA, 0
lwz    rB, xSibling(rJObj)
cmpwi  cr3, rB,0
cror  bValid, 8+lt, 12+lt
bf-   bValid, _terminate

_count_down:
addic. rCTD, rCTD, -1
bgt+ _iter  # if positive, we're still counting down
  blt+ _recording # else, we're counting down to -32 to finish our recording in rOR
  # if not skipping or recording, then initialize recording

    _initiate_recording:
    li rOR, 0
    # recorded bits start to go in rOR only after rCTD passes 0

  _recording:
  addic. rA, rCTD, 32
  lwzx   rB, rJObj, rLookup
  rlwnm  rB, rB, rTarget, 0, 0
  # rB = target bit, shifted all the way to bit 0

  rlwnm  rB, rB, rA, -1
  # rB = target bit, aligned to next record index in array of 32

  or     rOR, rOR, rB
  # rOR |= aligned target bit

_iter:
lwz r3, xChild(rJObj)
bltl+ cr2, _recursion
# if valid, call self with xChild as next JObj

lwz r3, xSibling(rJObj)
bltl+ cr3, _recursion
# if Valid, call self with xSibling as next JObj

_terminate:
# this will not be executed until the execution stack has met conditions to resolve the parse
# all actions are performed on call, so this merely collapses the frames created in recursion
lwz  r0, 0xC(sp)
mtcr r0
lwz  r31, 0x8(sp)
addi sp, sp, 0x10
lwz  r0, 0x4(sp)
mtlr r0
blr



-==-
!
ASM - Example 6 - Player AObj Detector
Print joints in skeleton as array of true or false bits
True = joint is using selected bit; else false
- Scroll skeleton array with dpad left and right
- select a bit to sample with dpad down
1.02 --- 8006cb7c ----- 8001001c -> branch
# end of player think,
# r30 = player entity
# r31 = player data

.set bInstDLeft,  31
.set bInstDRight, 30
.set bInstDDown,  29
.set bAuto,       28
.set bHeldDLeft,  27
.set bHeldDRight, 26
.set bHeldDDown,  25
# dpad bools


.set xLookup, 0
.set xStart,  2
.set xTime,   3
.set xTarget, 4
.set xDHeld,  5
.set xLHeld,  6
.set xRHeld,  7
# global memory offsets

.set holdtime, 45  # number of frames to hold button before rapid input starts

.macro updateHoldTimer, bool, instbool, offset
li r12, holdtime
bf+ \bool, _update_holding\@
  lbz r12, \offset(r3)
  subic. r12, r12, 1
  bge- _update_holding\@
    li r12, 0
    cror \instbool, bAuto, bAuto
_update_holding\@:
stb r12, \offset(r3)
.endm

lbz r3, 0xC(r31)
cmpwi r3, 0
bne+ _return

  _player_1:
  lwz r30, 0x28(r30)   # r30 = saved JObj root
  bl _nibble_print_memory_JObj_flag_detector
  mflr r3

  lis r8, 0x8047
  ori r8, r8, 0x9D60
  lwz r8, 0x0(r8)
  rlwinm r8, r8, 0, 0xFF
  lbz r9, xTime(r3)
  cmpw r8, r9
  beq- _return
  stb r8, xTime(r3)
  # run only on 1st player 1

  lwz r4, 0x668(r31)   # instant buttons
  lwz r5, 0x660(r31)   # held buttons
  rlwimi r4, r5, 4, 0xF0
  mtcrf  0b00000011, r4
  # dpad bits are in cr now


  andi. r8, r8, 0x3
  cror  bAuto, eq, eq
  # if r8 is 0, then the autoinput timer is 0, making held==instant input

  _update_timers_and_bools:
  updateHoldTimer bHeldDRight, bInstDRight, xRHeld
  updateHoldTimer bHeldDLeft, bInstDLeft, xLHeld
  updateHoldTimer bHeldDDown, bInstDDown, xDHeld
  # bInst* bools now gate input logic

  _check_DRight_event:
  lbz r5, xTarget(r3)
  lbz r6, xStart(r3)
  bf+ bInstDRight, _check_DLeft_event
    cmpwi r6, 0xFF
    beq- _update_UI_vars
    addi r6, r6, 1
    b _update_UI_vars
    # if dRight and not at max, incr starting joint

  _check_DLeft_event:
  bf+ bInstDLeft, _check_DDown_event
    subi r6, r6, 1
    cmpwi r6, 1
    bgt+ _update_UI_vars
      li r6, 1
      b _update_UI_vars
      # if dLeft and not at min, decr starting joint



      _nibble_print_memory_JObj_flag_detector:
      # this is placed here to take advantage of preceeding branch instruction placement
      blrl
      .hword 0x14    # offset to load from JObj
      .byte  0x1, 0  # starting joint
      .byte  0       # starting target bit
      .byte  0, 0, 0 # timers for holding dpad buttons



  _check_DDown_event:
  bf+ bInstDDown, _update_UI_vars
    addi r5, r5, 1
    rlwinm r5, r5, 0, 0x1F
    # if dDown, incr target bit and mask it to 5 bits
    # (creates a selector variable that cycles through JObj flag types)

  _update_UI_vars:
  stb r5, xTarget(r3)
  stb r6, xStart(r3)
  # update values post-event

  _record_bits:
  mr r31, r3
  mr r3, r30
  lhz r4, xLookup(r31)
  # r3 = root JObj
  # r4 = lookup offset var
  # r5 = target bit
  # r6 = starting joint id
  li r7, 0
  bl <record_32_JObj_bools>
  # r4 = lookup offset var
  # r5 = target bit
  # r7 = returned recording

  stw r7, -0x7E90(rtoc)
  # store record in bottom-left printer value

  mr r3, r30
  lbz r6, xStart(r31)
  addi r6, r6, 32
  li r7, 0
  bl <record_32_JObj_bools>
  stw r7, -0x7D88(rtoc)
  # store record of next 32 joints in bottom right printer

  lis r3, 0x8000
  srw r3, r3, r5
  stw r3, -0x7F98(rtoc)
  # display the currently targeted bit in top-left printer

  mr r3, r30
  li r4, 0
  bl <record_composite_JObj_bools>
  stw r4, -0x7E18(rtoc)
  # store composite bits for entire skeleton in top-right printer


  li  r0, 1
  stw r0, -0x7E8C(rtoc)
  stw r0, -0x7F94(rtoc)
  stw r0, -0x7D84(rtoc)
  stw r0, -0x7E14(rtoc)
  # enable printers

_return:
lwz    r0, 0x001C (sp)# original instruction
.long 0



<record_composite_JObj_bools>
# r3 = This JObj
# r4 = current OR record

.set xChild,   0x10 # points to next JObj (1st dimension)
.set xSibling, 0x08 # points to next JObj (2nd dimension)

_recursion:
mflr r0
stw  r0, 0x4(sp)
stwu sp, -0x10(sp)
stw  r31, 0x8(sp)
mr. r31, r3
bge- _terminate

lwz r3, xChild(r31)
bl _recursion
# for each existing child

lwz r3, xSibling(r31)
bl _recursion
# and for each existin sibling

_record:
lwz r0, 0x14(r31)
or  r4, r4, r0
# OR in this joint's flags to record word

_terminate:
lwz  r31, 0x8(sp)
addi sp, sp, 0x10
lwz  r0, 0x4(sp)
mtlr r0
blr

<record_32_JObj_bools>
# r3 = This JObj
# r4 = lookup index offset
# r5 = target bit
# r6 = nth joint to start from (must be >= 1)
# r7 = current OR record
.set rJObj,   31
.set rLookup, 4
.set rTarget, 5
.set rCTD,    6
.set rOR,     7

.set rAddr,   12
.set rA,      11
.set rB,      10
.set rC,      9

.set xChild,   0x10 # points to next JObj (1st dimension)
.set xSibling, 0x08 # points to next JObj (2nd dimension)

.set bValid, 16

_recursion:
mflr  r0
stw   r0, 0x4(sp)
stwu  sp, -0x10(sp)
stw   r31, 0x8(sp)
mfcr  r0
stw   r0,  0xC(sp)
mr    rJObj, r3
# JObj and CR are saved

_check_for_end:
lwz    rA, xChild(rJObj)
cmpwi  cr2, rA, 0
lwz    rB, xSibling(rJObj)
cmpwi  cr3, rB,0
cror  bValid, 8+lt, 12+lt
bf-   bValid, _terminate

_count_down:
addic. rCTD, rCTD, -1
bgt+ _iter  # if positive, we're still counting down
  blt+ _recording # else, we're counting down to -32 to finish our recording in rOR
  # if not skipping or recording, then initialize recording

    _initiate_recording:
    li rOR, 0
    # recorded bits start to go in rOR only after rCTD passes 0

  _recording:
  addic. rA, rCTD, 32
  lwzx   rB, rJObj, rLookup
  rlwnm  rB, rB, rTarget, 0, 0
  # rB = target bit, shifted all the way to bit 0

  rlwnm  rB, rB, rA, -1
  # rB = target bit, aligned to next record index in array of 32

  or     rOR, rOR, rB
  # rOR |= aligned target bit

_iter:
lwz r3, xChild(rJObj)
bltl+ cr2, _recursion
# if valid, call self with xChild as next JObj

lwz r3, xSibling(rJObj)
bltl+ cr3, _recursion
# if Valid, call self with xSibling as next JObj

_terminate:
# this will not be executed until the execution stack has met conditions to resolve the parse
# all actions are performed on call, so this merely collapses the frames created in recursion
lwz  r0, 0xC(sp)
mtcr r0
lwz  r31, 0x8(sp)
addi sp, sp, 0x10
lwz  r0, 0x4(sp)
mtlr r0
blr

Install one of these example codes (and only one) alongside the base code to run the experiment. Below are some details about the core module and example codes.

In the future, I’ll be taking another look at this concept and making a proper library out of it. For now, I just wanted to share the idea.

---

The Nibble Shape:

I think hexadecimal is a great way to visualize bit fields in a compact way, but I think I take my familiarity of it for granted. In this code, I’ve tried instead to use the symmetry of squares to represent the same concepts I imagine while watching bools in RAM.

Each corner of a square can be thought of as a bit inside of a nibble. You may think of each of these squares like a checkbox that disappears when false:


The “+” numbers in each square indicate the encoded equivalent of each “checkbox” within a 2x2 grid. (I prefer to use these mask numbers when referencing bits because of the common confusion between big and little endian orders -- where the side representing “0” in a bit index is swapped.)

When it comes to observing an array of bits, I think that this pattern communicates the same information in a slightly more intuitive way than using alphanumeric characters.

---

The Nibble Color:

The color of each True bit depends on the composition of its 4-bit nibble. This makes it particularly easy to detect changes in a flag field using your peripheral vision.

Each nibble’s RGB color is generated by adding together 4 colors (one for each bit in a printed nibble: )
+8 = 66% red
+4 = 66% green
+2 = 66% blue
+1 = 33% white

The resulting added color creates the following color key:


False = black
True = not black

---

Example 1 - Player Data Flags


This code samples and displays the flags at offset 0x2210 for Player 1.
  • 0x2210 -- 32-bit array of bools (?)
  • 0x2214 -- unknown floating point -- possibly related to throws
  • 0x2218 -- 64-bit array of bools
These flags are commonly seen used by functions that deal with various player mechanics. Action state functions, playerThink callback events, subaction event operations, move logic callbacks, etc.

Despite being common, they are mostly undocumented. The video displays the hitlag-oriented flags, as well a handful of flags that are triggered on the “rebirth” recovery platform when spawning in.

There are a few bools recorded in the SSBM Data sheet. My only personal notes about them are from ages ago, in this post:

0x2210 (0x80) - Projectile spawn? Just found this (go figure.) it spawns a projectile associated with the current move, then immediately unflags. Interesting to note that it can be reflagged on the next frame. EDIT: From a little more testing, this appears to just effect some character projectiles. The ones I tested successfully were Mario(s)/Luigi, Link Bombs/boomerang, and Samus bomb/missile.

0x2218 (0x80) - IASA flag, should allow for jump cancels (note that the IASA event function already flags this thing)
0x2218 (0x40) - "followup" flag? allows moves like jabs to be interrupted with their next move

0x2219 (0x10) - Htbox flag? appears to flag during active hitboxes. the terminate_collisions event unflags it
0x2219 (0x04) - Character Freeze? Also seems to break the BO timer when hit out of freeze
0x2219 (0x02) - ... unfreeze hitlag? Seems to allow the hitlag freeze (below) to unfreeze. Also messes up BO timer when hit.
0x2219 (0x01) - Hitlag freeze flag? Appears to freeze the character after attacking. Also messes up BO timer when hit.
---

Example 2 - Action State Transition Flags


This code samples the flags used in argument r5 of all generic action state transitions.
  • Samples are taken from both Players 1 and 2 and printed in the top row.
  • Samples from the previous action state transition are displayed in the bottom row.
UnclePunch UnclePunch -- these are the flags I was discussing with you a couple of weeks ago. This and the next 2 codes use them to create visualizations of how the interface is used when calling various action state changes.

All new action states are assigned by the same generic function with an intricate argument interface that includes ints, bools, and floats.

r3 = Player Entity (AKA GObj)
r4 = Action State ID
r5 = Transition Flags
r6 = (unknown address -- possibly another GObj)
f1 = Starting Frame
f2 = Animation Speed
f3 = Blend Transition Frames

In the past, learning how to use this function’s extensive argument interface has granted Melee code writers many powers over actions and animations that were once not available -- but its power today seems to be taken for granted, with parts of it still unstudied by the community.

Argument r5 is particularly complex. It appears to be a string of at least 30 boolean values. These bools appear to be options that change the behavior of the parse; enabling and disabling certain routines in the function.

By visualizing them with the above code, it becomes apparent that the majority of these flags are used in state transitions that continue a B-special from the air into a grounded state. From this, we might infer that the other aspects of the action state change can be disabled in a controllable manner.

Perhaps even more interesting are the ways in which these flags are used outside of air-ground transitions.

---

Example 3 - Action State Transition Flags (Composite Record)


This version of the code simply ORs each action’s r5 argument to the displayed nibble printer value. The stored data persists throughout the game’s runtime, so the resulting mask will display a pattern resembling how the interface has been used since the game powered on.

Here’s a mask made from landing various moves -- including all specials -- from each playable character in training mode:


I poured over the action state change function for uses of the r5 argument, and found 31 uses of 30 bits in total.

Here’s a list of RAM addresses from within the function:

Code:
800693dc - addi r31, r5, 0
# save r5 arg as param r31

# ordered by bit usage:
80069cd8 - rlwinm. r0, r31, 0,  2,  2 (20000000)
800696b4 - rlwinm. r0, r31, 0,  3,  3 (10000000)
8006969c - rlwinm. r0, r31, 0,  4,  4 (08000000)
8006989c - rlwinm. r0, r31, 0,  5,  5 (04000000)
80069be4 - rlwinm. r0, r31, 0,  6,  6 (02000000)
80069a80 - rlwinm. r0, r31, 0,  7,  7 (01000000)
80069684 - rlwinm. r0, r31, 0,  8,  8 (00800000)
80069884 - rlwinm. r0, r31, 0,  9,  9 (00400000)
80069c4c - rlwinm. r0, r31, 0, 10, 10 (00200000)
80069a60 - rlwinm. r0, r31, 0, 11, 11 (00100000)
8006966c - rlwinm. r0, r31, 0, 12, 12 (00080000)
80069844 - rlwinm. r0, r31, 0, 13, 13 (00040000)
80069a34 - rlwinm. r0, r31, 0, 14, 14 (00020000)
80069604 - rlwinm. r0, r31, 0, 15, 15 (00010000)
80069a44 - rlwinm. r0, r31, 0, 16, 16 (00008000)
8006a098 - rlwinm. r0, r31, 0, 17, 17 (00004000)
800698f8 - rlwinm. r0, r31, 0, 18, 18 (00002000)
800698cc - rlwinm. r0, r31, 0, 19, 19 (00001000)
800695e4 - rlwinm. r0, r31, 0, 20, 20 (00000800)
80069568 - rlwinm. r0, r31, 0, 21, 21 (00000400)
80069a14 - rlwinm. r0, r31, 0, 22, 22 (00000200)
800694d4 - rlwinm. r0, r31, 0, 23, 23 (00000100)
8006954c - rlwinm. r0, r31, 0, 24, 24 (00000080)
80069b10 - rlwinm. r0, r31, 0, 25, 25 (00000040)
80069ffc - rlwinm. r0, r31, 0, 26, 26 (00000020)
8006a070 - rlwinm. r0, r31, 0, 26, 26 (00000020)
80069530 - rlwinm. r0, r31, 0, 27, 27 (00000010)
800694a4 - rlwinm. r0, r31, 0, 28, 28 (00000008)
800694e4 - rlwinm. r0, r31, 0, 29, 29 (00000004)
800698dc - rlwinm. r0, r31, 0, 30, 30 (00000002)
800698b4 - rlwinm. r0, r31, 0, 31, 31 (00000001)
If you find the branches that use these comparisons, you may use breakpoints to pause the emulation any time a particular bit is read as true or false.

---

According to the above list, the composite mask from the screenshot I took is still missing these 4 bits:
01000000 - bit 4
00800000 - bit 5
00200000 - bit 7
00100000 - bit 8

---

Example 4 - Action State Transition Flags (Filtered Samples)


This last variation of the code uses the D-Pad :GCDpad: to let player 1 sample their most recent action state r5 argument, and then compare it to a second sample.

Included in the codes are 2 versions of this comparison filter:
XOR - show the difference between samples A and B
AND - show the common bits between samples A and B

:GCDpad: Down - compile sample A
sample A is shown in the bottom-left printer
hold this button for 45 frames to clear the sample

:GCDpad: Right - compile sample B
sample B is shown in the top-right printer
hold this button for 45 frames to clear the sample

---

You can OR in multiple samples together by hitting the same dpad button during multiple states.
By holding the dPad buttons for 45 frames, you can clear samples A or B.

The top-left printer will show the currently available flags for sampling in A or B.
The bottom-right printer will filter the two samples by XORing or ANDing them together.

---

Example 5 - JObj Flag Detectors


This code creates a composite sample of each flagfield in offset 0x14 of the live JObjs comprising of (the first entity belonging to) player 1. The sample creates an image that displays all active flags in any bone in the entire skeleton at any given frame, making it possible to see all bones at a glance.

The dPad lets player 1 select a bit from the flagfield to observe in the lower 64 bits of the display; causing each bit to represent a bone in the skeleton, with its value representing whether the selected bit is true or false.

Use the top-right printer display to select a bit that at least 1 joint in the player 1 skeleton is using.

:GCDpad: Down - scroll through the 32 possible flags to select
:GCDpad: Left - scroll through the bone index (if skeleton has more than 64 bones)
:GCDpad: Right - scroll through the bone index (if skeleton has more than 64 bones)

The top-left printer will display the currently selected bit
The bottom 64 bits will print the selected bit’s value in each bone in the skeleton index

---

Example 6 - AObj Detectors


Uses the above concept to inquire about actively assigned AObjs within animated JObjs belonging to the player 1 skeleton.

This is almost an exact copy of the previous example -- except that it inquires about the sign bit of offset 0x7C instead of 0x14 of a target bit in each JObj of the player 1 skeleton.

This effectively gives a NULL or NOT NULL pointer bool for each bone’s AObj pointer. The True bits in the 64-bit display represent bones that are currently using a valid AObj pointer.

:GCDpad: Left - scroll through the bone index (if skeleton has more than 64 bones)
:GCDpad: Right - scroll through the bone index (if skeleton has more than 64 bones)

In the future, I’ll create a variation of this that simultaneously inquires about the player INTP_JObj skeleton AObjs -- but that’s probably enough example codes for now.
 
Last edited:
Top