• Welcome to Smashboards, the world's largest Super Smash Brothers community! Over 250,000 Smash Bros. fans from around the world have come to discuss these great games in over 19 million posts!

    You are currently viewing our boards as a visitor. Click here to sign up right now and start on your path in the Smash community!

Melee dat format...

SinsOfApathy

Smash Journeyman
Joined
Feb 24, 2015
Messages
474
NNID
Psion312
Punkline, you really miss out by not being on the Discord. LuigiBlood discovered the K7 maps were DWARFv1 symbols, which means they kept variable names. Basically, everything is named with the actual name besides TExp at this point in FRAY (and JObj parent being named to prev, because I wanted to use a consistent #define).

The “Operation Type” is an 8-bit (FF) value that differentiates the imageDesc FObj from the TLUTDesc FObj. The IDs for each:
01 = Create ImageDesc changes at defined keyframes in the Data String
0A = Create TLUTDesc changes at defined keyframes in the Data String.

Anyway, if you can see the pattern in these strings, then maybe you'll start seeing them in other kinds of FObjs too.
It would be pretty cool if we started figuring out what the various FObj operation types did for each animated object type.
The Operation Type actually covers 7 different types with overlapping values. What you referred to is actually only 2 of the 10 Texture values.
They types themselves are Joint, Weight, Camera, Camera Interest, Light, Material, and Texture.

https://docs.google.com/spreadsheets/d/1xfK5hpj5oBP9rCwlT9PTkyNrq3sHPZIRbtzqTTPNP3Q/

It seems to re-state the last used frame index, and then follow up with a null byte like a terminator
The parsing itself loops until the address it's reading & 0x80 == 0. If you were to pass a parse with only a null terminator, it would return 1 as the number of packed values.

--

I've had such a hard time with TExp, because it's built around doing something that compilers would warn you about, which is that sometimes it's an integer and sometimes it's a pointer. It's probably the worst part of the HSD I've dealt with.
 
Last edited:

Punkline

Dr. Frankenstack
Joined
May 15, 2015
Messages
423
SinsOfApathy SinsOfApathy - Oh wow. I see. And here I was just getting my feet wet, lol. Thanks for pointing this out. I’d figured there were plenty more ops, but it’s a really pleasant surprise to have it all filled out like that.

I’ll try to memorize some of these FObj variable names. I was using terms in my notes that were familiar to me through the Graphical Quantization Registers in PowerPC, since they work in a very similar way. I was actually surprised that the FObj functions I peaked at were just doing normal casting and float division to apply the shift instead of just using GQRs. Maybe it’s too obscure.


So if I’m soaking this up correctly, in these opcode strings --



Above are data strings describing the top row of icons in each of the 11 paired icons on the SSS. There are 2 blank images used for special icon statuses at the beginning of the array; making 13 total images. The red string shows the ImageDesc FObj data, while the blue string shows the TLUTDesc FObj data. Notice that they are identical.

In the FObjDescs below the strings, I’ve highlighted the Operation Type, Quantized Data Type, and Dequantization Shift values in yellow. As you can see, operation type 01 is used to define an ImageDesc array FObj operation; while 0A is used to define a TLUTDesc array FObj operation.

The Quantized Data Type is set to 4 (80) for these FObjDescs, which means that each frame is defined as 1 unsigned byte. The Dequantization shift is set to 4 as well (04), which means that each unsigned byte will be the frame number left-shifted by <<4.


The first value in the string is a uleb128 value that defines 2 variables. The first is a 4-bit (...000F) value that defines some kind of status for the FObj parser, while the second (...FFF0) is an array size for the ImageDesc and TLUTDesc operation to increment to. Attempting to index more images/palettes than defined here will cause the illegal indices to be ignored.


Edit - rewrote for clarity:

So, the first value used in the strings above, "D1 01" becomes "D1" when parsed:
(...D101)
(...
7F00) = 51 = (lowest 7 bits)
(...8000) = 80 = (flag = TRUE, so get next byte)
(...007F) = 01 = (highest 7 bits)
(...0080) = 00 = (flag = FALSE, so end of value)

(01 <<7) | 51 = 00D1

With 8 bits of data, this just barely requires 2 bytes from the uleb128 format.
As the first value, this then becomes split into 2 variables:

(00D1)
(000F) = 1 = Status value (1)
(FFF0) = D = Array Size (13 images)



After the first value, any following values in the string define a keyframe with a quantized integer followed by another uleb128 value.
This value pair defines a frame index, and a frame length.

With type 4, we can expect the quantized integer to be a single unsigned byte.
This is formatted like so:
(00 ...0001)
(
FF ...0000) = 00 = Quantized Frame Index (0.0)
(00 ...FFFF) = 0001 = 01 = Frame length (1.0)



One quirk about these definitions is that the frame value used to create an index does not seem to be displaced by varying frame lengths. So I guess rather than a literal frame, you are defining an intersection in an abstract frame space. (this can also be offset by the FObjDesc starting frame value.)

So when it sets the length to 01, it means that 1.0 worth of frames are assigned to the next image in the array. This makes it so that we can access each frame in the constructed AObj by setting the frame to a whole number. With a Dequantization shift of 4, we have a 4-bit mantissa that we can use to assign frames in-between whole numbers.

At the end of the string, you can see it assigning the last index with a ridiculous frame length:
(C0 ...B40C)
(FF ...0000) = 00C0 = Quantized Frame Index (12.0) (dequant gives 4-bit mantissa)
(00 ...FFFF) = B40C = 0634 = Frame length (1588.0)


This makes it so that if an AObj is set to a frame beyond its last index, it will remain on the last image instead of rewinding.
-- the first uleb128 value is a “Keyframe Opcode”? And all the pairs following it are “Value”, “Wait” opcodes -- since it's type 1 (CONSTANT) and doesn't use slope values?


I’m really interested to see how those other types work. Thanks again.
 
Last edited:

SinsOfApathy

Smash Journeyman
Joined
Feb 24, 2015
Messages
474
NNID
Psion312
SinsOfApathy SinsOfApathy - Oh wow. I see. And here I was just getting my feet wet, lol. Thanks for pointing this out. I’d figured there were plenty more ops, but it’s a really pleasant surprise to have it all filled out like that.

I’ll try to memorize some of these FObj variable names. I was using terms in my notes that were familiar to me through the Graphical Quantization Registers in PowerPC, since they work in a very similar way. I was actually surprised that the FObj functions I peaked at were just doing normal casting and float division to apply the shift instead of just using GQRs. Maybe it’s too obscure.


So if I’m soaking this up correctly, in these opcode strings --



-- the first uleb128 value is a “Keyframe Opcode”? And all the pairs following it are “Value”, “Wait” opcodes -- since it's type 1 (CONSTANT) and doesn't use slope values?


I’m really interested to see how those other types work. Thanks again.
All of them are in FRAY. FObj isn't one of my best written ones as I'm planning to come back to it later, and I don't remember if it's complete, but it has the what each does.

https://github.com/PsiLupan/FRAY/bl...a4e42637228efc4d63125/src/hsd/hsd_fobj.c#L237
 

SinsOfApathy

Smash Journeyman
Joined
Feb 24, 2015
Messages
474
NNID
Psion312
Punkline Punkline Wanted to task you with something or ask if you had details on it.

0x50 of the HSD_LObj structure (in-mem, not HSD_LightDesc) has a GXLightObj. I need to verify if it's a pointer or an actual struct. I'm starting to suspect it's a struct, because I noted once:

Code:
GXLightObj* lightobj; //0x50     
u8 spec_id; //0x90 GXLightID
A GXLightObj is a 0x40 byte array, which would make sense why there's a gap in that struct of the exact size.

80365820 should reveal it because the call to GXInitLightColor will either pass a pointer to the LightObj within the struct, or it'll load a pointer from the struct to pass.

I could do it myself, but I'm at work and it crossed my mind, as it's one of my last crashes before I see some sign of rendering the Title.
 
Last edited:

Punkline

Dr. Frankenstack
Joined
May 15, 2015
Messages
423
Punkline Punkline Wanted to task you with something or ask if you had details on it.

0x50 of the HSD_LObj structure (in-mem, not HSD_LightDesc) has a GXLightObj. I need to verify if it's a pointer or an actual struct. I'm starting to suspect it's a struct, because I noted once:

Code:
GXLightObj* lightobj; //0x50  
u8 spec_id; //0x90 GXLightID
A GXLightObj is a 0x40 byte array, which would make sense why there's a gap in that struct of the exact size.

80365820 should reveal it because the call to GXInitLightColor will either pass a pointer to the LightObj within the struct, or it'll load a pointer from the struct to pass.

I could do it myself, but I'm at work and it crossed my mind, as it's one of my last crashes before I see some sign of rendering the Title.
It adds 0x50 when passing to GXInitLightColor, so it appears to be a structure, like you suspect: https://i.imgur.com/gasPq5y.png
 
Last edited:

SinsOfApathy

Smash Journeyman
Joined
Feb 24, 2015
Messages
474
NNID
Psion312
Punkline Punkline

Figured this out a moment ago while reading your notes on GObj that DRGN DRGN posted in Discord and looking at my GObj function that runs GX procs.

GObj 0x20 is actually a 64-bit bitmask, this matches the fact the array sizes are sized at 64. 0x24 does not exist, it's just referenced in the assembly, because of the PPC CPU only have 32-bit load/stores. What happens is that 0x20/0x24 is set with the render priority that the CObj will run, where each bit is the priority enabled on that frame.

IE. For the title screen, 0x20/x24 is set to 0x209, which ends up meaning that bit 0, 3, and 9 are set, which matches the fact that Fog & Light are 0, the Logo JObj is 3, and the Background JObj is 9.

Then, it just runs a loop, right shifts by 1 every time, then does (current_priority & 1) != 0 to determine if the priority of proc objects runs that frame.
 
Last edited:

Punkline

Dr. Frankenstack
Joined
May 15, 2015
Messages
423
Punkline Punkline

Figured this out a moment ago while reading your notes on GObj that DRGN DRGN posted in Discord and looking at my GObj function that runs GX procs.

GObj 0x20 is actually a 64-bit bitmask, this matches the fact the array sizes are sized at 64. 0x24 does not exist, it's just referenced in the assembly, because of the PPC CPU only have 32-bit load/stores. What happens is that 0x20/0x24 is set with the render priority that the CObj will run, where each bit is the priority enabled on that frame.

IE. For the title screen, 0x20/x24 is set to 0x209, which ends up meaning that bit 0, 3, and 9 are set, which matches the fact that Fog & Light are 0, the Logo JObj is 3, and the Background JObj is 9.

Then, it just runs a loop, right shifts by 1 every time, then does (current_priority & 1) != 0 to determine if the priority of proc objects runs that frame.
Oh yeah, you can see in many of the menu scene initialization callbacks how the GObjs are set up to attach the cameras to these specialized camera GObjs. I tried modifying the flags once in a camera in the SSS (see block 8025aa50...8025aaa8) to better understand them a few months back when I was looking into how assets were loaded into a scene. I've actually been working out an interface for generating and initializing GObjs from a data format that can be read in by MCM standalone functions. I've tried to accommodate the generation of these 64-bit masks as an optional initialization feature by making GAS macros that accept a list of other GObj groups by targeting their gx_link IDs (0, 3, 9, etc).

I think of these as special GObjs, since they have their own dedicated linked list pointer at gx_link offset (gx_link_max + 1). You can see it being loaded into r30 here at 80390fec, as a part of the loop that takes care of the GX callbacks each frame. They seem to host events that handle the GX callback of other GObjs in the specified gx_link groups; like you said. It defines a sort of functional meaning to the gx_link groupings, which is pretty useful.


The flags they use are something I've recently been considering exploitable for their potential to extend GObjs that don't use cameras in their HSD Object attachment. I've noticed that other GObjs don't seem to use the allocation for this 64-bit mask -- so the space appears to be padding in most cases, since not many GObjs use cameras. Dan Salvato proved the practicality of this assumption back when he wrote his action state hack years ago, which repurposed these fields as an input buffer for storing a target action state ID. I think it would be much cooler to instead use them to extend the data allocations of an existing GObj data table using linked lists, and somehow extend the destruction callback trigger to destroy them. That would make it so much easier to do things like extend the player data section for codes that need to make extra player variables.

Do you know of any non-camera use of these mask fields? It would be nice to learn if they're just some kind of generic field for implementing some sort of extended functionality.
 
Last edited:

SinsOfApathy

Smash Journeyman
Joined
Feb 24, 2015
Messages
474
NNID
Psion312
I think of these as special GObjs, since they have their own dedicated linked list pointer at gx_link offset (gx_link_max + 1). You can see it being loaded into r30 here at 80390fec, as a part of the loop that takes care of the GX callbacks each frame. They seem to host events that handle the GX callback of other GObjs in the specified gx_link groups; like you said. It defines a sort of functional meaning to the gx_link groupings, which is pretty useful.
It's dedicated in the sense that it's at the end of the list, but Fountain of Dream's reflection "Izumi Mirror" is the only object in one link list by itself that is reserved at the same size. Apparently, they didn't do use that elsewhere for some weird reason. I called it "highpriority_gxprocs" but that's probably not accurate.

The flags they use are something I've recently been considering exploitable for their potential to extend GObjs that don't use cameras in their HSD Object attachment. I've noticed that other GObjs don't seem to use the allocation for this 64-bit mask -- so the space appears to be padding in most cases, since not many GObjs use cameras. Dan Salvato proved the practicality of this assumption back when he wrote his action state hack years ago, which repurposed these fields as an input buffer for storing a target action state ID. I think it would be much cooler to instead use them to extend the data allocations of an existing GObj data table using linked lists, and somehow extend the destruction callback trigger to destroy them. That would make it so much easier to do things like extend the player data section for codes that need to make extra player variables.

Do you know of any non-camera use of these mask fields? It would be nice to learn if they're just some kind of generic field for implementing some sort of extended functionality.
Ghidra lets me search the variable within the struct up, and I haven't seen it used elsewhere. Only cameras setup for render events. Some cameras don't use it, ie. the erase camera.
 

DRGN

Technowizard
Moderator
Joined
Aug 20, 2005
Messages
2,178
Location
Sacramento, CA
SinsOfApathy SinsOfApathy Punkline Punkline UnclePunch UnclePunch Furil Furil

I'm looking at the structure pointed to by 0x4 of stage map_heads (the Game (a.k.a. Generic) Objects Array); i.e. the array of 0x34-byte GObjDescs. I thought it might interest some of you that I just scanned all of the stage files, to see what pointers may appear in that structure, and found a few things. It seems all but two of the values in those descriptor entries are pointers. (By 'descriptor entry' I mean one set of 0x34 bytes in the array struct; i.e. one entry within the array.) The other two values, at 0x24 & 0x30, appear to be array counts for the arrays that are pointed to by the pointers just before the counts. In other words, as far as I understand it:

0x0 : Root_Joint_Pointer
0x4 : Joint_Anim_Joint_Pointer
0x8 : Material_Anim_Joint_Pointer
0xC : Shape_Anim_Joint_Pointer
0x10: Camera_Pointer
0x14: Unknown_1_Pointer
0x18: Light_Pointer
0x1C: Unknown_2_Pointer
0x20: Unknown_3_Array_Pointer
0x24: Unknown_3_Array_Count
0x28: Unknown_4_Pointer
0x2C: Unknown_5_Array_Pointer
0x30: Unknown_5_Array_Count

The Unknown 3 and Unknown 5 Arrays seem to be null-terminated arrays of 6-byte descriptors of something. Quite a few stages use the Unknown 3 Arrays, but interestingly there's only one stage that uses the Unknown 5 Array: Ness' target test stage, for some reason.

Do you guys have more info on what the above unknowns are? I figure there's a good chance some of these are descriptions to structures/values you guys have already touched on, and maybe I just haven't read enough or connected them in my head/notes yet.
 

Punkline

Dr. Frankenstack
Joined
May 15, 2015
Messages
423
SinsOfApathy SinsOfApathy Punkline Punkline UnclePunch UnclePunch Furil Furil

I'm looking at the structure pointed to by 0x4 of stage map_heads (the Game (a.k.a. Generic) Objects Array); i.e. the array of 0x34-byte GObjDescs. I thought it might interest some of you that I just scanned all of the stage files, to see what pointers may appear in that structure, and found a few things. It seems all but two of the values in those descriptor entries are pointers. (By 'descriptor entry' I mean one set of 0x34 bytes in the array struct; i.e. one entry within the array.) The other two values, at 0x24 & 0x30, appear to be array counts for the arrays that are pointed to by the pointers just before the counts. In other words, as far as I understand it:

0x0 : Root_Joint_Pointer
0x4 : Joint_Anim_Joint_Pointer
0x8 : Material_Anim_Joint_Pointer
0xC : Shape_Anim_Joint_Pointer
0x10: Camera_Pointer
0x14: Unknown_1_Pointer
0x18: Light_Pointer
0x1C: Unknown_2_Pointer
0x20: Unknown_3_Array_Pointer
0x24: Unknown_3_Array_Count
0x28: Unknown_4_Pointer
0x2C: Unknown_5_Array_Pointer
0x30: Unknown_5_Array_Count

The Unknown 3 and Unknown 5 Arrays seem to be null-terminated arrays of 6-byte descriptors of something. Quite a few stages use the Unknown 3 Arrays, but interestingly there's only one stage that uses the Unknown 5 Array: Ness' target test stage, for some reason.

Do you guys have more info on what the above unknowns are? I figure there's a good chance some of these are descriptions to structures/values you guys have already touched on, and maybe I just haven't read enough or connected them in my head/notes yet.
I looked into this a bit. I wasn’t able to observe any memory checks for unknowns 1 and 2 from the examples I looked into, but I have some info about 3, 4, and 5.

In short -- they are for selecting moving platform collision links, initializing certain JObjs to loop their AObj animations, and selecting JObj IDs that will automatically flag all of their contained MObjs for rendering shadows that are projected onto them -- respectively.


---

There’s a static 4-element array in RAM (starting at 8049ee10) that holds pointers to the current stage's dat file, like this:
Code:
0x0: point to stage dat file
0x4: point to map_head in dat file
0x8: unk identifier? flag? (Can be seen in 2nd element in pokemon stadium transformations)

The first element is something I’ve used before to reference stage dat files once they’re loaded, but it appears that the other elements can hold up to 3 additional map_heads from other files.

A function at 801c6330 interfaces with these global elements to check the pointer at 0x8 of the map head. The function takes in an argument ID and checks it against the array size at 0xC, then multiplies the ID by 0x34 and adds it to the destination from 0x8 of the map head. It then loads what would be the JObj pointer (offset 0x0) in your notes to check if it exists, then passes the map head back to the caller.


---

In the example unknown_5 element in the Ness BTT stage, the array element size appears to just be 2 bytes. Each element is an hword ID that selects a JObj from a tree of JObjs (presumably from the root provided in the 0x34-byte element). This ID is 1-dimensional, so it traces child/sibling links (in that priority) to count.

The JObj that gets selected for Ness’s stage draws the floors representing the ground collision links. The process that uses this ID selects this JObj in order to set a render flag (04000000) in every MObj of that DObj list belonging to the JObj. This flag simply enables shadows to be rendered.

It seems redundant, because the MObjs already appear to have the flag by default. It's interesting that it only appears in this Ness BTT stage.
I confirmed that disabling both this and the default MObj flags stops the shadow from rendering, while doing only one or the other does not:




---

The unknown_4 pointer seems to point to an array of bytes. Each byte appears to be boolean -- either 00 or 01 -- representing an indexed JObj. These bools are checked with an ID that translates into a byte index that uses the unknown_4 pointer destination as a base.

The bytes that are not 0 seem to set a flag in the JObj’s AObj that causes it to loop its animation. I didn’t get a chance to see how it corresponds with the element’s JObj indices, but I tried zeroing out all of the flags on one of the elements in Big Blue and noticed that the background stopped scrolling, and some of the “jet” animations were frozen.


---

The unknown_3 array appears to define moving platforms’ collision links. Or, at least select them.

The first 2 hwords in the 6-byte element look similar to the first 2 hwords used in collision link definitions -- where -1 is null, and other numbers select an indexed vertex from an array of descriptions. It’s hard to tell from just the example in Ness’s BTT stage if this is for vertex descriptions though. If they are, then they’re just keys for another lookup somewhere else.

In the Ness BTT stage example, the only array element that was present used the hwords 0000 FFFF 0009 -- which I’m assuming selects the single moving platform in the stage. If you set the 0000 to 0001 -- it causes the entire stage to move along the moving platform’s path instead of the normal platform. The JObjs don’t move with it -- just the stage collision geometry -- so it appears as though you are being pushed through the walls.

If you set 0009 to 0000 it causes the platform to connect to collision links all the way on the left side of the stage. The JObj platform doesn’t move with it. I know from experimenting with unknown_5 that JObj 9 was the moving platform -- but this doesn't seem to affect JObjs so that might just be a coincidence.

Not really sure what the 2nd FFFF hword is. I didn’t see it referenced in the functions I caught.
 
Last edited:

DRGN

Technowizard
Moderator
Joined
Aug 20, 2005
Messages
2,178
Location
Sacramento, CA
In the example unknown_5 element in the Ness BTT stage, the array element size appears to just be 2 bytes. Each element is an hword ID that selects a JObj from a tree of JObjs (presumably from the root provided in the 0x34-byte element). This ID is 1-dimensional, so it traces child/sibling links (in that priority) to count.

The JObj that gets selected for Ness’s stage draws the floors representing the ground collision links. The process that uses this ID selects this JObj in order to set a render flag (04000000) in every MObj of that DObj list belonging to the JObj. This flag simply enables shadows to be rendered.

It seems redundant, because the MObjs already appear to have the flag by default. It's interesting that it only appears in this Ness BTT stage.
I confirmed that disabling both this and the default MObj flags stops the shadow from rendering, while doing only one or the other does not:

Heh. So weird! I guess an oversight by the developers. Maybe they planned for this to be useful for some specific rending feature, but never ended up fleshing it out.

The unknown_4 pointer seems to point to an array of bytes. Each byte appears to be boolean -- either 00 or 01 -- representing an indexed JObj. These bools are checked with an ID that translates into a byte index that uses the unknown_4 pointer destination as a base.

The bytes that are not 0 seem to set a flag in the JObj’s AObj that causes it to loop its animation. I didn’t get a chance to see how it corresponds with the element’s JObj indices, but I tried zeroing out all of the flags on one of the elements in Big Blue and noticed that the background stopped scrolling, and some of the “jet” animations were frozen.
Is it null-terminated as well, or is it just a coincidence that I keep seeing the structs end in 0000? The longest array I've stumbled upon so far is 0x80 in Fourside, at 12 entries long (might be useful for testing).

The unknown_3 array appears to define moving platforms’ collision links. Or, at least select them.

The first 2 hwords in the 6-byte element look similar to the first 2 hwords used in collision link definitions -- where -1 is null, and other numbers select an indexed vertex from an array of descriptions. It’s hard to tell from just the example in Ness’s BTT stage if this is for vertex descriptions though. If they are, then they’re just keys for another lookup somewhere else.

In the Ness BTT stage example, the only array element that was present used the hwords 0000 FFFF 0009 -- which I’m assuming selects the single moving platform in the stage. If you set the 0000 to 0001 -- it causes the entire stage to move along the moving platform’s path instead of the normal platform. The JObjs don’t move with it -- just the stage collision geometry -- so it appears as though you are being pushed through the walls.

If you set 0009 to 0000 it causes the platform to connect to collision links all the way on the left side of the stage. The JObj platform doesn’t move with it. I know from experimenting with unknown_5 that JObj 9 was the moving platform -- but this doesn't seem to affect JObjs so that might just be a coincidence.

Not really sure what the 2nd FFFF hword is. I didn’t see it referenced in the functions I caught.
So perhaps it would be accurate to call this structure a 'Collision Animation Enable Array'. Is this what's responsible for associating collisions to move with their respective joint objects, or is there already some other known structure that handles that? Maybe that's what you were referring to when you said "The first 2 hwords in the 6-byte element look similar to the first 2 hwords used in collision link definitions"; do you mean the array pointed to by 0x8 of coll_data?

Another good example of this (where the array is much longer) is in Yoshi's Island; struct 0x88 (9 elements).
 

Punkline

Dr. Frankenstack
Joined
May 15, 2015
Messages
423
Heh. So weird! I guess an oversight by the developers. Maybe they planned for this to be useful for some specific rending feature, but never ended up fleshing it out.
My guess is that it was probably a convenience feature or something, but was never used because the flags could be set from a different level.

Something I've been thinking about is that -- since the only instance of it being used appears to be redundant -- it could probably be overridden and repurposed for stage modifications. It might then be an interesting vector for plugging models/structures/behaviors into specific stage GObjs from the DAT file using some code in MCM to modify the way the pointer and size values are interpreted.


---

Is it null-terminated as well, or is it just a coincidence that I keep seeing the structs end in 0000? The longest array I've stumbled upon so far is 0x80 in Fourside, at 12 entries long (might be useful for testing).
It’s hard to tell since the bytes are referred to individually by IDs and are looked up with random access rather than sequential access. As far as I can tell, null bytes are not selected by the function, and probably couldn't be since they would be confused with FALSE values. Unused bytes do however seem to be present...

It doesn’t seem to have an array size in the main 0x34-byte element structure, but that might be because of the fact that they can apparently contain multiple arrays. There might be more information packed away in the other unknowns, but these appear to be used by specialized gproc callbacks that can potentially just hardcode implied structure information like that.


---

After looking closely at Fourside, it appears that each array of bytes is word-aligned, so they often get padded. It seems to be pretty common to have only 1 byte in the array, where additional bytes only appear when there are additional states in an animation sequence.

The UFO seems to imply that multiple sequences accessed from the same array base are either word aligned or separated by nulls -- though it’s hard to tell which in this example with 3 similar 3-byte arrays... The helicopter seems to suggest it's the latter.

One of these 3 sequences for the UFO is selected initially by a random number to determine what orientation it will come in from, and the next sequence is chosen pseudo-randomly; avoiding only repeats of the last choice. Once a random number is chosen, it’s shifted to a word alignment to select one of the animation sequeunces. The IDs appear to skip over one byte for each of the 3 sequences sharing the same array, using 0,1,2; 4,5,6; 8,9,10. The bool values for each of these sequences is 0,1,0 -- causing only the middle phase to loop.

When an animation sequence is initialized, the IDs are memorized in a variable stored in offset 0xC5 of the instantiated UFO GObj data table so that the gproc can reference them later

Here are a few more notes I scribbled down:
Code:
Breakpoints:
801c8294 - lookup used on demand, when animations are being initialized for some new anim state
801c7fa4 - seems to only trigger on stage init

for each element with a pointer in 0x28 in fourside:
# element ID - element description
# state - bool value - state description
# ...

element 1 - on init - 801c7fa4
0 - 0 - unknown

element 2 - on init - 801c8294
0 - 1 - flashing lights

element 3 - on Helicopter - 801c8294
0 - 0 - starting in sky

2 - 0 - taking off from platform

element 5 - on UFO - 801c8294
0 - 0 - come in from far right
1 - 1 - stop and wobble around as a platform
2 - 0 - blink (and disappear?)

4 - 0 - from top right
5 - 1 - stop
6 - 0 - blink

8 - 0 - from top left
9 - 1 - stop
10- 0 - blink

UFO GObj data table:
0x14 word  ID = this 0x34-byte map_head element ID
0xC5 byte  ID = this animation state
# I wonder if this is a generic GObj data structure for these map_head GObj elements


The effect of the boolean value of the bytes loaded from these IDs can be seen clearly in the behavior of the helicopter and the UFO. It can also be seen in the flashing lights on the building.

Basically, a new animation state is being initialized each time an ID is used to look up one of these bytes -- so the 1 value causes the new animation state to loop until the animation state is changed again. If the state does not change -- as is the case for the flashing lights on the buildings -- then they will loop forever. If you set the lights bool to 0, then they will flash exactly once and then turn off. This can also be seen in the “wobble” effect applied to the UFO once it stops flying in.

If the helicopter’s fly-in state bool is set to 1 instead of 0, then it will seem to disappear shortly after landing before flying in a second time. This happens because the animation is looping inappropriately from the TRUE value in the bool.

The helicopter animation stops once it lands and seems to just wait for the next animation state. What's interesting is that it appears to skip from state 0 to state 2 (from the perspective of this byte lookup); implying that the landing and takeoff are still separated by an unused state -- kind of like the nulls in-between the UFO animation sequences. I think that maybe the animation state is used for more than looking up these bytes, so the unused bytes just reflect states that don't require an animation loop flag (maybe because they have no animation)


---

So perhaps it would be accurate to call this structure a 'Collision Animation Enable Array'. Is this what's responsible for associating collisions to move with their respective joint objects, or is there already some other known structure that handles that? Maybe that's what you were referring to when you said "The first 2 hwords in the 6-byte element look similar to the first 2 hwords used in collision link definitions"; do you mean the array pointed to by 0x8 of coll_data?

Another good example of this (where the array is much longer) is in Yoshi's Island; struct 0x88 (9 elements).
Oh! Yeah. I didn’t realize these were from coll_data. Or at least, I didn't remember.
The IDs look like they use a similar style, and it would make sense if they were keys for looking up tables, with nulls referencing the end of a linked list.
Not sure if you need them, but I have some notes that might help you map out coll_data from back when I worked on this project.


These global pointer variables are how I’m familiar with these structures. They are available from r13, so they can be accessed at any point in the game:
Code:
804D64AC | -0x51F4(r13)  word  unk timer, seems to count from stage start  
804D64B0 | -0x51F0(r13)  word  unk flag used when checking something from collision links    -- seems to write 1 as a 32-bit value

804D64B4 | -0x51EC(r13)  point to Stage File Collision Link Info  
804D64B8 | -0x51E8(r13)  point to Instantiated Collision Link vertices array  
804D64BC | -0x51E4(r13)  point to Instantiated Collision Link descriptions array
804D64C0 | -0x51E0(r13)  point to Instantiated Collision Link polygons array
804D64C4 | -0x51DC(r13)  point to First Active stage polygon -- Polygons are linked to create an order
804D64C8 | -0x51D8(r13)  point to Last Active stage polygon -- unsure if this is end of array, or end of links. They are commonly the same thing

The pointers to instantiated arrays are base addresses that can be used with an ID to select indexed collision link vertices, descriptions, and polygons. I just now confirmed that the top pointer to “Stage File Collision Link Info” is the same place in RAM pointed to by the “coll_data” symbol in the DAT archive.

So, my notes for “coll_data”:
Code:
804D64B4 | -0x51EC(r13)   point to Stage File Collision Link Info (aka: coll_data)
8049ED74   point to coll_data for current stage

coll_data (Stage File Collision Link Info):
0x00  point  to start of Stage File Vertex Array
0x04  word   Stage Collision Vertex Count
0x08  point  to start of Stage File Collision Link Description Array
0x0C  word   Stage Collision Link Count

Vertex element:
Code:
804D64B8 | -0x51E8(r13)  point to Instantiated Collision Link vertices array base
0x0(coll_data)  point to static array base

Instantiated Collision Link vertices:
0x00  float  Initial X
0x04  float  Initial Y
0x08  float  current X -- animated, or moved on init may differ from initial coords
0x0C  float  current Y
0x10  float  previous X
0x14  float  previous Y

Static (from file):
0x00  float  Initial X
0x04  float  Initial Y

Link Description element:
Code:
804D64BC | -0x51E4(r13)  point to Instantiated Collision Link descriptions array base
0x0(description)  point to this static array element, for link description
0x8(coll_data)  point to static array base

Instantiated Collision Link descriptions:
0x00   point  to This Collision Link in Stage File Data
0x04   word   flags -- the following are the only ones I took note of:
0x04  (00010000) = collision link floor is active, and will allow characters to land on it
0x04  (00040000) = temporarily disabled
0x04  (00000001) = Link is a floor  -- I recall that these are updated for omnidirectional links
0x04  (00000002) = Link is a ceiling ?
0x04  (00000004) = Link is a left wall
0x04  (00000008) = Link is a right wall
0x04  (00000010) = Link is omnidirectional (updates type according to facing)
0x04  (00000100) = (seems to be related to updating facing type for omnidirectional links)

Static (from file):
0x00  hword  Vertex 1 ID -- Link uses 2 vertices to create a line
0x02  hword  Vertex 2 ID -- null ID = -1 (FFFF)
0x04  hword  Previous Link ID  
0x06  hword  Next Link ID
0x08  hword  unk ID  -- I recall seeing these in some pokemon stadium transforms. I think they make Y and X joints?
0x0A  hword  unk ID
0x0C  word  flags:
0x0C  (00010000) = enable collisions
0x0C  (00040000) = temporarily disable collisions?
0x0C  (00000100) = Platform can be fallen through by holding down
0x0C  (00000200) = Link edge(s) may be used as a grabbable ledge
0x0C  (00000400) = Omnidirectional fallthrough platforms
0x0C  (000000FF) = ID for surface material type (for sfx, and ice physics)
(These are the elements pointed to by the 0x8 pointer you referred to, from coll_data.)

Together, the tables and ordered elements create a sort of database that can use index keys to refer to relatable nodes. So, while the array gives each element a specific index, the order used by the polygons is defined by the link values stored in the static Collision Link descriptions. The IDs function like pointers.

The links are made of vertices, and the polygons are made of links; all related by their IDs on these tables.

The polygon instances act sort of like high-level heads of individual groups of links. They can be given a JObj and a callback pointer for executing code when a player lands on it. I’m not sure how to reach the static polygons in the file, but each polygon instance points back to its static data in order to reach read-only data -- so I have notes on the structure:


Polygon element:
Code:
804D64C0 | -0x51E0(r13)  point to Instantiated Collision Link polygons array
804D64C4 | -0x51DC(r13)  point to First Active stage polygon
804D64C8 | -0x51D8(r13)  point to Last Active stage polygon
0x4(polygon)  point to this static array element
???  point to static array base? (haven't found pointer yet)

Instantiated Collision Link polygons:
0x00  point  to Next Polygon  
0x04  point  to Polygon Info in Stage File  
0x08  word   flags:  
0x08  (00040000) = temporarily disable polygon collisions?
0x08  (00010000) = enable polygon landing collisions -- if FALSE, players can't land on floors, though they can still walk on them if already grounded
0x08  (00000100) = enable stage polygon?    Unknown -- appears to be set when a polygon (or perhaps just a root polygon) is active
0x08  (00000200) = animated polygon? -- see brinstar depths
0x0C  hword  unk counter -- sometimes doesn't increment
0x0E  hword  more flags?  
0x10  float  X -- these coords select a rectangle region around the polygon
0x14  float  Y  
0x18  float  X2
0x1C  float  Y2  
0x20  point  to Polygon Joint (JObj) -- Joint is used to move piece of stage around; NULL = no joint
0x24  point  to Landing Callback Function    -- Called each frame that a player is standing on this polygon; Event resides in 80043268  $!_stage_platformThink; null==no callback
0x28  point  to Stage GObj Data (if exists)    -- If a stage polygon needs space for allocating variables, a class 03 GObj is given a data table. Strangely, the data table is pointed to rather than the GObj.; null==no data
0x2C  unknown  
0x30  unknown  

Static (in file):
0x00  hword  Root Link ID -- floors
0x02  hword  number of links
0x04  hword  Root Link ID -- ceilings
0x06  hword  number of links
0x08  hword  Root Link ID -- right walls
0x0A  hword  number of links
0x0C  hword  Root Link ID -- left walls
0x0E  hword  number of links
0x10  hword  Root Link ID -- omnidirectional
0x12  hword  number of links

When you have the instantiated polygon, link, and vertex arrays available, you can navigate the entire set of stage data using the pointers to the read-only static data in each instance element. I'm not sure how to reference the static polygon info from the file though, since I was just using the pointer in the polygon instances in my code.
 
Last edited:
Top Bottom