Skip to content
MegaMech edited this page Sep 27, 2023 · 41 revisions

A table of mk64's segments are at the bottom of this page.

Segments can be very confusing especially for those with poor memory retention. As such, this page hopes to explain this in an easily refreshable manner.

  • Data - Geometry, object models, animations, course paths, textures, audio, etc.
  • Displaylist- A list of commands sent to the N64 graphics processor (rsp and rdp). Synonymous to a function or block of code.
  • Virtual - A segmented address. Consider it a fake memory location that does not actually exist (ex 0x06000210).
  • Physical - A real memory address. N64 memory always begins with 0x80 (ex. 0x80160010).
  • Rom address - An address that references a location in the games cartridge (ex. 0x8284D0).

Data needs to be loaded from the cartridge and placed into ram. The confusing part is how the game magically knows where to find this data on cartridge and its assigned location in memory. In essence, it's very simple but it's not easy to comprehend.

mk64 creates sixteen segments labelled 0x0 to 0xF.

During compile, the linker gathers all the pieces of the program and puts them together into the final rom. While doing so, it generates symbols, each containing a rom address. The games memory code calculates the size of the data by subtracting the rom end address from the rom start address. This value gets aligned to the next sixteen bytes. A global variable always points to the next free memory location. This pointers current address is saved prior to adding the data's size to it so that it points to the next free location (for next time the game loads data from the rom).

Finally the meat and potatoes, set_segment_base_addr is passed a hard-coded segment value between 0-16 along with the location of memory free for writing. The data is loaded to this address which is saved to the segment table array after having its segment number removed: 0x80. Only saving the offset of the address allows calculating between segmented addresses and physical memory. The get_segment_base_addr adds the 0x80 back.

Example: Inspecting the following rom address in a hex editor displays MIO0 in the text panel. This compressed data contains course geography vertex data.

_mario_raceway_vertexSegmentRomStart = 0x88FA10
_mario_raceway_vertexSegmentRomEnd = 0x89B510
size   = end      - start
0xBB00 = 0x89B510 - 0x88FA10
align16(0xBB00)
0xBB00 = (0xBB00 + 0xF) AND NOT F (NOT F is FFFFFFFFFFFFFFF0)

Examples of align16 at work:
align16(0xBB01) = 0xBB10, align16(0xBB0F) = 0xBB10, align16(0xBB10) = 0xBB10, align16(0xBB11) = 0xBB20

Aligning ensures that the next chunk of data is placed on a 0x00 boundary and not an oddball number such as 0x03.

Data is copied to ram and its segment base addr is set:
gSegmentTable[segment] = addr & 0x1FFFFFFF;
gSegmentTable[0xF] = 0x802887A8 & 0x1FFFFFFF; // Address saved as 002887A8

Now, if the game has a texture saved at a known offset, ex: 0x210. We can take the value in the segment table and add 0x210. If you add the physical memory value of 0x80, you now have the textures location in memory (0x802889B8).

Segmented addresses are necessary because the N64 only has four megabytes of memory. The console cannot load all twenty courses at the same time. However, even unloaded data needs to be referenced, this is done using segmented addresses. Unloaded data cannot use hard-coded physical memory addresses as physical memory addresses are defined during runtime by the memory management code. The work around is using a segmented address. Instead of using an address such as 0x8017BA58, 0x060000000 is used. The six can be replaced by any number between 0x1 and 0xF. Now all the data uses neatly placed offsets starting at that segmented address. A texture could be placed at 0x06000210. If a segments physical memory address is known then a texture can be referenced by adding its offset to that address. For example lets call get_segment_base_addr(0x06):

gSegmentTable[0x06] | 0x80000000
base addr  = segment offset | adds 0x80
0x802887A8 = 0x002887A8 | 0x80000000

Now to find the texture add 0x210 to 0x802887A8. All of these steps are done on the fly using functions and macros.

To receive a pointer to some data implement these concepts like so:

s32 segment = SEGMENT_NUMBER2(D_06013F78);
s32 offset = SEGMENT_OFFSET(D_06013F78);
struct ActorSpawnData *data = VIRTUAL_TO_PHYSICAL2(gSegmentTable[segment] + offset);

The segment variable contains: 0x06 The offset variable contains: 0x13F78 gSegmentTable[segment] contains: 0x002887A8 then has 0x13F78 added to it (0x29C720). VIRTUAL_TO_PHYSICAL adds the 0x80 to the address (0x8029C720) and returns a pointer to the data. The term virtual corresponds to a segmented address whereas physical refers to an actual memory address. Virtual is a fake representation. Remember that physical memory always begins with 0x80.

The below table is a work-in-progress.

Seg Desc
0 General Purpose; contains the game code addressed as 0x80000000 ex 0x8029E158
1 gGfxPool
2 Variety of fonts, UI textures, animations, vertices, and displaylists
3 Common and Course Specific Textures loaded by func_8029E158. Sets data to segment F for extraction.
4 Vertex data for course geography.
5 Course Textures
6 Startup logo & Course data consisting of displaylists, textures, models, actor spawn locations, and path data.
7 Course displaylists which renders triangles and textures into the course geography.
8 Unused.
9 Course offsets for texture lists and a jump table consisting of segment 06 display list.
A Unused.
B Trophy and podium data used during the awards ceremony.
C Unused.
D Common course data; textures, vertices, displaylists, etc.
E Unused.
F Temporary mio0 buffer for compressed course vtx and packed dlists / Course collisions table (44 byte entries)
Clone this wiki locally