Completed Primitive Drawing Module

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
379
#1
Update 04/28/2019:
PRIM LITE is a new, tiny version of the Primitive Drawing Module that packs all of its punch into just two user-level functions. In less than 0x100 bytes, this module provides a means for drawing geometry to the screen by exploiting routines that already exist in the game for drawing sword trails. The simplified module interface now utilizes mytoc to create static addresses for its functions, and may be accessed from gecko codes.

PRIM LITE can also optionally be installed as a gecko “master code” in place of a DOL mod. Note that the gecko code is more than twice as large as the DOL mod (0x260 bytes) and will not provide named functions for MCM -- but it should otherwise be functionally identical, and useful for interfacing with fragile builds.


PRIM LITE 1.1
uses mytoc block 327, and 328

Code:
-==-

PRIM LITE 1.1
Primitive Drawing Module: LITE
rtoc - 0x2194 = 804DD84C = <prim.new>
rtoc - 0x2198 = 804DD848 = <prim.close>
rtoc - 0x2180 = 804DD860 = sword trail GX options
See ( https://smashboards.com/threads/primitive-drawing-module.454232/ ) for more info.
[Punkline]
1.02 ----- 0x802EAA38 --- C822DE80 -> C82280A0
1.02 ----- 0x802E8D68 --- C862DE80 -> C86280A0
1.02 ----- 0x804DD860 --- 4330000080000000 -> 0010130300001455
1.02 ----- 0x800c2684 ----
38600001 38800004 38a00005 38c00005
->
57E3A7BE 57E4C77E 57E5E77E 57E6073E
1.02 ----- 0x800c26b0 ----
38600001 38800003 38A00000
->
57C3A7FE 57C4C77E 57C5AFFE
1.02 ----- 0x800c26c0 --- 38600000 -> 57C39FFE
1.02 ----- 0x800c272c --- 38600000 -> 57C317BE
<prim.setup> 1.02
7C0802A6 90010004 9421C000 BFC10010 7C7E1B78 7C9F2378 38600001
b 0x800c2678
<prim.setup2> 1.02
7C0802A6 90010004 9421C000
bl 0x8033C3C8
b 0x800c2d40
<projection_return> 1.02
38214000 80010004 7C0803A6 4E800020
1.02 ----- 0x800c2da0 --- 38b80001 -> b <projection_return>
1.02 ----- 0x800c2674 --- 38600001 -> b <prim.setup_projection>
<prim.setup_projection> 1.02
8062DE80 8082DE84
bl <prim.setup>
881C2101
b 0x800c2738
1.02 ----- 0x800c2734 --- 881c2101 -> b <prim.setup_projection_epilog>
<prim.setup_projection_epilog> 1.02
BBC10010
b <projection_return>
1.02 ----- 0x800c2d3c --- 4827968d -> b <prim.setup2_projection>
<prim.setup2_projection> 1.02
bl <prim.setup2>
38B80001
b 0x800c2da4
1.02 ----- 0x802E8B48 --- C822DE68 -> C82280A0
1.02 ----- 0x804DD848 --- 43300000 -> b <prim.close>
<prim.close> 1.02
3860FFFF
b 0x80361fc4
1.02 ----- 0x804DD84C --- 80000000 -> b <prim.new>
<prim.new> 1.02
7C0802A6 90010004 9421FFC0 BFA10010 7C7E1B78 7C9F2378 7C832378 7CA42B78
bl <prim.setup>
bl <prim.setup2>
57E3063E 2C030005 41A00024 2C030007 4181001C 57E385BE 38800005 4182000C
bl 0x8033D240
48000008
bl 0x8033d298
57E31E38 38630080 38800000 57C5043E
bl 0x8033D0DC
3C60CC01 38638000 BBA10010 38210040 80010004 7C0803A6 4E800020
#
Code:
-==-
!
ASM - PRIM LITE 1.1
Primitive Drawing Module: LITE
rtoc - 0x2194 = 804DD84C = <prim.new>
rtoc - 0x2198 = 804DD848 = <prim.close>
rtoc - 0x2180 = 804DD860 = sword trail GX options
See ( https://smashboards.com/threads/primitive-drawing-module.454232/ ) for more info.
[Punkline]
1.02 ----- 0x802EAA38 --- C822DE80 -> C82280A0
1.02 ----- 0x802E8D68 --- C862DE80 -> C86280A0
1.02 ----- 0x804DD860 --- 4330000080000000 -> 0010130300001455
# mytoc block 328, 804DD860, -0x2180(rtoc), -0x217C(rtoc)
# = default sword trail GX params

# 00101303  00001455 = default values:

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

# 03FF0000  00000000 = Line Width/Point Size
#   1/16 pixel increments; 0x2A8 maximum width
# -- does not apply to sword trails

# 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

1.02 ----- 0x800c2684 ----
38600001 38800004 38a00005 38c00005
->
57E3A7BE 57E4C77E 57E5E77E 57E6073E
1.02 ----- 0x800c26b0 ----
38600001 38800003 38A00000
->
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 with prim.new
# -- injections allow for 2 static functions to be written, for gecko code support

<prim.setup> 1.02
# 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.setup2> 1.02
mflr r0
stw  r0, 0x4(sp)
stwu sp, -0x4000(sp)
bl 0x8033C3C8    # GXClearVtxDesc -- original instruction
b 0x800c2d40

<projection_return> 1.02
addi  sp, sp, 0x4000
lwz  r0, 0x4(sp)
mtlr r0
blr

1.02 ----- 0x800c2da0 --- 38b80001 -> b <projection_return>
# projection end <prim.setup2>


1.02 ----- 0x800c2674 --- 38600001 -> b <prim.setup_projection>
<prim.setup_projection> 1.02
# -- 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 param is parsed by an rlwinm (see static overwrites)
# (line width and primite type are not parsed here)

lwz r3, -0x2180(rtoc)
lwz r4, -0x217C(rtoc)
# 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

1.02 ----- 0x800c2734 --- 881c2101 -> b <prim.setup_projection_epilog>
<prim.setup_projection_epilog> 1.02
# lbz r0, 0x2101 (r28)
lmw r30, 0x10(sp)
b <projection_return>


1.02 ----- 0x800c2d3c --- 4827968d -> b <prim.setup2_projection>
<prim.setup2_projection> 1.02
bl <prim.setup2>
addi r5, r24, 1
b 0x800c2da4


1.02 ----- 0x802E8B48 --- C822DE68 -> C82280A0
# Mytoc Block_327 -0x2198(rtoc)

1.02 ----- 0x804DD848 --- 43300000 -> b <prim.close>
<prim.close> 1.02
# rtoc - 0x2198 (804DD848)
li r3, -1
b 0x80361fc4

1.02 ----- 0x804DD84C --- 80000000 -> b <prim.new>
<prim.new> 1.02
# rtoc - 0x2194 (804DD84C)

# 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

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.setup2> # set up mtx

rlwinm r3, r31, 0, 0xFF  # r3 = primitive ID
cmpwi  r3, 5             # if it's larger than
blt+ _end_width_handle
  cmpwi  r3, 7
  bgt- _end_width_handle
    rlwinm r3, r31, 16, 0x03FF
    li     r4, 5
    beq- _point_handle

    _width_handle: # exclusively for lines and linestrips
    bl 0x8033D240
    b _end_width_handle

    _point_handle: # exclusively for points
    bl 0x8033d298

_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
rlwinm r5, r30, 0, 0xFFFF # use N number of vertices
bl 0x8033D0DC

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
$PRIM LITE Mastercode [Punkline]
C20C2684 00000003
57E3A7BE 57E4C77E
57E5E77E 57E6073E
60000000 00000000
040c2688 4800000C
040c26b0 57C3A7FE
040c26b4 57C4C77E
040c26b8 57C5AFFE
040c26c0 57C39FFE
040c272c 57C317BE
C20C268C 00000006
7C0802A6 90010004
9421C000 BFC10010
7C7E1B78 7C9F2378
3C00800C 38600001
60002678 7C0903A6
4E800420 00000000
C20C2690 00000006
7C0802A6 90010004
9421C000 3C008033
6000C3C8 7C0803A6
4E800021 3C00800C
60002D40 7C0903A6
4E800420 00000000
C20C2DA0 00000003
38214000 80010004
7C0803A6 4E800020
60000000 00000000
042EAA38 C82280A0
042E8D68 C86280A0
044DD860 00101303
044DD864 00001455
C20C2674 00000006
8062DE80 8082DE84
3C00800C 6000268C
7C0803A6 4E800021
3C00800C 60002738
7C0903A6 881C2101
4E800420 00000000
C20C2734 00000003
BBC10010 38214000
80010004 7C0803A6
4E800020 00000000
C20C2D3C 00000005
3C00800C 60002690
7C0803A6 4E800021
38B80001 3C00800C
60002DA4 7C0903A6
4E800420 00000000
042E8B48 C82280A0
C24DD848 00000003
3C008036 60031FC4
7C6903A6 3860FFFF
4E800420 00000000
C24DD84C 00000019
7C0802A6 90010004
9421FFC0 BFA10010
7C7E1B78 7C9F2378
7C832378 7CA42B78
3C00800C 6000268C
7C0803A6 4E800021
3C00800C 60002690
7C0803A6 4E800021
57E3063E 2C030005
41A0003C 2C030007
41810034 57E385BE
38800005 41820018
3C008033 6000D240
7C0803A6 4E800021
48000014 3C008033
6000D298 7C0803A6
4E800021 57E31E38
38630080 38800000
57C5043E 3C008033
6000D0DC 7C0803A6
4E800021 3C60CC01
38638000 BBA10010
38210040 80010004
7C0803A6 4E800020
60000000 00000000


---

EXAMPLES:

- Simple Stage Geometry 2.0
- Sword Trail Blend Modes


---

HOW TO USE:

There are two user-level static functions in PRIM LITE.
They may be accessed from either a standalone function handle, or a static address.

One is for starting a new primitive:
rtoc - 0x2194 = 804DD84C = <prim.new>

The other safely ends drawing routines:
rtoc - 0x2198 = 804DD848 = <prim.close>


From MCM, you may call these using the special branch syntax:
Code:
bl <prim.new>

From a gecko code (or any other format), you may call these using rtoc, or a literal address:
Code:
addi r0, rtoc, -0x2194
mtlr r0
blrl

---

HOW TO DRAW PRIMITIVES:

Primitives will use the currently active HSD_CObj (camera object) to orient their location on the screen. Note that the "current" camera is changed multiple times per frame, and is not necessarily a reference to the main scene camera.

You can inject code into gx_link callbacks to hitch a ride on a desired drawing function that uses a camera of your choice; or you may set the camera yourself using HSD_CObjSetCurrent (80368458) before drawing. You can also check the currently active camera with HSD_CObjGetCurrent (8036a288), if you need to back it up. Each "camera" is referenced by the 32-bit address of its CObj.


You can call <prim.new> to open the gx up to reading vertex information. The function returns hardware address 0xCC008000 in r3 -- which is the gx_fifo pipe used for reading in a stream of geometry data from the game program.

You must store vertices at offset 0x0 of this hardware address, and use the same offset for each piece of information. This causes each piece of data to enter the pipe.

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


When you have finished drawing your primitives, you must call <prim.close> before returning.

You may draw multiple primitives in a sequence with several <prim.new> calls before finally calling <prim.close>; however if you neglect to call it at least once at the end then the next time that the game tries to draw something, it may cause graphical errors and/or crashes.


---

<prim.new>
rtoc -0x2194
804DD84C

This function takes its given argument values and uses them to set up a primitive/drawing type. It then opens a gx stream that will expect to be fed a specified number of vertices through the GX pipe. The address of the GX pipe is then returned in r3.


The long-form argument format of <prim.new> uses r3 to pass a vert count, and r4, r5 like a string containing several small int fields for specifying draw options:

r3 = vert count
r4 = params (cull mode, line/point size, z-buffer options, primitive type)
r5 = params (blending options)



Sword trails and 1-pixel (native) lines use the following argument values:
r3 = n
r4 = 0x00101303
r5
= 0x00001455

The above values will create standard primitive geometry that should obey the z-buffer logic in a way similar to sword trails.
Each value can be tweaked using any of the following options:



-- r4 -- -- r5 --
C0000000 00000000 = Cull Mode

+8 = cull backface
+4 = cull frontface



03FF0000 00000000 = Line Width/Point Size
unit = 1/16 pixels; 0x2A8 maximum width



00002000 00000000 = Z Compare Timing
+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
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 key)

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


examples of blend equation -- https://i.imgur.com/Qx6cuXX.png
examples of blend logic -- https://i.imgur.com/KnXNYYz.png



---

<prim.close>
rtoc -0x2198
804DD848

This function takes no arguments, and must be called before exiting your drawing routine.

It does not need to be called after every single <prim.new> stream finishes, but it does need to be called at least once before returning to the game program.


---

2018 LEGACY MODULE:
(depricated concept, supports legacy examples)
From 04/15/2018:
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 current camera coordiantes:

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
 
Last edited:

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
379
#2
Edit 02/14/2019: The example(s) in this post are for the legacy module.


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.


(click for gif)​

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:


(click for gif)​

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.


(click for gif)​

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,341
#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
379
#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.

---

Edit 02/14/2019: The examples in this post are for the legacy module.


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
379
#6
Edit 02/14/2019: The examples in this post are for the legacy module.


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


(click for gif)​

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


(click for gif)​

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)


(click for gif)​

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)


(click for gif)​

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


(click for gif)​

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


(click for gif)​

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:

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
379
#7
I’ve updated the op to detail a new version of this module called PRIM LITE. It is a much smaller version of the original module that has been refactored to use static function addresses as part of its interface for better compatibility. There’s more info in the op this time about how to use the module.

I'm now referring to the old module as the LEGACY MODULE, which can still be found in the op. I went through the previous example posts and added a little note about it. (also fixed some broken gfycat embeds...)

The new module compromises none of the power of the original, but it loses all of the flowery convenience functions as a trade for minimizing the code size. The interface now relies on just 2 functions to create drawings, making the module a very light-weight package that can be easily included with other codes.

In addition, the simplified interface and static functions allow the module to be used in gecko codes.
The gecko code can be used as a prerequisite code for multiple other codes, so I've labeled it as a Mastercode.

UnclePunch UnclePunch T tauKhan


The code affords to do what it does in so few instructions because the functionality is being projected from sections of sword trail functions that already exist in the game:



---

In the future, I will likely be using this PRIM LITE module as a base for creating a richer version of the Primitive Drawing Module. I’m thinking it would be fun to design a higher-level system for stroking/filling geometry brushes, with streamlined blending options.

I also want to address the problem of camera objects by using a set of drawing events that set up various cameras at the beginning/end of the game's draw routine, in the scene function.
 
Last edited:

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
379
#8
I’ve updated the PRIM LITE module to put some global variables in rtoc that can be modified to remotely change the way vanilla sword trails are drawn using mytoc block 328.

Now any code can access -0x2180(rtoc) and -0x217C(rtoc) (located at 804DD860) to load/store these variables, and remotely influence the way regular sword trails are drawn without creating any conflicts with the module.


---

B Brandondorf9999 -- I’ll be using these new variables to create options for the new sword trail code I mentioned in this thread in a way that lets them be configured for any given character/costume combinations.

In the meantime, here are some examples of different blend mode options that you can test by changing the bytes in the rtoc overwrites at the top of the code, or through memory edits with dolphin.


This first grid uses each row to show 4 different base settings:
dst = default
dst = 0
src = 0
src = default

Each column then shows the 8 possible combinations that can be used for each base setting in the blend equation:




This second grid shows different types of settings for the logic blend type, rather than the blend equation.
It also includes blend types 0 (NONE) and 3 (SUBTRACT) since those don’t seem to be affected by any other parameters:




In all examples, the default trail colors CYAN and WHITE are used. Colors are transformed by the blend settings.

When drawing primitives with <prim.new> -- blend settings can be configured in the same way.
 
Last edited:
Joined
Nov 9, 2014
Messages
652
#9
ive been at this for a while and i cant get any results...

i feel like ive done everything but i cant get the linestrip to display. atm, my code:
-changes current CObj to the in-game CObj (the one develop mode uses to draw ECBs etc)
-inits by calling prim.new w/ parameters: 30 (vertices), 00101306 and 00001455
-sends line point data to the HW register (loops 30 times)
-calls prim.close
-restores original CObj

ive read this post a bunch and checked out your simple stage geometry example code but i guess im missing something. im injecting @ 8006b808, relevant code is below

Code:
#Init Loop
    li    REG_LoopCount,0
#Change current CObj
  lwz    REG_CObjBackup, -0x4044 (r13)
  load  r3,0x80452C68
  lwz r3,0x0(r3)
  lwz r3,0x28(r3)
  branchl r12,0x80368458
#Init GX Prim
  lwz    r5, 0x02D4 (REG_P2Data)
  lfs    f1, 0x0068 (r5)
  fctiwz  f1,f1
  stfd  f1,-0x10(sp)
  lwz r3,-0x0C(sp)
  load  r4,0x00101306
  load  r5,0x00001455
  branchl r12,prim.new
  mr  REG_GX,r3
ArmadaShineThink_RecoverStart_CollisionLoop:
#Store current position
    stfs    REG_CurrXPos,0x1C(REG_ECBStruct)
    stfs    REG_CurrYPos,0x20(REG_ECBStruct)
#Check if frame X or greater
  lwz    r5, 0x02D4 (REG_P2Data)
  lfs f1,0x70(r5)
  fctiwz  f1,f1
  stfd  f1,-0x10(sp)
  lwz r3,-0x0C(sp)
    cmpw    REG_LoopCount,r3
    blt    ArmadaShineThink_RecoverStart_CollisionLoop_SkipDecay
ArmadaShineThink_RecoverStart_CollisionLoop_Decay:
    lfs    f1,0x78(r5)
    fmuls    f1,f1,REG_XComp
    lfs    f2, 0x002C (REG_P2Data)
    fmuls    f1,f1,f2
    fsubs    REG_XPerFrame,REG_XPerFrame,f1
    lfs    f1,0x78(r5)
    fmuls    f1,f1,REG_YComp
    fsubs    REG_YPerFrame,REG_YPerFrame,f1
ArmadaShineThink_RecoverStart_CollisionLoop_SkipDecay:
#Store next position
    fadds    f1,REG_CurrXPos,REG_XPerFrame
    stfs    f1,0x4(REG_ECBStruct)
    fadds    f1,REG_CurrYPos,REG_YPerFrame
    stfs    f1,0x8(REG_ECBStruct)
    lfs    f1,0x24(REG_ECBStruct)
    stfs    f1,0xC(REG_ECBStruct)
    mr    r3,REG_ECBStruct
    mr    r4,REG_ECBBoneStruct
    branchl    r12,0x8004730c
    lfs    REG_CurrXPos,0x4(REG_ECBStruct)
    lfs    REG_CurrYPos,0x8(REG_ECBStruct)
#Draw this point
  lfs f1,0x84(REG_ECBStruct)
  fadds f1,REG_CurrXPos,f1
  lfs f2,0x88(REG_ECBStruct)
  fadds f2,REG_CurrYPos,f2
  li  r3,0
  stw r3,Stack_MiscSpace(sp)
  lfs f3,Stack_MiscSpace(sp)
  load  r4,0x4B75FFFF
  mr  r3,REG_GX
  stfs  f1,0x0(r3)
  stfs  f2,0x0(r3)
  stfs  f3,0x0(r3)
  stw   r4,0x0(r3)
#Inc Loop
    addi    REG_LoopCount,REG_LoopCount,1
  lwz    r5, 0x02D4 (REG_P2Data)
  lfs    f1, 0x0068 (r5)
  fctiwz  f1,f1
  stfd  f1,-0x10(sp)
  lwz r3,-0x0C(sp)
    cmpw    REG_LoopCount,r3
    blt    ArmadaShineThink_RecoverStart_CollisionLoop
#End Loop
  branchl r12,prim.close
  mr  r3,REG_CObjBackup
  branchl r12,0x80368458
 

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
379
#10
ive been at this for a while and i cant get any results...

i feel like ive done everything but i cant get the linestrip to display. atm, my code:
-changes current CObj to the in-game CObj (the one develop mode uses to draw ECBs etc)
-inits by calling prim.new w/ parameters: 30 (vertices), 00101306 and 00001455
-sends line point data to the HW register (loops 30 times)
-calls prim.close
-restores original CObj

ive read this post a bunch and checked out your simple stage geometry example code but i guess im missing something. im injecting @ 8006b808, relevant code is below

Code:
#Init Loop
    li    REG_LoopCount,0
#Change current CObj
  lwz    REG_CObjBackup, -0x4044 (r13)
  load  r3,0x80452C68
  lwz r3,0x0(r3)
  lwz r3,0x28(r3)
  branchl r12,0x80368458
#Init GX Prim
  lwz    r5, 0x02D4 (REG_P2Data)
  lfs    f1, 0x0068 (r5)
  fctiwz  f1,f1
  stfd  f1,-0x10(sp)
  lwz r3,-0x0C(sp)
  load  r4,0x00101306
  load  r5,0x00001455
  branchl r12,prim.new
  mr  REG_GX,r3
ArmadaShineThink_RecoverStart_CollisionLoop:
#Store current position
    stfs    REG_CurrXPos,0x1C(REG_ECBStruct)
    stfs    REG_CurrYPos,0x20(REG_ECBStruct)
#Check if frame X or greater
  lwz    r5, 0x02D4 (REG_P2Data)
  lfs f1,0x70(r5)
  fctiwz  f1,f1
  stfd  f1,-0x10(sp)
  lwz r3,-0x0C(sp)
    cmpw    REG_LoopCount,r3
    blt    ArmadaShineThink_RecoverStart_CollisionLoop_SkipDecay
ArmadaShineThink_RecoverStart_CollisionLoop_Decay:
    lfs    f1,0x78(r5)
    fmuls    f1,f1,REG_XComp
    lfs    f2, 0x002C (REG_P2Data)
    fmuls    f1,f1,f2
    fsubs    REG_XPerFrame,REG_XPerFrame,f1
    lfs    f1,0x78(r5)
    fmuls    f1,f1,REG_YComp
    fsubs    REG_YPerFrame,REG_YPerFrame,f1
ArmadaShineThink_RecoverStart_CollisionLoop_SkipDecay:
#Store next position
    fadds    f1,REG_CurrXPos,REG_XPerFrame
    stfs    f1,0x4(REG_ECBStruct)
    fadds    f1,REG_CurrYPos,REG_YPerFrame
    stfs    f1,0x8(REG_ECBStruct)
    lfs    f1,0x24(REG_ECBStruct)
    stfs    f1,0xC(REG_ECBStruct)
    mr    r3,REG_ECBStruct
    mr    r4,REG_ECBBoneStruct
    branchl    r12,0x8004730c
    lfs    REG_CurrXPos,0x4(REG_ECBStruct)
    lfs    REG_CurrYPos,0x8(REG_ECBStruct)
#Draw this point
  lfs f1,0x84(REG_ECBStruct)
  fadds f1,REG_CurrXPos,f1
  lfs f2,0x88(REG_ECBStruct)
  fadds f2,REG_CurrYPos,f2
  li  r3,0
  stw r3,Stack_MiscSpace(sp)
  lfs f3,Stack_MiscSpace(sp)
  load  r4,0x4B75FFFF
  mr  r3,REG_GX
  stfs  f1,0x0(r3)
  stfs  f2,0x0(r3)
  stfs  f3,0x0(r3)
  stw   r4,0x0(r3)
#Inc Loop
    addi    REG_LoopCount,REG_LoopCount,1
  lwz    r5, 0x02D4 (REG_P2Data)
  lfs    f1, 0x0068 (r5)
  fctiwz  f1,f1
  stfd  f1,-0x10(sp)
  lwz r3,-0x0C(sp)
    cmpw    REG_LoopCount,r3
    blt    ArmadaShineThink_RecoverStart_CollisionLoop
#End Loop
  branchl r12,prim.close
  mr  r3,REG_CObjBackup
  branchl r12,0x80368458

One thing I picked up on recently while studying the frame handler loop in the “Heart of Melee” is that the context in which all GObj GX callbacks are called is within the scope of some kind of GX setup. I have it commented here as "GObj GX drawing events":



I think that the functions surrounding that call make it so that you can only reliably draw to the GX while executing from within that context. I've had problems in the past trying to draw from other contexts.

You can of course create a GObj and give it a custom GX callback function to handle this if you like. I'm beginning to think that's actually the best option for making codes with the primitive drawing module -- but a simpler solution would be to just move your injection to a player-oriented GX function context.


GX callbacks are called within the scope of a special camera event launched by that $!_main_draw_loop function in the frame handler screenshot above. Each of these camera events is defined as a GX function for a special camera GObj.

These camera event functions are neat because they can define up to 32 drawing layers for its enclosed events -- allowing for multiple drawing passes.
The callback argument format passes r3 = GObj, r4 = drawing pass.

---


An example of an injection point I like to use to try drawing player-oriented data is 80081104, which takes place at the end of the normal player GX callback -- so it will run for each player and give you access to their GObjs. From this context, r28 = Player GObj, r29 = drawing pass. If you move your injection to this place and draw on 0-based pass 2, I think your code will work as intended. (let me know if it doesn't)

Note that you don’t need to change the CObj in this context, since it should already be set to the one used by the player through the camera event.
 
Last edited:
Joined
Nov 9, 2014
Messages
652
#13
okay ive got an interesting dilemma with this...

so performing this many collision checks (21/30) per frame is a *bit* taxing on the CPU, but nothing crazy.

however, when these checks are performed at the injection point you specified, it severely lags the game. heres a comparison between performing the checks in the PlayerThink_Interrupt (8006ad10) function, and the player GXLink (80081104)

https://gfycat.com/compassionateslushycowbird

not sure why it makes a difference...
 

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
379
#14
okay ive got an interesting dilemma with this...

so performing this many collision checks (21/30) per frame is a *bit* taxing on the CPU, but nothing crazy.

however, when these checks are performed at the injection point you specified, it severely lags the game. heres a comparison between performing the checks in the PlayerThink_Interrupt (8006ad10) function, and the player GXLink (80081104)

https://gfycat.com/compassionateslushycowbird

not sure why it makes a difference...
Is it possible that you're running the code once for every draw pass?

Remember that the GX context you're using has a draw pass counter in r29 -- so if you check it for "2" and only run the code on that pass, it will run on "top". If you don't check it at all though, it will run once for every pass.
 

SinsOfApathy

Smash Journeyman
Joined
Feb 24, 2015
Messages
461
NNID
Psion312
#15
okay ive got an interesting dilemma with this...

so performing this many collision checks (21/30) per frame is a *bit* taxing on the CPU, but nothing crazy.

however, when these checks are performed at the injection point you specified, it severely lags the game. heres a comparison between performing the checks in the PlayerThink_Interrupt (8006ad10) function, and the player GXLink (80081104)

https://gfycat.com/compassionateslushycowbird

not sure why it makes a difference...
In addition to what Punkline said about checking the render pass, I'd validate the number of render events happening per frame on the GXLink proc you're attaching to - Potentially it's getting looped over more than once per frame?

Is it possible that you're running the code once for every draw pass?

Remember that the GX context you're using has a draw pass counter in r29 -- so if you check it for "2" and only run the code on that pass, it will run on "top". If you don't check it at all though, it will run once for every pass.
2 is bottom half, AFAIK. The switch in 80375F74 makes that apparent because top sets GX_SetCopyClamp(GX_CLAMP_TOP).

HSD_RP_SCREEN = 0
HSD_RP_TOPHALF = 1
HSD_RP_BOTTOMHALF = 2
HSD_RP_OFFSCREEN = 3
 

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
379
#16
2 is bottom half, AFAIK. The switch in 80375F74 makes that apparent because top sets GX_SetCopyClamp(GX_CLAMP_TOP).
for 80081104? That’s the return address of a call to the function used to draw player hitboxes, sword trails, and other things for the player:

https://i.imgur.com/WoE0Pzi.png


The value in r29 of this context is passed to all drawing functions for the player, and is checked before the drawing routine continues to make sure it’s on the right pass.

800c2600 is a good example of this. It’s the function I used to capture the GX calls that set up sword trails for the primitive drawing module code. The argument r4 is the same value in r29 of the injection point unclepunch is using, and you can see it there being checked for “2”, and skipping the function if it’s not equal. If you put a breakpoint on it, you can see that it runs 3 times per character frame, and the argument being checked increments from 0...1...2 like a drawing pass counter.

Hitboxes and sword trails are drawn on pass 2 so that they appear on "top of the player". (I think that was the confusion)
If they were drawn on pass 0, they appear behind the player.
 
Last edited:

SinsOfApathy

Smash Journeyman
Joined
Feb 24, 2015
Messages
461
NNID
Psion312
#17
for 80081104? That’s the return address of a call to the function used to draw player hitboxes, sword trails, and other things for the player:

https://i.imgur.com/WoE0Pzi.png


The value in r29 of this context is passed to all drawing functions for the player, and is checked before the drawing routine continues to make sure it’s on the right pass.

800c2600 is a good example of this. It’s the function I used to capture the GX calls that set up sword trails for the primitive drawing module code. The argument r4 is the same value in r29 of the injection point unclepunch is using, and you can see it there being checked for “2”, and skipping the function if it’s not equal. If you put a breakpoint on it, you can see that it runs 3 times per character frame, and the argument being checked increments from 0...1...2 like a drawing pass counter.

Hitboxes and sword trails are drawn on pass 2 so that they appear on "top of the player". (I think that was the confusion)
If they were drawn on pass 0, they appear behind the player.
HAL copied what the original Dolphin SDK does for the CLAMP ordering, which is:
#define GX_CLAMP_NONE 0
#define GX_CLAMP_TOP 1
#define GX_CLAMP_BOTTOM 2
801A4D34 calls HSD_StartRender(HSD_RP_SCREEN), which is visible in your screenshot from before on the line before call into the GObj_GXLink callback loop and sets the current render pass. HAL does some conversions from HSD indexes to Dolphin SDK types, but this isn't one of those situations that exists in the library. So, if there's not a similar call in those functions, it likely isn't actually renderpass.

It sounds like it's mostly terminology overlap.
 
Joined
Nov 9, 2014
Messages
652
#18
Is it possible that you're running the code once for every draw pass?

Remember that the GX context you're using has a draw pass counter in r29 -- so if you check it for "2" and only run the code on that pass, it will run on "top". If you don't check it at all though, it will run once for every pass.
that was exactly it, thanks again punkline!

something i noticed was that using this prim code seems to change the width of the ledgegrab box. not sure if its related to my code or the prim code in general though.

https://gfycat.com/disfiguredtimelyamethystsunbird
 
Last edited:
Joined
Nov 9, 2014
Messages
652
#19
have you tested this prim code on console Punkline Punkline ?

it seems to work on dolphin but using it on console seems to make it crash hard after a using it for an extended amount of time. doesnt happen immediately for some reason. i cant even get a stack trace...

different stack trace each crash too, always random data

EDIT*
yeah i've been letting it run on console minus the calls to prim.new/close and the writes to the GX addr and it hasnt crashed. could this be some interrupt-related behavior?

EDIT2*
it seems it was interrupt related,,, just wrapped my collision/draw loop with a call to disable and restore interrupts and it seems to have fixed it.

IMG_20190812_223817.jpg
 
Last edited:

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
379
#20
UnclePunch UnclePunch
Concerning the line-width glitch -- thanks. I think that's indicative of a glitch, where the line width adjustment in the code doesn't reset the parameter (like a sort of global value).

Specifically, I think it's happening because the context I gave you is very close to the function that draws those lines that are being changed. (the function that that context returns to ends up running the ECB drawing/camera shapes at the end of drawing the last player debug overlays -- iirc. I think they're the only process in-between that draws lines.)

I think it's inheriting the settings you might be setting -- which shouldn't be a part of the code. I'll look into this and see if I can reinforce opening and closing functions to handle this by saving the parameter somehow.

---


Concerning the crash (on console only) -- are you doing anything funky with your stack frame? It looks, sounds, and smells like an interrupt problem.

I haven't had a chance to set up my console for some time; so could you do me a favor and help me troubleshoot by trying to encapsulate your code somehow in this little supervisor-level sandwich, and try it again?

Code:
mfmsr  r0
rlwinm r0, r0, 0, 0xFFFF7FFF
mtmsr  r0
# disable interrupts

# <-- code goes here

mfmsr r0
ori   r0, r0, 0x8000
mtmsr r0
# enable interrupts
This will disable interrupts for the duration of whatever you execute in the middle. If this ends up "fixing" the problem, then it sounds like maybe there's something invoking an interrupt in a way that has trouble returning back to the context it interrupted -- which in my experience usually has to do with abnormalities in the stack frame. I recall running into a few console-only issues that involved interrupts because of things like this. T tauKhan

It could also be PRIM, because it uses injections to capture part of a function with a fake stack frame. I think I handled the activation record in a way that should be safe, but it could very well be the problem.
 
Joined
Nov 9, 2014
Messages
652
#21
UnclePunch UnclePunch
Concerning the line-width glitch -- thanks. I think that's indicative of a glitch, where the line width adjustment in the code doesn't reset the parameter (like a sort of global value).

Specifically, I think it's happening because the context I gave you is very close to the function that draws those lines that are being changed. (the function that that context returns to ends up running the ECB drawing/camera shapes at the end of drawing the last player debug overlays -- iirc. I think they're the only process in-between that draws lines.)

I think it's inheriting the settings you might be setting -- which shouldn't be a part of the code. I'll look into this and see if I can reinforce opening and closing functions to handle this by saving the parameter somehow.

---


Concerning the crash (on console only) -- are you doing anything funky with your stack frame? It looks, sounds, and smells like an interrupt problem.

I haven't had a chance to set up my console for some time; so could you do me a favor and help me troubleshoot by trying to encapsulate your code somehow in this little supervisor-level sandwich, and try it again?

Code:
mfmsr  r0
rlwinm r0, r0, 0, 0xFFFF7FFF
mtmsr  r0
# disable interrupts

# <-- code goes here

mfmsr r0
ori   r0, r0, 0x8000
mtmsr r0
# enable interrupts
This will disable interrupts for the duration of whatever you execute in the middle. If this ends up "fixing" the problem, then it sounds like maybe there's something invoking an interrupt in a way that has trouble returning back to the context it interrupted -- which in my experience usually has to do with abnormalities in the stack frame. I recall running into a few console-only issues that involved interrupts because of things like this. T tauKhan

It could also be PRIM, because it uses injections to capture part of a function with a fake stack frame. I think I handled the activation record in a way that should be safe, but it could very well be the problem.
yeah i edited my post a few mins before you sent this and disabling interrupts fixed it
 

tauKhan

Smash Lord
Joined
Feb 9, 2014
Messages
1,341
#22
I think the crashes may very well be due to the wrong order with the restoration of stack frame, loading link from volatile space 0x4(sp).

Code:
<projection_return> 1.02
addi  sp, sp, 0x4000
lwz  r0, 0x4(sp)
mtlr r0
blr
 
Joined
Nov 9, 2014
Messages
652
#23
I think the crashes may very well be due to the wrong order with the restoration of stack frame, loading link from volatile space 0x4(sp).

Code:
<projection_return> 1.02
addi  sp, sp, 0x4000
lwz  r0, 0x4(sp)
mtlr r0
blr
yup, just adjusted all the stack cleanups to load the return address before incrementing the stack pointer and it doesnt crash anymore w/ interrupts enabled.
 
Last edited:

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
379
#24
yup, just adjusted all the stack cleanups to load the return address before incrementing the stack pointer and it doesnt crash anymore w/ interrupts enabled.
I think the crashes may very well be due to the wrong order with the restoration of stack frame, loading link from volatile space 0x4(sp).

Code:
<projection_return> 1.02
addi  sp, sp, 0x4000
lwz  r0, 0x4(sp)
mtlr r0
blr
Thanks fellas. Sorry for the delayed response, just been a little busy.

Tomorrow-ish, I'll have a new version of the code available that fixes this problem. I think I've figured out a way to cache the point/line width values in a way that should also solve the context issue you were having before, UnclePunch UnclePunch .

Additionally, I've been working out a convenient way to instantiate GObjs from a data format that can be used like a sort of header for a code, exploiting the same lr mechanic in blrl data tables. I'll probably have more info on that soon, but it will definitely help with making it easier to draw via code.
 

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
379
#26
hey punkline, do you know if i can get the primitives to render over effect textures? this hit texture is getting in the way of displaying my line =(


https://gfycat.com/incrediblecandidbird
Try setting the Z Buffer compare argument to FALSE, and the Z Buffer update argument to TRUE, if you haven't already.
That should cause things to be drawn on top of other things regardless of the state of the z buffer at the time of drawing.

If that doesn't work, then you may need to use a GObj gx callback with a higher gx_link (or a higher gx_priority for like-gx_links) than the ones making those effects -- because it would mean that they're simply being drawn after your line. (The Z buffer update set to TRUE may or may not help with this.)
 
Last edited:
Joined
Nov 9, 2014
Messages
652
#27
Try setting the Z Buffer compare argument to FALSE, and the Z Buffer update argument to TRUE, if you haven't already.
That should cause things to be drawn on top of other things regardless of the state of the z buffer at the time of drawing.

If that doesn't work, then you may need to use a GObj gx callback with a higher gx_link (or a higher gx_priority for like-gx_links) than the ones making those effects -- because it would mean that they're simply being drawn after your line. (The Z buffer update set to TRUE may or may not help with this.)
yeah looks like its drawn after because that didnt help. maybe ill look into changing the effect gxlink's priority
 

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
379
#28
yeah looks like its drawn after because that didnt help. maybe ill look into changing the effect gxlink's priority
iirc, the gx priority only affects what place in the list a GObj is added through the generic GX callback assigner function. So if you want to use the priority, you’ll have to do it from where it gets generated -- else you’ll need to edit the gx_link pointers to modify its order.

I’ll put up a WIP of my GObjDesc idea soon, which might make it easier to avoid these kinds of problems later on.


Tomorrow-ish, I'll have a new version of the code available that fixes this problem. I think I've figured out a way to cache the point/line width values in a way that should also solve the context issue you were having before
I’ve had a few failed concepts teach me a couple of things about the GX over the last couple of days, so the update has been delayed a little bit. I noticed that in addition to the line width -- other settings would end up corrupting a local gx context. I’m working out a modification to prim.close that should basically cache all of the settings prim.new modifies on its first call from the last prim.close. It may still cause trouble if you interrupt some GX routine with a different vertex format; but should otherwise preserve the settings defined in r3,r4,r5 without issues. Hopefully.

---


UnclePunch UnclePunch T tauKhan -- I want to share a WIP of some macros I’ve been working on that you might be interested in. If you have any interest in using this mod, then .including this can help abstract away some arguments that you don’t need to define differently from defaults through a namespace called “prim.*
Code:
.ifndef prim.defaults
# if this has been included already, skip initialization
# else, begin defining namespace attributes for prim.----

# prim.s:

  # prim.ptyp :  primative type
  prim.ptyp.q  = 0  # q  quads          (0, 1, 2, 3, 0, ...)
  prim.ptyp.t  = 2  # t  triangles      (0, 1, 2, 0 ...)
  prim.ptyp.ts = 3  # ts trianglestrip  (0, 1, 2, 1, 2, ...)
  prim.ptyp.tf = 4  # tf trianglefan    (0, 1, 1, 1, ...)
  prim.ptyp.l  = 5  # l  lines          (0, 1, 0, 1, ...)
  prim.ptyp.ls = 6  # ls linestrip      (0, 1, 1, 1, ...)
  prim.ptyp.p  = 7  # p  points         (0, 0, 0, 0, ...)

  # prim.zcmp :  z buffer compare flag
  prim.zcmp.f = 0   # f FALSE  (drawings are not occluded by geometry)
  prim.zcmp.t = 1   # t TRUE   (drawings will be occluded by geometry)

  # prim.bsrc :  blend source
  prim.bsrc.sc  = 2  # sc  source color
  prim.bsrc.isc = 3  # isc inverted source color
  prim.bsrc.sa  = 4  # sa  source alpha
  prim.bsrc.isa = 5  # isa inverted source alpha
  prim.bsrc.da  = 6  # da  destination alpha
  prim.bsrc.ida = 7  # ida inverted destination alpha

  # prim.bdst :  blend destination
  prim.bdst.sc  = 2  # sc  source color
  prim.bdst.isc = 3  # isc inverted source color
  prim.bdst.sa  = 4  # sa  source alpha
  prim.bdst.isa = 5  # isa inverted source alpha
  prim.bdst.da  = 6  # da  destination alpha
  prim.bdst.ida = 7  # ida inverted destination alpha

  # prim.cull :
  prim.cull.n = 0  # n NONE (no culling, both sides are drawn)
  prim.cull.f = 1  # f front (cull front-facing surfaces)
  prim.cull.b = 2  # b back (cull back-facing surfaces)
  prim.cull.a = 3  # a ALL (cull all geometry)

  # prim.zupd :
  prim.zupd.f = 0 # f FALSE (drawings do not update Z buffer)
  prim.zupd.t = 1 # t TRUE  (drawings update Z buffer)

  # prim.zlgc :
  prim.zlgc.n  = 0 # n  NONE
  prim.zlgc.lt = 1 # lt <
  prim.zlgc.eq = 2 # eq ==
  prim.zlgc.le = 3 # le <=
  prim.zlgc.gt = 4 # gt >
  prim.zlgc.ne = 5 # ne !=
  prim.zlgc.ge = 6 # ge >=
  prim.zlgc.a  = 7 # a  ALL

  # prim.btyp :
  prim.btyp.n = 0 # NONE
  prim.btyp.b = 1 # blend -- use blend equation
  prim.btyp.l = 2 # logic -- use bitwise operation
  prim.btyp.s = 3 # subtract -- subtract from existing pixel

  # prim.blgc :
  prim.blgc.clr     = 0  # dst = 0
  prim.blgc.and     = 1  # dst = src & dst
  prim.blgc.revand  = 2  # dst = src & ~dst
  prim.blgc.copy    = 3  # dst = src
  prim.blgc.invand  = 4  # dst = ~src & dst
  prim.blgc.noop    = 5  # dst = dst
  prim.blgc.xor     = 6  # dst = src ^ dst
  prim.blgc.or      = 7  # dst = src | dst
  prim.blgc.nor     = 8  # dst = ~(src | dst)
  prim.blgc.equiv   = 9  # dst = ~(src ^ dst)
  prim.blgc.inv     = 10 # dst = ~dst
  prim.blgc.revor   = 11 # dst = src | ~dst
  prim.blgc.invcopy = 12 # dst = ~src
  prim.blgc.invor   = 13 # dst = ~src | dst
  prim.blgc.nand    = 14 # dst = ~(src & dst)
  prim.blgc.set     = 15 # dst = 1

  # prim.cloc :
  prim.cloc.a = 0 # z buffer compare after texturing
  prim.cloc.b = 1 # z buffer compare before texturing

  .macro prim.defaults, vrts=0, ptyp=7, zcmp=1, bsrc=4, bdst=7, size=16, cull=0, zupd=0, zlgc=3, btyp=1, blgc=5, cloc=0, txcr=0
  # sets active argument values to defaults, with any modifications you include in the args
    prim.vrts = \vrts;  prim.ptyp = \ptyp;  prim.size = \size;  prim.cull = \cull
    prim.cloc = \cloc;  prim.zcmp = \zcmp;  prim.zupd = \zupd;  prim.zlgc = \zlgc
    prim.btyp = \btyp;  prim.bsrc = \bsrc;  prim.bdst = \bdst;  prim.blgc = \blgc
    prim.txcr = \txcr
    prim.r3l = \vrts&0xFFFF
    prim.r4h = ((\cull&3)<<14)|((\txcr&3)<<8)|(\size&0xFF)
    prim.r4l = ((\cloc&1)<<13)|((\zcmp&1)<<12)|((\zupd&1)<<11)|((\zlgc&7)<<8)|(\ptyp&0xFF)&0xFFFF
    prim.r5l = ((\btyp&3)<<12)|((\bsrc&7)<<8)|((\bsrc&7)<<4)|(\blgc&0xF)&0xFFFF
  .endm

  prim.defaults
  prim.mytoc.new = -0x2194
  prim.mytoc.close = -0x2198
  # setup

  .macro prim.set, vrts=prim.vrts, ptyp=prim.ptyp, zcmp=prim.zcmp, bsrc=prim.bsrc, bdst=prim.bdst, size=prim.size, cull=prim.cull, zupd=prim.zupd, zlgc=prim.zlgc, btyp=prim.btyp, blgc=prim.blgc, cloc=prim.cloc, txcr=prim.txcr
  # modify only the stated arguments; others will remain at their current value
    prim.vrts = \vrts;  prim.ptyp = \ptyp;  prim.size = \size;  prim.cull = \cull
    prim.cloc = \cloc;  prim.zcmp = \zcmp;  prim.zupd = \zupd;  prim.zlgc = \zlgc
    prim.btyp = \btyp;  prim.bsrc = \bsrc;  prim.bdst = \bdst;  prim.blgc = \blgc
    prim.txcr = \txcr
    prim.r3l = \vrts
    prim.r4h = ((\cull&3)<<14)|((\txcr&3)<<8)|(\size&0xFF)
    prim.r4l = ((\cloc&1)<<13)|((\zcmp&1)<<12)|((\zupd&1)<<11)|((\zlgc&7)<<8)|(\ptyp&0xFF)
    prim.r5l = ((\btyp&3)<<12)|((\bsrc&7)<<8)|((\bsrc&7)<<4)|(\blgc&0xF)
  .endm

  .macro prim.load, r3l=prim.r3l, r4h=prim.r4h, r4l=prim.r4l, r5l=prim.r5l
  # use active arguments to  oad r3, r4, r5 values in preparation for call
  # - macro will use as few lines as possible
  # - use -1 to tell it what has already been loaded, and it will append to/skip it

    .ifgt \r4h
    # if r4high is >= 0,
      .ifge \r4l; lis r4, \r4h
      # and r4low is pending,  THEN: load high before low
      .else; oris r4, r4, \r4h
      # ELSE: load high after low (implied that low has already been loaded)
      # - this case is for when r4low is built from a variable
      .endif
    .endif

    .ifeq \r4h
    # if r4high is 0,
      .ifeq \r4l; li r4, 0
      # and r4low is 0,  THEN: just set r4=0
      .endif
    .endif
    # if r4high is < 0,  THEN: compile nothing for r4 high

    .ifeq \r3l
      .error "prim.load needs a value for r3 (vertex count)"
      # warn about fatal arguments
    .endif
    .ifgt \r3l;  li r3, \r3l
    # ELSE: use vert count for r3low (no high is used)
    .endif

    .ifgt \r4l
    # if r4low is > 0,
      .ifle \r4h; li r4, \r4l
      # and r4h is <= 0,  THEN: load r4low by itself
      .else; ori r4, r4, \r4l
      # ELSE: load r4low after r4high
      .endif
    .endif

    .ifge \r5l; li r5, \r5l
    # if r5 exists, then load low (no high is used)
    .endif
  .endm

  .macro prim.new vrts=prim.vrts, ptyp=prim.ptyp, zcmp=prim.zcmp, bsrc=prim.bsrc, bdst=prim.bdst, size=prim.size, cull=prim.cull, zupd=prim.zupd, zlgc=prim.zlgc, btyp=prim.btyp, blgc=prim.blgc, cloc=prim.cloc, txcr=prim.txcr, args=0
  # uses currently set attributes to load arguments and then call
  # - to skip loading args, set args to a number other than 0
  # - uses as few instructions as possible, for macro compatability

    prim.set \vrts, \ptyp, \zcmp, \bsrc, \bdst, \size, \cull, \zupd, \zlgc, \btyp, \blgc, \cloc, \txcr
    prim.load
    addi r0, rtoc, prim.mytoc.new; mtlr r0; blrl
  .endm

  .macro prim.drawvert, rFifo=3, fX=1, fY=2, fZ=3, rC=4
  stfs \fX, 0(\rFifo); stfs \fY, 0(\rFifo); stfs \fZ, 0(\rFifo); stw \rC, 0(\rFifo)
  .endm

  .macro prim.close
  # - uses as few instructions as possible, for macro compatability
    addi r0, rtoc, prim.mytoc.close; mtlr r0; blrl
  .endm
.endif
It's still a WIP, but this should generate gecko-friendly function calls for macro symbols “prim.new” and “prim.close” in a way that lets you define zeroed arguments with symbol keywords. If you include this as part of your own little “macros.s” file or something, you can also change any of the names to whatever you like.


The macros in “prim.s” keep global symbol names with the “prim.*” namespace to define attributes of the currently set arguments -- so you can define them individually like “prim.zcmp = 0”. Then, the next prim.new, or prim.set will use that value by default if not defined in the arguments of the macro:



Each macro uses a local argument name that you can reference like a keyword. So, something like “prim.new 3, size=0x40” would generate the arguments for drawing 3 verts, any currently set non-default attributes, and a point/line size set to 0x40 -- and then call prim.new in long-form. If you don’t want to emit anything, you can use “prim.set” instead to set the symbol values for the use of “prim.new” or “prim.load”. (The latter will generate the arguments without calling the function.)

The .include also defines the names of constants in a namespace made after each attribute, so you can state things like:

prim.bsrc=prim.bsrc.sc

-- which sets the blend source to the index of the “source color” option for each new prim.new macro call that follows it.
In the context of a macro argument, you could also state:

ptyp=prim.ptyp.ls

--which sets the primitive type to "linestrip" :




I made an example using some of the macros in this file to recreate the bug that you guys found, using the same injection point:
Code:
-==-

ECB Bottom Test
[Punkline]
1.02 ----- 80081104 --- bb61008c -> Branch
.include "prim.s"
rPlayer = 28; rPass = 29
xECBTopN = 0x6F4; xECBBottom = 0x77C; xECBAnchor1 = 0x808; xECBAnchor2 = 0x80C;
bSecondPass = 31
thicc = 0x28

cmpwi rPass, 2
bne+ _return

lwz r27, 0x2C(r28)
lwz r0, 0xE0(r27)
cmpwi r0, 0
beq+ _return
# do not draw while on the ground

lwz r30, xECBAnchor1(r27)
lwz r31, xECBAnchor2(r27)
crclr bSecondPass
prim.new 3, ptyp=prim.ptyp.ls, size=thicc, zcmp=prim.zcmp.f, zupd=prim.zupd.f
# draw first pass with thick linestrip "prim.ptyp.ls"

_loop:
li  r4, -1 # white
lfs f4, xECBTopN+0(r27)
lfs f5, xECBTopN+4(r27)
lfs f3, xECBTopN+8(r27)
lfs f1, 0x50(r30)
lfs f2, 0x60(r30)
prim.drawvert
lfs f6, xECBBottom+0(r27)
lfs f7, xECBBottom+4(r27)
fadds f1, f6, f4
fadds f2, f7, f5
prim.drawvert
lfs f1, 0x50(r31)
lfs f2, 0x60(r31)
prim.drawvert
bt bSecondPass, _close
  crnot bSecondPass, bSecondPass
  prim.new 3, ptyp=prim.ptyp.p
  b _loop
  # draw second pass with thick points "prim.ptyp.p"

_close:
prim.close

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


Hopefully the update will fix this bug, and maybe even some other bugs that may be caused by the same circumstances.
 
Last edited:
Top