Level Script Commands
#1
This thread is dedicated to the discussion, notes, and tools about the Level script commands used in SM64.

You can view the wiki page for the list of commands: http://hack64.net/wiki/doku.php?id=super...l_commands



Related variables (Notes by shygoo)
8038BE20: Statuses?
8038BE24: Accumulator
8038BE28: Pointer to current command
8038B8A0: ?
8038B8AC: ?
8038B8B0: Primary stack pointer
8038B8B4: Secondary stack pointer?


Related functions (Notes by shygoo)
803805C8: Command interpreter
Arguments (A0 = pointer to command) ; uses the jump table at 8038B8B0
~~~~
80277F50: Uses the segments table to convert a given segment offset address to a RAM address
Arguments (A0 = segment offset address)
Returns (V0 = RAM address)
802783E8: () ?
80278498: () ?
802786F0: Load raw data to RAM address
Arguments (A0 = RAM address, A1 = ROM address start, A2 = ROM address end)




Level script commands that we currently don't know what they do:
  • 0x23 - Unused, unknown purpose.
  • 0x29 / 0x2A / 0x2C / 0x2D / 0x35 / 0x3A / 0x3C - Unknown purpose.


Reply
#2
Back in late January I discovered how the room culling in Inside Castle, Big Boo's Haunt, and Hazy Maze Cave worked. It is a fairly simple process that involves the level script command 0x2F, and the geometry layout switch command (0xE). Each of the three level's geometry layouts contain a 0xE switch command that uses the ASM function 0x8029DBD4.

Geometry Layout for Inside Castle Area 1: https://pastebin.com/raw/iZdm0zNs

The function 0x8029DBD4 will find the collision triangle below Mario, and use the byte at offset 0x5 in the collision triangle structure to determine which switch case to use.

Code:
#define MARIO_OBJ_POINTER 0x80361158
typedef char s8;
int proc_8029DBD4(int a0, Node* a1) // function 0x8029DBD4
{
    if(a0 == 1 && *(MARIO_OBJ_POINTER) != NULL) 
    {
        *((short*)0x80361182) = 1;

        collisionTriangle* triangle;
        Object* mario_obj = *(MARIO_OBJ_POINTER);

        // Function that finds triangle underneath XYZ position.
        proc_80381900(mario_obj->x_pos, mario_obj->y_pos, mario_obj->z_pos, &triangle);

        if((*triangle) != NULL)
        {
            *((short*)0x80361250) = (s8)(triangle->_0x5);
            short sp_26 = (s8)(triangle->_0x5) - 1;

            // Some printing function that calls PrintInt. 0x803377B0 = "areainfo %d"
            proc_802CA5B8((char*)0x803377B0, (s8)(triangle->_0x5));

            if(sp_26 >= 0)
            {
                a1->_0x1E = sp_26; // Switch value = sp_26
            }
        } 
        else 
        {
            a1->_0x1E = 0; // Switch value = 0
        }
    }

    return 0;
}

The purpose of the level script command 0x2F is to setup the pointer of an array of byte values that will populate the collision triangles. This pointer will be stored at offset 0x0C in the structure of the current area being processed. The length of this byte array is the same as the number of collision triangles inside the current area. If the level script doesn't have a 0x2F command, then each collision triangle will have zero as their offset 0x5.

Code:
typedef char s8;
void LevelScript2F()
{
    // 0x8038B8AC = current area index being processed in the level scripts
    if(*((short*)0x8038B8AC) != -1)
    {
        Area* t8 = *((Area**)0x8032DDC8); // 0x8032DDC8 = pointer to start of area struct array.
        int t0 = (*((short*)0x8038B8AC) * 0x3C);
        Area* area_struct = (Area*)(t8 + t0);
        area_struct->_0xC = SegmentedToVirtual(*((int*)(*((int*)0x8038BE28) + 4)));
    }

    *((int*)0x8038BE28) += *((s8*)(*((int*)0x8038BE28) + 1)); // Go to the next level script command
}

The collision triangle's offset 0x05 is populated from a loop in the function 0x80383068, which is eventually called from the level initialize command ([ 11 08 0000 8024BCD8 ]) before the main level loop command starts ([ 12 08 0001 8024BCD8 ]).

Here's a video showing me editing the switch byte in the collision triangle underneath where Mario is standing:


Reply




Users browsing this thread: 2 Guest(s)