* ObjTree OK, data imported

* EnMs OK, data imported

* And the spec

* OK

* Some minor edits

* A lot of preliminary stuff

* Mostly complete beginning

* First draft of other functions doc

* Whoops, forgot the GlobalContext pad

* Draw functions (minus colour), create Data

* Data

* gitignore, some progress on documenting

* Review comments, continue documenting

* spec

* Finish off documentation

* undefined_syms

* Add a couple of todos

* One more

* At least add tools for object decomp

* Start conversion table stuff

* Document ObjTree

* Document EnMs

* Add more tables to conversions

* Maide's review

* Review

* Review

* Typos and incomplete thoughts

* Update vscode.md

* Correct function/variable names

* Review suggestions

* Format

* Missed one

* Rename functions and format

* Fix ObjTree

* Update actorfixer.py, fix some variable names

* Some review

* Review suggestions

* More review

* Hopefully fix all the thisx references

* Missed one
This commit is contained in:
EllipticEllipsis
2021-12-16 18:47:18 -05:00
committed by GitHub
parent a6cd0e4427
commit d5b71bd0f5
54 changed files with 4878 additions and 270 deletions
+1
View File
@@ -44,6 +44,7 @@ tools/mips_to_c/
# Docs
!docs/**/*.png
!.vscode/extensions.json
# Per-user configuration
.python-version
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+56
View File
@@ -0,0 +1,56 @@
# Getting started
## [Introduction to decomp](introduction.md)
- What we are doing
- Structure of the code
## Pre-decompilation
- Building the repo (follow the instructions in the [README.md](../../README.md))
- Most of us use VSCode. Some useful information is [here](vscode.md).
<!-- Feel free to document Emacs/Vi/Sublime/whatever if you're familiar with them -->
- Choosing a first actor (You want something small that has simple interactions with the environment. A simple NPC can also work, and is what we will use as an illustration for most of the tutorial. There is a collection of actors we think are suitable for beginners on the spreadsheet or Trello)
## Decompilation
- [Begining decompilation: order, Init and the actor struct](beginning_decomp.md)
- Order of decompilation
- Init and common actor features
- Initchains
- Actors and dynapoly actors
- Colliders
- Skelanime
- [The rest of the functions in the actor](other_functions.md)
- Order of decompilation
- Action Functions and other functions
- [Draw functions](draw_functions.md)
- [Data, migration and non-migration](data.md)
- Importing the data: early and late
- Segmented pointers
- Fake symbols
- Inlining
## [Object Decompilation](object_decomp.md) (TODO)
- Object files
- How we decompile objects
## After Decompilation
- See the [CONTRIBUTING.md](../../CONTRIBUTING.md) for most of the details for submitting PRs. Remember to format again after making adjustments from reviews!
- More information about specific preparations is in [this document](merging.md).
## Appendices
- [Types, Structs and Padding](types_structs_padding.md) (a miscellany of useful stuff)
- [Advanced control flow](advanced_control_flow.md) (an example of a more complex function which mips2c is not so good at)
- [Using the diff script and the permuter](diff_and_permuter.md) (using the diff script and the permuter to match something)
- control flow (branches) -> instruction ordering -> register allocation -> stack
- [Helper scripts] TODO: link when merged
To be written, maybe
- How we use git and GitHub
- Some notes on the basic structure of N64 MIPS
- Glossary
- Conventions
+171
View File
@@ -0,0 +1,171 @@
# Data
Up: [Contents](contents.md)
Previous: [Draw functions](draw_functions.md)
## Table of Contents
- [Data first](#data-first)
- [Extern and data last](#extern-and-data-last)
- [Segmented pointers](#segmented-pointers)
- [Fake symbols](#fake-symbols)
- [Inlining](#inlining)
Each actor's data is stored in a separate file. EnRecepgirl's data is in `data/overlays/ovl_En_Recepgirl/ovl_En_Recepgirl.data.s`, for example. At some point in the decompilation process we need to convert this raw data into recognisable information for the C to use.
There are two main ways to do this: either
1. import the data first and type it later, or
2. wait until the data appears in functions, extern it, then import it at the end
Sometimes something between these two is appropriate: wait until the largest or strangest bits of data appear in functions, get some typing information out of that, and then import it, but for now, let's stick to both of these.
Both approaches have their advantages and disadvantages.
## Data first
This way is good for smaller actors with little data. The OoT tutorial [covers this in plenty of detail](https://github.com/zeldaret/oot/blob/master/docs/tutorial/data.md), and the process in MM is essentially identical, so we won't go over it here.
## Extern and data last
Externing is explained in detail in the document about the [Init function](beginning_decomp.md). To summarize, every time a `D_address` appears that is in the data file, we put a
```C
extern UNK_TYPE D_address;
```
at the top of the file, in the same order that the data appears in the data file. We can also give it a type if we know what the type actually is (e.g. for colliders, initchains, etc.), and convert the actual data and place it commented-out under the corresponding line. This means we don't have to do everything at once at the end.
Once we have decompiled enough things to know what the data is, we can import it. The advantage of doing it this way is we should know what type everything is already: in our work on EnRecepgirl, for example, we ended up with the following data at the top of the file
```C
#if 0
const ActorInit En_Recepgirl_InitVars = {
ACTOR_EN_RECEPGIRL,
ACTORCAT_NPC,
FLAGS,
OBJECT_BG,
sizeof(EnRecepgirl),
(ActorFunc)EnRecepgirl_Init,
(ActorFunc)EnRecepgirl_Destroy,
(ActorFunc)EnRecepgirl_Update,
(ActorFunc)EnRecepgirl_Draw,
};
static void* D_80C106B0[4] = { (void*)0x600F8F0, (void*)0x600FCF0, (void*)0x60100F0, (void*)0x600FCF0 };
// static InitChainEntry sInitChain[] = {
static InitChainEntry D_80C106C0[] = {
ICHAIN_U8(targetMode, 6, ICHAIN_CONTINUE),
ICHAIN_F32(targetArrowOffset, 1000, ICHAIN_STOP),
};
static s32 D_80C106C8 = 0;
#endif
```
and the main thing we need to understand is `D_80C106B0`
*Before doing anything else, make sure `make` gives `OK`.*
First, we tell the compiler to ignore the original data file. To do this, open the file called `spec` in the main directory of the repository, and search for the actor name. You will find a section that looks like
```
beginseg
name "ovl_En_Recepgirl"
compress
include "build/src/overlays/actors/ovl_En_Recepgirl/z_en_recepgirl.o"
include "build/data/ovl_En_Recepgirl/ovl_En_Recepgirl.data.o"
include "build/data/ovl_En_Recepgirl/ovl_En_Recepgirl.reloc.o"
endseg
```
We will eventually remove both of the bottom two lines and replace them with our own reloc file, but for now, just comment out the data line:
```
beginseg
name "ovl_En_Recepgirl"
compress
include "build/src/overlays/actors/ovl_En_Recepgirl/z_en_recepgirl.o"
//include "build/data/ovl_En_Recepgirl/ovl_En_Recepgirl.data.o"
include "build/data/ovl_En_Recepgirl/ovl_En_Recepgirl.reloc.o"
endseg
```
Next remove all the externs, and uncomment their corresponding commented data:
```C
const ActorInit En_Recepgirl_InitVars = {
ACTOR_EN_RECEPGIRL,
ACTORCAT_NPC,
FLAGS,
OBJECT_BG,
sizeof(EnRecepgirl),
(ActorFunc)EnRecepgirl_Init,
(ActorFunc)EnRecepgirl_Destroy,
(ActorFunc)EnRecepgirl_Update,
(ActorFunc)EnRecepgirl_Draw,
};
static void* D_80C106B0[4] = { (void*)0x600F8F0, (void*)0x600FCF0, (void*)0x60100F0, (void*)0x600FCF0 };
// static InitChainEntry sInitChain[] = {
static InitChainEntry D_80C106C0[] = {
ICHAIN_U8(targetMode, 6, ICHAIN_CONTINUE),
ICHAIN_F32(targetArrowOffset, 1000, ICHAIN_STOP),
};
static s32 D_80C106C8 = 0;
```
That should be everything, and we should now be able to `make` without the data file with no issues.
## Segmented pointers
The game has a convenient system that allows it to sometimes effectively use offsets into a file instead of raw memory addresses to reference things. This is done by setting a file address to a *segment*. A segmented address is of the form `0x0XYYYYYY`, where `X` is the segment number. There are 16 available segments, and actors always set segment 6 to their object file, which is a file containing assets (skeleton, animations, textures, etc.) that they use. This is what all those `D_06...` are, and it is also what the entries in `D_80C106B0` are: they are currently raw numbers instead of symbols, though, and we would like to replace them.
There is an obvious problem here, which is that is that these symbols have to be defined *somewhere*, or the linker will complain (indeed, if we change the ones in the array to `D_...`, even if we extern them, we get
```
mips-linux-gnu-ld: build/src/overlays/actors/ovl_En_Recepgirl/z_en_recepgirl.o:(.data+0x20): undefined reference to `D_0600F8F0'
````
As we'd expect, of course: we didn't fulfil our promise that they were defined elsewhere.)
This is mitigated by use of the file `undefined_syms.txt`, which feeds the linker the raw addresses to use as the symbol definitions. If you find a segmented address that is not in already externed, `extern` it at the top of the file and add it to the actor's section in undefined_syms:
```C
extern void* D_0600F8F0;
extern void* D_0600FCF0;
extern void* D_060100F0;
extern void* D_0600FCF0;
static void* D_80C106B0[4] = { &D_0600F8F0, &D_0600FCF0, &D_060100F0, &D_0600FCF0 };
```
and in undefined_syms.txt:
```
// ovl_En_Recepgirl
D_06000968 = 0x06000968;
D_06001384 = 0x06001384;
D_06009890 = 0x06009890;
D_0600A280 = 0x0600A280;
D_0600AD98 = 0x0600AD98;
D_0600F8F0 = 0x0600F8F0;
D_0600FCF0 = 0x0600FCF0;
D_060100F0 = 0x060100F0;
D_06011B60 = 0x06011B60;
```
We will come back and name these later when we do the object.
## Fake symbols
Some symbols in the data have been decompiled wrongly, being incorrectly separated from the previous symbol due to how it was accessed by the actor's functions. However, most of these have now been fixed. Some more detail is given in [Types, structs and padding](types_structs_padding.md) If you are unsure, ask!
## Inlining
After the file is finished, it is possible to move some static data into functions. This requires that:
1. The data is used in only one function
2. The ordering of the data can be maintained
Additionally, we prefer to keep larger data (more than a line or two) out of functions anyway.
# Finally: .bss
A .bss contains data that is uninitialised (actually initialised to `0`). For most actors all you need to do is declare it at the top of the actor file without giving it a value, once you find out what type it is. In `code`, it's much more of a problem.
Next: [Documenting](documenting.md)
+145
View File
@@ -0,0 +1,145 @@
# `diff.py` and the permuter
This document is intended as a step-by-step demonstration of matching a reasonably complex function using the diff script `diff.py` and the decomp permuter, both included in the repo. For general information on both see [the tools documentation](../tools.md).
Until such time as someone finds a suitable function, you can look at the OoT tutorial: [here for diff.py](https://github.com/zeldaret/oot/blob/master/docs/tutorial/beginning_decomp.md#diff) and [here for the permuter](https://github.com/zeldaret/oot/blob/master/docs/tutorial/other_functions.md#the-permuter).
<!--
The following is left here to give a rough idea of what the diff script doc could look like.
This gives the following:
<details>
<summary>
Large image, click to show.
</summary>
![Init diff 1](images/init_diff1.png)
</details>
The code we want is on the left, current code on the right. To spot where the function ends, either look for where stuff is added and subtracted from the stack pointer in successive lines, or for a
```MIPS
jr ra
nop
```
The colours mean the following:
- White/gray is matching lines
- Red is lines missing
- Green is extra lines
- Blue denotes significant differences in instructions, be they just numerical ones, or whole instructions
- Yellow/Gold denotes that instructions are correct but register usage is wrong
- Other colors are used to distinguish incorrectly used registers or stack variables, to make it easy to follow where they are used.
- The colored arrows denote branching. An arrow of one color on the right leads to the arrow of the same color on the left.
Obviously we want to make the whole thing white. This is the tricky bit: you have to have the imagination to try different things until you get the diff to match. You learn these with experience.
Generally, the order of what to fix should be:
1. Control flow (conditionals, where branches go)
2. Instruction ordering and type (functions cannot change order, which is a useful indicator)
3. Regalloc (register allocation) differences
4. Stack differences
(It is this order because the things that happen earlier can influence the things that happen later.)
You can keep the diff open in the terminal, and it will refresh when the C file (but not the H file) is changed with these settings.
In this case, we see that various branches are happening in the wrong place. Here I fear experience is necessary: notice that the function has three blocks that look quite similar, and three separate conditionals that depend on the same variable. This is a good indicator of a switch. Changing the function to use a switch,
```C
void EnJj_Init(Actor* thisx, GlobalContext* globalCtx) {
EnJj* this = THIS;
s32 sp4C;
s16 temp_v0;
sp4C = 0;
Actor_ProcessInitChain(&this->dyna.actor, D_80A88CE0);
ActorShape_Init(&this->dyna.actor.shape, 0.0f, NULL, 0.0f);
temp_v0 = this->dyna.actor.params;
switch (temp_v0) {
case -1:
SkelAnime_InitFlex(globalCtx, &this->skelAnime, &D_0600B9A8, &D_06001F4C, this->jointTable,
this->morphTable, 22);
Animation_PlayLoop(&this->skelAnime, &D_06001F4C);
this->unk_30A = 0;
this->unk_30E = 0;
this->unk_30F = 0;
this->unk_310 = 0;
this->unk_311 = 0;
if ((gSaveContext.eventChkInf[3] & 0x400) != 0) {
func_80A87800(this, func_80A87BEC);
} else {
func_80A87800(this, func_80A87C30);
}
this->childActor = Actor_SpawnAsChild(
&globalCtx->actorCtx, &this->dyna.actor, globalCtx, ACTOR_EN_JJ, this->dyna.actor.world.pos.x - 10.0f,
this->dyna.actor.world.pos.y, this->dyna.actor.world.pos.z, 0, this->dyna.actor.world.rot.y, 0, 0);
DynaPolyActor_Init(&this->dyna, 0);
CollisionHeader_GetVirtual(&D_06000A1C, &sp4C);
this->dyna.bgId =
DynaPoly_SetBgActor(globalCtx, &globalCtx->colCtx.dyna, &this->dyna.actor, sp4C);
Collider_InitCylinder(globalCtx, &this->collider);
Collider_SetCylinder(globalCtx, &this->collider, &this->dyna.actor, &D_80A88CB4);
this->dyna.actor.colChkInfo.mass = 0xFF;
break;
case 0:
DynaPolyActor_Init(&this->dyna, 0);
CollisionHeader_GetVirtual(&D_06001830, &sp4C);
// temp_a1_2 = &globalCtx->colCtx.dyna;
// sp44 = temp_a1_2;
this->dyna.bgId =
DynaPoly_SetBgActor(globalCtx, &globalCtx->colCtx.dyna, &this->dyna.actor, sp4C);
func_8003ECA8(globalCtx, &globalCtx->colCtx.dyna, this->dyna.bgId);
this->dyna.actor.update = func_80A87F44;
this->dyna.actor.draw = NULL;
Actor_SetScale(&this->dyna.actor, 0.087f);
break;
case 1:
DynaPolyActor_Init(&this->dyna, 0);
CollisionHeader_GetVirtual(&D_0600BA8C, &sp4C);
this->dyna.bgId =
DynaPoly_SetBgActor(globalCtx, &globalCtx->colCtx.dyna, &this->dyna.actor, sp4C);
this->dyna.actor.update = func_80A87F44;
this->dyna.actor.draw = NULL;
Actor_SetScale(&this->dyna.actor, 0.087f);
break;
}
}
```
we see that the diff is nearly correct (note that `-3` lets you compare current with previous):
<details>
<summary>
Large image, click to show.
</summary>
![Init diff 2](images/init_diff2.png)
</details>
except we still have some stack issues. Now that `temp_v0` is only used once, it looks fake. Eliminating it actually seems to make the stack worse. To fix this, we employ something that we have evidence that the developers did: namely, we make a copy of `globalCtx` (the theory is that they actually used `gameState` as an argument of the main 4 functions, just like we used `Actor* thisx` as the first argument.) The quick way to do this is to change the top of the function to
```C
void EnJj_Init(Actor* thisx, GlobalContext* globalCtx2) {
GlobalContext* globalCtx = globalCtx2;
EnJj* this = THIS;
...
```
It turns out that this is enough to completely fix the diff:
![Init diff 2](images/init_diff3top.png)
(last two edits, only top shown for brevity)
Everything *looks* fine, but we only know for sure when we run `make`. Thankfully doing so gives
```
zelda_ocarina_mq_dbg.z64: OK
```
which is either a sense of triumph or relief depending on how long you've spent on a function. -->
+36
View File
@@ -0,0 +1,36 @@
# Disassembly quirks
As MM's disassembly is automatic, there are certain unique problems it has.
## Renaming functions and variables
A function must be renamed in `tools/disasm/functions.txt` in addition to the source code, for the disassembler to know what to call the symbol at that address when it sees it.
Variables must be renamed in `tools/disasm/variables.txt`. It may also be necessary to change their type, count or size to stop the disassembler misusing them.
You can avoid having to redisassemble every time by running `rename_global_asm.py`, which will rename the individual functions' assembly files in `asm/nonmatchings/` to the name of the function they contain.
## Fake and incorrect symbols
TODO
## Resplitting a file
The files `boot` and `code` are each divided up into dozens of separate files, that are all joined together into one text, data, rodata and bss section when building the ROM. As such, it has been necessary to guess where the file boundaries are, and not every file contains the correct functions or the correct data (rodata is mostly the exception since it is automatically split).
To change a split for a file, find its entry in `tools/disasm/files.txt`, and change or create entries to accurately reflect where the file(s) should start. For example, it was found that the last function in `z_nmi_buff.c` had nothing to do with the rest, so it should be split into its own file. Looking up the address of the last function, it was found to be at `0x8010C1B0`, so adding the line:
```diff
0x8010C0C0 : "z_nmi_buff",
+++ 0x8010C1B0 : "code_8010C1B0",
0x8010C230 : "z_olib",
```
to the file will extract it correctly as a separate file. It also is necessary to make a new C file and move the `GLOBAL_ASM` declaration into it.
Unfortunately you essentially have to redisassemble after telling the disassembler to resplit a file.
##
File diff suppressed because it is too large Load Diff
+303
View File
@@ -0,0 +1,303 @@
# Draw functions
Up: [Contents](contents.md)
Previous: [The rest of the functions in the actor](other_functions.md)
Draw functions behave completely differently from the other functions in an actor. They often use a lot of macros.
This document will be a bit different: we will look at the draw functions in EnRecepgirl, then consider some more complicated examples.
## A first example
Unless it is completely invisible, an actor usually has a draw function as one of the main four actor functions. Hence its prototype looks like
```C
void EnRecepgirl_Draw(Actor* thisx, GlobalContext* globalCtx);
```
From now on, the process is rather different from the decompilation process used for the other functions. Here is the output of mips2c after sorting out the actor struct from Init, and with the arguments set back to `Actor* thisx`:
```C
s32 func_80C10558(GlobalContext *globalCtx, s32 limbIndex, Gfx **dList, Vec3f *pos, Vec3s *rot, Actor *actor); // extern
void func_80C10590(GlobalContext *globalCtx, s32 limbIndex, Actor *actor); // extern
void *D_80C106B0[4] = {(void *)0x600F8F0, (void *)0x600FCF0, (void *)0x60100F0, (void *)0x600FCF0};
void EnRecepgirl_Draw(Actor *thisx, GlobalContext *globalCtx) {
EnRecepgirl* this = (EnRecepgirl *) thisx;
GraphicsContext *sp30;
Gfx *temp_v1;
GraphicsContext *temp_a0;
temp_a0 = globalCtx->state.gfxCtx;
sp30 = temp_a0;
func_8012C28C(temp_a0);
temp_v1 = sp30->polyOpa.p;
sp30->polyOpa.p = temp_v1 + 8;
temp_v1->words.w0 = 0xDB060020;
temp_v1->words.w1 = (u32) D_80C106B0[this->unk_2AC];
func_801343C0(globalCtx, this->skelAnime.skeleton, this->skelAnime.jointTable, (s32) this->skelAnime.dListCount, func_80C10558, NULL, func_80C10590, (Actor *) this);
}
```
Notable features are the GraphicsContext temps, and blocks of the form
```C
temp_v1 = sp30->polyOpa.p;
sp30->polyOpa.p = temp_v1 + 8;
temp_v1->words.w0 = 0xDB060020;
temp_v1->words.w1 = (u32) D_80C106B0[this->unk_2AC];
```
(This is a particularly simple example, since there's only one of these blocks. We will give a more involved example later.)
Each of these blocks converts into a graphics macro. They are usually (but not always) straightforward, but manually converting them is a pain, and there are sometimes special cases. To deal with them easily, we will use a tool from glank's N64 tools. To install these, follow the instructions [here](https://practicerom.com/public/packages/debian/howto.txt).
For our purposes, we only need one of the programs this provides: `gfxdis.f3dex2`.
Graphics are actually 64-bit on the Nintendo 64. This code block is a result of instructions telling the processor what to do with the graphics pointer. There are two main types of graphics pointer (there are a couple of others used in `code`, but actors will only use these two),
- polyOpa ("opaque") for solid textures
- polyXlu ("Xlucent" i.e. "translucent") for translucent textures
Our example is polyOpa, not surprisingly since our receptionist is solid.
`words.w0` and `words.w1` contain the actual graphics instruction, in hex format. Usually, `w0` is constant and `w1` contains the arguments. To find out what sort of macro we are dealing with, we use `gfxdis.f3dex2`. `w1` is variable, but we need to give the program a constant placeholder. A common word to use is 12345678, so in this case we run
```
gfxdis.f3dex2 -x -g "POLY_OPA_DISP++" -d DB06002012345678
```
- `-x` uses hex instead of the default qu macros (never mind what those are, MM doesn't use them)
- `-g` is used to specify which graphics pointer macro to use
- `-d` is for the graphics dword
Our standard now is to use decimal colors. If you have a constant second argument rather than a variable one, you can also use `-dc` to get decimal colors instead of the default hex.
The output looks like
```
gSPSegment(POLY_OPA_DISP++, 0x08, 0x12345678);
```
We can now replace the `0x12345678` by the actual second word, namely `D_80C106B0[this->unk_2AC]`. We can see mips2c has pulled in this data again: we saw it before in the `Init`.
The words look like pointers to assets in the actor's object segment, which would make sense if we're looking for textures to draw. Because this data is used in a graphics macro, it will be either a displaylist or a texture; it may as well stay as `void*` until we come back to it later.
```C
gSPSegment(POLY_OPA_DISP++, 0x08, D_80C106B0[this->unk_2AC]);
```
You repeat this for every block in the function.
If you have worked on OoT, you will be aware of the functions `Graph_OpenDisps` and `Graph_CloseDisps`, and might be surprised to see them missing here. These functions are actually a debug feature: the `OPEN_DISPS` and `CLOSE_DISPS` macros still exist, but they don't expand to functions. Of course this means you have to guess where they go. A sensible guess for `OPEN_DISPS` is where the `gfxCtx` temp assignment first happens; `CLOSE_DISPS` is a bit harder, although it's basically just a `}`, so it *shouldn't* matter as much.
It's sensible to eliminate all the `gfxCtx` temps and reintroduce as needed. Also remember to change the prototype and function definition back!
```C
s32 func_80C10558(GlobalContext *globalCtx, s32 limbIndex, Gfx **dList, Vec3f *pos, Vec3s *rot, Actor *actor);
#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Recepgirl/func_80C10558.s")
void func_80C10590(GlobalContext *globalCtx, s32 limbIndex, Actor *actor);
#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Recepgirl/func_80C10590.s")
// #pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Recepgirl/EnRecepgirl_Draw.s")
void EnRecepgirl_Draw(Actor *thisx, GlobalContext *globalCtx) {
EnRecepgirl* this = THIS;
OPEN_DISPS(globalCtx->state.gfxCtx);
func_8012C28C(globalCtx->state.gfxCtx);
gSPSegment(POLY_OPA_DISP++, 0x08, D_80C106B0[this->unk_2AC]);
func_801343C0(globalCtx, this->skelAnime.skeleton, this->skelAnime.jointTable, this->skelAnime.dListCount, func_80C10558, NULL, func_80C10590, &this->actor);
CLOSE_DISPS(globalCtx->state.gfxCtx);
}
```
And this matches.
The last two functions in the actor are used as arguments in `func_801343C0`. This is a `SkelAnime` function, except unlike the OoT ones, it has three function callback arguments instead of two: in `functions.h` or `z_skelanime.c`, we find
```C
void func_801343C0(GlobalContext* globalCtx, void** skeleton, Vec3s* jointTable, s32 dListCount,
OverrideLimbDraw overrideLimbDraw, PostLimbDraw postLimbDraw, UnkActorDraw unkDraw, Actor* actor)
```
The typedefs of the callbacks it uses are in `z64animation.h`:
```C
typedef s32 (*OverrideLimbDraw)(struct GlobalContext* globalCtx, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot,
struct Actor* actor);
typedef void (*PostLimbDraw)(struct GlobalContext* globalCtx, s32 limbIndex, Gfx** dList, Vec3s* rot,
struct Actor* actor);
[...]
typedef void (*UnkActorDraw)(struct GlobalContext* globalCtx, s32 limbIndex, struct Actor* actor);
```
which is where mips2c got them from.
In this case, only two of them are used, and it is these that are the last functions standing between us and a decompiled actor.
## OverrideLimbDraw, PostLimbDraw, UnkActorDraw
Well, we don't have a PostLimbDraw here, but as we see from the prototype, it's much the same as the OverrideLimbDraw but without the `pos` argument and no return value.
```C
s32 func_80C10558(GlobalContext *globalCtx, s32 limbIndex, Gfx **dList, Vec3f *pos, Vec3s *rot, Actor *actor) {
if (limbIndex == 5) {
rot->x += actor->unk2B0;
}
return 0;
}
```
Only two things to do here: we need to use `EnRecepgirl` to get to `actor + 0x2B0`, and the return value is used as a boolean, so we replace `0` by `false` (`true` means "don't draw the limb", and is hardly ever used).
```C
s32 func_80C10558(GlobalContext *globalCtx, s32 limbIndex, Gfx **dList, Vec3f *pos, Vec3s *rot, Actor *thisx) {
EnRecepgirl* this = THIS;
if (limbIndex == 5) {
rot->x += this->unk_2AE.y;
}
return false;
}
```
As for the UnkActorDraw, it has a much simpler prototype. mips2c gives
```C
void func_80C10590(GlobalContext *globalCtx, s32 limbIndex, Actor *actor) {
if (limbIndex == 5) {
Matrix_RotateY((s16) (0x400 - actor->unk2AE), 1);
Matrix_GetStateTranslationAndScaledX(500.0f, (Vec3f *) &actor->focus);
}
}
```
There is only minor cleanup needed here:
- recasting the last argument,
- replacing the last argument of `Matrix_RotateY` by the enum `MTXMODE_APPLY` (which means "use the current matrix instead of starting from a new identity matrix"), and the first argument by `0x400 - this->unk_2AE.x`.
- `(Vec3f *) &actor->focus` to `&actor->focus.pos` (this is the same issue as `(Actor*)this`, where mips2c doesn't climb deep enough into the struct).
```C
void func_80C10590(GlobalContext *globalCtx, s32 limbIndex, Actor *thisx) {
EnRecepgirl* this = THIS;
if (limbIndex == 5) {
Matrix_RotateY(0x400 - this->unk_2AE.x, MTXMODE_APPLY);
Matrix_GetStateTranslationAndScaledX(500.0f, &this->actor.focus.pos);
}
}
```
## Some more examples: ObjTree
Since EnRecepgirl was a bit light on graphics macros, we will look at an example that has a few more. A nice simple one is `ObjTree_Draw`: the original mips2c output is
```C
void ObjTree_Draw(Actor *thisx, GlobalContext *globalCtx) {
s16 sp36;
s16 sp34;
Gfx *sp28;
Gfx *sp20;
Gfx *temp_v0;
Gfx *temp_v0_2;
Gfx *temp_v0_3;
Gfx *temp_v0_4;
GraphicsContext *temp_a0;
GraphicsContext *temp_s0;
sp36 = (s16) (s32) (f32) thisx->shape.rot.x;
sp34 = (s16) (s32) (f32) thisx->shape.rot.z;
temp_a0 = globalCtx->state.gfxCtx;
temp_s0 = temp_a0;
func_8012C28C(temp_a0);
temp_v0 = temp_s0->polyOpa.p;
temp_s0->polyOpa.p = temp_v0 + 8;
temp_v0->words.w0 = 0xDA380003;
sp28 = temp_v0;
sp28->words.w1 = Matrix_NewMtx(globalCtx->state.gfxCtx);
temp_v0_2 = temp_s0->polyOpa.p;
temp_s0->polyOpa.p = temp_v0_2 + 8;
temp_v0_2->words.w1 = (u32) &D_06000680;
temp_v0_2->words.w0 = 0xDE000000;
Matrix_InsertRotation(sp36, 0, sp34, 1);
temp_v0_3 = temp_s0->polyOpa.p;
temp_s0->polyOpa.p = temp_v0_3 + 8;
temp_v0_3->words.w0 = 0xDA380003;
sp20 = temp_v0_3;
sp20->words.w1 = Matrix_NewMtx(globalCtx->state.gfxCtx);
temp_v0_4 = temp_s0->polyOpa.p;
temp_s0->polyOpa.p = temp_v0_4 + 8;
temp_v0_4->words.w1 = (u32) &D_060007C8;
temp_v0_4->words.w0 = 0xDE000000;
}
```
We can see there are four blocks here, although only two different macros:
```C
temp_v0 = temp_s0->polyOpa.p;
temp_s0->polyOpa.p = temp_v0 + 8;
temp_v0->words.w0 = 0xDA380003;
sp28 = temp_v0;
sp28->words.w1 = Matrix_NewMtx(globalCtx->state.gfxCtx);
```
gfxdis gives
```
$ gfxdis.f3dex2 -x -g POLY_OPA_DISP++ -d DA38000312345678
gSPMatrix(POLY_OPA_DISP++, 0x12345678, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
```
so it becomes
```C
gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(globalCtx->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
```
```C
temp_v0_2 = temp_s0->polyOpa.p;
temp_s0->polyOpa.p = temp_v0_2 + 8;
temp_v0_2->words.w1 = (u32) &D_06000680;
temp_v0_2->words.w0 = 0xDE000000;
```
```
$ gfxdis.f3dex2 -x -g POLY_OPA_DISP++ -d DE00000012345678
gSPDisplayList(POLY_OPA_DISP++, 0x12345678);
```
so this one is
```C
gSPDisplayList(POLY_OPA_DISP++, D_06000680);
```
```C
temp_v0_3 = temp_s0->polyOpa.p;
temp_s0->polyOpa.p = temp_v0_3 + 8;
temp_v0_3->words.w0 = 0xDA380003;
sp20 = temp_v0_3;
sp20->words.w1 = Matrix_NewMtx(globalCtx->state.gfxCtx);
```
This is the same as the first one. Indeed, it's identical.
```C
temp_v0_4 = temp_s0->polyOpa.p;
temp_s0->polyOpa.p = temp_v0_4 + 8;
temp_v0_4->words.w1 = (u32) &D_060007C8;
temp_v0_4->words.w0 = 0xDE000000;
```
This is the same as the second one, but with a different second word.
Tidying up and inserting `OPEN_DISPS` and `CLOSE_DISPS`, we end up with
```C
void ObjTree_Draw(Actor* thisx, GlobalContext* globalCtx) {
s16 sp36 = (f32) thisx->shape.rot.x;
s16 sp34 = (f32) thisx->shape.rot.z;
OPEN_DISPS(globalCtx->state.gfxCtx);
func_8012C28C(globalCtx->state.gfxCtx);
gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(globalCtx->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
gSPDisplayList(POLY_OPA_DISP++, D_06000680);
Matrix_InsertRotation(sp36, 0, sp34, MTXMODE_APPLY);
gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(globalCtx->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
gSPDisplayList(POLY_OPA_DISP++, D_060007C8);
CLOSE_DISPS(globalCtx->state.gfxCtx);
}
```
## RGB macros and bitpacking
TODO: find some examples for this one.
For even more examples, you can consult [the OoT tutorial](https://github.com/zeldaret/oot/blob/master/docs/tutorial/draw_functions.md)
Next: [Data](data.md)
Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Some files were not shown because too many files have changed in this diff Show More