• 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!

Generating resource groups/entries in brres files (Method complete!)

Kryal

Smash Ace
Joined
May 28, 2009
Messages
560
UPDATE: Read a few posts down.

The brres resource groups/nodes have posed problems for us, due to that fact that we didn't know how to generate the values in order to add/remove nodes as we pleased. This is particularly useful for models, because any given model might have more or less bones/polygons/textures.

I believe we may have figured it out, and with some dedication a person could build their own brres files from scratch. Using the following function, you can get a better idea of how the game processes node look-ups.

Each resource group is organized like a binary tree. Each node has an ID value that contains an index and bit number for the binary comparison. It also contains left/right index values for traversing the tree.

The root node in a group has an ID of 0xFFFF, and points to the first node in the tree. The last node in the tree points back to the root node.

So here it is (all values are big-endian):

Code:
struct ResourceGroup
{
    int dataLength;
    int nodeCount;
    ResourceNode rootNode; //root node has an ID of 0xFFFF
}

struct ResourceNode //each node is 16 bytes long
{
    ushort nodeId;
    ushort padding;
    ushort leftIndex;
    ushort rightIndex;
    int stringOffset;
    int dataOffset;
}

ResourceNode* FindResource(ResourceGroup* group, string nodeName)
{
    int strLen = nodeName.Length; //length of node name we're searching for
    
    ResourceNode* rootEntry = &group->rootEntry;

    ResourceNode* lastEntry = rootEntry;
    ResourceNode* currentEntry = rootEntry[rootEntry->leftIndex]; //get first node

    //Continue until last id is greater than current id
    while(lastEntry->nodeId > currentEntry->nodeId)
    {
        lastEntry = currentEntry;

        int charIndex = (currentEntry->nodeId & 0xFFF8) >> 3;
        int charBit = currentEntry->nodeId & 7;

        //If index is too high, continue to next size group
        //If bit compared is zero, traverse left. Otherwise traverse right
        if((charIndex >= strlen) || ((nodeName[charIndex] >> charBit) & 1 == 0))
            currentEntry = rootEntry[currentEntry->leftIndex];
        else
            currentEntry = rootEntry[currentEntry->rightIndex];
    }

    //Compare the strings to make sure we've found a match
    byte* stringAddress = (byte*)group + currentEntry->stringOffset;
    if(nodeName != String(stringAddress))
        return null;
    else
        return currentEntry;
}
 

...:::VILE:::...

Smash Ace
Joined
Apr 15, 2009
Messages
786
I actually found most of this out a while ago when i was working to hard code character slots in. I had help from some dude in the SS irc though. I managed to build a shoddily made brres file that actually worked.
 

Pharrox

Smash Journeyman
Joined
Jan 26, 2007
Messages
397
Location
Belleville, MI
Looks pretty good. :)

A few notes on the subject, though. Firstly, not all node are organized by name, even though that is how it usually works out. An example of this would be looking at the bone entries in the MDL0 blocks. The order that prev/next/ids work isn't so much about the order they were in before they were added, as it is about the length of the names themselves (you show this in your code, but I wasn't sure from your description).

Root entry will always point to the node with the longest name (the first one if there are several). The prev entry from there will point to the first node with the next longest name, and so on. The next entry will always (except in the rarest of cases) point to the node with the next highest ID or itself if it comes to another node with a higher ID first. If more than one node has the same length name and ends in the same character, the ID will point to the first unique character from the end.

The first entry with the shortest name will always point back to rootEntry, and there are a few other quirks that I'm currently trying to work out. The biggest problem with reversing the process is the generation of the charBits variable. If no other nodes depend on the node being tested (that is, "HipN" depends on "TopN" because they are the same length and the last two letters are the same) it will work just as you said. However if there are dependencies it must be able to return the right result so that all possible names will be checked and then link back to the start to check again if nothing else is found.

It's an annoying problem that I'm not really sure how to deal with yet (at least without killing efficiency with all the necessary checks). That's gonna have to be my project for the next couple days. :(

All I have to say though is wow! You were really quick on getting that all figured out since I pretty much just asked about it yesterday. Took me forever to get through that (I suck at disassembly).
 

_Vuze_

Smash Apprentice
Joined
Aug 22, 2009
Messages
97
Location
Germany
Great work, I don't know how to use this though lol, but as long as it is useful for custom models and that ^_^
 

Kryal

Smash Ace
Joined
May 28, 2009
Messages
560
Haha, root entry pointing to the longest name. Very obvious, and I was even thinking about that last night but it was also 3AM. Yes, these IDs are traversed in descending order, and when a node with the same size and case ending (0,A,a) is found (which means id is <= last) it quits.

As for bones, yes they are ordered by hierarchy. This makes me think that it doesn't actually matter how they're ordered, but they ARE ordered in a way that makes them 'flatten'. Also, any child nodes within a bone are ordered by name.

There is another part to this that I'm looking at now, which calls a function with the resulting node's name. I'm assuming it will continue down the chain doing a string comparison. There are actually two calls that are exactly like this. One is for the group name, another is for the child name. What they do AFTER they find the sorting group differs slightly.
 

Kryal

Smash Ace
Joined
May 28, 2009
Messages
560
Okay, I've mapped part of the bone structure for captain falcon. Numbers in parenthesis () are the node ids, square brackets [] are left/right node indexes. Nodes moving down are on the left side of the binary tree, right are on the right. The far-left nodes are the topmost nodes for that size group, and are not organized.

Code:
FitCaptain00/Bones
First Branch = 2

Size	|
-------------------------------------
11	ToothCloseM(86)	[02]->	*END*
	|[03]
	|
	V----------------------------------------------------
10	ToothOpenM(78)	[60]->	ScarfNbase(77)	[60]->	*END*
	|[64]			|[21]
	|			|
	|			V----------------------------------------------------------------------------
	|			LShoulderJ(74)	[20]->	LShoulderN(73)	[41]->	RShoulderN(4)	[41]->	*END*
	|			|[43]			<-[03]			<-[20]
	|			|
	|			V----------------------------
	|			RShoulderJ(4) 	[43]->	*END*
	|			<-[21]
	|
	V----------------------------
9	ScarfNend(70)	[64]->	*END*	
	|[8]
	|
	V----------------------------------------------------------------------------------------------------
8	HolsterN(62)	[34]->	LThumbNa(61)	[35]->	LThumbNb(57)	[58]->	RThumbNb(4)	[58]->	*END*
	|[19]			<-[8]			|[57]			<-[35]
	|						|
	|						V----------------------------
	|						RThumbNa(4)	[57]->	*END*
	|						<-[34]
	|
	V----------------------------------------------------------------------------------------------------
7	CollarN(54)	[61]->	ScarfNa(53)	[62]->	ScarfNb(49)	[63]->	ScarfNc(48)	[63]->	*END*
	|[04]			|[32]			<-[61]			<-[62]
	|			|
	|			V----------------------------
	|			LHandBN(45)	<-[19]
	|			|[39]
	|			|
	|			V----------------------------
	|			MouthBN(35)	[39]->	*END*
	|			|[55]
	|			|
	|			V----------------------------
	|			RHandBN(4)	[55]->	*END*
	|			<-[32]
	|
	V---------------------------------------------
6	TransN(46)	[24]->
	|[05]
I have confirmed that ALL node lookups are done by name. This of course does not include traversing by way of index. The game passes the name of the brres group and node name to a function. This in turn finds the group with the specified name, and then searches that group for the node with the specified name. Both group and node lookups use the same method posted previously.

Using the table above, you can test this yourself. Choose any name from above and follow the steps, you'll always find the right one. Why? The magic is in the charOffset/charBits fields. The tree is like a binary tree, with each node giving a character index to compare against, and the bit of that character that differs. If the bit result is 0, it moves down (left). If it's 1, it moves right. When the ID is less than or equal to the last ID, you have a match. Run a string comparison and you're done.

Organization occurs as follows:
  • Arrange child nodes by name, in a flat hierarchy. (only for bones)
  • Group names by name length
  • Compare names in a binary tree fashion.
  • Characters are compared in right-to-left order, bits are compared left-to-right.

You will know it's organized properly when node IDs are all in descending order.
 
Top Bottom