Completed Isolated ECB Display -- Code Projection Example

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
356
#1
This highly experimental code is meant to demonstrate a hacky concept that I've been calling a "projection" mod. See the second part of this post for more details.

Code:Isolated ECB Display
Code:
-==-
Isolated ECB Display
Press DpadDown + A to toggle for player, or DpadLeft + R to toggle for everyone.

Code creates a reusable function for drawing individual player or item ECB quads.
Call <drawECB_fromEntity> to draw ECB for a given entity in r3.
[Punkline]
1.02 ----- 0x800808e4 --- 4182001c -> 60000000
1.00 ----- 0x80080614 --- 4182001c -> 60000000
1.01 ----- 0x80080724 --- 4182001c -> 60000000
PAL ----- 0x80080F5C --- 4182001c -> 60000000

1.02 ----- 0x80059190 --- 7f63db78 ->
b <projection_return>
1.00 ----- 0x8005906C --- 7f63db78 ->
b <projection_return>
1.01 ----- 0x8005917C --- 7f63db78 ->
b <projection_return>
PAL ----- 0x80059854 --- 7f63db78 ->
b <projection_return>

1.02 ----- 0x800808d4 --- 5400efff -> Branch
806D9368 2C030002 41800030 807F0660 70630104 809F0668 2C030104 4182001C 70840104 7C652378 2C050104 40A2000C 68000008 981F21FC 5400EFFF 00000000
1.00 ----- 0x80080604 --- 5400efff -> Branch
806D9368 2C030002 41800030 807F0660 70630104 809F0668 2C030104 4182001C 70840104 7C652378 2C050104 40A2000C 68000008 981F21FC 5400EFFF 00000000
1.01 ----- 0x800808d4 --- 5400efff -> Branch
806D9368 2C030002 41800030 807F0660 70630104 809F0668 2C030104 4182001C 70840104 7C652378 2C050104 40A2000C 68000008 981F21FC 5400EFFF 00000000
PAL ----- 0x800808d4 --- 5400efff -> Branch
806D9388 2C030002 41800030 807F0660 70630104 809F0668 2C030104 4182001C 70840104 7C652378 2C050104 40A2000C 68000008 981F21FC 5400EFFF 00000000

1.02 ----- 0x800808f0 --- 4bf93e81 -> Branch
2C1C0002 40A2000C 7F63DB78
bl <drawECB_fromEntity>
00000000
1.00 ----- 0x80080620 --- 4BF940D1 -> Branch
2C1C0002 40A2000C 7F63DB78
bl <drawECB_fromEntity>
00000000
1.01 ----- 0x80080730 --- 4BF94041 -> Branch
2C1C0002 40A2000C 7F63DB78
bl <drawECB_fromEntity>
00000000
PAL ----- 0x80080F68 --- 4BF93969 -> Branch
2C1C0002 40A2000C 7F63DB78
bl <drawECB_fromEntity>
00000000

1.02 ----- 0x80059140 --- 38600000 -> Branch
bl <drawECB_setup>
C3A286FC
b 0x80059194
60000000
1.00 ----- 0x8005901C --- 38600000 -> Branch
bl <drawECB_setup>
C3A286FC
b 0x80059070
60000000
1.01 ----- 0x8005912C --- 38600000 -> Branch
bl <drawECB_setup>
C3A286FC
b 0x80059180
60000000
PAL ----- 0x80059804 --- 38600000 -> Branch
bl <drawECB_setup>
C3A286F4
b 0x80059858
60000000

<drawECB_setup> 1.02
7C0802A6 90010004 9421C000 38600000
b 0x80059144
<projection_return> All
38214000 80010004 7C0803A6 4E800020


<drawECB_fromEntity> All
7C0802A6 90010004 9421FFF0 93E1000C A0830000 2C040004 8063002C 3BE306F0 41820008 3BE30378
bl <drawECB_setup>
7FE3FB78
bl <drawECB>
83E1000C 38210010 80010004 7C0803A6 4E800020

<drawECB> 1.02
b 0x80058b5c
<drawECB> 1.00
b 0x80058A38
<drawECB> 1.01
b 0x80058B48
<drawECB> PAL
b 0x80059220
Code:
$Isolated ECB Display [Punkline]
042EF654 C8228000
040808e4 60000000
C24DD960 00000005
7C0802A6 90010004
9421C000 38600000
3D608005 616B9144
7D6903A6 4E800420
60000000 00000000
C2059190 00000003
38214000 80010004
7C0803A6 4E800020
60000000 00000000
C2059140 00000005
3962DF80 7D6803A6
4E800021 C3A286FC
3D608005 616B9194
7D6903A6 4E800420
60000000 00000000
C20808D4 00000008
806D9368 2C030002
41800030 807F0660
70630104 809F0668
2C030104 4182001C
70840104 7C652378
2C050104 40A2000C
68000008 981F21FC
5400EFFF 00000000
C20808F0 0000000D
2C1C0002 40A20060
7F63DB78 7C0802A6
90010004 9421FFF0
93E1000C A0830000
2C040004 8063002C
3BE306F0 41820008
3BE30378 3962DF80
7D6803A6 4E800021
7FE3FB78 3D608005
616B8B5C 7D6803A6
4E800021 83E1000C
38210010 80010004
7C0803A6 00000000


In developer mode:
Toggle ECB quads individually by pressing :GCA: + :GCDpad: (down)

Toggle ECB quads for everyone by pressing :GCRT: + :GCDpad: (left)

Displayed quads will intersect with player model, but will be drawn on top of other overlays such as hurtboxes and hitboxes. Display modes that include the “invisible player” option will create a relatively unobscured visualization of the ECB quads:


---

Display of ECB quads replaces the flag logic for existing aerial trail visualization used in developer mode.

Because of this, the effect may be toggled by XORing the appropriate flag in player data word 0x21FC:


---

Included MCM standalone function allows for calls to <drawECB_fromEntity> with a player or item entity in r3:



Gecko codes may call this function by branching directly to rtoc - 0x2080



Custom implementations like these do not require use of developer mode.

---

Code:
-==-
!
ASM - Isolated ECB Display
Press DpadDown + A to toggle for player, or DpadLeft + R to toggle for everyone.

Code creates a reusable function for drawing individual player or item ECB quads.
Call <drawECB_fromEntity> to draw ECB for a given entity in r3.
[Punkline]
1.02 ----- 0x800808e4 --- 4182001c -> 60000000
# removes aerial check for aerial TopN trail

1.02 ----- 0x80059190 --- 7f63db78 ->
# return from projection scope
b <projection_return>


1.02 ----- 0x800808d4 --- 5400efff -> Branch
# adds button logic for DpadDown + A = toggle ECB
# r0 contains flags word, loaded from 0x21FC(r31)
lwz r3, -0x6C98(r13)
cmpwi r3, 2
blt- _default         # if not in some form of debug mode, don't allow for hotkey

lwz   r3, 0x660(r31)
andi. r3, r3, 0x104   # r3 = dPadDown + A bits for HELD buttons
lwz   r4, 0x668(r31)
cmpwi r3, 0x104
beq   _default        # if both buttons are held, then don't toggle display
andi. r4, r4, 0x104   # r4 = dPadDown + A bits for INSTANT buttons
or    r5, r3, r4      # r5 = r3 OR r4 bits
cmpwi r5, 0x104       # if both buttons have been pressed despite not both being held,
bne+ _default
xori  r0, r0, 0x08    # then toggle the display bit, and update the flag word
stb   r0, 0x21FC(r31)

_default:
rlwinm.    r0, r0, 29, 31, 31  # original instruction checks if flag is true/false
.long 0


1.02 ----- 0x80059140 --- 38600000 -> Branch
# ECB setup INJ1 -- projection start (external)
# - external code may be appended here
bl <drawECB_setup> # call our code handle drawECB_setup
_return_from_INJ2:
lfs f29, -0x7904(rtoc)
b 0x80059194
nop


1.02 ----- 0x800808f0 --- 4bf93e81 -> Branch
# overwrites the game's call to drawing function for aerial TopN trail
# r31 = player data
# r27 = player entity
# r28 = draw loop iteration
cmpwi r28, 2
bne+ _endDraw
mr r3, r27
bl <drawECB_fromEntity>
_endDraw:
.long 0


<drawECB_setup> 1.02
# setup GXVtxAttr for ECB draw format
mflr r0
stw  r0, 0x4(sp)
stwu sp, -0x4000(sp)
li  r3, 0            # original instruction for INJ1 hook
b 0x80059144

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


<drawECB_fromEntity> All
# draw individual player or item ECB
# r3 = entity
mflr r0
stw  r0, 0x4(sp)
stwu sp, -0x10(sp)
stw  r31, 0xC(sp)

lhz   r4, 0(r3)
cmpwi r4, 4           # class ID: 4 = player, else = item
lwz   r3, 0x2C(r3)
addi  r31, r3, 0x6F0  # start of player ECB struct
beq   _player_offset  # if ==, then class is player
_item_offset:         # else, assume class is item
addi  r31, r3, 0x378  # start of item ECB struct
_player_offset:

bl <drawECB_setup>    # else, set up GXVtxAttrFmt
mr r3, r31
bl <drawECB>          # draw ECB from given entity ECB struct

_return:
lwz r31, 0xC(sp)
addi sp, sp, 0x10
lwz r0, 0x4(sp)
mtlr r0
blr


<drawECB> 1.02
b 0x80058b5c

To better understand this, consider the following:

The vanilla ECB display function in developer mode is tied to the camera, and some of its special flags for representing camera display options.

One option gates the execution of a camera function each frame.

This function creates a loop for the purpose of iterating through all available entities with collision data.

This loop makes it impossible to call the function for the purpose of displaying ECB for individual entities. Recreating the effect for only a single player would require rewriting the same string of GFX function calls all over again, from scratch.
(and then porting all of it...)

---

Considering all of the above; the aim of this code here is to get around the described technicality by splitting up the vanilla function in a way that allows a part of it to be “projected” by itself, as opposed to being called with the whole function.

It may be helpful to think of it as a “reverse injection” mod.

Injection mods: insert custom code into a vanilla function.
Projection mods: insert vanilla code into custom functions.

By using 2 injection mods to define the beginning and end of a scope inside of the camera function exploited in this code; we can create and collapse a fake stack frame for the purpose of isolating the coveted ECB setup code from its surrounding loop instructions:


Using this format with an MCM standalone function allows us to create a recallable handle for the purpose of recycling the code multiple times.

---

It’s easier to see how this projection mod works when you look at all of the pieces together:


Any code formatted like this may potentially be safe to recall as a function in other contexts:


If you pluck all the instructions from each piece of the code and line it up as an order of operations, it becomes easier to see the internal/external relationship that is created:



The “external” region allows for code to be inserted before and after the projection, and only as a part of the vanilla routine.

The opening and closing injection hook instructions may be repaired in this region without having an effect on the projection routine when called. Additionally, extra code may be placed here to alter the arguments for the resulting projection function -- allowing for variables to replace implicit parameters, or hardcoded immediates.

The “internal” region allows for code to be inserted before and after the projection as well -- but in a way that is also included with calls to the resulting projection function.

---

Encapsulating code like this doesn’t take very many instructions, and potentially grants access to a comparatively large amount of otherwise unusable vanilla code. There is a lot of potential here for saving code space when the alternative is rewriting parts of a function that already exists in the game -- but is convoluted by unwanted or unrelated routines.

Any references to static addresses within the scope of a projection mod will not need to be ported between Melee versions; and any injections included in the scope of a projection will be included internally. This has some interesting potential to provide interactions modularly between separate codes.

---

While this projection format can be useful, it has some serious pitfalls that you should be aware of before trying to create your own. These codes are highly dependent on context, so you must carefully observe your target function as a whole -- not just your target code region -- for register usage and stack usage.

Because of the fact that a projection moves the stack frame, any stack-sensitive instructions in the projection scope will have to be considered with special care. If a routine in your projection reads something from the stack that is not written within the scope of your projection, then you must copy what it reads from the original frame into the fake stack frame. Alternatively, you may overwrite problematic instructions to use appropriately displaced stack offsets so that they reach back into the original frame.

This is not an issue if the initial write to the stack frame is also done within your projection. However, if this is the case, then you must then also check to make sure that no other point beyond the projection makes a reference to the same offset. If it does, then you must also account for repairing the missing stack elements, lest you accidentally cause the vanilla function to read in arbitrary data.

In the case of this example code, the stack is used to copy a small array, which is then passed as a pointer argument to another call within the projection.



Both the reading and writing of this allocation is done within the projection, and there are no other references to these stack offsets in the rest of the function -- so the stack sensitive instructions here are safe to include without modification.
 

DRGN

Technowizard
Moderator
Premium
Joined
Aug 20, 2005
Messages
2,036
Location
Sacramento, CA
#2
Nice! Some of the general concept is what I had envisioned for custom branch syntaxes, but there's also stuff here I hadn't thought of before.

Do you know the calculation for how ECB coordinates are generated? It's been a while since I've looked at them, but I wonder if it could be as simple as a percentage of the model's x and y deltas. But if so, what are the percentages?
 

Punkline

Dr. Frankenstack
Premium
Joined
May 15, 2015
Messages
356
#3
Nice! Some of the general concept is what I had envisioned for custom branch syntaxes, but there's also stuff here I hadn't thought of before.

Do you know the calculation for how ECB coordinates are generated? It's been a while since I've looked at them, but I wonder if it could be as simple as a percentage of the model's x and y deltas. But if so, what are the percentages?
I did some research last night and checked out the unfamiliar parts of the ECB data structure in the player data tables.

A structure starting at player data offset 0x6F0 seems to store all of the ECB data for a player, and is identical to a structure included in item entities. I made gifs while exploring it a bit in Cheat Engine memory views.

---

From offset 0x6F0 in players, and 0x378 in items:


Offset 0x0: Points back to the owner entity. This is presumably so that any function receiving this structure as an argument may derive information about what class type it is, where its JObj skeleton root is, and stuff in its entity data allocation.


Offset 0x4 : an array of 4 vertices, each representing TopN for one of the quads. The cross-shaped base star is based on these points, and each quad vertex is translated from the base star.


Offset 0x34 : a set of flags, followed by what appear to be more floats. Unknown for the most part.


Offset 0x8C : a set of X and Y displacements that the draw function uses to translate TopN coords, creating vertices for the quads.



Offset 0x104 : a special flag word that enables/disables the update of ECB X/Y translations for quad verts. Disabling it stops the anchor joints from influencing the shape, causing the quad to stick to TopN like a point:



Offset 0x108 : an array of JObj pointers that seem to serve as anchor joints for the ECB shape. The first joint appears to be the TopN FT_JObj. The model I was testing with (Marth) used 6 joints following that, each coming from his player skeleton.

In this gif, you can see what happens when all pointers are set to equal TopN:



Following that are more floats. I’m not sure what all of them do, but changing them around seemed to change the shape of the quads as my character did backflips. Maybe they describe limits, or something.

---

There’s a useful flag in live JObj instances that can be used to cease transformations of its internal mtx. It has the effect of basically freezing a joint:


It’s useful because it can be poked from memory in order to give a pretty clear idea about what joint you’re looking at:

 
Last edited:
Top