Remove tutorial, link to the latest zeldaret/oot:main revision with it (#2013)
* Remove tutorial, link to the latest zeldaret/oot:main revision with it * proofreading here too * rename to decompiling_tutorial --------- Co-authored-by: fig02 <fig02srl@gmail.com>
10
docs/decompiling_tutorial.md
Normal file
@ -0,0 +1,10 @@
|
||||
This repository used to contain a tutorial for how to do
|
||||
decompilation work in the repo.
|
||||
|
||||
It has been less useful as more and more was decompiled,
|
||||
and also more of a chore to keep up-to-date, so it has been
|
||||
removed from the repo.
|
||||
|
||||
It is still interesting for historical purposes or for
|
||||
curiosity, you can find the last version of it in revision
|
||||
[9963e7f5d5fa8caee329f6b40e393d8a2c45390b](https://github.com/zeldaret/oot/blob/9963e7f5d5fa8caee329f6b40e393d8a2c45390b/docs/tutorial/contents.md).
|
@ -1,926 +0,0 @@
|
||||
# Beginning decompilation: the Init function and the Actor struct
|
||||
|
||||
Up: [Contents](contents.md)
|
||||
|
||||
Open the C file and the H file with your actor's name from the appropriate directory in `src/overlays/actors/`. These will be the main files we work with. We will be using EnJj (Lord Jabu Jabu) as our example: despite being a fairly small actor, it has a number of interesting features.
|
||||
|
||||
Each actor has associated to it a data file and one assembly file per function. During the process, we will transfer the contents of all or most of these into the main C file. VSCode's search feature usually makes it quite easy to find the appropriate files without troubling the directory tree.
|
||||
|
||||
## Anatomy of the C file
|
||||
|
||||
The actor file starts off looking like:
|
||||
|
||||
![Fresh actor file annotated](images/fresh_actor_file_annotated.png)
|
||||
|
||||
It is currently divided into six sections as follows:
|
||||
|
||||
1. Preliminary description of the actor. This is not present for all actors, but gives a short description based on what we know about the actor already. It may be inaccurate, so feel free to correct it after you understand the actor better.
|
||||
|
||||
2. Specific `include`s and `define`s for the actor. You may need to add more header files, but otherwise this section is unlikely to change.
|
||||
|
||||
3. These are prototypes for the "main four" functions that almost every actor has. You add more functions here if they need to be declared above their first use.
|
||||
|
||||
4. A set of `extern`s. These refer to data that comes from other files, usually in the actor's corresponding object file. They point to addresses in the ROM where assets are stored (usually collision data, animations or display lists). Once the corresponding object files have been decompiled, these will simply be replaced by including the object file (see [Object Decompilation](object_decomp.md) for how this process works). For now, you can put them between blocks 5 and 6 to conform with how the rest of our files are structured. These symbols have been automatically extracted from the MIPS code. There may turn out to be some that were not caught by the script, in which case they need to be placed in the file called `undefined_syms.txt` in the root directory of the project. Ask in Discord for how to do this: it is simple, but rare enough to not be worth covering here.
|
||||
|
||||
5. Commented-out section containing the `InitVars`. This can be ignored until we import the data: it is a section of the actor data that has been imported automatically since, unlike most of the data, it has the same format in every actor. (This image is out of date: actors now also have their ColliderInits in here)
|
||||
|
||||
6. List of functions. Each `#pragma` is letting the compiler use the corresponding assembly file while we do not have decompiled C code for that function. The majority of the decompilation work is converting these functions into C that it looks like a human wrote.
|
||||
|
||||
## Header file
|
||||
|
||||
The header file looks like this at the moment:
|
||||
|
||||
![Fresh actor header](images/fresh_actor_header.png)
|
||||
|
||||
The struct currently contains a variable that is the `Actor` struct, which all actors use one way or another, plus other items. Currently we don't know what those items are, so we have an array of chars as padding instead, just so the struct is the right size. As we understand the actor better, we will be able to gradually replace this padding with the actual variables that the actor uses.
|
||||
|
||||
The header file is also used to declare structs and other information about the actor that is needed globally (e.g. by other actors).
|
||||
|
||||
## Order of decompilation
|
||||
|
||||
The general rule for order of decompilation is
|
||||
- Start with `Init`, because it usually contains the most information about the structure of the actor.
|
||||
- Next, decompile any other functions from the actor you have found in `Init`. You generally start with the action functions, because they return nothing and all take the same arguments,
|
||||
|
||||
```C
|
||||
void func_80whatever(EnJj* this, PlayState* play);
|
||||
```
|
||||
|
||||
- Decompile each action function in turn until you run out. Along the way, do any other functions in the actor for which you have discovered the argument types. (You are probably better doing depth-first on action functions than breadth-first: it's normally easier to follow along one branch of the actions than )
|
||||
|
||||
- After you've run out, do `Update`. This usually provides the rest of the function tree, apart from posibly some Draw functions.
|
||||
|
||||
- Finally, do the draw functions.
|
||||
|
||||
The above is a rough ordering for the beginner. As you become more experienced, you can deviate from this scheme, but the general principle remains that you should work on functions that you already know something about.
|
||||
|
||||
## Data
|
||||
|
||||
![Fresh actor data](images/fresh_actor_data.png)
|
||||
|
||||
Associated to each actor is a `.data` file, containing data that the actor uses. This ranges from spawn positions, to display lists, to even some cutscene data. Since the structure of the data is very inconsistent between actors, automatic importing has been very limited, so the vast majority must be done manually.
|
||||
|
||||
There are two ways of transfering the data into an actor: we can either
|
||||
- import it all naively as words (`s32`s), which will still allow it to compile, and sort out the actual types later, or
|
||||
- we can extern each piece of data as we come across it, and come back to it later when we have a better idea of what it is.
|
||||
|
||||
We will concentrate on the second here; the other is covered in [the document about data](data.md). Thankfully this means we essentially don't have to do anything to the data yet. Nevertheless, it is often quite helpful to copy over at least some of the data and leave it commented out for later replacement. *Data must go in the same order as in the data file, and data is "all or nothing": you cannot only import some of it*.
|
||||
|
||||
![Data copied in and commented out](images/data_inserted_commented_out.png)
|
||||
|
||||
**WARNING** The way in which the data was extracted from the ROM means that there are sometimes "fake symbols" in the data, which have to be removed to avoid confusing the compiler. Thankfully it will turn out that this is not the case here, although there will be other data issues.
|
||||
|
||||
(Sometimes it is useful to import the data in the middle of doing functions: you just have to choose an appropriate moment.)
|
||||
|
||||
|
||||
Some actors also have a `.bss` file. This is just data that is initialised to 0, and can be imported immediately once you know what type it is, by declaring it without giving it a value.
|
||||
|
||||
## Init
|
||||
|
||||
The Init function sets up the various components of the actor when it is first loaded. It is hence usually very useful for finding out what is in the actor struct, and so we usually start with it. (Some people like starting with Destroy, which is usually shorter and simpler, but gives some basic information about the actor, but Init is probably best for beginners.)
|
||||
|
||||
### mips2c
|
||||
|
||||
The first stage of decompilation is done by a program called mips2c or mips_to_c, which constructs a C interpretation of the assembly code based on reading it very literally. This means that considerable cleanup will be required to turn it into something that firstly compiles at all, and secondly looks like a human wrote it, let alone a Zelda developer from the late '90s.
|
||||
|
||||
The web version of mips2c can be found [here](https://simonsoftware.se/other/mips_to_c.py). There is also a downloadable version, but let's just use the online one for now.
|
||||
|
||||
Since the actor depends on the rest of the codebase, we can't expect to get much intelligible out of mips2c without giving it some context. We make this using a Python script in the `tools` directory called `m2ctx.py`, so run
|
||||
```
|
||||
$ ./tools/m2ctx.py <path_to_c_file>
|
||||
```
|
||||
from the main directory of the repository. In this case, the C file is `src/overlays/actors/ovl_En_Jj/z_en_jj.c`. This generates a file called `ctx.c` in the main directory of the repository. Open this file in a text editor (Notepad will do) and copy the whole contents into the "Existing C source, preprocessed" box.
|
||||
|
||||
![Copying the context](images/ctx.png)
|
||||
|
||||
Now, open the file containing the assembly for `EnJj_Init`.
|
||||
|
||||
![Copying the Init asm](images/init_asm.png)
|
||||
|
||||
Copy the entire contents of this file into the upper box, labelled "MIPS assembly". Now, for Init (and also the other "main 4" functions `Destroy`, `Update` and `Draw`), the function's first argument is `Actor* thisx`. But we would like mips2c to use the fields in the actual actor struct; we can make it do this by deliberately changing the prototype of the `EnJj_Init` in the pasted context to have first argument `EnJj* this` instead.
|
||||
|
||||
![Changing init prototype](images/changing_init_prototype.png)
|
||||
|
||||
Now press "Decompile". This should produce C code:
|
||||
```C
|
||||
void EnJj_Init(EnJj *this, PlayState *play) {
|
||||
CollisionHeader *sp4C;
|
||||
DynaCollisionContext *sp44;
|
||||
DynaCollisionContext *temp_a1;
|
||||
DynaCollisionContext *temp_a1_2;
|
||||
DynaCollisionContext *temp_a1_3;
|
||||
char *temp_v0_2;
|
||||
s16 temp_v0;
|
||||
|
||||
sp4C = NULL;
|
||||
Actor_ProcessInitChain((Actor *) this, &D_80A88CE0);
|
||||
ActorShape_Init(&this->actor.shape, 0.0f, NULL, 0.0f);
|
||||
temp_v0 = this->actor.params;
|
||||
temp_a1 = this + 0x164;
|
||||
[...]
|
||||
```
|
||||
|
||||
Typically for all buth the simplest functions, there is a lot that needs fixing before we are anywhere near seeing how close we are to the original code. You will notice that mips2c creates a lot of temporary variables. Usually most of these will turn out to not be real, and we need to remove the right ones to get the code to match.
|
||||
|
||||
First, change the first argument back to `Actor* thisx` so that the function matches its prototype above. To allow the function to find the variables, we need another correction. Half of this has already been done at the top of the function, where we have
|
||||
|
||||
```C
|
||||
#define THIS ((EnJj*)thisx)
|
||||
```
|
||||
|
||||
To do the other half, write the following at the beginning of the function, before any declarations:
|
||||
|
||||
```C
|
||||
EnJj* this = THIS;
|
||||
```
|
||||
|
||||
Now everything points to the right place, even though the argument of the function seems inconsistent with the contents.
|
||||
|
||||
(This step is only necessary for the "main 4" functions, and sometimes functions that are used by these: it relates to how such functions are used outside the actor.)
|
||||
|
||||
While we are carrying out initial changes, you can also find-and-replace any instances of `(Actor *) this` by `&this->actor`. The function now looks like this:
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Large code block, click to show.
|
||||
</summary>
|
||||
|
||||
```C
|
||||
void EnJj_Init(Actor *thisx, PlayState *play) {
|
||||
EnJj* this = THIS;
|
||||
|
||||
CollisionHeader *sp4C;
|
||||
DynaCollisionContext *sp44;
|
||||
DynaCollisionContext *temp_a1;
|
||||
DynaCollisionContext *temp_a1_2;
|
||||
DynaCollisionContext *temp_a1_3;
|
||||
char *temp_v0_2;
|
||||
s16 temp_v0;
|
||||
|
||||
sp4C = NULL;
|
||||
Actor_ProcessInitChain(&this->actor, &D_80A88CE0);
|
||||
ActorShape_Init(&this->actor.shape, 0.0f, NULL, 0.0f);
|
||||
temp_v0 = this->actor.params;
|
||||
temp_a1 = this + 0x164;
|
||||
if (temp_v0 == -1) {
|
||||
sp44 = temp_a1;
|
||||
SkelAnime_InitFlex(play, (SkelAnime *) temp_a1, (FlexSkeletonHeader *) &D_0600B9A8, (AnimationHeader *) &D_06001F4C, this + 0x1A8, this + 0x22C, 0x16);
|
||||
Animation_PlayLoop((SkelAnime *) sp44, (AnimationHeader *) &D_06001F4C);
|
||||
this->unk30A = (u16)0;
|
||||
this->unk30E = (u8)0;
|
||||
this->unk30F = (u8)0;
|
||||
this->unk310 = (u8)0;
|
||||
this->unk311 = (u8)0;
|
||||
if ((*(&gSaveContext + 0xEDA) & 0x400) != 0) {
|
||||
func_80A87800(this, &func_80A87BEC);
|
||||
} else {
|
||||
func_80A87800(this, &func_80A87C30);
|
||||
}
|
||||
this->unk300 = Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, (u16)0x5A, this->actor.world.pos.x - 10.0f, this->actor.world.pos.y, this->actor.world.pos.z, 0, (?32) this->actor.world.rot.y, 0, 0);
|
||||
DynaPolyActor_Init((DynaPolyActor *) this, 0);
|
||||
CollisionHeader_GetVirtual((void *) &D_06000A1C, &sp4C);
|
||||
this->unk_14C = DynaPoly_SetBgActor(play, &play->colCtx.dyna, &this->actor, sp4C);
|
||||
temp_a1_3 = this + 0x2B0;
|
||||
sp44 = temp_a1_3;
|
||||
Collider_InitCylinder(play, (ColliderCylinder *) temp_a1_3);
|
||||
Collider_SetCylinder(play, (ColliderCylinder *) temp_a1_3, &this->actor, &D_80A88CB4);
|
||||
this->actor.colChkInfo.mass = 0xFF;
|
||||
return;
|
||||
}
|
||||
if (temp_v0 == 0) {
|
||||
DynaPolyActor_Init((DynaPolyActor *) this, 0);
|
||||
CollisionHeader_GetVirtual((void *) &D_06001830, &sp4C);
|
||||
temp_a1_2 = &play->colCtx.dyna;
|
||||
sp44 = temp_a1_2;
|
||||
temp_v0_2 = DynaPoly_SetBgActor(play, temp_a1_2, &this->actor, sp4C);
|
||||
this->unk_14C = temp_v0_2;
|
||||
func_8003ECA8(play, temp_a1_2, (s32) temp_v0_2);
|
||||
this->actor.update = &func_80A87F44;
|
||||
this->actor.draw = NULL;
|
||||
Actor_SetScale(&this->actor, 0.087f);
|
||||
return;
|
||||
}
|
||||
if (temp_v0 != 1) {
|
||||
return;
|
||||
}
|
||||
DynaPolyActor_Init((DynaPolyActor *) this, 0);
|
||||
CollisionHeader_GetVirtual((void *) &D_0600BA8C, &sp4C);
|
||||
this->unk_14C = DynaPoly_SetBgActor(play, &play->colCtx.dyna, &this->actor, sp4C);
|
||||
this->actor.update = &func_80A87F44;
|
||||
this->actor.draw = NULL;
|
||||
Actor_SetScale(&this->actor, 0.087f);
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
In the next sections, we shall sort out the various initialisation functions that occur in Init. There are several types, and one of the reasons we are using EnJj as the example is that it has several of the most common ones. A disadvantage of this actor is that it has an unusually complicated Init: we can see that it does three different things depending on the value of its params.
|
||||
|
||||
### Init chains
|
||||
|
||||
Almost always, one of the first items in `Init` is a function that looks like
|
||||
|
||||
```C
|
||||
Actor_ProcessInitChain(&this->actor, &D_80A88CE0);
|
||||
```
|
||||
|
||||
which initialises common properties of actor using an InitChain, which is usually somewhere near the top of the data, in this case in the variable `D_80A88CE0`. Although we don't need to do this now since we we will extern the data, we might as well work out what it is now. Fortunately, we have a script to do this.
|
||||
|
||||
The InitChain script is also in the tools directory, and is called `ichaindis.py`. Simply passing it the ROM address will spit out the entire contents of the InitChain, in this case:
|
||||
|
||||
```
|
||||
$ ./tools/ichaindis.py baseroms/gc-eu-mq-dbg/baserom-decompressed.z64 80A88CE0
|
||||
static InitChainEntry sInitChain[] = {
|
||||
ICHAIN_VEC3F_DIV1000(unk_50, 87, ICHAIN_CONTINUE),
|
||||
ICHAIN_F32(unk_F4, 4000, ICHAIN_CONTINUE),
|
||||
ICHAIN_F32(unk_F8, 3300, ICHAIN_CONTINUE),
|
||||
ICHAIN_F32(unk_FC, 1100, ICHAIN_STOP),
|
||||
};
|
||||
```
|
||||
|
||||
However, some of these variables have now been given names in the Actor struct. Pass it `--names` to fill these in automatically:
|
||||
```
|
||||
$ ./tools/ichaindis.py --names baseroms/gc-eu-mq-dbg/baserom-decompressed.z64 80A88CE0
|
||||
static InitChainEntry sInitChain[] = {
|
||||
ICHAIN_VEC3F_DIV1000(scale, 87, ICHAIN_CONTINUE),
|
||||
ICHAIN_F32(uncullZoneForward, 4000, ICHAIN_CONTINUE),
|
||||
ICHAIN_F32(uncullZoneScale, 3300, ICHAIN_CONTINUE),
|
||||
ICHAIN_F32(uncullZoneDownward, 1100, ICHAIN_STOP),
|
||||
};
|
||||
```
|
||||
|
||||
Replace the commented-out .words for the `glabel D_80A88CE0` with this, and comment it out, instead adding
|
||||
```C
|
||||
extern InitChainEntry D_80A88CE0[];
|
||||
|
||||
```
|
||||
above it:
|
||||
|
||||
```C
|
||||
extern InitChainEntry D_80A88CE0[];
|
||||
// static InitChainEntry sInitChain[] = {
|
||||
// ICHAIN_VEC3F_DIV1000(scale, 87, ICHAIN_CONTINUE),
|
||||
// ICHAIN_F32(uncullZoneForward, 4000, ICHAIN_CONTINUE),
|
||||
// ICHAIN_F32(uncullZoneScale, 3300, ICHAIN_CONTINUE),
|
||||
// ICHAIN_F32(uncullZoneDownward, 1100, ICHAIN_STOP),
|
||||
// };
|
||||
```
|
||||
|
||||
(We will come back and actually import it after doing the rest of the actor.)
|
||||
|
||||
Since this is an array, we do not need the `&` in the function any more, which leaves us with
|
||||
```C
|
||||
Actor_ProcessInitChain(&this->actor, D_80A88CE0);
|
||||
```
|
||||
|
||||
in `EnJj_Init`.
|
||||
|
||||
### DynaPoly
|
||||
|
||||
Glancing through the rest of `EnJj_Init`, we notice some references to DynaPoly, for example
|
||||
```C
|
||||
DynaPolyActor_Init((DynaPolyActor *) this, 0);
|
||||
CollisionHeader_GetVirtual((void *) &D_06000A1C, &sp4C);
|
||||
this->unk_14C = DynaPoly_SetBgActor(play, &play->colCtx.dyna, &this->actor, sp4C);
|
||||
```
|
||||
|
||||
This means that EnJj is not an ordinary actor: it is instead a DynaPoly actor. In-game this is to do with how the actor interacts with Link and the environment (a good rule of thumb is that Link can often stand on DynaPoly actors as if they were ground). For decompilation purposes, it means that the actor struct is wrong: the first element of a DynaPoly actor's struct is not an `Actor` struct, but a `DynaPolyActor`, usually called `dyna`. We should fix this immediately to avoid confusion later. (Some actors have this correctly identified already; we were unlucky with this one.)
|
||||
|
||||
Since there's basically nothing in the struct at present, the change is easy to make: replace `Actor actor` with `DynaPolyActor dyna`. Now, `DynaPolyActor` is a different size to `Actor`, so we need to account for that. To find out what size it is, you need to find the definition. In VSCode you can usually Ctrl+Left Click on things to go to where they are defined. Doing so takes us to `z64actor.h`, where most actor-related structs are defined: we find
|
||||
```C
|
||||
typedef struct {
|
||||
/* 0x000 */ Actor actor;
|
||||
/* 0x14C */ u32 bgId;
|
||||
/* 0x150 */ f32 unk_150;
|
||||
/* 0x154 */ f32 unk_154;
|
||||
/* 0x158 */ s16 unk_158;
|
||||
/* 0x15A */ u16 unk_15A;
|
||||
/* 0x15C */ u32 unk_15C;
|
||||
/* 0x160 */ u8 unk_160;
|
||||
/* 0x162 */ s16 unk_162;
|
||||
} DynaPolyActor; // size = 0x164
|
||||
```
|
||||
|
||||
so a `DynaPolyActor` struct is an `Actor` with various other things after it. For now all we care about is the size, i.e. `0x164`. This tells us that the next thing after the `DynaPolyActor` struct in the `EnJj` struct begins at `0x164`, not `0x14C` as it does for `Actor`s.
|
||||
|
||||
So rename the variable to `unk_164` and change the comment to say `0x0164` (the comments don't do anything, they just make it easier to keep track of where everything is when it's named).
|
||||
|
||||
Next we need to adjust the size of the array so that the struct is still the right size. In this case, we just subtract the address of the padding variable from the total size of the struct:
|
||||
```0x314 - 0x164 = 1B0```. Hence the struct is now
|
||||
```C
|
||||
typedef struct EnJj {
|
||||
/* 0x0000 */ DynaPolyActor dyna;
|
||||
/* 0x0164 */ char unk_164[0x1B0];
|
||||
} EnJj; // size = 0x0314
|
||||
```
|
||||
|
||||
Now that we know this, it is worth remaking the context file and running mips2c again, since we have changed the struct significantly. Doing so, and replacing `(Actor*) this` with `&this->dyna.actor` this time, we find that the block we quoted above has become
|
||||
```C
|
||||
DynaPolyActor_Init((DynaPolyActor *) this, 0);
|
||||
CollisionHeader_GetVirtual((void *) &D_06000A1C, &sp4C);
|
||||
this->dyna.bgId = DynaPoly_SetBgActor(play, &play->colCtx.dyna, &this->dyna.actor, sp4C);
|
||||
```
|
||||
|
||||
Next, replace `(DynaPolyActor *) this` by `&this->dyna`. There's not a lot more we can do to the DynaPoly stuff right now, so just remove the casts to void and move on.
|
||||
|
||||
### Colliders
|
||||
|
||||
The next common thing that actors have is colliders. Not every actor has these, but most do, even if they don't just use them for collision system purposes.
|
||||
|
||||
The relevant functions in this actor are
|
||||
```C
|
||||
temp_a1_3 = this + 0x2B0;
|
||||
sp44 = temp_a1_3;
|
||||
Collider_InitCylinder(play, (ColliderCylinder *) temp_a1_3);
|
||||
Collider_SetCylinder(play, (ColliderCylinder *) temp_a1_3, &this->dyna.actor, &D_80A88CB4);
|
||||
```
|
||||
|
||||
Notice that `sp44` is set, but actually not used anywhere in the actor. This is a good indication that it is fake. We'll get back to that. Similarly, `temp_a1_3` is only used in these functions, so is likely to be fake as well: it's simply trying to get the pointer into the `a1` register.
|
||||
|
||||
Since mips2c doesn't know about the collider, it has just told us where it is, namely `this + 0x2B0`. So insert a `ColliderCylinder collider` variable in the actor struct, look up its size, and redo the padding. This should give
|
||||
```C
|
||||
typedef struct EnJj {
|
||||
/* 0x0000 */ DynaPolyActor dyna;
|
||||
/* 0x0164 */ char unk_164[0x14C];
|
||||
/* 0x02B0 */ ColliderCylinder collider;
|
||||
/* 0x02FC */ char unk_2FC[0x18];
|
||||
} EnJj; // size = 0x0314
|
||||
```
|
||||
|
||||
Now replace the temps, so we have
|
||||
```C
|
||||
Collider_InitCylinder(play, &this->collider);
|
||||
Collider_SetCylinder(play, &this->collider, &this->dyna.actor, &D_80A88CB4);
|
||||
```
|
||||
|
||||
(You may prefer to just comment out temps initially, to keep track of where they were.)
|
||||
|
||||
The last thing we need to deal with is the last variable of `Collider_SetCylinder`, which is again data.
|
||||
|
||||
<!--Also again we have a script to translate the raw data. This one is called `colliderinit.py`, and lives in `tools/overlayhelpers`. It takes the VRAM address of the data and the type of collider (for more info on use, pass it `-h`). We find
|
||||
```
|
||||
$ ./tools/overlayhelpers/colliderinit.py 80A88CB4 ColliderCylinderInit
|
||||
ovl_En_Jj: Rom 00E3E3D0:00E3F9E0 VRam 80A87800:80A88E10 Offset 0014B4
|
||||
|
||||
static ColliderCylinderInit sCylinderInit =
|
||||
{
|
||||
{ COLTYPE_UNK10, 0x00, 0x09, 0x39, 0x10, COLSHAPE_CYLINDER },
|
||||
{ 0x00, { 0x00000000, 0x00, 0x00 }, { 0x00000004, 0x00, 0x00 }, 0x00, 0x01, 0x01 },
|
||||
{ 170, 150, 0, { 0, 0, 0 } },
|
||||
};
|
||||
```
|
||||
|
||||
As with the InitChain, replace the commented-out data we copied into the C file with this:
|
||||
```C
|
||||
extern ColliderCylinderInit D_80A88CB4;
|
||||
// static ColliderCylinderInit sCylinderInit =
|
||||
// {
|
||||
// { COLTYPE_UNK10, 0x00, 0x09, 0x39, 0x10, COLSHAPE_CYLINDER },
|
||||
// { 0x00, { 0x00000000, 0x00, 0x00 }, { 0x00000004, 0x00, 0x00 }, 0x00, 0x01, 0x01 },
|
||||
// { 170, 150, 0, { 0, 0, 0 } },
|
||||
// };
|
||||
```-->
|
||||
|
||||
This is already in the actor file in the correct format, all you need to do is add an extern for it underneath:
|
||||
```C
|
||||
/*
|
||||
[...]
|
||||
*/
|
||||
extern ColliderCylinderInit D_80A88CB4;
|
||||
```
|
||||
|
||||
Unlike the InitChain, this is not an array, so keep the `&` in the function.
|
||||
|
||||
### SkelAnime
|
||||
|
||||
This is the combined system that handles actors' skeletons and their animations. It is the other significant part of most actor structs. We see its initialisation in this part of the code:
|
||||
```C
|
||||
temp_a1 = this->unk_164;
|
||||
...
|
||||
sp44 = (DynaCollisionContext *) temp_a1;
|
||||
SkelAnime_InitFlex(play, (SkelAnime *) temp_a1, (FlexSkeletonHeader *) &D_0600B9A8, (AnimationHeader *) &D_06001F4C, this + 0x1A8, this + 0x22C, 0x16);
|
||||
Animation_PlayLoop((SkelAnime *) sp44, (AnimationHeader *) &D_06001F4C);
|
||||
```
|
||||
|
||||
(Both of the temps are likely to be fake.)
|
||||
|
||||
An actor with SkelAnime has three structs in the Actor struct that handle it: one called SkelAnime, and two arrays of `Vec3s`, called `jointTable` and `overrideDrawTable` (for now). Usually, although not always, they are next to one another.
|
||||
|
||||
There are two different sorts of SkelAnime, although for decompilation purposes there is not much difference between them. From `SkelAnime_InitFlex` we can read off that
|
||||
|
||||
- The `SkelAnime` struct is at `this + 0x164`
|
||||
- The `jointTable` is at `this + 0x1A8`
|
||||
- The `overrideDrawTable` is at `this + 0x22C`
|
||||
- The number of limbs is `0x16 = 22`
|
||||
- Hence the `jointTable` and `overrideDrawTable` both have `22` elements
|
||||
|
||||
Looking in `z64animation.h`, we find that `SkelAnime` has size `0x44`, and looking in `z64math.h`, that `Vec3s` has size `0x6`. Since ` 0x164 + 0x44 = 0x1A8 `, `jointTable` is immediately after the `SkelAnime`, and since `0x1A8 + 0x6 * 0x16 = 0x22C`, `overrideDrawTable` is immediately after the `jointTable`. Finally, `0x22C + 0x6 * 0x16 = 2B0`, and we have filled all the space between the `dyna` and `collider`. Therefore the struct now looks like
|
||||
```C
|
||||
typedef struct EnJj {
|
||||
/* 0x0000 */ DynaPolyActor dyna;
|
||||
/* 0x0164 */ SkelAnime skelAnime;
|
||||
/* 0x01A8 */ Vec3s jointTable[22];
|
||||
/* 0x022C */ Vec3s overrideDrawTable[22];
|
||||
/* 0x02B0 */ ColliderCylinder collider;
|
||||
/* 0x02FC */ char unk_2FC[0x18];
|
||||
} EnJj; // size = 0x0314
|
||||
```
|
||||
|
||||
The last information we get from the SkelAnime functions is the types of two of the externed symbols: `D_0600B9A8` is a `FlexSkeletonHeader`, and `D_06001F4C` is an `AnimationHeader`. So we can change these in the C file:
|
||||
|
||||
```C
|
||||
extern UNK_TYPE D_06000A1C;
|
||||
extern UNK_TYPE D_06001830;
|
||||
extern AnimationHeader D_06001F4C;
|
||||
extern FlexSkeletonHeader D_0600B9A8;
|
||||
extern UNK_TYPE D_0600BA8C;
|
||||
```
|
||||
|
||||
and removing the temps,
|
||||
```C
|
||||
SkelAnime_InitFlex(play, &this->skelAnime, &D_0600B9A8, &D_06001F4C, this->jointTable, this->morphTable, 22);
|
||||
Animation_PlayLoop(&this->skelAnime, &D_06001F4C);
|
||||
```
|
||||
|
||||
### More struct variables
|
||||
|
||||
This function also gives us information about other things in the struct. One obvious thing that sticks out is
|
||||
```C
|
||||
this->unk300 = Actor_SpawnAsChild(&play->actorCtx, &this->dyna.actor, play, (u16)0x5A, this->dyna.actor.world.pos.x - 10.0f, this->dyna.actor.world.pos.y, this->dyna.actor.world.pos.z, 0, (?32) this->dyna.actor.world.rot.y, 0, 0);
|
||||
```
|
||||
Hovering over this function tells us it outputs a pointer to the spawned actor, so `this->unk_300` is an `Actor*`. We may or may not care what this actor actually is, depending on how it is used later on, so let's just add `/* 0x0300 */ Actor* childActor` to the struct for now.
|
||||
|
||||
We can look up what the actor with ID 0x5A is in `z64actor.h`: we find it is `ACTOR_EN_JJ`. So some Jabus spawn another Jabu. Filling this in and removing the spurious cast, we have
|
||||
```C
|
||||
this->childActor = Actor_SpawnAsChild(&play->actorCtx, &this->dyna.actor, play, 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);
|
||||
```
|
||||
|
||||
Finally, we have this block:
|
||||
```C
|
||||
this->unk30A = (u16)0;
|
||||
this->unk30E = (u8)0;
|
||||
this->unk30F = (u8)0;
|
||||
this->unk310 = (u8)0;
|
||||
this->unk311 = (u8)0;
|
||||
```
|
||||
This is not quite as helpful as you might think: it tells us the size of these variables, but despite mips2c's assertion that they are all unsigned, they may actually be signed: you can't tell from the MIPS unless they are loaded: there is only `sh`, but there are both `lh` and `lhu`, for example. There's not much to choose between them when guessing, but generally signed is a better guess with no other context. For unnamed struct variables, our convention is `unk_30A` etc. Adding them to the struct, we end up with
|
||||
<details>
|
||||
<summary>
|
||||
Large code block, click to show.
|
||||
</summary>
|
||||
|
||||
```C
|
||||
typedef struct EnJj {
|
||||
/* 0x0000 */ DynaPolyActor dyna;
|
||||
/* 0x0164 */ SkelAnime skelAnime;
|
||||
/* 0x01A8 */ Vec3s jointTable[22];
|
||||
/* 0x022C */ Vec3s morphTable[22];
|
||||
/* 0x02B0 */ ColliderCylinder collider;
|
||||
/* 0x02FC */ char unk_2FC[0x4];
|
||||
/* 0x0300 */ Actor* childActor;
|
||||
/* 0x0304 */ char unk_304[0x6];
|
||||
/* 0x030A */ s16 unk_30A;
|
||||
/* 0x030C */ char unk_30C[0x2];
|
||||
/* 0x030E */ s8 unk_30E;
|
||||
/* 0x030F */ s8 unk_30F;
|
||||
/* 0x0310 */ s8 unk_310;
|
||||
/* 0x0311 */ s8 unk_311;
|
||||
/* 0x0312 */ char unk_312[0x2];
|
||||
} EnJj; // size = 0x0314
|
||||
```
|
||||
|
||||
We can remove a few more temps that don't look real, and end up with
|
||||
```C
|
||||
void EnJj_Init(Actor *thisx, PlayState *play) {
|
||||
EnJj* this = THIS;
|
||||
|
||||
CollisionHeader *sp4C;
|
||||
// DynaCollisionContext *sp44;
|
||||
// DynaCollisionContext *temp_a1_2;
|
||||
// DynaCollisionContext *temp_a1_3;
|
||||
// char *temp_a1;
|
||||
s16 temp_v0;
|
||||
// u32 temp_v0_2;
|
||||
|
||||
sp4C = NULL;
|
||||
Actor_ProcessInitChain(&this->dyna.actor, D_80A88CE0);
|
||||
ActorShape_Init(&this->dyna.actor.shape, 0.0f, NULL, 0.0f);
|
||||
temp_v0 = this->dyna.actor.params;
|
||||
// temp_a1 = this->unk_164;
|
||||
if (temp_v0 == -1) {
|
||||
// sp44 = (DynaCollisionContext *) temp_a1;
|
||||
SkelAnime_InitFlex(play, &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 + 0xEDA) & 0x400) != 0) {
|
||||
func_80A87800(this, &func_80A87BEC);
|
||||
} else {
|
||||
func_80A87800(this, &func_80A87C30);
|
||||
}
|
||||
this->childActor = Actor_SpawnAsChild(&play->actorCtx, &this->dyna.actor, play, 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(play, &play->colCtx.dyna, &this->dyna.actor, sp4C);
|
||||
// temp_a1_3 = this + 0x2B0;
|
||||
// sp44 = temp_a1_3;
|
||||
Collider_InitCylinder(play, &this->collider);
|
||||
Collider_SetCylinder(play, &this->collider, &this->dyna.actor, &D_80A88CB4);
|
||||
this->dyna.actor.colChkInfo.mass = 0xFF;
|
||||
return;
|
||||
}
|
||||
if (temp_v0 == 0) {
|
||||
DynaPolyActor_Init(&this->dyna, 0);
|
||||
CollisionHeader_GetVirtual(&D_06001830, &sp4C);
|
||||
// temp_a1_2 = &play->colCtx.dyna;
|
||||
// sp44 = temp_a1_2;
|
||||
this->dyna.bgId = DynaPoly_SetBgActor(play, &play->colCtx.dyna, &this->dyna.actor, sp4C);
|
||||
func_8003ECA8(play, &play->colCtx.dyna, this->dyna.bgId);
|
||||
this->dyna.actor.update = &func_80A87F44;
|
||||
this->dyna.actor.draw = NULL;
|
||||
Actor_SetScale(&this->dyna.actor, 0.087f);
|
||||
return;
|
||||
}
|
||||
if (temp_v0 != 1) {
|
||||
return;
|
||||
}
|
||||
DynaPolyActor_Init(&this->dyna, 0);
|
||||
CollisionHeader_GetVirtual(&D_0600BA8C, &sp4C);
|
||||
this->dyna.bgId = DynaPoly_SetBgActor(play, &play->colCtx.dyna, &this->dyna.actor, sp4C);
|
||||
this->dyna.actor.update = &func_80A87F44;
|
||||
this->dyna.actor.draw = NULL;
|
||||
Actor_SetScale(&this->dyna.actor, 0.087f);
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
This will still not compile without errors: we need to know what the functions it calls are.
|
||||
|
||||
### Functions called
|
||||
|
||||
Function pointers do not need `&`, so remove all those. There are three functions that are called in this actor. Firstly, `this->dyna.actor.update = func_80A87F44;` tells us that `func_80A87F44` is an alternative update function for this actor. We therefore give it a prototype similar to the original Update:
|
||||
```C
|
||||
void EnJj_Init(Actor* thisx, PlayState* play);
|
||||
void EnJj_Destroy(Actor* thisx, PlayState* play);
|
||||
void EnJj_Update(Actor* thisx, PlayState* play);
|
||||
void EnJj_Draw(Actor* thisx, PlayState* play);
|
||||
|
||||
void func_80A87F44(Actor* thisx, PlayState* play);
|
||||
```
|
||||
|
||||
Unfortunately the others are not so easy to deal with. In order to find out what type the functions called by `func_80A87800`, we have to look at `func_80A87800` itself. But fortunately, this is the entire MIPS for `func_80A87800`:
|
||||
|
||||
```MIPS
|
||||
glabel func_80A87800
|
||||
/* 00000 80A87800 03E00008 */ jr $ra
|
||||
/* 00004 80A87804 AC8502FC */ sw $a1, 0x02FC($a0) ## 000002FC
|
||||
```
|
||||
|
||||
This is simple enough to read that we don't even need to appeal to mips2c: it saves its second argument into its first argument `+ 0x2FC`. Many actors use this type of function, which we call `SetupAction`: it simply changes the action function.
|
||||
|
||||
*Action functions* are the main other kind of function in most actors: they are usually run by Update every frame, and carry out the main actions that the actor does (hence the name). They all have the same arguments, and so we have a typedef for such things: it is
|
||||
```C
|
||||
typedef void (*EnJjActionFunc)(struct EnJj*, PlayState*);
|
||||
```
|
||||
Put this between `struct EnJj;` and the actor struct in the header file. This also gives us another bit of the struct, conveniently plugging the gap at `0x2FC`:
|
||||
```C
|
||||
/* 0x02FC */ EnJjActionFunc actionFunc;
|
||||
```
|
||||
|
||||
We have actually learnt three useful pieces of information from this, the other two being that the function above Init is simply
|
||||
```C
|
||||
void func_80A87800(EnJj* this, EnJjActionFunc actionFunc) {
|
||||
this->actionFunc = actionFunc;
|
||||
}
|
||||
```
|
||||
|
||||
and that `func_80A87BEC` and `func_80A87C30`, passed to it in `EnJj_Init`, are action functions. Since they are first used above where they are defined, we prototype them at the top as well,
|
||||
```C
|
||||
void EnJj_Init(Actor* thisx, PlayState* play);
|
||||
void EnJj_Destroy(Actor* thisx, PlayState* play);
|
||||
void EnJj_Update(Actor* thisx, PlayState* play);
|
||||
void EnJj_Draw(Actor* thisx, PlayState* play);
|
||||
|
||||
void func_80A87F44(Actor* thisx, PlayState* play);
|
||||
void func_80A87BEC(EnJj* this, PlayState* play);
|
||||
void func_80A87C30(EnJj* this, PlayState* play);
|
||||
```
|
||||
|
||||
|
||||
### Other pointer issues
|
||||
|
||||
Mips2c is bad at arrays. We see this in the `(*(&gSaveContext + 0xEDA) & 0x400) != 0`, which will actually stop the compiler working. We need to go and look up what this is actually pointing to, and replace it in the code. Following the trail, we find that:
|
||||
|
||||
1. `gSaveContext` is of type `SaveContext`
|
||||
2. The struct `SaveContext` is defined in `z64save.h`
|
||||
3. The entry in `SaveContext` that contains `0xEDA` is `/* 0x0ED4 */ u16 eventChkInf[14];`
|
||||
4. Since `0xEDA - 0xED4 = 0x6`, and `u16`s take up 2 bytes each, we conclude that it is `eventChkInf[3]` that is being referenced.
|
||||
|
||||
Therefore, the condition should be `(gSaveContext.save.info.eventChkInf[3] & 0x400) != 0`. This is a flag comparison, so we can also leave off the `!= 0`.
|
||||
|
||||
With all of this implemented, the function should now compile without errors. The parts of the file that we have changed now look like
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Large code block, click to show.
|
||||
</summary>
|
||||
|
||||
```C
|
||||
void EnJj_Init(Actor* thisx, PlayState* play);
|
||||
void EnJj_Destroy(Actor* thisx, PlayState* play);
|
||||
void EnJj_Update(Actor* thisx, PlayState* play);
|
||||
void EnJj_Draw(Actor* thisx, PlayState* play);
|
||||
|
||||
void func_80A87F44(Actor* thisx, PlayState* play);
|
||||
void func_80A87BEC(EnJj* this, PlayState* play);
|
||||
void func_80A87C30(EnJj* this, PlayState* play);
|
||||
|
||||
#if 0
|
||||
ActorInit En_Jj_InitVars = {
|
||||
/**/ ACTOR_EN_JJ,
|
||||
/**/ ACTORTYPE_ITEMACTION,
|
||||
/**/ FLAGS,
|
||||
/**/ OBJECT_JJ,
|
||||
/**/ sizeof(EnJj),
|
||||
/**/ EnJj_Init,
|
||||
/**/ EnJj_Destroy,
|
||||
/**/ EnJj_Update,
|
||||
/**/ EnJj_Draw,
|
||||
};
|
||||
#endif
|
||||
|
||||
extern ColliderCylinderInit D_80A88CB4;
|
||||
// static ColliderCylinderInit sCylinderInit = {
|
||||
// {
|
||||
// COLTYPE_NONE,
|
||||
// AT_NONE,
|
||||
// AC_ON | AC_TYPE_PLAYER,
|
||||
// OC1_ON | OC1_TYPE_ALL,
|
||||
// OC2_TYPE_1,
|
||||
// COLSHAPE_CYLINDER,
|
||||
// },
|
||||
// {
|
||||
// ELEMTYPE_UNK0,
|
||||
// { 0x00000000, 0x00, 0x00 },
|
||||
// { 0x00000004, 0x00, 0x00 },
|
||||
// ATELEM_NONE,
|
||||
// ACELEM_ON,
|
||||
// OCELEM_ON,
|
||||
// },
|
||||
// { 170, 150, 0, { 0, 0, 0 } },
|
||||
// };
|
||||
|
||||
extern InitChainEntry D_80A88CE0[];
|
||||
// static InitChainEntry sInitChain[] = {
|
||||
// ICHAIN_VEC3F_DIV1000(scale, 87, ICHAIN_CONTINUE),
|
||||
// ICHAIN_F32(uncullZoneForward, 4000, ICHAIN_CONTINUE),
|
||||
// ICHAIN_F32(uncullZoneScale, 3300, ICHAIN_CONTINUE),
|
||||
// ICHAIN_F32(uncullZoneDownward, 1100, ICHAIN_STOP),
|
||||
// };
|
||||
|
||||
// glabel D_80A88CF0
|
||||
// .word 0xC4C6A000, 0x42540000, 0xC22C0000
|
||||
// glabel D_80A88CFC
|
||||
// .word 0x06007698, 0x06007A98, 0x06007E98, 0x00000000, 0x00000000
|
||||
|
||||
|
||||
extern UNK_TYPE D_06000A1C;
|
||||
extern UNK_TYPE D_06001830;
|
||||
extern AnimationHeader D_06001F4C;
|
||||
extern FlexSkeletonHeader D_0600B9A8;
|
||||
extern UNK_TYPE D_0600BA8C;
|
||||
|
||||
// #pragma GLOBAL_ASM("asm/non_matchings/overlays/actors/ovl_En_Jj/func_80A87800.s")
|
||||
void func_80A87800(EnJj* this, EnJjActionFunc actionFunc) {
|
||||
this->actionFunc = actionFunc;
|
||||
}
|
||||
|
||||
// #pragma GLOBAL_ASM("asm/non_matchings/overlays/actors/ovl_En_Jj/EnJj_Init.s")
|
||||
void EnJj_Init(Actor *thisx, PlayState *play) {
|
||||
EnJj* this = THIS;
|
||||
|
||||
CollisionHeader *sp4C;
|
||||
// DynaCollisionContext *sp44;
|
||||
// DynaCollisionContext *temp_a1_2;
|
||||
// DynaCollisionContext *temp_a1_3;
|
||||
// char *temp_a1;
|
||||
s16 temp_v0;
|
||||
// u32 temp_v0_2;
|
||||
|
||||
sp4C = NULL;
|
||||
Actor_ProcessInitChain(&this->dyna.actor, D_80A88CE0);
|
||||
ActorShape_Init(&this->dyna.actor.shape, 0.0f, NULL, 0.0f);
|
||||
temp_v0 = this->dyna.actor.params;
|
||||
// temp_a1 = this->unk_164;
|
||||
if (temp_v0 == -1) {
|
||||
// sp44 = (DynaCollisionContext *) temp_a1;
|
||||
SkelAnime_InitFlex(play, &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.save.info.eventChkInf[3] & 0x400) != 0) {
|
||||
func_80A87800(this, func_80A87BEC);
|
||||
} else {
|
||||
func_80A87800(this, func_80A87C30);
|
||||
}
|
||||
this->childActor = Actor_SpawnAsChild(&play->actorCtx, &this->dyna.actor, play, 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(play, &play->colCtx.dyna, &this->dyna.actor, sp4C);
|
||||
// temp_a1_3 = this + 0x2B0;
|
||||
// sp44 = temp_a1_3;
|
||||
Collider_InitCylinder(play, &this->collider);
|
||||
Collider_SetCylinder(play, &this->collider, &this->dyna.actor, &D_80A88CB4);
|
||||
this->dyna.actor.colChkInfo.mass = 0xFF;
|
||||
return;
|
||||
}
|
||||
if (temp_v0 == 0) {
|
||||
DynaPolyActor_Init(&this->dyna, 0);
|
||||
CollisionHeader_GetVirtual(&D_06001830, &sp4C);
|
||||
// temp_a1_2 = &play->colCtx.dyna;
|
||||
// sp44 = temp_a1_2;
|
||||
this->dyna.bgId = DynaPoly_SetBgActor(play, &play->colCtx.dyna, &this->dyna.actor, sp4C);
|
||||
func_8003ECA8(play, &play->colCtx.dyna, this->dyna.bgId);
|
||||
this->dyna.actor.update = func_80A87F44;
|
||||
this->dyna.actor.draw = NULL;
|
||||
Actor_SetScale(&this->dyna.actor, 0.087f);
|
||||
return;
|
||||
}
|
||||
if (temp_v0 != 1) {
|
||||
return;
|
||||
}
|
||||
DynaPolyActor_Init(&this->dyna, 0);
|
||||
CollisionHeader_GetVirtual(&D_0600BA8C, &sp4C);
|
||||
this->dyna.bgId = DynaPoly_SetBgActor(play, &play->colCtx.dyna, &this->dyna.actor, sp4C);
|
||||
this->dyna.actor.update = func_80A87F44;
|
||||
this->dyna.actor.draw = NULL;
|
||||
Actor_SetScale(&this->dyna.actor, 0.087f);
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
## Diff
|
||||
|
||||
Once preliminary cleanup and struct filling is done, most time spent matching functions is done by comparing the original code with the code you have compiled. This is aided by a program called `diff.py`.
|
||||
|
||||
In order to use `diff.py` with the symbol names, we need a copy of the code to compare against. This is done by copying the `build` directory into a directory called `expected`. Copying in Windows on WSL is very slow, so run
|
||||
```
|
||||
$ mkdir expected
|
||||
cp -r build expected/
|
||||
```
|
||||
from the main directory of the repository. You should end up with the directory structure `expected/build/...`.
|
||||
|
||||
You may want to do this again when you start renaming functions. *Make sure that you copy an OK build, or you are going to get very confused.* You should also do this again after needing to do a `make clean`.
|
||||
|
||||
Now, we run diff on the function name: in the main directory,
|
||||
```
|
||||
$ ./diff.py -mwo3 EnJj_Init
|
||||
```
|
||||
|
||||
(To see what these arguments do, run it with `./diff.py -h` or look in the scripts documentation.)
|
||||
|
||||
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, PlayState* play) {
|
||||
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(play, &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.save.info.eventChkInf[3] & 0x400) != 0) {
|
||||
func_80A87800(this, func_80A87BEC);
|
||||
} else {
|
||||
func_80A87800(this, func_80A87C30);
|
||||
}
|
||||
this->childActor = Actor_SpawnAsChild(
|
||||
&play->actorCtx, &this->dyna.actor, play, 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(play, &play->colCtx.dyna, &this->dyna.actor, sp4C);
|
||||
Collider_InitCylinder(play, &this->collider);
|
||||
Collider_SetCylinder(play, &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 = &play->colCtx.dyna;
|
||||
// sp44 = temp_a1_2;
|
||||
this->dyna.bgId =
|
||||
DynaPoly_SetBgActor(play, &play->colCtx.dyna, &this->dyna.actor, sp4C);
|
||||
func_8003ECA8(play, &play->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(play, &play->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 `play` (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, PlayState* play2) {
|
||||
PlayState* play = play2;
|
||||
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
|
||||
```
|
||||
oot-gc-eu-mq-dbg.z64: OK
|
||||
```
|
||||
|
||||
which is either a sense of triumph or relief depending on how long you've spent on a function.
|
||||
|
||||
And with that, we have successfully matched our first function. (Or first two counting `func_80A87800`)
|
||||
|
||||
**N.B** Notice that we don't yet have much idea of what this code actually does: this should be clarified by going through the rest of the actor's functions, which is discussed in the next document.
|
||||
|
||||
Next: [Other functions in the actor](other_functions.md)
|
@ -1,62 +0,0 @@
|
||||
# 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)
|
||||
- Most of us use VSCode. (You can watch Fig's video to get an idea of how this can be used). Some useful information is [here](../vscode.md).
|
||||
- Choosing a first actor (You want something small that has simple interactions with the environment. But OoT is far enough in that there are basically no unreserved actors left anyway now.)
|
||||
|
||||
## 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
|
||||
|
||||
- Matching
|
||||
- Using diff
|
||||
- control flow (branches) -> instruction ordering -> register allocation -> stack
|
||||
|
||||
- [The rest of the functions in the actor](other_functions.md)
|
||||
- Order of decompilation
|
||||
- Action Functions and other functions
|
||||
|
||||
- More on matching: the permuter
|
||||
|
||||
- [Draw functions](draw_functions.md)
|
||||
|
||||
- [Data, migration and non-migration](data.md)
|
||||
- Importing the data: early and late
|
||||
- Fake symbols
|
||||
- Inlining
|
||||
|
||||
## [Object Decompilation](object_decomp.md)
|
||||
- Object files
|
||||
- How we decompile objects
|
||||
- [Example](object_decomp_example.md)
|
||||
|
||||
|
||||
## After Decompilation
|
||||
|
||||
- [Preparing to merge](merging.md)
|
||||
- Preliminary documentation
|
||||
- Preparing to PR
|
||||
- Pull Requests
|
||||
- Trello
|
||||
|
||||
## Appendices
|
||||
- [Types, Structs and Padding](types_structs_padding.md) (a miscellany of useful stuff)
|
||||
- [Helper scripts](helper_scripts.md)
|
||||
|
||||
To be written, maybe
|
||||
|
||||
- How we use git and GitHub
|
||||
- Some notes on the basic structure of N64 MIPS
|
||||
- Glossary
|
||||
- Conventions
|
@ -1,648 +0,0 @@
|
||||
# Data
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Data first](#data-first)
|
||||
* [Example: `EnTg`](#example-entg)
|
||||
- [Extern and data last](#extern-and-data-last)
|
||||
- [Fake symbols](#fake-symbols)
|
||||
- [Inlining](#inlining)
|
||||
|
||||
Each actor's data is stored in a separate file. EnJj's data is in `data/overlays/actors/z_en_jj.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
|
||||
|
||||
<!-- Fig shows how to do this in his video. -->
|
||||
This way is good for smaller actors with little data. It is not really suitable for EnJj, for example, because of the enormous section of data labelled as `D_80A88164`.
|
||||
|
||||
### Example: `EnTg`
|
||||
|
||||
We give a simple example of this approach with a small NPC actor, EnTg, that is, the spinning couple.
|
||||
|
||||
The data file looks like
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Large code block, click to show
|
||||
</summary>
|
||||
|
||||
```
|
||||
.include "macro.inc"
|
||||
|
||||
/* assembler directives */
|
||||
.set noat /* allow manual use of $at */
|
||||
.set noreorder /* don't insert nops after branches */
|
||||
.set gp=64 /* allow use of 64-bit general purpose registers */
|
||||
|
||||
.section .data
|
||||
|
||||
.balign 16
|
||||
|
||||
glabel D_80B18910
|
||||
.word 0x0A000039, 0x20010000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000100, 0x00140040, 0x00000000, 0x00000000
|
||||
glabel D_80B1893C
|
||||
.word 0x00000000, 0x00000000, 0xFF000000
|
||||
glabel En_Tg_InitVars
|
||||
.word 0x01AC0400, 0x00000009, 0x01820000, 0x0000020C
|
||||
.word EnTg_Init
|
||||
.word EnTg_Destroy
|
||||
.word EnTg_Update
|
||||
.word EnTg_Draw
|
||||
glabel D_80B18968
|
||||
.word 0x00000000, 0x44480000, 0x00000000, 0x00000000, 0x00000000, 0x00000000
|
||||
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
We transfer this data into the actor file by pretending it is an array of words. The InitVars have already been processed and inserted into the C file, so just need to be uncommented. Data cannot change order, so the two pieces above the InitVars must stay there. At the end of this process, the top of the file will look like
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Large code block, click to show
|
||||
</summary>
|
||||
|
||||
```C
|
||||
/*
|
||||
* File: z_en_tg.c
|
||||
* Overlay: ovl_En_Tg
|
||||
* Description: Honey & Darling
|
||||
*/
|
||||
|
||||
#include "z_en_tg.h"
|
||||
|
||||
#define FLAGS 0x00000009
|
||||
|
||||
#define THIS ((EnTg*)thisx)
|
||||
|
||||
void EnTg_Init(Actor* thisx, PlayState* play);
|
||||
void EnTg_Destroy(Actor* thisx, PlayState* play);
|
||||
void EnTg_Update(Actor* thisx, PlayState* play);
|
||||
void EnTg_Draw(Actor* thisx, PlayState* play);
|
||||
|
||||
s32 D_80B18910[] = { 0x0A000039, 0x20010000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000100, 0x00140040, 0x00000000, 0x00000000 };
|
||||
|
||||
s32 D_80B1893C[] = { 0x00000000, 0x00000000, 0xFF000000 };
|
||||
|
||||
ActorInit En_Tg_InitVars = {
|
||||
/**/ ACTOR_EN_TG,
|
||||
/**/ ACTORTYPE_NPC,
|
||||
/**/ FLAGS,
|
||||
/**/ OBJECT_MU,
|
||||
/**/ sizeof(EnTg),
|
||||
/**/ EnTg_Init,
|
||||
/**/ EnTg_Destroy,
|
||||
/**/ EnTg_Update,
|
||||
/**/ EnTg_Draw,
|
||||
};
|
||||
|
||||
s32 D_80B18968[] = { 0x00000000, 0x44480000, 0x00000000, 0x00000000, 0x00000000, 0x00000000 };
|
||||
|
||||
extern UNK_TYPE D_06005040;
|
||||
extern UNK_TYPE D_0600AE40;
|
||||
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
Now, open the file called `spec` in the base directory, find the section corresponding to EnTg:
|
||||
```
|
||||
beginseg
|
||||
name "ovl_En_Tg"
|
||||
include "$(BUILD_DIR)/src/overlays/actors/ovl_En_Tg/z_en_tg.o"
|
||||
include "$(BUILD_DIR)/data/overlays/actors/z_en_tg.data.o"
|
||||
include "$(BUILD_DIR)/data/overlays/actors/z_en_tg.reloc.o"
|
||||
endseg
|
||||
```
|
||||
and comment out the .data line,
|
||||
```
|
||||
beginseg
|
||||
name "ovl_En_Tg"
|
||||
include "$(BUILD_DIR)/src/overlays/actors/ovl_En_Tg/z_en_tg.o"
|
||||
//include "$(BUILD_DIR)/data/overlays/actors/z_en_tg.data.o"
|
||||
include "$(BUILD_DIR)/data/overlays/actors/z_en_tg.reloc.o"
|
||||
endseg
|
||||
```
|
||||
to tell the compiler not to look for the data in that file any more. Now run `make -j`, and if you did both steps correctly, you should get `OK`.
|
||||
|
||||
Now carry out the usual steps to decompile `Init`. The usual cleanup and struct population gets us to
|
||||
```C
|
||||
void EnTg_Init(Actor *thisx, PlayState *play) {
|
||||
EnTg *this = THIS;
|
||||
|
||||
ActorShape_Init(&this->actor.shape, 0.0f, ActorShadow_DrawFunc_Circle, 28.0f);
|
||||
SkelAnime_InitFlex(play, &this->skelAnime, &D_0600AE40, &D_06005040, 0, 0, 0);
|
||||
Collider_InitCylinder(play, &this->collider);
|
||||
Collider_SetCylinder(play, &this->collider, &this->actor, (ColliderCylinderInit *) D_80B18910);
|
||||
func_80061EFC(&this->actor.colChkInfo, NULL, (CollisionCheckInfoInit2 *) D_80B1893C);
|
||||
this->actor.unk_1F = 6;
|
||||
Actor_SetScale(&this->actor, 0.01f);
|
||||
this->actionFunc = func_80B185C0;
|
||||
this->unk_208 = play->state.frames & 1;
|
||||
}
|
||||
```
|
||||
and it remains to deal with the data. mips2c has told us what the types should be. We run `colliderinit` on `D_80B18910` as usual, which gives
|
||||
```
|
||||
$ ./tools/overlayhelpers/colliderinit.py 80B18910 ColliderCylinderInit
|
||||
ovl_En_Tg: Rom 00ECE1F0:00ECE910 VRam 80B18360:80B18A80 Offset 0005B0
|
||||
|
||||
static ColliderCylinderInit sCylinderInit =
|
||||
{
|
||||
{ COLTYPE_UNK10, 0x00, 0x00, 0x39, 0x20, COLSHAPE_CYLINDER },
|
||||
{ 0x00, { 0x00000000, 0x00, 0x00 }, { 0x00000000, 0x00, 0x00 }, 0x00, 0x00, 0x01 },
|
||||
{ 20, 64, 0, { 0, 0, 0 } },
|
||||
};
|
||||
```
|
||||
|
||||
Copy this in below `D_80B18910`, delete the original words of data, change the name back to `D_80B18910`, and put `sCylinderInit` commented out above it:
|
||||
```C
|
||||
// sCylinderInit
|
||||
static ColliderCylinderInit D_80B18910 =
|
||||
{
|
||||
{ COLTYPE_UNK10, 0x00, 0x00, 0x39, 0x20, COLSHAPE_CYLINDER },
|
||||
{ 0x00, { 0x00000000, 0x00, 0x00 }, { 0x00000000, 0x00, 0x00 }, 0x00, 0x00, 0x01 },
|
||||
{ 20, 64, 0, { 0, 0, 0 } },
|
||||
};
|
||||
```
|
||||
|
||||
For the `CollisionCheckInfoInit2`, we don't have a script to separate it, but you can look in other files to see that it should be separated as
|
||||
```C
|
||||
// sColChkInit
|
||||
CollisionCheckInfoInit2 D_80B1893C = { 0, 0, 0, 0, 0xFF };
|
||||
```
|
||||
|
||||
One more thing needs to change: since both are no longer arrays, we need to make the uses in the functions pointers:
|
||||
```C
|
||||
Collider_SetCylinder(play, &this->collider, &this->actor, &D_80B18910);
|
||||
func_80061EFC(&this->actor.colChkInfo, NULL, &D_80B1893C);
|
||||
```
|
||||
|
||||
A quick check of the diff shows that we just need to put the action function set to last, and it matches.
|
||||
|
||||
Following the function tree as usual, we find the only other place any data is used is in `func_80B1871C`. From its use in `EnTg_Draw`, we realise that this is a `PostLimbDraw` function. Giving mips2c the correct prototype, it comes out as
|
||||
```C
|
||||
void func_80B1871C(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) {
|
||||
? sp18;
|
||||
|
||||
sp18.unk0 = (s32) D_80B18968->unk0;
|
||||
sp18.unk4 = (s32) D_80B18968[1];
|
||||
sp18.unk8 = (s32) D_80B18968[2];
|
||||
if (limbIndex == 9) {
|
||||
Matrix_MultVec3f((Vec3f *) &sp18, thisx + 0x38);
|
||||
}
|
||||
}
|
||||
```
|
||||
which clearly doesn't like the words we fed it. We see that `sp18` should be a `Vec3f` from the cast in the `Matrix_MultVec3f`, so the last three words are padding (a `Vec3f` has size `0xC`, and it's not using it like an array), and we can convert it to
|
||||
```C
|
||||
Vec3f D_80B18968 = { 0.0f, 800.0f, 0.0f };
|
||||
```
|
||||
and the function matches as
|
||||
```C
|
||||
void func_80B1871C(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx) {
|
||||
EnTg* this = THIS;
|
||||
|
||||
Vec3f sp18 = D_80B18968;
|
||||
|
||||
if (limbIndex == 9) {
|
||||
Matrix_MultVec3f(&sp18, &this->actor.world2.pos);
|
||||
}
|
||||
}
|
||||
```
|
||||
(we can see from the assembly doing `lw` and `sw` rather than `lwc1` and `swc1` that it is doing a struct copy, rather than setting it componentwise).
|
||||
|
||||
|
||||
## 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 EnJj, for example, we ended up with the following data at the top of the file
|
||||
```C
|
||||
extern UNK_TYPE D_80A88164;
|
||||
|
||||
extern ColliderCylinderInit D_80A88CB4;
|
||||
// static ColliderCylinderInit sCylinderInit =
|
||||
// {
|
||||
// { COLTYPE_UNK10, 0x00, 0x09, 0x39, 0x10, COLSHAPE_CYLINDER },
|
||||
// { 0x00, { 0x00000000, 0x00, 0x00 }, { 0x00000004, 0x00, 0x00 }, 0x00, 0x01, 0x01 },
|
||||
// { 170, 150, 0, { 0, 0, 0 } },
|
||||
// };
|
||||
|
||||
extern InitChainEntry D_80A88CE0[];
|
||||
// static InitChainEntry sInitChain[] = {
|
||||
// ICHAIN_VEC3F_DIV1000(scale, 87, ICHAIN_CONTINUE),
|
||||
// ICHAIN_F32(uncullZoneForward, 4000, ICHAIN_CONTINUE),
|
||||
// ICHAIN_F32(uncullZoneScale, 3300, ICHAIN_CONTINUE),
|
||||
// ICHAIN_F32(uncullZoneDownward, 1100, ICHAIN_STOP),
|
||||
// };
|
||||
|
||||
extern Vec3f D_80A88CF0;
|
||||
// static Vec3f D_80A88CF0 = { -1589.0f, 53.0f, -43.0f };
|
||||
|
||||
extern Gfx* D_80A88CFC[];
|
||||
// static Gfx* D_80A88CFC[] = { 0x06007698, 0x06007A98, 0x06007E98, }
|
||||
```
|
||||
and the only thing we don't know about is the cutscene data `D_80A88164`.
|
||||
|
||||
*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_Jj"
|
||||
include "$(BUILD_DIR)/src/overlays/actors/ovl_En_Jj/z_en_jj.o"
|
||||
include "$(BUILD_DIR)/data/overlays/actors/z_en_jj.data.o"
|
||||
include "$(BUILD_DIR)/data/overlays/actors/z_en_jj.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_Jj"
|
||||
include "$(BUILD_DIR)/src/overlays/actors/ovl_En_Jj/z_en_jj.o"
|
||||
//include "$(BUILD_DIR)/data/overlays/actors/z_en_jj.data.o"
|
||||
include "$(BUILD_DIR)/data/overlays/actors/z_en_jj.reloc.o"
|
||||
endseg
|
||||
```
|
||||
|
||||
Next remove all the externs, and uncomment their corresponding commented data:
|
||||
```C
|
||||
extern UNK_TYPE D_80A88164;
|
||||
|
||||
static ColliderCylinderInit sCylinderInit =
|
||||
{
|
||||
{ COLTYPE_UNK10, 0x00, 0x09, 0x39, 0x10, COLSHAPE_CYLINDER },
|
||||
{ 0x00, { 0x00000000, 0x00, 0x00 }, { 0x00000004, 0x00, 0x00 }, 0x00, 0x01, 0x01 },
|
||||
{ 170, 150, 0, { 0, 0, 0 } },
|
||||
};
|
||||
|
||||
static InitChainEntry sInitChain[] = {
|
||||
ICHAIN_VEC3F_DIV1000(scale, 87, ICHAIN_CONTINUE),
|
||||
ICHAIN_F32(uncullZoneForward, 4000, ICHAIN_CONTINUE),
|
||||
ICHAIN_F32(uncullZoneScale, 3300, ICHAIN_CONTINUE),
|
||||
ICHAIN_F32(uncullZoneDownward, 1100, ICHAIN_STOP),
|
||||
};
|
||||
|
||||
static Vec3f D_80A88CF0 = { -1589.0f, 53.0f, -43.0f };
|
||||
|
||||
static Gfx* D_80A88CFC[] = { 0x06007698, 0x06007A98, 0x06007E98, }
|
||||
```
|
||||
Find-and-replace `D_80A88CB4` and `D_80A88CE0` by `sCylinderInit` and `sInitChain` respectively. Notice the naming scheme: static data symbols always start with `s` in our style. (Unless they are inlined, but we'll worry about that later.)
|
||||
|
||||
We still need to deal with the cutscene data. This is special: because it's so large, it goes in its own file. Make a new file called `z_en_jj_cutscene_data.c` in the same directory as `z_en_jj.c`. Include the actor's header file and `z64cutscene_commands.h`, and put `// clang-format off` and `// clang-format on` in the file (this is so that our formatting script doesn't wreak havoc with the formatting of the cutscene macros). Thus the contents of the file is currently
|
||||
|
||||
```C
|
||||
#include "z_en_jj.h"
|
||||
#include "z64cutscene_commands.h"
|
||||
// clang-format off
|
||||
|
||||
// clang-format on
|
||||
```
|
||||
|
||||
Finally, we have a script to convert the cutscene data into macros, namely `csdis.py` in the tools folder. We can just give it the VRAM address that the `D_address` is referring to, and it will output the cs macros:
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
(Very) long code block, click to view
|
||||
</summary>
|
||||
|
||||
```
|
||||
$ ./tools/csdis.py 80A88164
|
||||
ovl_En_Jj: Rom 00E3E3D0:00E3F9E0 VRam 80A87800:80A88E10 Offset 000964
|
||||
|
||||
static CutsceneData D_80A88164[] = {
|
||||
CS_BEGIN_CUTSCENE(26, 1629),
|
||||
CS_PLAYER_CUE_LIST(4),
|
||||
CS_PLAYER_CUE(PLAYER_CUEID_5, 0, 240, 0x0000, 0x4000, 0x0000, -1732, 52, -44, -1732, 52, -44, 1.1393037197548209e-29f, 0.0f, 1.401298464324817e-45f),
|
||||
CS_PLAYER_CUE(PLAYER_CUEID_3, 240, 255, 0x0000, 0x4000, 0x0000, -1732, 52, -44, -1732, 52, -44, 1.1393037197548209e-29f, 0.0f, 1.401298464324817e-45f),
|
||||
CS_PLAYER_CUE(PLAYER_CUEID_6, 255, 285, 0x0000, 0x4000, 0x0000, -1732, 52, -44, -1732, 52, -44, 1.1393037197548209e-29f, 0.0f, 1.401298464324817e-45f),
|
||||
CS_PLAYER_CUE(PLAYER_CUEID_32, 285, 300, 0x0000, 0xC000, 0x0000, -1732, 52, -44, -1537, 109, -44, 1.1393037197548209e-29f, 0.0f, 1.401298464324817e-45f),
|
||||
CS_ACTOR_CUE_LIST(68, 4),
|
||||
CS_ACTOR_CUE(0x0001, 0, 234, 0x0000, 0x4000, 0x0000, -1665, 52, -44, -1665, 52, -44, 1.1393037197548209e-29f, 0.0f, 1.401298464324817e-45f),
|
||||
CS_ACTOR_CUE(0x0002, 234, 241, 0x41F8, 0x0000, 0x0000, -1665, 52, -44, -1603, 130, -47, 8.857142448425293f, 11.142857551574707f, -8.857142448425293f),
|
||||
CS_ACTOR_CUE(0x0002, 241, 280, 0x4031, 0x0000, 0x0000, -1603, 130, -47, -549, 130, -52, 27.0256404876709f, 0.0f, -27.0256404876709f),
|
||||
CS_ACTOR_CUE(0x0003, 280, 300, 0x0000, 0x0000, 0x0000, -549, 130, -52, -549, 130, -52, 0.0f, 0.0f, 0.0f),
|
||||
CS_ACTOR_CUE_LIST(67, 5),
|
||||
CS_ACTOR_CUE(0x0001, 0, 93, 0x0000, 0x0000, 0x0000, 0, 51, 124, 0, 51, 124, 0.0f, 0.0f, 0.0f),
|
||||
CS_ACTOR_CUE(0x0003, 93, 121, 0x0000, 0x0000, 0x0000, 0, 51, 124, 0, 51, 124, 0.0f, 0.0f, 0.0f),
|
||||
CS_ACTOR_CUE(0x0001, 121, 146, 0x0000, 0x0000, 0x0000, 0, 51, 124, 0, 51, 124, 0.0f, 0.0f, 0.0f),
|
||||
CS_ACTOR_CUE(0x0002, 146, 241, 0x0000, 0x0000, 0x0000, 0, 51, 124, 0, 51, 124, 0.0f, 0.0f, 0.0f),
|
||||
CS_ACTOR_CUE(0x0001, 241, 441, 0x0000, 0x0000, 0x0000, 0, 51, 124, 0, 51, 124, 0.0f, 0.0f, 0.0f),
|
||||
CS_ACTOR_CUE_LIST(69, 3),
|
||||
CS_ACTOR_CUE(0x0001, 0, 90, 0x0000, 0x0000, 0x0000, 0, -33, 9, 0, -33, 9, 0.0f, 0.0f, 0.0f),
|
||||
CS_ACTOR_CUE(0x0002, 90, 330, 0x0000, 0x0000, 0x0000, 0, -33, 9, 0, -62, 22, 0.0f, -0.12083332985639572f, 0.0f),
|
||||
CS_ACTOR_CUE(0x0003, 330, 380, 0x0000, 0x0000, 0x0000, 0, -62, 22, 0, -62, 22, 0.0f, 0.0f, 0.0f),
|
||||
CS_MISC_LIST(1),
|
||||
CS_MISC(0x000C, 1095, 1161, 0x0000, 0x00000000, 0xFFFFFFD2, 0x00000000, 0xFFFFFFD0, 0xFFFFFFD2, 0x00000000, 0xFFFFFFD0, 0x00000000, 0x00000000, 0x00000000),
|
||||
CS_TRANSITION(0x0009, 0, 10),
|
||||
CS_PLAYER_CUE_LIST(1),
|
||||
CS_PLAYER_CUE(PLAYER_CUEID_53, 300, 1629, 0x0000, 0x0000, 0x0000, -1630, 52, -52, -1630, 52, -52, 0.0f, 0.0f, 0.0f),
|
||||
CS_CAM_EYE_SPLINE(0, 1091),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1641, 95, -41, 0x015C),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1641, 95, -41, 0x016D),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1641, 95, -41, 0x017E),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1641, 95, -41, 0x0223),
|
||||
CS_CAM_POINT(CS_CAM_STOP, 0x00, 0, 45.39994430541992f, -1641, 95, -41, 0x7065),
|
||||
CS_CAM_EYE_SPLINE(60, 1151),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1532, 251, 222, 0x015C),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1532, 251, 222, 0x016D),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1532, 251, 222, 0x017E),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1532, 251, 222, 0x0223),
|
||||
CS_CAM_POINT(CS_CAM_STOP, 0x00, 0, 45.39994430541992f, -1532, 251, 222, 0x7065),
|
||||
CS_CAM_EYE_SPLINE(90, 351),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1698, 382, 455, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1698, 382, 455, 0xAC34),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1698, 382, 455, 0x4428),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1694, 380, 451, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 32.99989700317383f, -1694, 380, 451, 0xAC10),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 32.99989700317383f, -1694, 380, 451, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 32.99989700317383f, -1694, 380, 451, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 32.99989700317383f, -1694, 380, 451, 0x0164),
|
||||
CS_CAM_POINT(CS_CAM_STOP, 0x00, 0, 32.99989700317383f, -1694, 380, 451, 0xAD78),
|
||||
CS_CAM_EYE_SPLINE(220, 392),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1641, 95, -41, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1641, 95, -41, 0xAC34),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1641, 95, -41, 0x4428),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1641, 95, -41, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1641, 95, -41, 0xAC10),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1641, 95, -41, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_STOP, 0x00, 0, 45.39994430541992f, -1641, 95, -41, 0x0000),
|
||||
CS_CAM_EYE_SPLINE(240, 1331),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.599945068359375f, -1810, 65, -15, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.599945068359375f, -1810, 65, -15, 0xAC34),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.599945068359375f, -1810, 65, -15, 0x4428),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.599945068359375f, -1810, 65, -15, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_STOP, 0x00, 0, 45.599945068359375f, -1810, 65, -15, 0xAC10),
|
||||
CS_CAM_EYE_SPLINE(280, 1371),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.599945068359375f, -1531, 95, -7, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.599945068359375f, -1531, 95, -7, 0xAC34),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.599945068359375f, -1531, 95, -7, 0x4428),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.599945068359375f, -1531, 95, -7, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_STOP, 0x00, 0, 45.599945068359375f, -1531, 95, -7, 0xAC10),
|
||||
CS_CAM_EYE_SPLINE(310, 1421),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1717, 83, -59, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1717, 83, -59, 0xAC34),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1649, 177, -59, 0x4428),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1533, 224, -59, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -1243, 180, -59, 0xAC10),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -953, 71, -55, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -953, 71, -55, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 45.39994430541992f, -953, 71, -55, 0x0164),
|
||||
CS_CAM_POINT(CS_CAM_STOP, 0x00, 0, 45.39994430541992f, -953, 71, -55, 0xAD78),
|
||||
CS_CAM_EYE_SPLINE(355, 1466),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 60.60000228881836f, -1830, 103, 18, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 60.60000228881836f, -1830, 103, 18, 0xAC34),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 60.60000228881836f, -1830, 103, 18, 0x4428),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 60.60000228881836f, -1830, 103, 18, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 60.60000228881836f, -1830, 103, 18, 0xAC10),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 0, 60.60000228881836f, -1830, 103, 18, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_STOP, 0x00, 0, 60.60000228881836f, -1830, 103, 18, 0x0000),
|
||||
CS_CAM_AT_SPLINE(0, 1120),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 45.39994430541992f, -1724, -5, -45, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 45.39994430541992f, -1724, -5, -45, 0xAC34),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 1000, 45.39994430541992f, -1724, -5, -45, 0x4428),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 45.39994430541992f, -1724, -5, -45, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_STOP, 0x00, 30, 45.39994430541992f, -1724, -5, -45, 0xAC10),
|
||||
CS_CAM_AT_SPLINE(60, 1180),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 45.39994430541992f, -1440, 241, 134, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 45.39994430541992f, -1440, 241, 134, 0xAC34),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 1000, 45.39994430541992f, -1440, 241, 134, 0x4428),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 45.39994430541992f, -1440, 241, 134, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_STOP, 0x00, 30, 45.39994430541992f, -1440, 241, 134, 0xAC10),
|
||||
CS_CAM_AT_SPLINE(90, 380),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 45.39994430541992f, -1610, 348, 373, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 45.39994430541992f, -1610, 348, 373, 0xAC34),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 50, 45.39994430541992f, -1610, 348, 373, 0x4428),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 35.399906158447266f, -1614, 338, 367, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 32.99989700317383f, -1614, 338, 367, 0xAC10),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 32.99989700317383f, -1614, 338, 367, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 32.99989700317383f, -1614, 338, 367, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 32.99989700317383f, -1614, 338, 367, 0x0164),
|
||||
CS_CAM_POINT(CS_CAM_STOP, 0x00, 30, 32.99989700317383f, -1614, 338, 367, 0xAD78),
|
||||
CS_CAM_AT_SPLINE(220, 421),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 45.39994430541992f, -1724, -5, -45, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 70, 45.39994430541992f, -1724, -5, -45, 0xAC34),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 5, 45.39994430541992f, -1724, -5, -45, 0x4428),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 6, 45.79994583129883f, -1593, 150, -146, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 45.39994430541992f, -1531, 152, -75, 0xAC10),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 45.39994430541992f, -1531, 152, -75, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_STOP, 0x00, 30, 45.39994430541992f, -1531, 152, -75, 0x0000),
|
||||
CS_CAM_AT_SPLINE(240, 1360),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 45.599945068359375f, -1712, 74, -37, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 45.599945068359375f, -1712, 74, -37, 0xAC34),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 1000, 45.599945068359375f, -1712, 74, -37, 0x4428),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 45.599945068359375f, -1712, 74, -37, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_STOP, 0x00, 30, 45.599945068359375f, -1712, 74, -37, 0xAC10),
|
||||
CS_CAM_AT_SPLINE(280, 1400),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 45.599945068359375f, -1619, 99, -50, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 45.599945068359375f, -1619, 99, -50, 0xAC34),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 1000, 45.599945068359375f, -1619, 99, -50, 0x4428),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 45.599945068359375f, -1619, 99, -50, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_STOP, 0x00, 30, 45.599945068359375f, -1619, 99, -50, 0xAC10),
|
||||
CS_CAM_AT_SPLINE(310, 1450),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x0B, 30, 90.99960327148438f, -1610, 141, -59, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x09, 10, 90.79960632324219f, -1599, 114, -57, 0xAC34),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0xFC, 10, 90.39961242675781f, -1528, 192, -54, 0x4428),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 10, 90.599609375f, -1427, 164, -54, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0xCB, 10, 90.39961242675781f, -1138, 119, -37, 0xAC10),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x20, 10, 90.39961242675781f, -832, 50, -51, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 1000, 45.39994430541992f, -836, 35, -51, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 45.39994430541992f, -836, 35, -51, 0x0164),
|
||||
CS_CAM_POINT(CS_CAM_STOP, 0x00, 30, 45.39994430541992f, -836, 35, -51, 0xAD78),
|
||||
CS_CAM_AT_SPLINE(355, 1495),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 60.60000228881836f, -1706, 111, -6, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 60.60000228881836f, -1706, 111, -6, 0xAC34),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 10, 60.60000228881836f, -1706, 111, -6, 0x4428),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 10, 60.60000228881836f, -1721, 82, -42, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 1000, 60.60000228881836f, -1721, 82, -42, 0xAC10),
|
||||
CS_CAM_POINT(CS_CAM_CONTINUE, 0x00, 30, 60.60000228881836f, -1721, 82, -42, 0x0000),
|
||||
CS_CAM_POINT(CS_CAM_STOP, 0x00, 30, 60.60000228881836f, -1721, 82, -42, 0x0000),
|
||||
CS_TRANSITION(0x000B, 335, 342),
|
||||
CS_DESTINATION(CS_DEST_JABU_JABU, 345, 395),
|
||||
CS_ACTOR_CUE_LIST(62, 1),
|
||||
CS_ACTOR_CUE(0x0001, 305, 494, 0x0000, 0x0000, 0x0000, -1399, 452, -53, -1399, 452, -53, 0.0f, 0.0f, 0.0f),
|
||||
CS_END(),
|
||||
};
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
Copy this into the file we just made (given the length, you may prefer to `>` it into a file and copy it from there, rather than the terminal itself). Save and close that file: we won't need it any more.
|
||||
|
||||
To replace the `extern`, because the data is in a separate file, we include the file in a particular way:
|
||||
```C
|
||||
#include "z_en_jj_cutscene_data.c" EARLY
|
||||
```
|
||||
(`EARLY` is required specifically for cutscene data. See [the definition of the CutsceneData struct](../include/z64cutscene.h) for exactly why.)
|
||||
|
||||
Lastly, uncomment the InitVars block that's been sitting there the whole time. The data section of the file now looks like
|
||||
```C
|
||||
ActorInit En_Jj_InitVars = {
|
||||
/**/ ACTOR_EN_JJ,
|
||||
/**/ ACTORTYPE_ITEMACTION,
|
||||
/**/ FLAGS,
|
||||
/**/ OBJECT_JJ,
|
||||
/**/ sizeof(EnJj),
|
||||
/**/ EnJj_Init,
|
||||
/**/ EnJj_Destroy,
|
||||
/**/ EnJj_Update,
|
||||
/**/ EnJj_Draw,
|
||||
};
|
||||
|
||||
#include "en_jj_cutscene_data.c" EARLY
|
||||
|
||||
static ColliderCylinderInit sCylinderInit =
|
||||
{
|
||||
{ COLTYPE_UNK10, 0x00, 0x09, 0x39, 0x10, COLSHAPE_CYLINDER },
|
||||
{ 0x00, { 0x00000000, 0x00, 0x00 }, { 0x00000004, 0x00, 0x00 }, 0x00, 0x01, 0x01 },
|
||||
{ 170, 150, 0, { 0, 0, 0 } },
|
||||
};
|
||||
|
||||
static InitChainEntry sInitChain[] = {
|
||||
ICHAIN_VEC3F_DIV1000(scale, 87, ICHAIN_CONTINUE),
|
||||
ICHAIN_F32(uncullZoneForward, 4000, ICHAIN_CONTINUE),
|
||||
ICHAIN_F32(uncullZoneScale, 3300, ICHAIN_CONTINUE),
|
||||
ICHAIN_F32(uncullZoneDownward, 1100, ICHAIN_STOP),
|
||||
};
|
||||
|
||||
static Vec3f D_80A88CF0 = { -1589.0f, 53.0f, -43.0f };
|
||||
|
||||
static Gfx* D_80A88CFC[] = { 0x06007698, 0x06007A98, 0x06007E98, };
|
||||
```
|
||||
|
||||
That should be everything, and we should now be able to `make` without the data file with no issues
|
||||
|
||||
But running `make`, we get the dreaded Error 1:
|
||||
|
||||
```
|
||||
md5sum: WARNING: 1 computed checksum did NOT match
|
||||
make: *** [Makefile:172: all] Error 1
|
||||
```
|
||||
|
||||
Oh no! What went wrong?
|
||||
|
||||
To find out what went wrong, we need to use `firstdiff.py`. This tells us where our ROM starts to differ:
|
||||
```
|
||||
$ ./firstdiff.py
|
||||
First difference at ROM addr 0x144F4, gDmaDataTable (RAM 0x80016DA0, ROM 0x12F70, build/asm/dmadata.o)
|
||||
Bytes: 00:E3:F9:D0 vs 00:E3:F9:E0
|
||||
Instruction difference at ROM addr 0xE3ED48, En_Jj_InitVars (RAM 0x80A88140, ROM 0xE3ED10, build/src/overlays/actors/ovl_En_Jj/z_en_jj.o)
|
||||
Bytes: 40:00:00:00 vs 00:F0:00:00
|
||||
Instruction difference at ROM addr 0xE3F900, D_80A88D40 (RAM 0x80A88D30, ROM 0xE3F900, build/data/overlays/actors/z_en_jj.reloc.o)
|
||||
Bytes: 00:00:09:40 vs C4:89:80:00
|
||||
Instruction difference at ROM addr 0xE3F9D4, En_Js_SetupAction (RAM 0x80A88E00, ROM 0xE3F9D0, build/src/overlays/actors/ovl_En_Js/z_en_js.o)
|
||||
Bytes: AC:85:02:8C vs 00:00:00:00
|
||||
Instruction difference at ROM addr 0xE3F9D8, EnJs_Init (RAM 0x80A88E08, ROM 0xE3F9D8, build/src/overlays/actors/ovl_En_Js/z_en_js.o)
|
||||
Bytes: 27:BD:FF:B0 vs 00:00:00:00
|
||||
Instruction difference at ROM addr 0xE3FAFC, EnJs_Destroy (RAM 0x80A88F2C, ROM 0xE3FAFC, build/src/overlays/actors/ovl_En_Js/z_en_js.o)
|
||||
Bytes: 27:BD:FF:E8 vs 8F:B0:00:34
|
||||
|
||||
Over 1000 differing words, must be a shifted ROM.
|
||||
Map appears to have shifted just before D_80A88D40 (build/data/overlays/actors/z_en_jj.reloc.o) -- in En_Jj_InitVars?
|
||||
```
|
||||
|
||||
Ignore the first line: `gDmaDataTable` is always different if the ROM is shifted. The useful lines are usually the next line, and the guess it makes at the end.
|
||||
|
||||
To fix this, we use a binary diff program. A suitable one is `vbindiff`: run it on the baserom and the zelda_whatever one the compiler generates:
|
||||
```
|
||||
vbindiff baseroms/gc-eu-mq-dbg/baserom-decompressed.z64 oot-gc-eu-mq-dbg.z64
|
||||
```
|
||||
In this, press `g` to open up goto position, and paste in the address `0xE3ED10` from the first important line of the `first_diff` output. This gives us the following:
|
||||
|
||||
![vbindiff for data](images/vbindiff_data_1.png)
|
||||
|
||||
Notice that the numbers in the bottom pane is all shifted one word to the left. We therefore need some extra padding somewhere. The real issue is where. Thankfully the guess at the bottom gives us a hint: let's try just under `InitVars`. Just put a padding variable straight after them:
|
||||
|
||||
```C
|
||||
ActorInit En_Jj_InitVars = {
|
||||
/**/ ACTOR_EN_JJ,
|
||||
/**/ ACTORTYPE_ITEMACTION,
|
||||
/**/ FLAGS,
|
||||
/**/ OBJECT_JJ,
|
||||
/**/ sizeof(EnJj),
|
||||
/**/ EnJj_Init,
|
||||
/**/ EnJj_Destroy,
|
||||
/**/ EnJj_Update,
|
||||
/**/ EnJj_Draw,
|
||||
};
|
||||
|
||||
s32 usused = 0;
|
||||
|
||||
#include "z_en_jj_cutscene_data.c" EARLY
|
||||
```
|
||||
|
||||
This isn't good enough: we still get Error 1, but:
|
||||
```
|
||||
$ ./first_diff.py
|
||||
First difference at ROM addr 0x144F4, gDmaDataTable (RAM 0x80016DA0, ROM 0x12F70, build/asm/dmadata.o)
|
||||
Bytes: 00:E3:F9:D0 vs 00:E3:F9:E0
|
||||
Instruction difference at ROM addr 0xE3F87C, unused (RAM 0x80A88160, ROM 0xE3ED30, build/src/overlays/actors/ovl_En_Jj/z_en_jj.o)
|
||||
Bytes: 0A:00:09:39 vs 00:00:00:00
|
||||
Instruction difference at ROM addr 0xE3F900, D_80A88D40 (RAM 0x80A88D30, ROM 0xE3F900, build/data/overlays/actors/z_en_jj.reloc.o)
|
||||
Bytes: 00:00:09:40 vs C4:89:80:00
|
||||
Instruction difference at ROM addr 0xE3F9D4, En_Js_SetupAction (RAM 0x80A88E00, ROM 0xE3F9D0, build/src/overlays/actors/ovl_En_Js/z_en_js.o)
|
||||
Bytes: AC:85:02:8C vs 00:00:00:00
|
||||
Instruction difference at ROM addr 0xE3F9D8, EnJs_Init (RAM 0x80A88E08, ROM 0xE3F9D8, build/src/overlays/actors/ovl_En_Js/z_en_js.o)
|
||||
Bytes: 27:BD:FF:B0 vs 00:00:00:00
|
||||
Instruction difference at ROM addr 0xE3FAFC, EnJs_Destroy (RAM 0x80A88F2C, ROM 0xE3FAFC, build/src/overlays/actors/ovl_En_Js/z_en_js.o)
|
||||
Bytes: 27:BD:FF:E8 vs 8F:B0:00:34
|
||||
|
||||
Over 1000 differing words, must be a shifted ROM.
|
||||
Map appears to have shifted just before D_80A88D40 (build/data/overlays/actors/z_en_jj.reloc.o) -- in unused?
|
||||
(Base map file expected/build/z64.map out of date due to new or renamed symbols, so result may be imprecise.)
|
||||
```
|
||||
We've managed to get rid of one issue, but there's still another one. Looking in vbindiff again,
|
||||
|
||||
![vbindiff data 2](images/vbindiff_data_2.png)
|
||||
|
||||
we see that everything is shifted left by 2 words. Guessing based on the hint from `first_diff`, we put two words after the cutscene include:
|
||||
```C
|
||||
#include "z_en_jj_cutscene_data.c" EARLY
|
||||
|
||||
s32 usused2[] = { 0, 0 };
|
||||
|
||||
static ColliderCylinderInit sCylinderInit =
|
||||
{
|
||||
{ COLTYPE_UNK10, 0x00, 0x09, 0x39, 0x10, COLSHAPE_CYLINDER },
|
||||
{ 0x00, { 0x00000000, 0x00, 0x00 }, { 0x00000004, 0x00, 0x00 }, 0x00, 0x01, 0x01 },
|
||||
{ 170, 150, 0, { 0, 0, 0 } },
|
||||
};
|
||||
```
|
||||
|
||||
Running `make -j` again,
|
||||
```
|
||||
oot-gc-eu-mq-dbg.z64: OK
|
||||
```
|
||||
|
||||
Hooray, we won!
|
||||
|
||||
|
||||
## 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.
|
@ -1,476 +0,0 @@
|
||||
# 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.
|
||||
|
||||
For this tutorial we will first look at the `EnJj` draw function, `EnJj_Draw`, then 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
|
||||
EnJj_Draw(Actor* thisx, PlayState* play);
|
||||
```
|
||||
|
||||
As in Init, Destroy and Update, it is much more convenient to feed mips2c the fake prototype
|
||||
|
||||
```C
|
||||
EnJj_Draw(EnJj* this, PlayState* play);
|
||||
```
|
||||
|
||||
so that it fills out the struct fields from the actuar actor; we then put a
|
||||
|
||||
```C
|
||||
EnJj* this = THIS;
|
||||
```
|
||||
|
||||
in the declarations as before. 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
|
||||
void EnJj_Draw(Actor *thisx, PlayState *play) {
|
||||
EnJj* this = THIS;
|
||||
|
||||
GraphicsContext *sp4C;
|
||||
Gfx *sp3C;
|
||||
EnJj *sp18;
|
||||
Gfx *temp_v1;
|
||||
GraphicsContext *temp_a1;
|
||||
s32 temp_a0;
|
||||
|
||||
temp_a1 = play->state.gfxCtx;
|
||||
sp4C = temp_a1;
|
||||
Graph_OpenDisps(&sp3C, temp_a1, (const char *) "../z_en_jj.c", 0x36F);
|
||||
Gfx_SetupDL_37Opa(play->state.gfxCtx);
|
||||
Matrix_Translate(0.0f, (cosf(this->skelAnime.curFrame * 0.076624215f) * 10.0f) - 10.0f, 0.0f, (u8)1U);
|
||||
Matrix_Scale(10.0f, 10.0f, 10.0f, (u8)1U);
|
||||
temp_v1 = temp_a1->polyOpa.p;
|
||||
temp_a1->polyOpa.p = temp_v1 + 8;
|
||||
temp_v1->words.w0 = 0xDB060020;
|
||||
temp_a0 = *(&D_80A88CFC + (this->unk_30E * 4));
|
||||
temp_v1->words.w1 = (temp_a0 & 0xFFFFFF) + gSegments[(u32) (temp_a0 * 0x10) >> 0x1C] + 0x80000000;
|
||||
sp18 = this;
|
||||
SkelAnime_DrawFlexOpa(play, this->skelAnime.skeleton, this->skelAnime.jointTable, (s32) this->skelAnime.dListCount, 0, 0);
|
||||
Graph_CloseDisps(&sp3C, play->state.gfxCtx, (const char *) "../z_en_jj.c", 0x382);
|
||||
}
|
||||
```
|
||||
|
||||
Notable features are the Open and Close Disps functions, and blocks of the form
|
||||
|
||||
```C
|
||||
temp_v1 = temp_a1->polyOpa.p;
|
||||
temp_a1->polyOpa.p = temp_v1 + 8;
|
||||
temp_v1->words.w0 = 0xDB060020;
|
||||
temp_a0 = *(&D_80A88CFC + (this->unk_30E * 4));
|
||||
temp_v1->words.w1 = (temp_a0 & 0xFFFFFF) + gSegments[(u32) (temp_a0 * 0x10) >> 0x1C] + 0x80000000;
|
||||
```
|
||||
|
||||
(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 types of graphics pointer,
|
||||
- polyOpa for solid textures
|
||||
- polyXlu for translucent textures
|
||||
|
||||
Our example is polyOpa, not surprisingly since Jabu 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, OoT doesn't have 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. Or we could, if we had worked out what it was.
|
||||
|
||||
Firstly, `*(&D_80A88CFC + (this->unk_30E * 4))` is referring to a piece of data we haven't externed yet. It looks like
|
||||
```
|
||||
glabel D_80A88CFC
|
||||
.word 0x06007698, 0x06007A98, 0x06007E98, 0x00000000, 0x00000000
|
||||
```
|
||||
|
||||
The first three words look like pointers to assets in the actor segment, which would make sense if we're looking for textures to draw. The last two words are 0, which is strange. A check in the rest of the actor file shows that `unk_30E` only takes the values `0,1,2`. We conclude that the last two words are just padding, and can be removed. Because this data is used in a graphics macro, it will be either a displaylist or a texture. We therefore set it up to be an array of unknown pointers for now:
|
||||
```C
|
||||
extern UNK_PTR D_80A88CFC[];
|
||||
// static Gfx* D_80A88CFC[] = { 0x06007698, 0x06007A98, 0x06007E98, }
|
||||
```
|
||||
|
||||
It goes through further processing before it is used in the macro: `(temp_a0 & 0xFFFFFF) + gSegments[(u32) (temp_a0 * 0x10) >> 0x1C] + 0x80000000` is a conversion of a segmented address into a VRAM address. We have a macro for this too: `SEGMENTED_TO_VIRTUAL`. So after all this, the second word is
|
||||
```C
|
||||
SEGMENTED_TO_VIRTUAL(D_80A88CFC[this->unk_30E]);
|
||||
```
|
||||
and the whole macro is
|
||||
```C
|
||||
gSPSegment(POLY_OPA_DISP++, 0x08, SEGMENTED_TO_VIRTUAL(D_80A88CFC[this->unk_30E]));
|
||||
```
|
||||
The contents of a desegmentation macro used like this are almost always textures in this context, so we can replace `UNK_PTR` by `u64*`, the type for textures.
|
||||
|
||||
You repeat this for every block in the function.
|
||||
|
||||
We also have macros for Graph_OpenDisps and Graph_CloseDisps: you can replace
|
||||
```C
|
||||
Graph_OpenDisps(&sp3C, temp_a1, (const char *) "../z_en_jj.c", 0x36F);
|
||||
```
|
||||
by
|
||||
```C
|
||||
OPEN_DISPS(temp_a1, "../z_en_jj.c", 879);
|
||||
```
|
||||
(the last argument is a line number, so should be in decimal).
|
||||
|
||||
The function may or may not use a temp for `play->state.gfxCtx`: you have to work it out using the diff.
|
||||
|
||||
Once you've replaced all the blocks and the open and close with macros, you proceed with the function as usual.
|
||||
|
||||
Two last things: the last argument of the matrix functions tells the compiler whether to use the previously stored matrix or not, so we have the enums `MTXMODE_NEW` and `MTXMODE_APPLY` for these. And the rather weird-looking float `0.076624215f` is, of all things, `(M_PI/41.0f)` (try Wolfram Alpha for these things, and if that doesn't produce anything sensible, ask in Discord).
|
||||
|
||||
(The actual reason is probably that the animation is 41 frames long, but you won't necessarily spot this sort of thing for most floats)
|
||||
|
||||
After all that, it turns out that
|
||||
```C
|
||||
void EnJj_Draw(Actor *thisx, PlayState *play) {
|
||||
EnJj *this = THIS;
|
||||
|
||||
OPEN_DISPS(play->state.gfxCtx, "../z_en_jj.c", 879);
|
||||
Gfx_SetupDL_37Opa(play->state.gfxCtx);
|
||||
Matrix_Translate(0.0f, (cosf(this->skelAnime.curFrame * (M_PI/41.0f)) * 10.0f) - 10.0f, 0.0f, 1);
|
||||
Matrix_Scale(10.0f, 10.0f, 10.0f, 1);
|
||||
gSPSegment(POLY_OPA_DISP++, 0x08, SEGMENTED_TO_VIRTUAL(D_80A88CFC[this->unk_30E]));
|
||||
SkelAnime_DrawFlexOpa(play, this->skelAnime.skeleton, this->skelAnime.jointTable,
|
||||
this->skelAnime.dListCount, 0, 0, this);
|
||||
CLOSE_DISPS(play->state.gfxCtx, "../z_en_jj.c", 898);
|
||||
}
|
||||
```
|
||||
|
||||
matches apart from a couple of stack differences. This can be resolved by giving it `PlayState* play = play2;` at the top of the function and changing the second argument to `play2` as usual.
|
||||
|
||||
We have enums for the last argument of the matrix functions: `0` is `MTXMODE_NEW`, `1` is `MTXMODE_APPLY`.
|
||||
|
||||
Lastly, the penultimate and antepenultimate arguments of `SkelAnime_DrawFlexOpa` are actually pointers to functions, so they should be `NULL` instead of `0`.
|
||||
|
||||
|
||||
## More examples: OverrideLimbDraw and PostLimbDraw
|
||||
|
||||
For more examples of graphics macros and the structure of Draw functions, we look at a function from `EnDntNormal`, which is some Deku Scrubs used in the minigame stuff in Lost Woods. This has a good selection of macros, and two functions that are commonly combined with Draw, namely OverrideLimbDraw and PostLimbDraw.
|
||||
|
||||
The mips2c output for
|
||||
|
||||
```C
|
||||
void func_809F5A6C(Actor *thisx, PlayState *play) {
|
||||
? sp60;
|
||||
Gfx *sp48;
|
||||
Gfx *sp38;
|
||||
Actor *sp14;
|
||||
Gfx *temp_v0;
|
||||
Gfx *temp_v0_2;
|
||||
Gfx *temp_v0_3;
|
||||
Gfx *temp_v0_4;
|
||||
Gfx *temp_v0_5;
|
||||
GraphicsContext *temp_a1;
|
||||
GraphicsContext *temp_s0;
|
||||
s32 temp_a0;
|
||||
void *temp_v1;
|
||||
|
||||
sp60.unk0 = (s32) D_809F5E94.unk0;
|
||||
sp60.unk4 = (s32) D_809F5E94.unk4;
|
||||
sp60.unk8 = (s32) D_809F5E94.unk8;
|
||||
temp_a1 = play->state.gfxCtx;
|
||||
temp_s0 = temp_a1;
|
||||
Graph_OpenDisps(&sp48, temp_a1, (const char *) "../z_en_dnt_nomal.c", 0x6FE);
|
||||
Gfx_SetupDL_25Opa(play->state.gfxCtx);
|
||||
temp_v0 = temp_s0->polyOpa.p;
|
||||
temp_s0->polyOpa.p = temp_v0 + 8;
|
||||
temp_v0->words.w0 = 0xDB060020;
|
||||
temp_a0 = *(&D_809F5EA0 + (thisx->unk268 * 4));
|
||||
temp_v0->words.w1 = (temp_a0 & 0xFFFFFF) + gSegments[(u32) (temp_a0 * 0x10) >> 0x1C] + 0x80000000;
|
||||
sp14 = thisx;
|
||||
SkelAnime_DrawOpa(play, thisx->unk150, thisx->unk16C, &func_809F58E4, &func_809F59E4);
|
||||
Matrix_Translate(thisx->unk21C, thisx->unk220, (bitwise f32) thisx->unk224, (u8)0U);
|
||||
Matrix_Scale(0.01f, 0.01f, 0.01f, (u8)1U);
|
||||
temp_v0_2 = temp_s0->polyOpa.p;
|
||||
temp_s0->polyOpa.p = temp_v0_2 + 8;
|
||||
temp_v0_2->words.w0 = 0xE7000000;
|
||||
temp_v0_2->words.w1 = 0;
|
||||
temp_v0_3 = temp_s0->polyOpa.p;
|
||||
temp_s0->polyOpa.p = temp_v0_3 + 8;
|
||||
temp_v0_3->words.w0 = 0xFB000000;
|
||||
temp_v1 = (thisx->unk26A * 4) + &D_809F5E4C;
|
||||
temp_v0_3->words.w1 = (temp_v1->unk-2 << 8) | (temp_v1->unk-4 << 0x18) | (temp_v1->unk-3 << 0x10) | 0xFF;
|
||||
temp_v0_4 = temp_s0->polyOpa.p;
|
||||
temp_s0->polyOpa.p = temp_v0_4 + 8;
|
||||
temp_v0_4->words.w0 = 0xDA380003;
|
||||
sp38 = temp_v0_4;
|
||||
sp38->words.w1 = Matrix_NewMtx(play->state.gfxCtx, (char *) "../z_en_dnt_nomal.c", 0x716);
|
||||
temp_v0_5 = temp_s0->polyOpa.p;
|
||||
temp_s0->polyOpa.p = temp_v0_5 + 8;
|
||||
temp_v0_5->words.w1 = (u32) &D_06001B00;
|
||||
temp_v0_5->words.w0 = 0xDE000000;
|
||||
Graph_CloseDisps(&sp48, play->state.gfxCtx, (const char *) "../z_en_dnt_nomal.c", 0x719);
|
||||
if (&func_809F49A4 == thisx->unk214) {
|
||||
func_80033C30((Vec3f *) &thisx->world, (Vec3f *) &sp60, (u8)0xFFU, play);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Graphics macros
|
||||
|
||||
There are 5 graphics macro blocks here:
|
||||
```C
|
||||
temp_v0 = temp_s0->polyOpa.p;
|
||||
temp_s0->polyOpa.p = temp_v0 + 8;
|
||||
temp_v0->words.w0 = 0xDB060020;
|
||||
temp_a0 = *(&D_809F5EA0 + (thisx->unk268 * 4));
|
||||
temp_v0->words.w1 = (temp_a0 & 0xFFFFFF) + gSegments[(u32) (temp_a0 * 0x10) >> 0x1C] + 0x80000000;
|
||||
```
|
||||
|
||||
We've seen one of these before: gfxdis gives
|
||||
```C
|
||||
$ gfxdis.f3dex2 -g "POLY_OPA_DISP++" -d DB06002012345678
|
||||
gSPSegment(POLY_OPA_DISP++, 0x08, 0x12345678);
|
||||
```
|
||||
and looking at the data shows
|
||||
```
|
||||
glabel D_809F5EA0
|
||||
.word 0x060027D0, 0x060025D0, 0x06002750, 0x00000000
|
||||
```
|
||||
which is an array of pointers to something again. It is used inside a `SEGMENTED_TO_VIRTUAL`, so they are most likely textures, and this block becomes
|
||||
```C
|
||||
gSPSegment(POLY_OPA_DISP++, 0x08, SEGMENTED_TO_VIRTUAL(D_809F5EA0[this->unk_268]));
|
||||
```
|
||||
|
||||
Next,
|
||||
```C
|
||||
temp_v0_2 = temp_s0->polyOpa.p;
|
||||
temp_s0->polyOpa.p = temp_v0_2 + 8;
|
||||
temp_v0_2->words.w0 = 0xE7000000;
|
||||
temp_v0_2->words.w1 = 0;
|
||||
```
|
||||
which we can find immediately using
|
||||
```
|
||||
$ gfxdis.f3dex2 -g "POLY_OPA_DISP++" -d E700000000000000
|
||||
gDPPipeSync(POLY_OPA_DISP++);
|
||||
```
|
||||
|
||||
Third,
|
||||
```C
|
||||
temp_v0_3 = temp_s0->polyOpa.p;
|
||||
temp_s0->polyOpa.p = temp_v0_3 + 8;
|
||||
temp_v0_3->words.w0 = 0xFB000000;
|
||||
temp_v1 = (thisx->unk26A * 4) + &D_809F5E4C;
|
||||
temp_v0_3->words.w1 = (temp_v1->unk-2 << 8) | (temp_v1->unk-4 << 0x18) | (temp_v1->unk-3 << 0x10) | 0xFF;
|
||||
```
|
||||
this looks more troublesome. We find
|
||||
```
|
||||
$ gfxdis.f3dex2 -g "POLY_OPA_DISP++" -d FB00000012345678
|
||||
gDPSetEnvColor(POLY_OPA_DISP++, 0x12, 0x34, 0x56, 0x78);
|
||||
```
|
||||
|
||||
Now we need to work out what the last four arguments are. Two things are going on here: `D_809F5E4C` is an array of something:
|
||||
```
|
||||
glabel D_809F5E4C
|
||||
.word 0xFFFFFFFF, 0xFFC3AFFF, 0xD2FF00FF, 0xFFFFFFFF, 0xD2FF00FF, 0xFFC3AFFF, 0xFFFFFFFF, 0xFFC3AFFF, 0xD2FF00FF
|
||||
```
|
||||
Worse, this is being accessed with pointer subtraction in the second word. `temp_v1 = (thisx->unk26A * 4) + &D_809F5E4C;` tells us that the array has elements of size 4 bytes, and the graphics macro implies the elements are colors. Colors of size 4 bytes are `Color_RGBA8`. Usually, we write colors in decimal, so `D_809F5E4C` becomes
|
||||
```C
|
||||
static Color_RGBA8 D_809F5E4C[] = {
|
||||
{ 255, 255, 255, 255 }, { 255, 195, 175, 255 }, { 210, 255, 0, 255 },
|
||||
{ 255, 255, 255, 255 }, { 210, 255, 0, 255 }, { 255, 195, 175, 255 },
|
||||
{ 255, 255, 255, 255 }, { 255, 195, 175, 255 }, { 210, 255, 0, 255 },
|
||||
};
|
||||
```
|
||||
|
||||
Now, we have two things to worry about: how to implement the negative pointer access, and how the second word is built. Negative accesses can be done by just subtracting 1, so that
|
||||
```C
|
||||
temp_v1 = D_809F5E4C[this->unk_26A - 1];
|
||||
```
|
||||
and then
|
||||
```C
|
||||
temp_v0_3->words.w1 = (temp_v1->unk2 << 8) | (temp_v1->unk0 << 0x18) | (temp_v1->unk3 << 0x10) | 0xFF;
|
||||
```
|
||||
or rather, since it is a `Color_RGB8`,
|
||||
```C
|
||||
temp_v0_3->words.w1 = (temp_v1.b << 8) | (temp_v1.r << 0x18) | (temp_v1.g << 0x10) | 0xFF;
|
||||
```
|
||||
|
||||
The last thing to worry about is how to put this word into the macro. Let's think about what the word actually is in a concrete case; it is easiest to see what is going on in hex, so suppose we are in the case
|
||||
```C
|
||||
temp_v1 = { 0xFF, 0xC3, 0xAF, 0xFF };
|
||||
```
|
||||
|
||||
Then the calculation is
|
||||
```
|
||||
(0xAF << 8) | (0xFF << 0x18) | (0xC3 << 0x10) | 0xFF = 0xAF00 | 0xC30000 | 0xFF0000000 | 0xFF = 0xFFC3AFFF
|
||||
```
|
||||
and so all this calculation is doing is turning `temp_v1` back into a word, with the last byte replaced by `0xFF` (that all the elements of `D_809F5E4C` have `0xFF` as their last element anyway is irrelevant here). Looking back at the output of gfxdis, we see that this actually means that the r,g,b just slot into the penultimate three arguments, the last being `0xFF`, leaving
|
||||
```C
|
||||
temp_v1 = D_809F5E4C[this->unk_26A - 1];
|
||||
gDPSetEnvColor(POLY_OPA_DISP++, temp_v1.r, temp_v1.g, temp_v1.b, 0xFF);
|
||||
```
|
||||
as the residue of this block; it may turn out later that we can eliminate the temp.
|
||||
|
||||
|
||||
The last two are much easier:
|
||||
```C
|
||||
temp_v0_4 = temp_s0->polyOpa.p;
|
||||
temp_s0->polyOpa.p = temp_v0_4 + 8;
|
||||
temp_v0_4->words.w0 = 0xDA380003;
|
||||
sp38 = temp_v0_4;
|
||||
sp38->words.w1 = Matrix_NewMtx(play->state.gfxCtx, (char *) "../z_en_dnt_nomal.c", 0x716);
|
||||
```
|
||||
The macro is
|
||||
```
|
||||
$ gfxdis.f3dex2 -g "POLY_OPA_DISP++" -d DA38000312345678
|
||||
gSPMatrix(POLY_OPA_DISP++, 0x12345678, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
|
||||
```
|
||||
and the second argument is filled by the `Matrix_NewMtx` function:
|
||||
```C
|
||||
gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(play->state.gfxCtx, "../z_en_dnt_nomal.c", 1814), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
|
||||
```
|
||||
|
||||
Lastly,
|
||||
```C
|
||||
temp_v0_5 = temp_s0->polyOpa.p;
|
||||
temp_s0->polyOpa.p = temp_v0_5 + 8;
|
||||
temp_v0_5->words.w1 = (u32) &D_06001B00;
|
||||
temp_v0_5->words.w0 = 0xDE000000;
|
||||
```
|
||||
The macro is
|
||||
```
|
||||
$ gfxdis.f3dex2 -g "POLY_OPA_DISP++" -d DE00000012345678
|
||||
gSPDisplayList(POLY_OPA_DISP++, 0x12345678);
|
||||
```
|
||||
and so `D_06001B00` is a displaylist, so the type in the externed data at the top of the file can be changed to `Gfx D_06001B00[]`. Arrays act like pointers, so we don't need the `&` in the macro:
|
||||
```C
|
||||
gSPDisplayList(POLY_OPA_DISP++, D_06001B00);
|
||||
```
|
||||
|
||||
Putting this all together
|
||||
```C
|
||||
void func_809F5A6C(Actor *thisx, PlayState *play) {
|
||||
EnDntNormal *this = THIS;
|
||||
? sp60;
|
||||
Actor *sp14;
|
||||
Color_RGBA8 temp_v1;
|
||||
|
||||
sp60.unk0 = (s32) D_809F5E94.unk0;
|
||||
sp60.unk4 = (s32) D_809F5E94.unk4;
|
||||
sp60.unk8 = (s32) D_809F5E94.unk8;
|
||||
|
||||
OPEN_DISPS(play->state.gfxCtx, "../z_en_dnt_nomal.c", 1790);
|
||||
Gfx_SetupDL_25Opa(play->state.gfxCtx);
|
||||
|
||||
gSPSegment(POLY_OPA_DISP++, 0x08, SEGMENTED_TO_VIRTUAL(D_809F5EA0[this->unk_268]));
|
||||
|
||||
sp14 = this;
|
||||
SkelAnime_DrawOpa(play, thisx->unk150, thisx->unk16C, &func_809F58E4, &func_809F59E4);
|
||||
Matrix_Translate(thisx->unk21C, thisx->unk220, (bitwise f32) thisx->unk224, (u8)0U);
|
||||
Matrix_Scale(0.01f, 0.01f, 0.01f, (u8)1U);
|
||||
|
||||
gDPPipeSync(POLY_OPA_DISP++);
|
||||
temp_v1 = D_809F5E4C[this->unk_26A - 1];
|
||||
gDPSetEnvColor(POLY_OPA_DISP++, temp_v1.r, temp_v1.g, temp_v1.r, 0xFF);
|
||||
gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(play->state.gfxCtx, "../z_en_dnt_nomal.c", 1814), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
|
||||
gSPDisplayList(POLY_OPA_DISP++, D_06001B00);
|
||||
|
||||
CLOSE_DISPS(play->state.gfxCtx, "../z_en_dnt_nomal.c", 1817);
|
||||
|
||||
if (&func_809F49A4 == this->unk214) {
|
||||
func_80033C30((Vec3f *) &this.actor->world, (Vec3f *) &sp60, (u8)0xFFU, play);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### SkelAnime_Draw and the LimbDraws
|
||||
|
||||
Some more general tidying up can be done here (`sp60` and so `D_809F5E94` are `Vec3f`, for example, and under normal circumstances we'd know that ), but the big remaining issue is
|
||||
```C
|
||||
sp14 = this;
|
||||
SkelAnime_DrawOpa(play, thisx->unk150, thisx->unk16C, func_809F58E4, func_809F59E4);
|
||||
```
|
||||
If we look at the definition of `SkelAnime_DrawOpa`, we find that it's missing the last argument. This is mips2c not noticing why `this` has been put on the stack: this code should actually be
|
||||
```C
|
||||
SkelAnime_DrawOpa(play, thisx->unk150, thisx->unk16C, func_809F58E4, func_809F59E4, this);
|
||||
```
|
||||
mips2c doing this is not especially unusual, so bear it in mind.
|
||||
|
||||
The other thing this tells us is that `func_809F58E4` is of type `OverrideLimbDraw`, and `func_809F59E4` of type `PostLimbDraw`. Their names are fairly self-explanatory. Filling in the prototypes as
|
||||
```C
|
||||
s32 func_809F58E4(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, void* thisx);
|
||||
void func_809F59E4(PlayState* play, s32 limbIndex, Gfx** dList, Vec3s* rot, void* thisx);
|
||||
```
|
||||
and running mips2c gives
|
||||
|
||||
```C
|
||||
s32 func_809F58E4(PlayState *play, s32 limbIndex, Gfx **dList, Vec3f *pos, Vec3s *rot, void *thisx) {
|
||||
GraphicsContext *sp38;
|
||||
Gfx *sp28;
|
||||
Gfx *temp_v1;
|
||||
Gfx *temp_v1_2;
|
||||
GraphicsContext *temp_a1;
|
||||
void *temp_v0;
|
||||
|
||||
if ((limbIndex == 1) || (limbIndex == 3) || (limbIndex == 4) || (limbIndex == 5) || (limbIndex == 6)) {
|
||||
temp_a1 = play->state.gfxCtx;
|
||||
sp38 = temp_a1;
|
||||
Graph_OpenDisps(&sp28, temp_a1, (const char *) "../z_en_dnt_nomal.c", 0x6C5);
|
||||
temp_v1 = sp38->polyOpa.p;
|
||||
sp38->polyOpa.p = temp_v1 + 8;
|
||||
temp_v1->words.w1 = 0;
|
||||
temp_v1->words.w0 = 0xE7000000;
|
||||
temp_v1_2 = sp38->polyOpa.p;
|
||||
sp38->polyOpa.p = temp_v1_2 + 8;
|
||||
temp_v1_2->words.w0 = 0xFB000000;
|
||||
temp_v0 = (thisx->unk26A * 4) + &D_809F5E4C;
|
||||
temp_v1_2->words.w1 = (temp_v0->unk-2 << 8) | (temp_v0->unk-4 << 0x18) | (temp_v0->unk-3 << 0x10) | 0xFF;
|
||||
Graph_CloseDisps(&sp28, play->state.gfxCtx, (const char *) "../z_en_dnt_nomal.c", 0x6CF);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void func_809F59E4(PlayState *play, s32 limbIndex, Gfx **dList, Vec3s *rot, void *thisx) {
|
||||
? sp18;
|
||||
|
||||
sp18.unk0 = (s32) D_809F5E88.unk0;
|
||||
sp18.unk4 = (s32) D_809F5E88.unk4;
|
||||
sp18.unk8 = (s32) D_809F5E88.unk8;
|
||||
if (thisx->unk26A == 0) {
|
||||
if (limbIndex == 5) {
|
||||
Matrix_MultVec3f((Vec3f *) &sp18, thisx + 0x27C);
|
||||
return;
|
||||
}
|
||||
} else if (limbIndex == 7) {
|
||||
Matrix_MultVec3f((Vec3f *) &sp18, thisx + 0x27C);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This structure is pretty typical: both edit what certain limbs do. Both also usually need a `ActorName *this = THIS;` at the top. We have seen both of the macros in the former before: applying the usual procedure, we find that it becomes
|
||||
```C
|
||||
s32 func_809F58E4(PlayState *play, s32 limbIndex, Gfx **dList, Vec3f *pos, Vec3s *rot, void *thisx) {
|
||||
EnDntNormal *this = THIS;
|
||||
|
||||
if ((limbIndex == 1) || (limbIndex == 3) || (limbIndex == 4) || (limbIndex == 5) || (limbIndex == 6)) {
|
||||
OPEN_DISPS(play->state.gfxCtx, "../z_en_dnt_nomal.c", 1733);
|
||||
gDPPipeSync(POLY_OPA_DISP++);
|
||||
gDPSetEnvColor(POLY_OPA_DISP++, D_809F5E4C[this->type - 1].r, D_809F5E4C[this->type - 1].g, D_809F5E4C[this->type - 1].b, 255);
|
||||
CLOSE_DISPS(play->state.gfxCtx, "../z_en_dnt_nomal.c", 1743);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
Notice that this function returns `0`. OverrideLimbDraw almost always returns `0`.
|
||||
|
||||
The latter function is easier, and it is probably unnecessary to explain to the reader what it is necessary to do to it to clean it up.
|
@ -1,202 +0,0 @@
|
||||
# List of helper scripts
|
||||
|
||||
This list gives brief information on the most common usage cases. For more information, first try using `-h` or `--help` as an argument, and failing that, ask in #oot-decomp-help or #tools-other in the Discord.
|
||||
|
||||
Many tools require activating a Python virtual environment that contains Python
|
||||
dependencies. This virtual environment is automatically installed into the
|
||||
`.venv` directory by `make setup`, but you need to **activate** it in your
|
||||
current terminal session in order to run Python tools. To start using the
|
||||
virtual environment in your current terminal run:
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
Keep in mind that for each new terminal session, you will need to activate the
|
||||
Python virtual environment again. That is, run the above `source .venv/bin/activate` command.
|
||||
|
||||
To deactivate the virtual environment, run
|
||||
|
||||
```bash
|
||||
deactivate
|
||||
```
|
||||
|
||||
and your terminal session state will be restored to what it was before.
|
||||
|
||||
## m2ctx
|
||||
|
||||
This generates the context for mips2c to use to type objects in its output. It lives in the tools directory. Running
|
||||
```sh
|
||||
./tools/m2ctx.py <path_to_c>
|
||||
```
|
||||
will produce a file in the root directory called `ctx.c`. You open this file and copy it into the mips2c context box.
|
||||
|
||||
The rule of thumb is to rerun this every time you change something significant to other functions, like the struct in the header or a function prototype, and probably after every function, at least at first. As with most other things on this project, you will develop intuition for when this is required.
|
||||
|
||||
## diff
|
||||
|
||||
This is in the repo's root directory. It is the main comparison tool to check your C code generates the right MIPS.
|
||||
|
||||
The usual way diff is used is
|
||||
```sh
|
||||
./diff.py -mwo3 <function_name>
|
||||
```
|
||||
|
||||
- `m` automatically runs make as necessary
|
||||
- `o` allows using symbol names
|
||||
- `w` refreshes the diff output when the c file is saved (only the c file, not the header)
|
||||
- `3` allows comparison of the previous and current saves of the file.
|
||||
|
||||
Many other options exist, use the `-h` to see them.
|
||||
|
||||
In order to use `diff.py` with the symbol names (with `o`), we need a copy of the code to compare against. This is done by copying the `build` folder into a folder called `expected`. Copying in Windows on WSL is very slow, so run
|
||||
```sh
|
||||
mkdir expected
|
||||
cp -r build/ expected/
|
||||
```
|
||||
from the main directory of the repository. You should end up with the folder structure `expected/build/...`.
|
||||
|
||||
![Example of a diff](images/func_80A87B9C_diff1.png)
|
||||
|
||||
The colors have the following meanings:
|
||||
|
||||
- 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 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.
|
||||
|
||||
## decomp-permuter
|
||||
|
||||
This is linked in #resources in the Discord.
|
||||
|
||||
For inspiration when you run out of ideas to match a function. It is unlikely to match it completely by itself, but if you can't see from the MIPS or your code where you have issues, it will often tell you where to start looking.
|
||||
|
||||
First, import the C file and MIPS of the function to compare using
|
||||
```sh
|
||||
./import.py <path_to_c> <path_to_func_name.s>
|
||||
```
|
||||
|
||||
It will put it in a subdirectory of `nonmatchings`. You then run
|
||||
```sh
|
||||
./permuter.py nonmatchings/<function_name>/
|
||||
```
|
||||
to produce suggestions. There are various arguments that can be used, of which the most important initially is `-j`: `-jN` tells it to use `N` CPU threads.
|
||||
|
||||
Suggestions are saved in the function directory it imported the function into.
|
||||
|
||||
## first_diff
|
||||
|
||||
Tells you where your built rom first differs from the baserom. It gives you a memory address that you can use to do, e.g. a binary diff, and also tries too find what function or data this address is in. Run with
|
||||
```C
|
||||
./first_diff.py
|
||||
```
|
||||
|
||||
If the rom is shifted, the first problem will be in gDMADataTable. Ignore this and look at the next one for where you actually need to look to see what's happened. The last line makes a guess on this location you need to edit to fix the problem.
|
||||
|
||||
## sym_info
|
||||
|
||||
Gives information about a `D_address` symbol (ROM address, RAM address, file). Run
|
||||
```C
|
||||
./sym_info.py <D_number>
|
||||
```
|
||||
|
||||
## ichaindis
|
||||
|
||||
This is used to convert the data associated to the `D_address` in
|
||||
```C
|
||||
Actor_ProcessInitChain(&this->actor, &D_address);
|
||||
```
|
||||
into an InitChain. It lives in the tools directory. Run
|
||||
```sh
|
||||
./tools/ichaindis.py <path_to_baserom> <D_address>
|
||||
```
|
||||
and copy the output. (This used to only take the ROM address, which you would need to get from `sym_info.py`. Now you can just give it the RAM address, or even the raw `D_address`.)
|
||||
|
||||
## colliderinit
|
||||
|
||||
This is used to convert data `D_address` in the various ColliderInit functions into the format of a collider. It lives in `tools/overlayhelpers`. Because there are different types of collider, you need to give it the type of collider as well. This does not need the baserom path, and a recent update allows it to be run from anywhere. You also have to give it the `<address>` without the leading `D_`.
|
||||
```sh
|
||||
./colliderinit.py <address> <type> <num>
|
||||
```
|
||||
Collider types supported are
|
||||
|
||||
- `ColliderJntSphInit`
|
||||
- `ColliderCylinderInit`
|
||||
- `ColliderTrisInit`
|
||||
- `ColliderQuadInit`
|
||||
- `ColliderJntSphElementInit`
|
||||
- `ColliderTrisElementInit`
|
||||
|
||||
and `num` is used only for `ColliderJntSphElementInit`.
|
||||
|
||||
## sfxconvert
|
||||
|
||||
Automatically converts sound effect numbers in a file into their corresponding `#defines`, taking into account if `SFX_FLAG` is used. Run on a specific C file,
|
||||
```sh
|
||||
./tools/sfxconvert.py <path_to_file> <path_to_repo>
|
||||
```
|
||||
|
||||
Optional arguments are `-o output` to output to a different file and `-v` to give verbose output (i.e. tell you what changes it has made).
|
||||
|
||||
## vt_fmt
|
||||
|
||||
This turns the strange strings in the `PRINTF`s into the human-readable equivalent instructions. Copy the contents, including the quotation marks, and run
|
||||
```sh
|
||||
./tools/vt_fmt.py "contents"
|
||||
```
|
||||
and replace the contents of the printf with the output.
|
||||
|
||||
## Glank's N64 tools
|
||||
|
||||
In particular, the ones used to decompile graphics macros. Their use is discussed in the section on [decompiling Draw functions](draw_functions.md).
|
||||
|
||||
## graphovl
|
||||
|
||||
This generates a directed graph showing an actor's function. Search for `graphovl.py` in the Discord. Put it in the root directory of the project, and run
|
||||
```sh
|
||||
./graphovl.py Actor_Name
|
||||
```
|
||||
to produce a png in the `graphs` subdirectory.
|
||||
|
||||
## format
|
||||
|
||||
Shell script that does a standardised format to the C code. Can be run on a file, a directory, or the whole codebase. Run this before you submit a PR.
|
||||
|
||||
## find_unused_asm
|
||||
|
||||
Tracks down any `.s` files no longer used by the project. Does not ignore comments, so you have to actually remove any `#pragma` lines for it to consider the file unused.
|
||||
```sh
|
||||
./tools/find_unused_asm.sh
|
||||
```
|
||||
will output a list of all such files, while adding `-d` deletes the files.
|
||||
|
||||
## csdis
|
||||
|
||||
This converts the cutscene data into macros that the cutscene system uses. Cutscenes are generally very long, so I recommend sending the output straight to a file with `>`, rather than trying to copy it all from the terminal. Run
|
||||
```sh
|
||||
./tools/csdis.py <address>
|
||||
```
|
||||
on the address from the `D_address` containing the cutscene data.
|
||||
|
||||
## regconvert
|
||||
|
||||
This converts the direct memory references, of the form `gRegEditor->data[index]` or `gRegEditor + 0x<offset>`, into the corresponding REG macros defined in [regs.h](../include/regs.h). Run
|
||||
```sh
|
||||
./tools/regconvert.py <index>
|
||||
```
|
||||
if you have it in the form `gRegEditor->data[index]`, or
|
||||
```sh
|
||||
./tools/regconvert.py --offset <offset>
|
||||
```
|
||||
if you have it in the form `gRegEditor + 0x<offset>`. You can also run it on a whole file using `--file <path/to/file>`.
|
||||
|
||||
## assist
|
||||
|
||||
This takes a function name, and looks for functions with very similar assembly code. It outputs the best matches, and tells you if there is a decompiled one.
|
||||
```sh
|
||||
./tools/assist.py <function_name>
|
||||
```
|
||||
It has two optional arguments:
|
||||
- `--threshold` adjust how high the matching threshold is, 1.0 being highest, 0.0 lowest
|
||||
- `--num-out` change the number of matches to output
|
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 80 KiB |
Before Width: | Height: | Size: 138 KiB |
Before Width: | Height: | Size: 237 KiB |
Before Width: | Height: | Size: 250 KiB |
Before Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 97 KiB |
@ -1,74 +0,0 @@
|
||||
# Introduction to decomp
|
||||
|
||||
In this project, we are decompiling The Legend of Zelda: Ocarina of Time. This means that we take the assembly language that is on the cartridge,
|
||||
|
||||
```
|
||||
glabel func_80A13098
|
||||
/* 00028 80A13098 8482001C */ lh $v0, 0x001C($a0) ## 0000001C
|
||||
/* 0002C 80A1309C 24010004 */ addiu $at, $zero, 0x0004 ## $at = 00000004
|
||||
/* 00030 80A130A0 14410003 */ bne $v0, $at, .L80A130B0
|
||||
/* 00034 80A130A4 244EFFFE */ addiu $t6, $v0, 0xFFFE ## $t6 = FFFFFFFE
|
||||
/* 00038 80A130A8 10000002 */ beq $zero, $zero, .L80A130B4
|
||||
/* 0003C 80A130AC A480001C */ sh $zero, 0x001C($a0) ## 0000001C
|
||||
.L80A130B0:
|
||||
/* 00040 80A130B0 A48E001C */ sh $t6, 0x001C($a0) ## 0000001C
|
||||
.L80A130B4:
|
||||
/* 00044 80A130B4 8C8F0330 */ lw $t7, 0x0330($a0) ## 00000330
|
||||
/* 00048 80A130B8 24020001 */ addiu $v0, $zero, 0x0001 ## $v0 = 00000001
|
||||
/* 0004C 80A130BC 24180011 */ addiu $t8, $zero, 0x0011 ## $t8 = 00000011
|
||||
/* 00050 80A130C0 A1E20004 */ sb $v0, 0x0004($t7) ## 00000004
|
||||
/* 00054 80A130C4 A08201B8 */ sb $v0, 0x01B8($a0) ## 000001B8
|
||||
/* 00058 80A130C8 A08201B9 */ sb $v0, 0x01B9($a0) ## 000001B9
|
||||
/* 0005C 80A130CC 03E00008 */ jr $ra
|
||||
/* 00060 80A130D0 A0980117 */ sb $t8, 0x0117($a0) ## 00000117
|
||||
```
|
||||
|
||||
(the commented numbers on the left are the original machine code, the middle the translation into MIPS assembly, the right useful information about the numbers in the code)
|
||||
and turn it into compilable C code:
|
||||
|
||||
```C
|
||||
void func_80A13098(EnFirefly* this) {
|
||||
if (this->actor.params == 4) {
|
||||
this->actor.params = 0;
|
||||
} else {
|
||||
this->actor.params -= 2;
|
||||
}
|
||||
this->collider.list->body.atDmgInfo.effect = 1;
|
||||
this->auraType = 1;
|
||||
this->onFire = 1;
|
||||
this->actor.naviEnemyId = 0x11;
|
||||
}
|
||||
```
|
||||
|
||||
which is intended to be as close to the original code as possible. We are doing *matching* decomp: in the right context, and with the right compiler settings, the above C compiles into *precisely* the assembly code above, not just equivalent code.
|
||||
|
||||
N.B. We are using only publicly available code. In particular, we are not looking at any of the recent Nintendo source code leaks. (These apparently contain very little Ocarina of Time material anyway.)
|
||||
|
||||
Progress of the project can be found at [https://zelda64.dev]. The long-term goal of this project is to obtain a complete compilable version of the code for every publicly released version of Ocarina of Time (there are also sister projects for Majora's Mask and other Zelda games). *We are not working on a PC Port, and this project will not be making one*, although the resulting code will be very useful if someone does intend to make such a port.
|
||||
|
||||
Most of the discussion on the project takes place on the Zelda Decompilation Discord (linked in the README.md). We are very welcoming to newcomers and are happy to help you with any problems you might have with the decompilation process.
|
||||
|
||||
## What do I need to know to take part?
|
||||
|
||||
Basic knowledge of C, particularly arrays and pointers, is extremely useful. Knowledge of MIPS is not required initially, but if you are serious about decompilation you will soon pick up a lot of it.
|
||||
|
||||
Knowledge of the fundamentals of git and GitHub is required. There are a number of tutorials available online, and a later document in this tutorial describes how you contribute to this project outside the actual decompilation process.
|
||||
|
||||
The most useful knowledge to have is a general understanding of how the game works. An afternoon of constructive mucking about in the [Practice Rom](http://practicerom.com) (aka GZ) will be very beneficial if you have not looked at the game's subsurface workings before.
|
||||
|
||||
## Structure of the code
|
||||
|
||||
A lot of work has already been done on the code to bring it into a format that is easy to decompile. I will discuss actors, since this is where the majority of new people should begin.
|
||||
|
||||
An *actor* is any thing in the game that moves or performs actions or interactions: Link is an actor, enemies are actors, NPCs are actors, props like grass are actors (Fishing is also an actor, the largest one, but you don't need to know about it). The vast majority of actors are *overlays*, which means they are loaded only when the game needs them.
|
||||
|
||||
In the code, each actor is associated to several files: there is
|
||||
- the main .c file, e.g. `src/overlays/actors/ovl_En_Firefly/z_en_firefly.c`
|
||||
- the actor's Header file, e.g. `src/overlays/actors/ovl_En_Firefly/z_en_firefly.h`
|
||||
- various .o files that tell the `make` script how to incorporate it into building the ROM,
|
||||
|
||||
and then for undecompiled actors, various assembly (.s) files, generally including:
|
||||
- one for the actor's *data* (this usually includes things like its collision information about how to draw it, and various other stuff that is used in it), e.g. `data/overlays/actors/z_en_firefly.data.s`
|
||||
- one for each function in the actor, e.g. `asm/non_matchings/overlays/actors/ovl_En_Firefly/func_80A13098.s`
|
||||
|
||||
The basic process of decomp is to take one of the .s files, run it through a decompilation program (mips_to_c) that reads the ASM very literally, and then, through humen ingenuity, reshape it into code that not only compiles in the first place, but completely matches the original code (well-written or otherwise).
|
@ -1,97 +0,0 @@
|
||||
# The merging process
|
||||
|
||||
## Optional: Documentation
|
||||
|
||||
It is helpful to document the functions and variables in the actor before you Pull Request it. The aim is to provide code that is sufficiently clear to be self-documenting, but it is worth leaving a comment on anything you find obscure or confusing. (Pull Request reviews will let you know if you are leaving too many comments.) Useful things to do documentation-wise:
|
||||
|
||||
- Name all (or most) of the functions.
|
||||
- Name all the variables in the actor struct.
|
||||
- Create enums for params, and any other numbers that would benefit from that sort of clarity.
|
||||
|
||||
You can test things using the practice rom for a retail version (watches and memory view is especially helpful), as well as the generated rom with Project 64 and something like Spectrum. The most important things to know if using a retail version to test are:
|
||||
|
||||
- all the addresses will be different
|
||||
- actor structs in debug have 10 bytes of padding at the end, so subtract `0x10` from any subsequent offsets.
|
||||
|
||||
If you want to use `diff` after renaming anything, particularly functions, remember to copy the `build/` folder into `expected/` so use the correct symbols. *Make sure that `make` gives `OK` before doing this, or you're going to get very confused.*
|
||||
|
||||
Finally, *if you are not sure what something does, either ask or leave it unnamed: it will be less confusing later if things are unnamed than if they are wrongly named*
|
||||
|
||||
## Preparing to PR
|
||||
|
||||
### Change the `spec`
|
||||
|
||||
Specifically, to use the automatically generated reloc, rather than the original. In the case of an entirely matched actor, you find the section relating to the actor that you edited before:
|
||||
|
||||
```
|
||||
beginseg
|
||||
name "ovl_En_Jj"
|
||||
include "$(BUILD_DIR)/src/overlays/actors/ovl_En_Jj/z_en_jj.o"
|
||||
//include "$(BUILD_DIR)/data/overlays/actors/z_en_jj.data.o"
|
||||
include "$(BUILD_DIR)/data/overlays/actors/z_en_jj.reloc.o"
|
||||
endseg
|
||||
```
|
||||
|
||||
and change to use our reloc:
|
||||
|
||||
```
|
||||
beginseg
|
||||
name "ovl_En_Jj"
|
||||
include "$(BUILD_DIR)/src/overlays/actors/ovl_En_Jj/z_en_jj.o"
|
||||
include "$(BUILD_DIR)/src/overlays/actors/ovl_En_Jj/ovl_En_Jj_reloc.o"
|
||||
endseg
|
||||
```
|
||||
|
||||
(copy the path, then copy the directory name and put `_reloc.o` after it).
|
||||
|
||||
### Delete the asm files.
|
||||
|
||||
We have a script that will detect and delete unused asm files, `find_unused_asm.py`. Running it bare will output a list of all the unused files, while passing `-d` will delete them.
|
||||
|
||||
This does not distinguish comments, so remove all commented references to the files themselves first. If you have left the `#pragma` lines in but commented out, the following regular expression will find them:
|
||||
```
|
||||
\n//.*#pragma .*?\)
|
||||
```
|
||||
|
||||
### Non-matchings
|
||||
|
||||
If you can't match a function even with everyone's, don't worry overlong about it. Hopefully you can get it to do the same thing as the original (non-matching), and then you set it up to use the original asm for the matching build, and your code for the non-matching. You can look at the other partially-matched actors to see how to set this up with `#ifdef`s.
|
||||
|
||||
### Format
|
||||
|
||||
Run the formatting script `format.py`, to format the C files in the standard way we use.
|
||||
|
||||
### Merge main
|
||||
|
||||
To make sure the PR builds correctly with the current main, you need to merge `upstream/main` before you make the PR. This tends to break things, that you have to fix to get it to compile correctly again.
|
||||
|
||||
## Pull Requests
|
||||
|
||||
Push commits to your fork of the repository on GitHub, and then open a pull request. Name the PR something sensible, like
|
||||
|
||||
- `EnJj OK and documented` (if all the functions match and your documentation is fairly complete)
|
||||
- `EnJj OK` (if all the functions match)
|
||||
- `EnJj (n nonmatching)` (if you couldn't get one or more functions to work, but to the best of your knowledge they are equivalent code)
|
||||
- `EnJj (1 nonequivalent)` (if you couldn't get one or more functions to work, and do not believe the code has the same effect)
|
||||
|
||||
and so on, although these four tend to cover most cases. Feel free to add a comment describing anything interesting you had to do or issues in non-matchings,
|
||||
|
||||
|
||||
### Reviews
|
||||
|
||||
Pull requests may be reviewed by anyone (who knows enough about the conventions of the project), but all are usually reviewed by Fig and Roman.
|
||||
|
||||
To implement suggestions made in reviews, it is generally easier to be consistent if you push more commits from your local branch. It is quite possible that in the meantime some other PR has gone in, and git will ask you to merge main before you add more commits. This is normally fairly painless, although often you have to resolve merge conflicts. If in doubt, backup your work before doing anything, and ask in Discord before doing anything drastic, or if you don't understand what git is telling you.
|
||||
|
||||
There is no need to wait for your PR to be approved and committed before working on your next actor.
|
||||
|
||||
|
||||
## Trello
|
||||
|
||||
It's helpful to use the labels on Trello.
|
||||
- RESERVED is obvious.
|
||||
- Work in Progress is for when you're actively working on something
|
||||
- Matched for when it is totally decompiled and matching
|
||||
- Documented if at least everything is named and odd code is commented. We'll likely wipe these and start over when proper documentation begins.
|
||||
|
||||
We now have a PR label on the Trello that you can use to indicate your actor is in a PR. When the actor is committed to main, you can move the actor into the `Decompiled Files (Overlays)` column: it goes at the top if there is a non-matching, and below if not.
|
@ -1,174 +0,0 @@
|
||||
# Object Decompilation
|
||||
|
||||
Object decompilation is the process of taking an object file and writing instructions to extract the various assets from it in the correct format, with appropriate labelling to distinguish their nature and/or use.
|
||||
|
||||
## What is an object file?
|
||||
|
||||
An object file is generally where most of the information used by overlays is stored. Its contents can include
|
||||
|
||||
- Vertices (positional/normal/color data used by displaylists)
|
||||
- Textures
|
||||
- DisplayLists (instructions to the graphics processor on how to put together textures and vertices)
|
||||
- Skeleton (The underlying structure of an actor's shape, that can be manipulated to change its "pose")
|
||||
- Animations
|
||||
- Prerendered backgrounds
|
||||
|
||||
## How we work with objects
|
||||
|
||||
Because these are regarded as copyrighted, we do not want them in the repository. We instead extract them from the ROM. To do this we use a system called ZAPD (Zelda Asset Processor for Decompilation). The main aim of object decompilation is to give ZAPD an XML file that tells it what the assets it is supposed to be extracting actually are, so it can put them in the right format and name them.
|
||||
|
||||
|
||||
## How to decomp an object
|
||||
|
||||
Choose an object to decomp. As usual, some will be easier than others. For reasons explained shortly, it is much easier to decomp an object if all actors that use it are decompiled.
|
||||
|
||||
### Files and folders
|
||||
|
||||
Select the XML file of your selected object, which should be in `assets/xml/objects/object_name.xml`.
|
||||
|
||||
The ZAPD output will go in the folder `assets/objects/object_name/`. You'll want this folder open later to check the output is correct.
|
||||
|
||||
### Examining actor files
|
||||
|
||||
Most objects are used by at least one actor. For those used by an actor, we can use the script `tools/xmlcreate.py` on the actor to separate all the blobs of data in the object that we already know about. (While it is possible to do this manually, it is much simpler to run the script first and sort it out afterwards, since it won't miss anything accidentally.)
|
||||
|
||||
Many objects have been added in an automated way, so most constituent parts of each object are already identified, but will still need to be named and documented properly. Also, these objects usually have some blobs unreferenced by the object's own contents and hence not automatically extracted; most of the time these can be identified by looking at references in the actor which uses said object.
|
||||
|
||||
### Extracting assets
|
||||
|
||||
You can run `extract_assets.py` to extract the object's assets. Running it with no arguments extracts *everything* all over again, though. A better way is to run it with `-s` (for single file), and give it the location of the object you want to extract relative to `assets`, i.e.
|
||||
|
||||
```bash
|
||||
./extract_assets.py -s objects/object_name
|
||||
```
|
||||
|
||||
This should populate the folder you created earlier. ZAPD produces a C file containing the extracted object data, which will be `assets/objects/object_name/object_name.c`. Any data that you have not specified the type of, or is not referenced elsewhere in the object, is extracted as unknown blobs (usually named `unaccounted_XXXXXX`). Open the C file to see if there are any such blobs. (Some are just padding with 0s and can be ignored.)
|
||||
|
||||
You now have to try and decipher these blobs using the [list of tools given below](#tools) to work out what they might be. In the case of unused parts of the object, this can be very difficult.
|
||||
|
||||
### Naming
|
||||
|
||||
A significant question is how deep into the object you want to explicitly categorise and name things.
|
||||
|
||||
1. As a minimum, you need to include every piece of data that is directly accessed by another part of the repo (generally, but not always, actors). This allows for elimination of entries in [`undefined_syms.txt`](../../undefined_syms.txt). For most objects, this includes several of a skeleton, animations, some textures, and collision information.
|
||||
|
||||
2. Naming display lists that are associated to limbs in the skeleton. This is usually straightforward, providing Z64Utils or Hylian Toolbox can show the skeleton properly.
|
||||
|
||||
3. The next level is to name and give a format to any texture files that are produced from the extraction that are so far unnamed.
|
||||
|
||||
4. If you really want to you can name the limbs themselves.
|
||||
|
||||
5. In very rare cases, you may also want to name a particular set of vertices.
|
||||
|
||||
Current naming practice is to name each item in the xml using camelCase as usual, with the `g` prefix (for "global"), and the type of data last (Skel, Anim, DL, Tex), while output texture files are named in `snake_case`.
|
||||
|
||||
Each pass of a successive level will require extracting the single asset again.
|
||||
|
||||
### Textures
|
||||
|
||||
Textures are especially troublesome due to the abundance of formats they can be in. Some are simple RGBA textures, while others use external palettes, and can look meaningless without. If the texture is used in a displaylist, it will tell you the format, but if not, you have to use your best judgement based on anything you know about its context.
|
||||
|
||||
The order of operations is that palettes are loaded first, then the texture, and then the vertices to which it is applied.
|
||||
|
||||
The first argument of `gsDPLoadTextureBlock` tells you the offset, the second the format, the third the bit depth, fourth the width and fifth the height
|
||||
|
||||
The following is a list of the texture formats the Nintendo 64 supports, with their gfxdis names and ZAPD format names.
|
||||
|
||||
|
||||
| Format name | Typing in `gsDPLoadTextureBlock` | "Format" in xml |
|
||||
| ----------------------------------------------- | -------------------------------- | --------------- |
|
||||
| 4-bit intensity (I) | `G_IM_FMT_I, G_IM_SIZ_4b` | i4 |
|
||||
| 4-bit intensity with alpha (I/A) (3/1) | `G_IM_FMT_IA, G_IM_SIZ_4b` | ia4 |
|
||||
| 4-bit color index (CI) | `G_IM_FMT_CI, G_IM_SIZ_4b` | ci4 |
|
||||
| 8-bit I | `G_IM_FMT_I, G_IM_SIZ_8b` | i8 |
|
||||
| 8-bit IA (4/4) | `G_IM_FMT_IA, G_IM_SIZ_8b` | ia8 |
|
||||
| 8-bit CI | `G_IM_FMT_CI, G_IM_SIZ_8b` | ci8 |
|
||||
| 16-bit red, green, blue, alpha (RGBA) (5/5/5/1) | `G_IM_FMT_RGBA, G_IM_SIZ_16b` | rgba16 |
|
||||
| 16-bit IA (8/8) | `G_IM_FMT_IA, G_IM_SIZ_16b` | ia16 |
|
||||
| 16-bit YUV (Luminance, Blue-Y, Red-Y) | `G_IM_FMT_YUV, G_IM_SIZ_16b` | (not used) |
|
||||
| 32-bit RGBA (8/8/8/8) | `G_IM_FMT_RGBA, G_IM_SIZ_32b` | rgba32 |
|
||||
|
||||
The 4-bit formats are loaded using `gDPLoadTextureBlock_4b`. The others use `gDPLoadTextureBlock`.
|
||||
|
||||
For example,
|
||||
|
||||
```c
|
||||
gsDPLoadTextureBlock(D_06006110, G_IM_FMT_RGBA, G_IM_SIZ_16b, 16, 16, 0, G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMIRROR | G_TX_CLAMP, 4, 4, 0, 0),
|
||||
```
|
||||
|
||||
says that there is a texture at offset `0x6110`, its Format is `rgba16`, Width is `16` and Height is `16`, so we can declare
|
||||
|
||||
```XML
|
||||
<Texture Name="gObjectNameSomethingTex" OutName="object_name_something" Format="rgba16" Width="16" Height="16" Offset="0x6110"/>
|
||||
```
|
||||
|
||||
See [this web page](http://n64devkit.square7.ch/tutorial/graphics/3/3_3.htm) for more information about these formats, and [gSP functions](http://n64devkit.square7.ch/n64man/gsp/gSP_INDEX.htm) and [gDP functions](http://n64devkit.square7.ch/n64man/gdp/gDP_INDEX.htm) for more about the graphics functions used.
|
||||
|
||||
The `ci` formats use palettes, which are declared separately. The shape you give the palette does not matter, but to avoid overlap errors it needs to fit into a rectangle; choose the one you think looks best.
|
||||
|
||||
If in doubt, look at completed objects in the repo, and if still in doubt, ask.
|
||||
|
||||
### Telling the repo to use the new files
|
||||
|
||||
Just as when you decomp an actor you have to change the `spec` to tell it to use the new files, you have to do a similar thing for the object. Find the appropriate section for the object you have decompiled, and replace the line
|
||||
|
||||
```c
|
||||
include "$(BUILD_DIR)/baserom/object_name.o"
|
||||
```
|
||||
|
||||
by
|
||||
|
||||
```c
|
||||
include "$(BUILD_DIR)/assets/objects/object_name/object_name.o"
|
||||
number 6
|
||||
```
|
||||
|
||||
(the second line tells it to assign the object to segment 6 for the actors that use it: some objects use other segments, but you'll know this from the generated XML already: the `Segment` argument in the `File` tag will be different).
|
||||
|
||||
Now, add
|
||||
|
||||
```c
|
||||
#include "assets/objects/object_name/object_name.h"
|
||||
```
|
||||
|
||||
to every actor that uses the file, to tell it about the new symbols you have defined.
|
||||
|
||||
Finally, replace all the symbols in the actors that use the file by the new names you gave them, and remove the corresponding sections in `undefined_syms.txt` and any `extern`s to the object's data in the actor files.
|
||||
|
||||
If you did everything correctly, you should still get OK when running `make`.
|
||||
|
||||
## Tools
|
||||
|
||||
Object decompilation is essentially a descriptive process, but it requires that we know what each blob of data in the object file actually is, and sometimes, the actor files that use an object are not sufficient to determine what everything is. Therefore it is useful to have a battery of romhacking and examination tools to bring to bear on the file to read its contents.
|
||||
|
||||
- The state-of-the-art is random's [Z64Utils](https://github.com/Random06457/Z64Utils). This can find and analyze the displaylists, textures and vertices in an object file, and even has an skeleton and animation viewer. It is not perfect; the best model viewer remains the debug rom itself.
|
||||
- The old solution to look at skeletons and animations is [Hylian Toolbox](http://wiki.maco64.com/Tools/Hylian_Toolbox). This suffers from numerous issues, but is usually suitable for looking at object files that contain one skeleton and a few animations.
|
||||
- To look at textures that you know something about, a texture viewer such as [Texture64](https://github.com/queueRAM/Texture64) is often useful. You may have trouble determining things like the palette a texture uses, depending on the format.
|
||||
|
||||
## Building and investigative modding
|
||||
|
||||
Thankfully it is not necessary to do a full make from clean to check that a particular object file has been decompiled successfully.
|
||||
|
||||
- With a valid xml file, run `extract_assets.py -s` on its object.
|
||||
- `make`
|
||||
|
||||
If you want to change a texture, for example to see precisely where it is used, the following steps ensure it will be used in the build
|
||||
|
||||
- Make sure the `spec` is updated to use the generated assets (see above)
|
||||
- Change and save the texture
|
||||
- Touch the C file in the same directory (that includes it)
|
||||
- make
|
||||
- Ironically, ERROR 1 implies success (your new texture has changed the checksum).
|
||||
|
||||
If you'd rather not have it tell you about the checksum, you can run `make COMPARE=0` instead.
|
||||
|
||||
---
|
||||
|
||||
To revert to the original texture, you can just run `extract_assets.py -s` on the object again.
|
||||
|
||||
N.B. doing this will overwrite every custom texture, as will running `make setup`.
|
||||
|
||||
|
||||
## Example
|
||||
|
||||
An example of decompiling a particular object is given [here](object_decomp_example.md).
|
@ -1,258 +0,0 @@
|
||||
# Object Decompilation Example
|
||||
|
||||
A fairly typical example of an NPC's object is `object_bg`. It is used by one actor: `ovl_En_Bom_Bowl_Man`.
|
||||
|
||||
## First pass: getting it to compile
|
||||
|
||||
Running `tools/xmlcreate.py` gives
|
||||
|
||||
```xml
|
||||
$ python3 tools/xmlcreate.py src/overlays/actors/ovl_En_Bom_Bowl_Man/z_en_bom_bowl_man.c chuGirl
|
||||
Unknown type at offset 004110
|
||||
Unknown type at offset 004910
|
||||
Unknown type at offset 005110
|
||||
<Root>
|
||||
<File Name="object_bg" Segment="6">
|
||||
<Skeleton Name="gchuGirlSkel_006EB0" Type="Flex" LimbType="Standard" Offset="0x6EB0"/>
|
||||
<Animation Name="gchuGirlAnim_000710" Offset="0x710"/>
|
||||
<Animation Name="gchuGirlAnim_000080" Offset="0x80"/>
|
||||
<Animation Name="gchuGirlAnim_0072AC" Offset="0x72AC"/>
|
||||
<!-- <UNK_PTR Name="gchuGirlUnknown_004110" Offset="0x4110"/> -->
|
||||
<!-- <UNK_PTR Name="gchuGirlUnknown_004910" Offset="0x4910"/> -->
|
||||
<!-- <UNK_PTR Name="gchuGirlUnknown_005110" Offset="0x5110"/> -->
|
||||
</File>
|
||||
</Root>
|
||||
```
|
||||
|
||||
We have two issues here that need to be resolved: naming the animations and sorting out the unknown things.
|
||||
|
||||
### Animations
|
||||
|
||||
You have three choices to work out what the animations are:
|
||||
|
||||
- Read the code and look at them in the game.
|
||||
- Hylian Toolbox
|
||||
- The latest versions of Z64Utils
|
||||
|
||||
The first of these is probably simplest if you know the game and the actor well. In this case, we know that she is dozing when you first enter, wakes up when you talk to her, then leans on the counter with both hands. We can thus name them accordingly.
|
||||
|
||||
Hylian Toolbox is terrible, but good for quick-and-dirty things like finding out what a particular animation does without booting up the game.
|
||||
|
||||
Z64Utils is way better than Hylian Toolbox, but still in development.
|
||||
|
||||
### Unknowns
|
||||
|
||||
Looking in the actor, the unknowns are assigned to segment 8 using `SEGMENTED_TO_VIRTUAL`. This indicates textures. To find out what type they are, we can find the displaylist that uses them, and look at it in Z64Utils: if we look at the object in the object analyser, then find the right displaylist, it will not display correctly in the DList viewer, asking for a texture to put on 08000000. Giving it one of the textures, we discover that it is the head displaylist and the textures are eye textures. Hence we can name them `gChuGirlEyeOpen/Half/ClosedTex` (we equivocate on half-open/half-closed since many actors use the half texture for both opening and closing). From the code in the displaylist that loads it, we can also extract the texture's format, namely
|
||||
|
||||
```c
|
||||
06002FD8: gsDPLoadTextureBlock(D_08000000, G_IM_FMT_RGBA, G_IM_SIZ_16b, 32, 32, 0, G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMIRROR | G_TX_CLAMP, 5, 5, 0, 0),
|
||||
```
|
||||
|
||||
So all three are `Format="rgba16" Width="32" Height="32"`.
|
||||
|
||||
```xml
|
||||
<Root>
|
||||
<File Name="object_bg" Segment="6">
|
||||
|
||||
<!-- Bombchu Bowling Girl skeleton -->
|
||||
<Skeleton Name="gChuGirlSkel" Type="Flex" LimbType="Standard" Offset="0x6EB0"/>
|
||||
|
||||
<!-- Bombchu Bowling Girl animations -->
|
||||
<Animation Name="gChuGirlWakeUpAnim" Offset="0x80"/>
|
||||
<Animation Name="gChuGirlNoddingOffAnim" Offset="0x710"/>
|
||||
<Animation Name="gChuGirlLeanOverCounterAnim" Offset="0x72AC"/>
|
||||
|
||||
<!-- Bombchu Bowling Girl eye textures -->
|
||||
<Texture Name="gChuGirlEyeOpenTex" OutName="chu_girl_eye_open" Format="rgba16" Width="32" Height="32" Offset="0x4110"/>
|
||||
<Texture Name="gChuGirlEyeHalfTex" OutName="chu_girl_eye_half" Format="rgba16" Width="32" Height="32" Offset="0x4910"/>
|
||||
<Texture Name="gChuGirlEyeClosedTex" OutName="chu_girl_eye_closed" Format="rgba16" Width="32" Height="32" Offset="0x5110"/>
|
||||
|
||||
</File>
|
||||
</Root>
|
||||
```
|
||||
|
||||
Having got this far, we can now run
|
||||
|
||||
```bash
|
||||
./extract_assets.py -s objects/object_bg
|
||||
```
|
||||
|
||||
to extract the contents of the object into the new folder, change the spec to use our newly extracted assets:
|
||||
|
||||
```txt
|
||||
beginseg
|
||||
name "object_bg"
|
||||
romalign 0x1000
|
||||
include "$(BUILD_DIR)/baserom/object_bg.o"
|
||||
endseg
|
||||
```
|
||||
|
||||
to
|
||||
|
||||
```txt
|
||||
beginseg
|
||||
name "object_bg"
|
||||
romalign 0x1000
|
||||
include "$(BUILD_DIR)/assets/objects/object_bg/object_bg.o"
|
||||
number 6
|
||||
endseg
|
||||
```
|
||||
|
||||
and wipe the `z_en_bom_bowl_man` section from `undefined_syms.txt`:
|
||||
|
||||
```txt
|
||||
// z_en_bom_bowl_man
|
||||
D_06006EB0 = 0x06006EB0;
|
||||
D_06000710 = 0x06000710;
|
||||
D_06000080 = 0x06000080;
|
||||
D_060072AC = 0x060072AC;
|
||||
```
|
||||
|
||||
Now `make` should give OK.
|
||||
|
||||
## The displaylists
|
||||
|
||||
For this step, we use Hylian Toolbox; if you have more than one skeleton in your actor, Hylian Toolbox will only show the first one. The others can be examined in the latest version of Z64Utils. Z64Utils can also give you a list of every displaylist in an object, be it just for cross-checking or to find extras.
|
||||
|
||||
Opening the rom in Hylian Toolbox and looking at object_bg, we can note down the displaylist associated to each limb.
|
||||
In this case naming is easy: we just have to note down the limb each is attached to. We only need a name and offset, and we end up with:
|
||||
|
||||
```xml
|
||||
<Root>
|
||||
<File Name="object_bg" Segment="6">
|
||||
|
||||
<!-- Bombchu Bowling Girl skeleton -->
|
||||
<Skeleton Name="gChuGirlSkel" Type="Flex" LimbType="Standard" Offset="0x6EB0"/>
|
||||
|
||||
<!-- Bombchu Bowling Girl animations -->
|
||||
<Animation Name="gChuGirlWakeUpAnim" Offset="0x80"/>
|
||||
<Animation Name="gChuGirlNoddingOffAnim" Offset="0x710"/>
|
||||
<Animation Name="gChuGirlLeanOverCounterAnim" Offset="0x72AC"/>
|
||||
|
||||
<!-- Bombchu Bowling Girl limb displaylists -->
|
||||
<DList Name="gChuGirlWaistDL" Offset="0x4038"/>
|
||||
<DList Name="gChuGirlTorsoDL" Offset="0x3DB0"/>
|
||||
<DList Name="gChuGirlNeckDL" Offset="0x33C8"/>
|
||||
<DList Name="gChuGirlHeadDL" Offset="0x2EE0"/>
|
||||
<DList Name="gChuGirlLeftUpperArmDL" Offset="0x3780"/>
|
||||
<DList Name="gChuGirlLeftForearmDL" Offset="0x3670"/>
|
||||
<DList Name="gChuGirlLeftHandDL" Offset="0x3520"/>
|
||||
<DList Name="gChuGirlRightUpperArmDL" Offset="0x3BC8"/>
|
||||
<DList Name="gChuGirlRightForearmDL" Offset="0x3AB8"/>
|
||||
<DList Name="gChuGirlRightHandDL" Offset="0x3968"/>
|
||||
|
||||
<!-- Bombchu Bowling Girl eye textures -->
|
||||
<Texture Name="gChuGirlEyeOpenTex" OutName="chu_girl_eye_open" Format="rgba16" Width="32" Height="32" Offset="0x4110"/>
|
||||
<Texture Name="gChuGirlEyeHalfTex" OutName="chu_girl_eye_half" Format="rgba16" Width="32" Height="32" Offset="0x4910"/>
|
||||
<Texture Name="gChuGirlEyeClosedTex" OutName="chu_girl_eye_closed" Format="rgba16" Width="32" Height="32" Offset="0x5110"/>
|
||||
|
||||
|
||||
</File>
|
||||
</Root>
|
||||
```
|
||||
|
||||
Checking the C file, these are all of the displaylists in the actor, so we can move on.
|
||||
|
||||
## The rest of the textures
|
||||
|
||||
This is the difficult bit: we have to work out what each texture in the extracted pile is actually used for. A lot of this can be done in Z64Utils, but sometimes it is necessary to check in-game, especially for the weirder-looking ones. You can edit a texture file and compile the rom (which shouldn't match any more if you did it successfully) to make it easier to find.
|
||||
|
||||
```xml
|
||||
<Root>
|
||||
<File Name="object_bg" Segment="6">
|
||||
|
||||
<!-- Bombchu Bowling Girl skeleton -->
|
||||
<Skeleton Name="gChuGirlSkel" Type="Flex" LimbType="Standard" Offset="0x6EB0"/>
|
||||
|
||||
<!-- Bombchu Bowling Girl animations -->
|
||||
<Animation Name="gChuGirlWakeUpAnim" Offset="0x80"/>
|
||||
<Animation Name="gChuGirlNoddingOffAnim" Offset="0x710"/>
|
||||
<Animation Name="gChuGirlLeanOverCounterAnim" Offset="0x72AC"/>
|
||||
|
||||
<!-- Bombchu Bowling Girl limb displaylists -->
|
||||
<DList Name="gChuGirlWaistDL" Offset="0x4038"/>
|
||||
<DList Name="gChuGirlTorsoDL" Offset="0x3DB0"/>
|
||||
<DList Name="gChuGirlNeckDL" Offset="0x33C8"/>
|
||||
<DList Name="gChuGirlHeadDL" Offset="0x2EE0"/>
|
||||
<DList Name="gChuGirlLeftUpperArmDL" Offset="0x3780"/>
|
||||
<DList Name="gChuGirlLeftForearmDL" Offset="0x3670"/>
|
||||
<DList Name="gChuGirlLeftHandDL" Offset="0x3520"/>
|
||||
<DList Name="gChuGirlRightUpperArmDL" Offset="0x3BC8"/>
|
||||
<DList Name="gChuGirlRightForearmDL" Offset="0x3AB8"/>
|
||||
<DList Name="gChuGirlRightHandDL" Offset="0x3968"/>
|
||||
|
||||
<!-- Bombchu Bowling Girl limb textures -->
|
||||
<Texture Name="gChuGirlMouthTex" OutName="chu_girl_mouth" Format="rgba16" Width="32" Height="32" Offset="0x5910"/>
|
||||
<Texture Name="gChuGirlSkinGradientTex" OutName="chu_girl_skin_gradient" Format="rgba16" Width="16" Height="16" Offset="0x6110"/>
|
||||
<Texture Name="gChuGirlSweaterTex" OutName="chu_girl_sweater" Format="rgba16" Width="32" Height="32" Offset="0x6510"/>
|
||||
<Texture Name="gChuGirlUmbEarLicusTex" OutName="chu_girl_umb_ear_licus" Format="rgba16" Width="16" Height="16" Offset="0x6310"/>
|
||||
<Texture Name="gChuGirlHairTex" OutName="chu_girl_hair" Format="rgba16" Width="8" Height="16" Offset="0x6D10"/>
|
||||
|
||||
<!-- Bombchu Bowling Girl eye textures -->
|
||||
<Texture Name="gChuGirlEyeOpenTex" OutName="chu_girl_eye_open" Format="rgba16" Width="32" Height="32" Offset="0x4110"/>
|
||||
<Texture Name="gChuGirlEyeHalfTex" OutName="chu_girl_eye_half" Format="rgba16" Width="32" Height="32" Offset="0x4910"/>
|
||||
<Texture Name="gChuGirlEyeClosedTex" OutName="chu_girl_eye_closed" Format="rgba16" Width="32" Height="32" Offset="0x5110"/>
|
||||
|
||||
|
||||
</File>
|
||||
</Root>
|
||||
```
|
||||
|
||||
|
||||
## Final pass
|
||||
|
||||
Therefore, we end up with the following:
|
||||
```xml
|
||||
<Root>
|
||||
<File Name="object_bg" Segment="6">
|
||||
|
||||
<!-- Bombchu Bowling Girl skeleton -->
|
||||
<Skeleton Name="gChuGirlSkel" Type="Flex" LimbType="Standard" Offset="0x6EB0"/>
|
||||
|
||||
<!-- Bombchu Bowling Girl animations -->
|
||||
<Animation Name="gChuGirlWakeUpAnim" Offset="0x80"/>
|
||||
<Animation Name="gChuGirlNoddingOffAnim" Offset="0x710"/>
|
||||
<Animation Name="gChuGirlLeanOverCounterAnim" Offset="0x72AC"/>
|
||||
|
||||
<!-- Bombchu Bowling Girl limb displaylists -->
|
||||
<DList Name="gChuGirlWaistDL" Offset="0x4038"/>
|
||||
<DList Name="gChuGirlTorsoDL" Offset="0x3DB0"/>
|
||||
<DList Name="gChuGirlNeckDL" Offset="0x33C8"/>
|
||||
<DList Name="gChuGirlHeadDL" Offset="0x2EE0"/>
|
||||
<DList Name="gChuGirlLeftUpperArmDL" Offset="0x3780"/>
|
||||
<DList Name="gChuGirlLeftForearmDL" Offset="0x3670"/>
|
||||
<DList Name="gChuGirlLeftHandDL" Offset="0x3520"/>
|
||||
<DList Name="gChuGirlRightUpperArmDL" Offset="0x3BC8"/>
|
||||
<DList Name="gChuGirlRightForearmDL" Offset="0x3AB8"/>
|
||||
<DList Name="gChuGirlRightHandDL" Offset="0x3968"/>
|
||||
|
||||
<!-- Bombchu Bowling Girl limbs -->
|
||||
<Limb Name="gChuGirlWaistLimb" LimbType="Standard" Offset="0x6E10"/>
|
||||
<Limb Name="gChuGirlTorsoLimb" LimbType="Standard" Offset="0x6E1C"/>
|
||||
<Limb Name="gChuGirlNeckLimb" LimbType="Standard" Offset="0x6E28"/>
|
||||
<Limb Name="gChuGirlHeadLimb" LimbType="Standard" Offset="0x6E34"/>
|
||||
<Limb Name="gChuGirlLeftUpperArmLimb" LimbType="Standard" Offset="0x6E40"/>
|
||||
<Limb Name="gChuGirlLeftForearmLimb" LimbType="Standard" Offset="0x6E4C"/>
|
||||
<Limb Name="gChuGirlLeftHandLimb" LimbType="Standard" Offset="0x6E58"/>
|
||||
<Limb Name="gChuGirlRightUpperArmLimb" LimbType="Standard" Offset="0x6E64"/>
|
||||
<Limb Name="gChuGirlRightForearmLimb" LimbType="Standard" Offset="0x6E70"/>
|
||||
<Limb Name="gChuGirlRightHandLimb" LimbType="Standard" Offset="0x6E7C"/>
|
||||
|
||||
<!-- Bombchu Bowling Girl limb textures -->
|
||||
<Texture Name="gChuGirlMouthTex" OutName="chu_girl_mouth" Format="rgba16" Width="32" Height="32" Offset="0x5910"/>
|
||||
<Texture Name="gChuGirlSkinGradientTex" OutName="chu_girl_skin_gradient" Format="rgba16" Width="16" Height="16" Offset="0x6110"/>
|
||||
<Texture Name="gChuGirlSweaterTex" OutName="chu_girl_sweater" Format="rgba16" Width="32" Height="32" Offset="0x6510"/>
|
||||
<Texture Name="gChuGirlUmbEarLicusTex" OutName="chu_girl_umb_ear_licus" Format="rgba16" Width="16" Height="16" Offset="0x6310"/>
|
||||
<Texture Name="gChuGirlHairTex" OutName="chu_girl_hair" Format="rgba16" Width="8" Height="16" Offset="0x6D10"/>
|
||||
|
||||
<!-- Bombchu Bowling Girl eye textures -->
|
||||
<Texture Name="gChuGirlEyeOpenTex" OutName="chu_girl_eye_open" Format="rgba16" Width="32" Height="32" Offset="0x4110"/>
|
||||
<Texture Name="gChuGirlEyeHalfTex" OutName="chu_girl_eye_half" Format="rgba16" Width="32" Height="32" Offset="0x4910"/>
|
||||
<Texture Name="gChuGirlEyeClosedTex" OutName="chu_girl_eye_closed" Format="rgba16" Width="32" Height="32" Offset="0x5110"/>
|
||||
|
||||
|
||||
</File>
|
||||
</Root>
|
||||
```
|
||||
|
||||
This was an easier object to do, both because it's a fairly simple collection of stuff associated to a single actor, and because a lot of the typing was already known.
|
@ -1,830 +0,0 @@
|
||||
# The rest of the functions in the actor
|
||||
|
||||
Up: [Contents](contents.md)
|
||||
Previous: [Beginning decompilation: the Init function and the Actor struct](beginning_decomp.md)
|
||||
|
||||
## Now what?
|
||||
|
||||
Following the scheme we gave last time, we have three options:
|
||||
- `func_80A87BEC`
|
||||
- `func_80A87C30`
|
||||
- `func_80A87F44`
|
||||
|
||||
Another option is to look at `Destroy`, which for smaller actors can often be done straight after Init, since it usually just removes colliders and deallocates dynapoly. However, glancing at the three given functions' assembly, there is an obvious standout:
|
||||
```MIPS
|
||||
glabel func_80A87F44
|
||||
/* 00744 80A87F44 AFA40000 */ sw $a0, 0x0000($sp)
|
||||
/* 00748 80A87F48 03E00008 */ jr $ra
|
||||
/* 0074C 80A87F4C AFA50004 */ sw $a1, 0x0004($sp)
|
||||
|
||||
```
|
||||
This is a classic "function with two arguments that does nothing". So we can simply comment out the appropriate pragma and put
|
||||
```C
|
||||
void func_80A87F44(Actor* thisx, PlayState* play) {
|
||||
|
||||
}
|
||||
```
|
||||
in the C file.
|
||||
|
||||
## Destroy
|
||||
|
||||
Destroy will be a dead end, but we might as well do it now. Remaking the context and using mips2c on it (main 4 function, so change the prototype to use `EnJj* this`!) gives
|
||||
```C
|
||||
void EnJj_Destroy(EnJj *this, PlayState *play) {
|
||||
PlayState *temp_a3;
|
||||
s16 temp_v0;
|
||||
|
||||
temp_v0 = this->dyna.actor.params;
|
||||
temp_a3 = play;
|
||||
if (temp_v0 == -1) {
|
||||
play = temp_a3;
|
||||
DynaPoly_DeleteBgActor(temp_a3, &temp_a3->colCtx.dyna, (s32) this->dyna.bgId);
|
||||
Collider_DestroyCylinder(play, &this->collider);
|
||||
return;
|
||||
}
|
||||
if ((temp_v0 != 0) && (temp_v0 != 1)) {
|
||||
return;
|
||||
}
|
||||
DynaPoly_DeleteBgActor(temp_a3, &temp_a3->colCtx.dyna, (s32) this->dyna.bgId);
|
||||
}
|
||||
```
|
||||
|
||||
Again remember to return the first argument to `Actor* this` and put `EnJj* this = THIS;` in the function body. Based on what we know about this actor already, we expect this to be another switch. Rearranging it as such, and removing the likely fake `temp_v0` gives
|
||||
```C
|
||||
void EnJj_Destroy(Actor* thisx, PlayState* play) {
|
||||
EnJj* this = THIS;
|
||||
PlayState* temp_a3;
|
||||
temp_a3 = play;
|
||||
|
||||
switch (this->dyna.actor.params) {
|
||||
case -1:
|
||||
DynaPoly_DeleteBgActor(play, &play->colCtx.dyna, this->dyna.bgId);
|
||||
Collider_DestroyCylinder(play, &this->collider);
|
||||
break;
|
||||
case 0:
|
||||
case 1:
|
||||
DynaPoly_DeleteBgActor(temp_a3, &temp_a3->colCtx.dyna, this->dyna.bgId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
Using `./diff.py -mwo3 EnJj_Destroy` shows that this matches already, but it seems like the temp usage should be more consistent. A little experimentation shows that
|
||||
```C
|
||||
void EnJj_Destroy(Actor* thisx, PlayState* play) {
|
||||
EnJj* this = THIS;
|
||||
|
||||
switch (this->dyna.actor.params) {
|
||||
case -1:
|
||||
DynaPoly_DeleteBgActor(play, &play->colCtx.dyna, this->dyna.bgId);
|
||||
Collider_DestroyCylinder(play, &this->collider);
|
||||
break;
|
||||
case 0:
|
||||
case 1:
|
||||
DynaPoly_DeleteBgActor(play, &play->colCtx.dyna, this->dyna.bgId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
also matches, with no need for the `PlayState*` temp.
|
||||
|
||||
## Action Functions
|
||||
|
||||
### `func_80A87BEC`
|
||||
|
||||
Of the two functions we have available, `func_80A87BEC` is shorter, so we do that next. Since we haven't changed any types or header file information, there is no need to remake the context. mips2c gives
|
||||
```C
|
||||
void func_80A87BEC(EnJj *this, PlayState *play) {
|
||||
if (this->dyna.actor.xzDistToPlayer < 300.0f) {
|
||||
func_80A87800(this, &func_80A87B9C);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We see that this function just sets another action function when Link is close enough to the actor. All we have to do to this is remove the `&`, and prototype `func_80A87B9C` to be an action function. Notably, this time it is not used before it is defined, so we don't need a prototype at the top: putting a placeholder one in at the function position will do, i.e. our total edits this time are
|
||||
```C
|
||||
#pragma GLOBAL_ASM("asm/non_matchings/overlays/actors/ovl_En_Jj/func_80A87B9C.s")
|
||||
void func_80A87B9C(EnJj *this, PlayState *play);
|
||||
|
||||
// #pragma GLOBAL_ASM("asm/non_matchings/overlays/actors/ovl_En_Jj/func_80A87BEC.s")
|
||||
void func_80A87BEC(EnJj *this, PlayState *play) {
|
||||
if (this->dyna.actor.xzDistToPlayer < 300.0f) {
|
||||
func_80A87800(this, func_80A87B9C);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We can now either follow this chain of action functions down, or start with the other one. We will go down in this case: it is usually easier to keep track that way.
|
||||
|
||||
### `func_80A87B9C`
|
||||
|
||||
We can remake the context, but it's simpler to just stick the function prototype we made at the bottom of the context. Either way, mips2c gives us
|
||||
```C
|
||||
void func_80A87B9C(EnJj *this, PlayState *play) {
|
||||
s16 temp_v0;
|
||||
|
||||
temp_v0 = this->unk308;
|
||||
if ((s32) temp_v0 >= -0x1450) {
|
||||
this->unk308 = (s16) (temp_v0 - 0x66);
|
||||
if ((s32) this->unk308 < -0xA28) {
|
||||
func_8003EBF8(play, &play->colCtx.dyna, this->childActor->unk14C);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
Here's a new variable for our actor struct! Don't be deceived by the `s32` cast in the comparison: mips2c always does that if it's not sure. The reliable one is the `s16` cast lower down. (An `s32` doesn't fit in the space we have anyway). So the actor struct is now
|
||||
```C
|
||||
typedef struct EnJj {
|
||||
/* 0x0000 */ DynaPolyActor dyna;
|
||||
/* 0x0164 */ SkelAnime skelAnime;
|
||||
/* 0x01A8 */ Vec3s jointTable[22];
|
||||
/* 0x022C */ Vec3s morphTable[22];
|
||||
/* 0x02B0 */ ColliderCylinder collider;
|
||||
/* 0x02FC */ EnJjActionFunc actionFunc;
|
||||
/* 0x0300 */ Actor* childActor;
|
||||
/* 0x0304 */ char unk_304[0x4];
|
||||
/* 0x0308 */ s16 unk_308;
|
||||
/* 0x030A */ s16 unk_30A;
|
||||
/* 0x030C */ char unk_30C[0x2];
|
||||
/* 0x030E */ s8 unk_30E;
|
||||
/* 0x030F */ s8 unk_30F;
|
||||
/* 0x0310 */ s8 unk_310;
|
||||
/* 0x0311 */ s8 unk_311;
|
||||
/* 0x0312 */ char unk_312[0x2];
|
||||
} EnJj; // size = 0x0314
|
||||
```
|
||||
|
||||
We can eliminate the temp since it's used in a simple way one after the other. `this->unk308 = (s16) (this->unk308 - 0x66);` can be written as `this->unk308 -= 0x66;`.
|
||||
|
||||
In the `func_8003EBF8` we see that we should have at least made `this->childActor` a `DynaPolyActor*`, so that the last argument is its `bgId`. To avoid compiler warnings, we also need to cast the `Actor_SpawnAsChild` as such in Init,
|
||||
```C
|
||||
this->childActor = (DynaPolyActor*)Actor_SpawnAsChild(...)
|
||||
```
|
||||
|
||||
Doing so, we are left with
|
||||
```C
|
||||
void func_80A87B9C(EnJj *this, PlayState *play) {
|
||||
if (this->unk_308 >= -0x1450) {
|
||||
this->unk_308 -= 0x66;
|
||||
if (this->unk_308 < -0xA28) {
|
||||
func_8003EBF8(play, &play->colCtx.dyna, this->childActor->bgId);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The diff shows this doesn't match:
|
||||
|
||||
![func_80A87B9C diff 1](images/func_80A87B9C_diff1.png)
|
||||
|
||||
It's not obvious why this doesn't match: the branching is correct, but it's loading stuff in the wrong order. Now is therefore a good time to introduce the permuter.
|
||||
|
||||
### The permuter
|
||||
|
||||
The permuter is a useful program for when you run out of ideas: it assigns a function a score based on how much it differs from the original code, then does random stuff that is known to improve matching, keeping ones that give better scores. It is not going to fix your control flow, but it does reasonably well on instruction differences and regalloc. It also has an option to worry about stack differences as well (`--stack-diffs`).
|
||||
|
||||
To use the permuter, clone the decomp-permuter repo from the link given in Discord. First, import the C file and MIPS of the function to compare using
|
||||
```sh
|
||||
./import.py <path_to_c> <path_to_func_name.s>
|
||||
```
|
||||
|
||||
It will put it in a subdirectory of `nonmatchings`. You then run
|
||||
```sh
|
||||
./permuter.py nonmatchings/<function_name>/
|
||||
```
|
||||
to produce suggestions. There are various arguments that can be used, of which the most important initially is `-j`: `-jN` tells it to use `N` CPU threads.
|
||||
|
||||
Suggestions are saved in the directory it imported the function into.
|
||||
|
||||
![Permuter console output](images/permuter_console_output.png)
|
||||
|
||||
The first suggestion looks plausible:
|
||||
```C
|
||||
--- before
|
||||
+++ after
|
||||
@@ -1390,12 +1390,14 @@
|
||||
} EnJj;
|
||||
void func_80A87B9C(EnJj *this, PlayState *play)
|
||||
{
|
||||
- if (this->unk_308 >= (-0x1450))
|
||||
+ DynaPolyActor *new_var;
|
||||
+ new_var = this->childActor;
|
||||
+ if (this->unk_308 > ((-0x1450) - 1))
|
||||
{
|
||||
this->unk_308 -= 0x66;
|
||||
if (this->unk_308 < (-0xA28))
|
||||
{
|
||||
- func_8003EBF8(play, &play->colCtx.dyna, this->childActor->bgId);
|
||||
+ func_8003EBF8(play, &play->colCtx.dyna, new_var->bgId);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
In particular, adding a temp for the actor. Some of the rest is rather puzzling, but let's just try the actor temp,
|
||||
```C
|
||||
void func_80A87B9C(EnJj *this, PlayState *play) {
|
||||
DynaPolyActor* child = this->childActor;
|
||||
|
||||
if (this->unk_308 >= -0x1450) {
|
||||
this->unk_308 -= 0x66;
|
||||
if (this->unk_308 < -0xA28) {
|
||||
func_8003EBF8(play, &play->colCtx.dyna, child->bgId);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
![func_80A87B9C diff 2](images/func_80A87B9C_diff2.png)
|
||||
|
||||
Hooray, that worked. The function now matches (as you can check by running `make -j`). In this case we are lucky and got a couple of 0s almost immediately. This will often not be the case, and you may have to go through multiple iterations to improve things. Or you get more ideas for what to do without the permuter necessarily doing everything for you.
|
||||
|
||||
However, the hex values look a bit strange, and it turns out the decimal equivalents look less strange, so it's a good idea to translate them:
|
||||
```C
|
||||
void func_80A87B9C(EnJj *this, PlayState *play) {
|
||||
DynaPolyActor* child = this->childActor;
|
||||
|
||||
if (this->unk_308 >= -5200) {
|
||||
this->unk_308 -= 102;
|
||||
if (this->unk_308 < -2600) {
|
||||
func_8003EBF8(play, &play->colCtx.dyna, child->bgId);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
With that, we have reached the end of this action function chain, and now have to look at `func_80A87C30`
|
||||
|
||||
### `func_80A87C30`
|
||||
|
||||
Remaking the context and running mips2c on the assembly gives
|
||||
```C
|
||||
void func_80A87C30(EnJj *this, PlayState *play) {
|
||||
if ((Math_Vec3f_DistXZ(&D_80A88CF0, play->unk1C44 + 0x24) < 300.0f) && (play->isPlayerDroppingFish(play) != 0)) {
|
||||
this->unk_30C = 0x64;
|
||||
func_80A87800(this, &func_80A87CEC);
|
||||
}
|
||||
this->collider.dim.pos.x = -0x4DD;
|
||||
this->collider.dim.pos.y = 0x14;
|
||||
this->collider.dim.pos.z = -0x30;
|
||||
CollisionCheck_SetOC(play, &play->colChkCtx, (Collider *) &this->collider);
|
||||
}
|
||||
```
|
||||
|
||||
If you know anything about this game, this is obviously the function that begins the process of the swallowing Link cutscene. Performing minor cleanups reduces us to
|
||||
```C
|
||||
void func_80A87C30(EnJj *this, PlayState *play) {
|
||||
if ((Math_Vec3f_DistXZ(&D_80A88CF0, play->unk1C44 + 0x24) < 300.0f) && (play->isPlayerDroppingFish(play) != 0)) {
|
||||
this->unk_30C = 100;
|
||||
func_80A87800(this, func_80A87CEC);
|
||||
}
|
||||
this->collider.dim.pos.x = -1245;
|
||||
this->collider.dim.pos.y = 20;
|
||||
this->collider.dim.pos.z = -48;
|
||||
CollisionCheck_SetOC(play, &play->colChkCtx, &this->collider);
|
||||
}
|
||||
```
|
||||
|
||||
There are three things left to do to this function:
|
||||
- prototype the new action function, `func_80A87CEC`. This one is used before its definition, so needs to be prototyped at the top of the file.
|
||||
- extern `D_80A88CF0`, and since the arguments of `Math_Vec3f_DistXZ` are `Vec3f`s, convert it to floats. To do float conversion, either use an online converter, or get an extension for VSCode that can do it. The data becomes
|
||||
```C
|
||||
extern Vec3f D_80A88CF0;
|
||||
// static Vec3f D_80A88CF0 = { -1589.0f, 53.0f, -43.0f };
|
||||
```
|
||||
(you must include the `.0f` parts even for integer floats: it can affect codegen, and as such it is part of our style).
|
||||
|
||||
- replace the mysterious `play->unk1C44 + 0x24`. The first part is so common that most people on decomp know it by heart: it is the location of the Player actor. `+ 0x24` is obviously an offset that leats to a `Vec3f`, and if you look in the actor struct, you find that this is the location of `world.pos`. To use `Player`, we put `Player* player = GET_PLAYER(play)` at the top of the function.
|
||||
|
||||
**NOTE:** mips_to_c will now output something like `&play->actorCtx.actorLists[2].head` for the Player pointer instead: this makes a lot more sense, but is not so easy to spot in the ASM without the characteristic `1C44`.
|
||||
|
||||
After all this, the function becomes
|
||||
```C
|
||||
void func_80A87C30(EnJj *this, PlayState *play) {
|
||||
Player* player = GET_PLAYER(play);
|
||||
|
||||
if ((Math_Vec3f_DistXZ(&D_80A88CF0, &player->actor.world.pos) < 300.0f) && (play->isPlayerDroppingFish(play) != 0)) {
|
||||
this->unk_30C = 100;
|
||||
func_80A87800(this, func_80A87CEC);
|
||||
}
|
||||
this->collider.dim.pos.x = -1245;
|
||||
this->collider.dim.pos.y = 20;
|
||||
this->collider.dim.pos.z = -48;
|
||||
CollisionCheck_SetOC(play, &play->colChkCtx, &this->collider);
|
||||
}
|
||||
```
|
||||
|
||||
One issue we have swept under the carpet thus far is what `unk_30C` is: we still have it as padding. For this we have to look at the MIPS, since mips2c hasn't told us. Scanning through the file, we find
|
||||
```MIPS
|
||||
/* 00498 80A87C98 A60E030C */ sh $t6, 0x030C($s0) ## 0000030C
|
||||
```
|
||||
|
||||
which tells us that `unk_30C` is an `s16`, filling another gap in the struct:
|
||||
```C
|
||||
typedef struct EnJj {
|
||||
/* 0x0000 */ DynaPolyActor dyna;
|
||||
/* 0x0164 */ SkelAnime skelAnime;
|
||||
/* 0x01A8 */ Vec3s jointTable[22];
|
||||
/* 0x022C */ Vec3s morphTable[22];
|
||||
/* 0x02B0 */ ColliderCylinder collider;
|
||||
/* 0x02FC */ EnJjActionFunc actionFunc;
|
||||
/* 0x0300 */ DynaPolyActor* childActor;
|
||||
/* 0x0304 */ char unk_304[0x4];
|
||||
/* 0x0308 */ s16 unk_308;
|
||||
/* 0x030A */ s16 unk_30A;
|
||||
/* 0x030C */ s16 unk_30C;
|
||||
/* 0x030E */ s8 unk_30E;
|
||||
/* 0x030F */ s8 unk_30F;
|
||||
/* 0x0310 */ s8 unk_310;
|
||||
/* 0x0311 */ s8 unk_311;
|
||||
/* 0x0312 */ char unk_312[0x2];
|
||||
} EnJj; // size = 0x0314
|
||||
```
|
||||
|
||||
The diff now looks fine for this function, but it gives compiler warnings about `CollisionCheck_SetOC(play, &play->colChkCtx, &this->collider);`: the last argument is the wrong type: we need to give it `&this->collider.base` instead, which points to the same address, but is the right type. So the matching function is
|
||||
```C
|
||||
void func_80A87C30(EnJj *this, PlayState *play) {
|
||||
Player* player = GET_PLAYER(play);
|
||||
|
||||
if ((Math_Vec3f_DistXZ(&D_80A88CF0, &player->actor.world.pos) < 300.0f) && (play->isPlayerDroppingFish(play) != 0)) {
|
||||
this->unk_30C = 100;
|
||||
func_80A87800(this, func_80A87CEC);
|
||||
}
|
||||
this->collider.dim.pos.x = -1245;
|
||||
this->collider.dim.pos.y = 20;
|
||||
this->collider.dim.pos.z = -48;
|
||||
CollisionCheck_SetOC(play, &play->colChkCtx, &this->collider.base);
|
||||
}
|
||||
```
|
||||
|
||||
Again we have only one choice for our next function, namely `func_80A87CEC`.
|
||||
|
||||
### `func_80A87CEC`
|
||||
|
||||
Remaking the context and running mips2c on the assembly gives
|
||||
```C
|
||||
void func_80A87CEC(EnJj *this, PlayState *play) {
|
||||
DynaPolyActor *sp1C;
|
||||
DynaPolyActor *temp_v1;
|
||||
s16 temp_v0;
|
||||
|
||||
temp_v0 = this->unk_30C;
|
||||
temp_v1 = this->childActor;
|
||||
if ((s32) temp_v0 > 0) {
|
||||
this->unk_30C = temp_v0 - 1;
|
||||
return;
|
||||
}
|
||||
sp1C = temp_v1;
|
||||
play = play;
|
||||
func_80A87800(this, &func_80A87EF0);
|
||||
play->csCtx.script = &D_80A88164;
|
||||
gSaveContext.cutsceneTrigger = (u8)1U;
|
||||
func_8003EBF8(play, &play->colCtx.dyna, (s32) temp_v1->bgId);
|
||||
Camera_SetFinishedFlag(play->cameraPtrs[play->activeCamId]);
|
||||
gSaveContext.unkEDA = (u16) (gSaveContext.unkEDA | 0x400);
|
||||
Sfx_PlaySfxCentered((u16)0x4802U);
|
||||
}
|
||||
```
|
||||
|
||||
Easy things to sort out:
|
||||
|
||||
- `func_80A87EF0` is another action function. Again it is defined below, so needs a prototype at the top.
|
||||
|
||||
- We have another unknown symbol, `D_80A88164`. This is the massive chunk of data that makes up the cutscene (of Jabu swallowing Link). We will worry about it when we have decompiled the rest of the actor. For now just extern it as `UNK_TYPE`.
|
||||
|
||||
- We can remove the casts from `(u8)1U` and just leave `1`.
|
||||
|
||||
- `play->cameraPtrs[play->activeCamId]` has a macro: it is `GET_ACTIVE_CAM(play)`, so this line can be written as
|
||||
```C
|
||||
Camera_SetFinishedFlag(GET_ACTIVE_CAM(play));
|
||||
```
|
||||
|
||||
- `gSaveContext.unkEDA` we have dealt with before: it is `gSaveContext.save.info.eventChkInf[3]`. This is a flag-setting function; it can be written more compactly as
|
||||
```C
|
||||
gSaveContext.unkEDA |= 0x400
|
||||
```
|
||||
|
||||
- The last function is an audio function: we can look up the argument in `sfx.h` and find it is `NA_SE_SY_CORRECT_CHIME`
|
||||
|
||||
It remains to work out which, if any, of the temps are real. Based on our previous experience, we expect `temp_v1` to be real. `sp1C` is unused and hence unlikely to be real. `temp_v0` is only used in the short if and so probably not real. Checking the diff shows that our suspicions were correct:
|
||||
```C
|
||||
void func_80A87CEC(EnJj *this, PlayState *play) {
|
||||
DynaPolyActor *child = this->childActor;
|
||||
if (this->unk_30C > 0) {
|
||||
this->unk_30C--;
|
||||
return;
|
||||
}
|
||||
func_80A87800(this, func_80A87EF0);
|
||||
play->csCtx.script = &D_80A88164;
|
||||
gSaveContext.cutsceneTrigger = 1;
|
||||
func_8003EBF8(play, &play->colCtx.dyna, child->bgId);
|
||||
Camera_SetFinishedFlag(GET_ACTIVE_CAM(play));
|
||||
gSaveContext.save.info.eventChkInf[3] |= 0x400;
|
||||
Sfx_PlaySfxCentered(NA_SE_SY_CORRECT_CHIME);
|
||||
}
|
||||
```
|
||||
|
||||
matches, but generates a complier warning for `Camera_SetFinishedFlag`, which it can't find. To fix this, add it to `functions.h`, in as near as possible the correct position in numerical order. Some detective work with VSCode's Search shows that this function lives in `z_camera.c`, and its prototype is `s16 Camera_SetFinishedFlag(Camera* camera)`, so add this line to `functions.h` at the bottom of the camera functions part.
|
||||
|
||||
Lastly, we prefer to limit use of early `return`s, and use `else`s instead if possible. That applies here: the function can be rewritten as
|
||||
```C
|
||||
void func_80A87CEC(EnJj* this, PlayState* play) {
|
||||
DynaPolyActor* child = this->childActor;
|
||||
if (this->unk_30C > 0) {
|
||||
this->unk_30C--;
|
||||
} else {
|
||||
func_80A87800(this, func_80A87EF0);
|
||||
play->csCtx.script = &D_80A88164;
|
||||
gSaveContext.cutsceneTrigger = 1;
|
||||
func_8003EBF8(play, &play->colCtx.dyna, child->bgId);
|
||||
Camera_SetFinishedFlag(GET_ACTIVE_CAM(play));
|
||||
gSaveContext.save.info.eventChkInf[3] |= 0x400;
|
||||
Sfx_PlaySfxCentered(NA_SE_SY_CORRECT_CHIME);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
and still match. (Early `return`s are used after an `Actor_Kill` and in a few other situations, but generally avoided for `else`s elsewhere if possible. Talking of which...)
|
||||
|
||||
## `func_80A87EF0`
|
||||
|
||||
mips2c with updated context gives
|
||||
```C
|
||||
void func_80A87EF0(EnJj *this, PlayState *play) {
|
||||
char *temp_a0;
|
||||
u16 temp_v0;
|
||||
|
||||
temp_v0 = (u16) this->unk_30A;
|
||||
if ((temp_v0 & 4) == 0) {
|
||||
this->unk_30A = temp_v0 | 4;
|
||||
temp_a0 = this->unk_304;
|
||||
if (temp_a0 != 0) {
|
||||
this = this;
|
||||
Actor_Kill((Actor *) temp_a0);
|
||||
this->dyna.actor.child = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now we're a bit stuck: this tells us that `this->unk_304` is an `Actor*`, but we know nothing else about it. So just make it an actor for the time being. As before, `this->unk_304` is a pointer, so we should compare `temp_a0` to `NULL`.
|
||||
|
||||
We also find in the MIPS
|
||||
```MIPS
|
||||
lhu $v0, 0x030A($a0)
|
||||
```
|
||||
which at last tells us that `unk_30A` is actually a `u16`. We can now eliminate `temp_v0`, and replace the ` == 0` by a `!(...)`, which leaves
|
||||
```C
|
||||
void func_80A87EF0(EnJj *this, PlayState *play) {
|
||||
Actor *temp_a0;
|
||||
|
||||
if (!(this->unk_30A & 4)) {
|
||||
this->unk_30A |= 4;
|
||||
temp_a0 = this->unk_304;
|
||||
if (temp_a0 != 0) {
|
||||
Actor_Kill(temp_a0);
|
||||
this->dyna.actor.child = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
although we are as yet none the wiser as to which actor `unk_304` actually points to.
|
||||
|
||||
Again we have run out of action functions. The rules suggest that we now look at Update.
|
||||
|
||||
## Update
|
||||
|
||||
Update runs every frame and updates the properties of the actor, and usually runs the action functions once per frame. As before we change the first argument to `EnJj* this` to get it to use our actor's struct. mips2c gives
|
||||
```C
|
||||
void EnJj_Update(EnJj *this, PlayState *play) {
|
||||
if ((play->csCtx.state != CS_STATE_IDLE) && (play->unk1D94 != 0)) {
|
||||
func_80A87D94();
|
||||
} else {
|
||||
this->actionFunc(this);
|
||||
if (this->skelAnime.curFrame == 41.0f) {
|
||||
Actor_PlaySfx((Actor *) this, (u16)0x28B6U);
|
||||
}
|
||||
}
|
||||
func_80A87B1C(this);
|
||||
SkelAnime_Update(&this->skelAnime);
|
||||
Actor_SetScale((Actor *) this, 0.087f);
|
||||
this->skelAnime.jointTable->unk40 = (s16) this->unk_308;
|
||||
}
|
||||
```
|
||||
|
||||
This has several problems: firstly, the action function is called with the wrong argument. We should thus be suspicious of previous functions this actor calls, and decompile them mith mips2c without context if necessary, if only to find out how many arguments they have. We find that `func_80A87D94` definitely takes `EnJj* this, PlayState* play` as arguments. Again, put this prototype at the function location above to avoid compiler warnings.
|
||||
|
||||
`unk40` of an array of `Vec3s`s is `0x40 = 0x6 * 0xA + 0x4`, so is actually `this->skelAnime.jointTable[10].z`
|
||||
|
||||
Lastly, what is `play->unk1D94`? It is at `play->csCtx + 0x30`, or `play->csCtx.actorCues + 0x8`, which is `play->csCtx.actorCues[2]` since this is an array of pointers. Hence it is a pointer, and so should be compared to `NULL`. Looking up the sfx Id again, we end up with
|
||||
```C
|
||||
void EnJj_Update(Actor *thisx, PlayState *play) {
|
||||
EnJj* this = THIS;
|
||||
|
||||
if ((play->csCtx.state != CS_STATE_IDLE) && (play->csCtx.actorCues[2] != NULL)) {
|
||||
func_80A87D94(this, play);
|
||||
} else {
|
||||
this->actionFunc(this, play);
|
||||
if (this->skelAnime.curFrame == 41.0f) {
|
||||
Actor_PlaySfx(&this->dyna.actor, NA_SE_EV_JABJAB_GROAN);
|
||||
}
|
||||
}
|
||||
func_80A87B1C(this);
|
||||
SkelAnime_Update(&this->skelAnime);
|
||||
Actor_SetScale(&this->dyna.actor, 0.087f);
|
||||
this->skelAnime.jointTable[10].z = this->unk_308;
|
||||
}
|
||||
```
|
||||
which matches.
|
||||
|
||||
|
||||
3 more functions to go: the two functions called here, and Draw.
|
||||
|
||||
|
||||
## `func_80A87B1C`
|
||||
|
||||
Now our typing problems come home to roost:
|
||||
|
||||
```C
|
||||
void func_80A87B1C(EnJj *this) {
|
||||
s8 temp_t8;
|
||||
u8 temp_v0;
|
||||
u8 temp_v0_2;
|
||||
|
||||
temp_v0 = (u8) this->unk_30F;
|
||||
if ((s32) temp_v0 > 0) {
|
||||
this->unk_30F = temp_v0 - 1;
|
||||
return;
|
||||
}
|
||||
temp_t8 = (u8) this->unk_30E + 1;
|
||||
this->unk_30E = temp_t8;
|
||||
if ((temp_t8 & 0xFF) >= 3) {
|
||||
temp_v0_2 = (u8) this->unk_310;
|
||||
this->unk_30E = 0;
|
||||
if ((s32) temp_v0_2 > 0) {
|
||||
this->unk_310 = temp_v0_2 - 1;
|
||||
return;
|
||||
}
|
||||
this = this;
|
||||
this->unk_30F = Rand_S16Offset((u16)0x14, (u16)0x14);
|
||||
this->unk_310 = (s8) (u8) this->unk_311;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
From this we can read off that `unk_30F` is a `u8`, `unk_30E` is a `u8`, `unk_310` is a `u8`, and `unk_311` is a `u8`. Giving mips2c new context and trying again,
|
||||
```C
|
||||
void func_80A87B1C(EnJj *this) {
|
||||
u8 temp_t8;
|
||||
u8 temp_v0;
|
||||
u8 temp_v0_2;
|
||||
|
||||
temp_v0 = this->unk_30F;
|
||||
if ((s32) temp_v0 > 0) {
|
||||
this->unk_30F = temp_v0 - 1;
|
||||
return;
|
||||
}
|
||||
temp_t8 = this->unk_30E + 1;
|
||||
this->unk_30E = temp_t8;
|
||||
if ((temp_t8 & 0xFF) >= 3) {
|
||||
temp_v0_2 = this->unk_310;
|
||||
this->unk_30E = 0;
|
||||
if ((s32) temp_v0_2 > 0) {
|
||||
this->unk_310 = temp_v0_2 - 1;
|
||||
return;
|
||||
}
|
||||
this = this;
|
||||
this->unk_30F = Rand_S16Offset((u16)0x14, (u16)0x14);
|
||||
this->unk_310 = this->unk_311;
|
||||
}
|
||||
}
|
||||
```
|
||||
and all the weird casts are gone. Eliminating the temps, replacing the hex, discarding pointless definitions, and replacing early `return`s by `else`s, we end up with
|
||||
```C
|
||||
void func_80A87B1C(EnJj* this) {
|
||||
if (this->unk_30F > 0) {
|
||||
this->unk_30F--;
|
||||
} else {
|
||||
this->unk_30E++;
|
||||
if ((this->unk_30E & 0xFF) >= 3) {
|
||||
this->unk_30E = 0;
|
||||
if (this->unk_310 > 0) {
|
||||
this->unk_310--;
|
||||
} else {
|
||||
this->unk_30F = Rand_S16Offset(20, 20);
|
||||
this->unk_310 = this->unk_311;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Sadly this doesn't match:
|
||||
|
||||
![func_80A87B1C diff 1](images/func_80A87B1C_diff1.png)
|
||||
|
||||
You will also find that the permuter is essentially useless here: most solutions it offers look very fake. But there's already something a bit weird in here: why does it do `this->unk_30E & 0xFF` explicitly in the comparison? It turns out if you remove this, the function matches: the compiler does this calculation automatically when doing comparisons with `u8`s anyway.
|
||||
|
||||
![func_80A87B1C diff 2](images/func_80A87B1C_diff2.png)
|
||||
|
||||
### `func_80A87D94`
|
||||
|
||||
This is our last ordinary function. Unfortunately, even with new context, mips2c gives us a bit of a mess:
|
||||
```C
|
||||
void func_80A87D94(EnJj *this, PlayState *play) {
|
||||
s16 temp_v0_2;
|
||||
u16 temp_t1;
|
||||
u16 temp_t4;
|
||||
u16 temp_t7;
|
||||
u16 temp_t9;
|
||||
u16 temp_v0;
|
||||
u16 temp_v1;
|
||||
u16 temp_v1_2;
|
||||
u16 temp_v1_3;
|
||||
u16 phi_v1;
|
||||
|
||||
temp_v0 = *play->unk1D94;
|
||||
if (temp_v0 != 1) {
|
||||
if (temp_v0 != 2) {
|
||||
if (temp_v0 != 3) {
|
||||
phi_v1 = this->unk_30A;
|
||||
} else {
|
||||
temp_v1 = this->unk_30A;
|
||||
temp_t7 = temp_v1 | 2;
|
||||
phi_v1 = temp_v1;
|
||||
if ((temp_v1 & 2) == 0) {
|
||||
this->unk_30E = 0;
|
||||
this->unk_30F = 0;
|
||||
this->unk_310 = 1;
|
||||
this->unk_311 = 0;
|
||||
this->unk_30A = temp_t7;
|
||||
phi_v1 = temp_t7 & 0xFFFF;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
temp_t1 = this->unk_30A | 1;
|
||||
temp_v1_2 = temp_t1 & 0xFFFF;
|
||||
this->unk_30A = temp_t1;
|
||||
phi_v1 = temp_v1_2;
|
||||
if ((temp_v1_2 & 8) == 0) {
|
||||
this->unk_304 = Actor_SpawnAsChild(&play->actorCtx, (Actor *) this, play, (u16)0x101, -1100.0f, 105.0f, -27.0f, 0, 0, 0, 0);
|
||||
temp_t4 = this->unk_30A | 8;
|
||||
this->unk_30A = temp_t4;
|
||||
phi_v1 = temp_t4 & 0xFFFF;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
temp_v1_3 = this->unk_30A;
|
||||
phi_v1 = temp_v1_3;
|
||||
if ((temp_v1_3 & 2) != 0) {
|
||||
this->unk_30E = 0;
|
||||
this->unk_30F = Rand_S16Offset((u16)0x14, (u16)0x14);
|
||||
this->unk_310 = 0;
|
||||
temp_t9 = this->unk_30A ^ 2;
|
||||
this->unk_311 = 0;
|
||||
this->unk_30A = temp_t9;
|
||||
phi_v1 = temp_t9 & 0xFFFF;
|
||||
}
|
||||
}
|
||||
if ((phi_v1 & 1) != 0) {
|
||||
Actor_PlaySfx((Actor *) this, (u16)0x206DU);
|
||||
temp_v0_2 = this->unk_308;
|
||||
if ((s32) temp_v0_2 >= -0x1450) {
|
||||
this->unk_308 = temp_v0_2 - 0x66;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
At the top we have
|
||||
```C
|
||||
temp_v0 = *play->unk1D94;
|
||||
if (temp_v0 != 1) {
|
||||
if (temp_v0 != 2) {
|
||||
if (temp_v0 != 3) {
|
||||
```
|
||||
Firstly, we are now comparing with the value of `play->unk1D94`, not just using a pointer, so we need the first thing in `play->csCtx.actorCues[2]`. This turns out to be `play->csCtx.actorCues[2]->id`.
|
||||
|
||||
The if structure here is another classic indicator of a switch: nested, with the same variable compared multiple times. If you were to diff this as-is, you would find that the code is in completely the wrong order. Reading how the ifs work, we see that if `temp_v0` is `1`, it executes the outermost else block, if it is `2`, the middle, if `3`, the innermost, and if it is anything else, the contents of the innermost if. Hence this becomes
|
||||
```C
|
||||
void func_80A87D94(EnJj *this, PlayState *play) {
|
||||
s16 temp_v0_2;
|
||||
u16 temp_t1;
|
||||
u16 temp_t4;
|
||||
u16 temp_t7;
|
||||
u16 temp_t9;
|
||||
// u16 temp_v0;
|
||||
u16 temp_v1;
|
||||
u16 temp_v1_2;
|
||||
u16 temp_v1_3;
|
||||
u16 phi_v1;
|
||||
|
||||
switch (play->csCtx.actorCues[2]->id) {
|
||||
case 1:
|
||||
temp_v1_3 = this->unk_30A;
|
||||
phi_v1 = temp_v1_3;
|
||||
if ((temp_v1_3 & 2) != 0) {
|
||||
this->unk_30E = 0;
|
||||
this->unk_30F = Rand_S16Offset((u16)0x14, (u16)0x14);
|
||||
this->unk_310 = 0;
|
||||
temp_t9 = this->unk_30A ^ 2;
|
||||
this->unk_311 = 0;
|
||||
this->unk_30A = temp_t9;
|
||||
phi_v1 = temp_t9 & 0xFFFF;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
temp_t1 = this->unk_30A | 1;
|
||||
temp_v1_2 = temp_t1 & 0xFFFF;
|
||||
this->unk_30A = temp_t1;
|
||||
phi_v1 = temp_v1_2;
|
||||
if ((temp_v1_2 & 8) == 0) {
|
||||
this->unk_304 = Actor_SpawnAsChild(&play->actorCtx, (Actor *) this, play, (u16)0x101, -1100.0f, 105.0f, -27.0f, 0, 0, 0, 0);
|
||||
temp_t4 = this->unk_30A | 8;
|
||||
this->unk_30A = temp_t4;
|
||||
phi_v1 = temp_t4 & 0xFFFF;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
temp_v1 = this->unk_30A;
|
||||
temp_t7 = temp_v1 | 2;
|
||||
phi_v1 = temp_v1;
|
||||
if ((temp_v1 & 2) == 0) {
|
||||
this->unk_30E = 0;
|
||||
this->unk_30F = 0;
|
||||
this->unk_310 = 1;
|
||||
this->unk_311 = 0;
|
||||
this->unk_30A = temp_t7;
|
||||
phi_v1 = temp_t7 & 0xFFFF;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
phi_v1 = this->unk_30A;
|
||||
break;
|
||||
}
|
||||
if ((phi_v1 & 1) != 0) {
|
||||
Actor_PlaySfx((Actor *) this, (u16)0x206DU);
|
||||
temp_v0_2 = this->unk_308;
|
||||
if ((s32) temp_v0_2 >= -0x1450) {
|
||||
this->unk_308 = temp_v0_2 - 0x66;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
(notice that this time we need a `default` to deal with the innermost if contents). If you try to replace `0x206D` in the `Actor_PlaySfx`, you will find there is no such sfxId in the list: this is because some sound effects have an extra offset of `0x800` to do with setting flags. Adding `0x800` to the sfxId shows that this sound effect is `NA_SE_EV_JABJAB_BREATHE`. To correct this to the id in the function, we have a macro `SFX_FLAG`, and it should therefore be
|
||||
```C
|
||||
Actor_PlaySfx(&this->dyna.actor, NA_SE_EV_JABJAB_BREATHE - SFX_FLAG);
|
||||
```
|
||||
|
||||
As usual, most of the remaining temps look fake. The only one that does not is possibly `phi_v1`. However, the way in which they are used here makes it hard to tell if they are fake, and if so, how to replace them. I encourage you to try this yourself, with the aid of the diff script; the final, matching result, with other cleanup, is hidden below
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Matching C for `func_80A87D94`
|
||||
</summary>
|
||||
|
||||
```C
|
||||
void func_80A87D94(EnJj* this, PlayState* play) {
|
||||
switch (play->csCtx.actorCues[2]->id) {
|
||||
case 1:
|
||||
if ((this->unk_30A & 2) != 0) {
|
||||
this->unk_30E = 0;
|
||||
this->unk_30F = Rand_S16Offset(20, 20);
|
||||
this->unk_310 = 0;
|
||||
this->unk_311 = 0;
|
||||
this->unk_30A ^= 2;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
this->unk_30A |= 1;
|
||||
if ((this->unk_30A & 8) == 0) {
|
||||
this->unk_304 = Actor_SpawnAsChild(&play->actorCtx, &this->dyna.actor, play, ACTOR_EFF_DUST,
|
||||
-1100.0f, 105.0f, -27.0f, 0, 0, 0, 0);
|
||||
this->unk_30A |= 8;
|
||||
}
|
||||
break;
|
||||
case 3:
|
||||
if ((this->unk_30A & 2) == 0) {
|
||||
this->unk_30E = 0;
|
||||
this->unk_30F = 0;
|
||||
this->unk_310 = 1;
|
||||
this->unk_311 = 0;
|
||||
this->unk_30A |= 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if ((this->unk_30A & 1) != 0) {
|
||||
Actor_PlaySfx(&this->dyna.actor, NA_SE_EV_JABJAB_BREATHE - SFX_FLAG);
|
||||
if (this->unk_308 >= -5200) {
|
||||
this->unk_308 -= 102;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
With that, the last remaining function is `EnJj_Draw`. Draw functions have an extra layer of macroing that is required, so we shall cover them separately.
|
||||
|
||||
Next: [Draw functions](draw_functions.md)
|
@ -1,31 +0,0 @@
|
||||
# Pre-decompilation
|
||||
|
||||
It is very important to get everything set up properly before you start trying to decompile anything. As usual, if you need help at any stage of the process, ask in the Discord and we'll do our best to help.
|
||||
|
||||
## Setting up the repository
|
||||
|
||||
This is covered on the main page of the repository.
|
||||
|
||||
## Git and GitHub workflow
|
||||
|
||||
First, make a GitHub fork of the repository, then clone it into a git-enabled local folder.
|
||||
|
||||
Once you have decided on an actor, you should make a new git branch to work on: your main branch is meant to stay level with the main repository. Name it something sensible: my `EnFirefly` branch is called "en_firefly", for example, but you may prefer to use a more descriptive name: "Dark_Link" is rather more suggestive than "en_torch2", for example. You work on the branch, commit changes, and periodically push them to GitHub, if only for backup purposes.
|
||||
|
||||
Once you have finished an actor, you submit a pull request for other people to check your work complies with the project's conventions, and once it is deemed satisfactory, it will be merged in to the main repository.
|
||||
|
||||
But we are getting ahead of ourselves:
|
||||
|
||||
## Choosing a first actor
|
||||
|
||||
Since actors cover so many different categories of stuff in the game, they come in a wide variety of shapes, sizes and difficulties. The following is a list of things to bear in mind:
|
||||
|
||||
- Bosses are by far the largest, and all are already taken, so you can safely ignore them. Likewise `ovl_Fishing`, the largest actor in the game.
|
||||
- Enemies are often quite complicated due to the many different interactions they have with Link, and sometimes one another.
|
||||
- NPCs vary hugely. Some of the smaller ones are suitable to be first actors, but many are not.
|
||||
- Cutscene objects tend to have very complicated functions, and are best left until later.
|
||||
- The best starter actors tend to be dungeon or scene objects: statues, platforms, decorations, gates, etc., although we are naturally running a bit short on these now.
|
||||
|
||||
The general rule of thumb is to pick something with few, simple interactions with its environment. (For example, my first actor was BgIceTurara: icicles, which can do about 3 different things: break, fall, and grow.) This hopefully means that the functions are mostly small and simple. You are also probably better off if Draw and Init are small functions, although difficulty is sometimes not correlated to size.
|
||||
|
||||
If in doubt, ask someone to have a quick look through the code to see if it's suitable.
|
@ -1,149 +0,0 @@
|
||||
# Types, structs, and padding
|
||||
|
||||
Reminders:
|
||||
- In N64 MIPS, 1 word is 4 bytes (yes, the N64 is meant to be 64-bit, but it mostly isn't used like it in OoT)
|
||||
- A byte is 8 bits, or 2 hex digits
|
||||
|
||||
|
||||
## Types
|
||||
|
||||
The following are the common data types used everywhere:
|
||||
|
||||
| Name | Size | Comment |
|
||||
| ---- | ----- | -------- |
|
||||
| char | 1 byte | character |
|
||||
| u8 | 1 byte | unsigned byte |
|
||||
| s8 | 1 byte | signed byte |
|
||||
| u16 | 2 bytes | unsigned short |
|
||||
| s16 | 2 bytes | signed short |
|
||||
| u32 | 4 bytes/1 word | unsigned int |
|
||||
| s32 | 4 bytes/1 word | signed int |
|
||||
| void* | 4 bytes/1 word | pointer |
|
||||
|
||||
A pointer is sometimes mistaken for an s32. s32 is the default thing to use in the absence of any other information about the data.
|
||||
|
||||
Useful data for guessing types:
|
||||
- u8 is about 7 times more common than s8 (so the tutorial is a bit misleading)
|
||||
- s16 is about 16 times more common than u16
|
||||
- s32 is about 8 times more common than u32
|
||||
|
||||
Another useful thing to put here: the prototype for an action function is
|
||||
```C
|
||||
typedef void (*ActorNameActionFunc)(struct ActorName*, PlayState*);
|
||||
```
|
||||
where you replace `ActorName` by the actual actor name as used elsewhere in the actor, e.g. `EnJj`.
|
||||
|
||||
|
||||
## Some Common Structs
|
||||
|
||||
|
||||
|
||||
Here are the usual names and the sizes of some of the most common structs used in actors and their structs:
|
||||
| Type | Usual name | Size |
|
||||
| ----------------------- | --------------------- | --------------- |
|
||||
| `Actor` | `actor` | 0x14C |
|
||||
| `DynaPolyActor` | `dyna` | 0x164 |
|
||||
| `Vec3f` | | 0xC |
|
||||
| `Vec3s` | | 0x6 |
|
||||
| `SkelAnime` | `skelAnime` | 0x44 |
|
||||
| `Vec3s[limbCount]` | `jointTable` | 0x6 * limbCount |
|
||||
| `Vec3s[limbCount]` | `morphTable` | 0x6 * limbCount |
|
||||
| `ColliderCylinder` | `collider` | 0x4C |
|
||||
| `ColliderQuad` | `collider` | 0x80 |
|
||||
| `ColliderJntSph` | `collider` | 0x20 |
|
||||
| `ColliderJntSphElement` | `colliderElements[n]` | 0x40 * n |
|
||||
| `ColliderTris` | `collider` | 0x20 |
|
||||
| `ColliderTrisElement` | `colliderElements[n]` | 0x5C * n |
|
||||
|
||||
(`colliderElements` used to be called `colliderItems`, and we have not switched over fully in the repo yet.)
|
||||
|
||||
## Padding
|
||||
|
||||
### Alignment
|
||||
|
||||
A stored variable or piece of data does not always start immediately after the previous one: there may be padding in between: `0`s that are never written or referred to, and so ignored. This is to do with how the processor accesses memory: it reads 1 word at a time, so multibyte objects are aligned so they cross as few word boundaries as possible.
|
||||
|
||||
The clearest example of this is that variables with types that are 1 word in size (`s32`s and pointers, for example) are automatically shifted so that they start at the beginning of the next word, i.e. at an offset ending with one of `0,4,8,C`: this is called 4-alignment. This will also happen to `s16`s, but with 2-alignment
|
||||
|
||||
### Struct padding
|
||||
|
||||
In actor structs, this manifests as some of the char arrays not being completely replaced by actual variables. For example, BgIceTurara, the actor for icicles, has the following actor struct:
|
||||
|
||||
```C
|
||||
typedef struct BgIceTurara {
|
||||
/* 0x0000 */ DynaPolyActor dyna;
|
||||
/* 0x0164 */ BgIceTuraraActionFunc actionFunc;
|
||||
/* 0x0168 */ s16 shiverTimer;
|
||||
/* 0x016C */ ColliderCylinder collider;
|
||||
} BgIceTurara; // size = 0x01B8
|
||||
```
|
||||
|
||||
Notice that even though `timer` is an `s16`, `collider` is at `this + 0x16C`: there was originally a `char unk_166[0x2]` left over from the original char array, but it has been removed without affecting the alignment.
|
||||
|
||||
How do structs themselves align? A struct has the same alignment properties as its longest constituent (that is not itself a struct). For example, a `Vec3f` has 4-alignment, while a `Vec3s` has 2-alignment.
|
||||
|
||||
A struct may also pad at the end: it will pad to the size of its largest non-struct element. Notably, every actor struct has size a whole number of words as well, so this phenomenon also occurs at the ends of structs: see, for example, EnJj's actor struct that we filled out in the tutorial: the `char unk_312[0x2]` is unnecessary, because it is used nowhere, and the struct pads to `0x314` anyway.
|
||||
|
||||
For more information on this topic, there are plenty of guides elsewhere on the Internet. *The main thing to bear in mind for decomp purposes is that after finishing the functions, there may be some small parts of the actor struct that are just not used, because they were originally just struct padding.*
|
||||
|
||||
### Padding at the end of sections
|
||||
|
||||
<!-- a file is made up of multiple sections
|
||||
.text (instructions)
|
||||
.data
|
||||
.rodata (read only data, includes strings, floats, jumptables etc)
|
||||
.bss (zero initialized variables, not assigned a value when declared)
|
||||
.reloc (only relevant for overlays, used to reloc pointers when loaded dynamically)
|
||||
|
||||
each of these sections are 0x10/16 aligned, meaning the compiler will insert padding between each section to align them to an address that ends in 0
|
||||
|
||||
any new entry in .data must be 4 aligned no matter its size
|
||||
(i forgot if rodata bss is 4 aligned for each new entry, should find that out)
|
||||
|
||||
additionally, if an object is split into multiple c files, you will see a 16 alignment in the different sections at the point of the file split -->
|
||||
|
||||
|
||||
|
||||
In the ROM, each actor is layed out in the following order:
|
||||
|
||||
- .text (Function instructions, separated into .s files, aka .text)
|
||||
- .data (contents of the .data.s file)
|
||||
- .rodata (read-only data, includes strings, floats, jumptables etc., moved to the appropriate function files in the OoT repo)
|
||||
- .bss (varibles initialised to 0, not assigned a value when declared)
|
||||
- .reloc (relocation information: you can ignore this)
|
||||
|
||||
Each section is 0x10/16-aligned (qword aligned): each new section begins at an address with last digit `0`. This means that there can occur up to three words of padding at the end of each section.
|
||||
|
||||
(The same occurs with any object divided into multiple .c files: each new file becomes 0x10 aligned.)
|
||||
|
||||
#### Padding at the end of .text (function instructions)
|
||||
|
||||
In function instructions, this manifests as a set of `nop`s at the end of the last function, and once the rest of the functions match, this is automatic. So you never need to worry about these.
|
||||
|
||||
#### Padding at the end of .data
|
||||
|
||||
In data, the last entry may contain up to 3 words of 0s as padding. These can safely be removed when migrating data, but make sure that you don't remove something that actually is accessed by the function and happens to be 0!
|
||||
|
||||
For example, in `EnJj` we found that the last symbol in the data,
|
||||
```C
|
||||
glabel D_80A88CFC
|
||||
.word 0x06007698, 0x06007A98, 0x06007E98, 0x00000000, 0x00000000
|
||||
```
|
||||
had 2 words of padding: only the first 3 entries are actually used.
|
||||
|
||||
### Padding within the .data section
|
||||
|
||||
Every distinct symbol in data is 4-aligned (word-aligned). So in the data, even if you have two `u8`s, they will be stored in addresses starting successive words:
|
||||
|
||||
```C
|
||||
u8 byte1 = 1 // will go to address ending in 0
|
||||
u8 byte2 = 2 // Will go to address ending in 4
|
||||
```
|
||||
|
||||
#### Fake symbols
|
||||
|
||||
The process of separating the data is not flawless, and sometimes gives rise to symbols that are fake: that is, containing data that should not be stored by itself, but part of a different earlier symbol.
|
||||
|
||||
Since `D_address` corresponds to the address where the symbol is stored, every address should be word-aligned, i.e. end in one of `0,4,8,C`. Any symbols that do not are fake, and the data attached to them should be joined to the data of previous symbol, with all references to them adjusted to the correct offset from the previous symbol.
|
||||
|
||||
Thankfully most of the fake symbols have now been automatically eliminated, but that does not mean that there are none left. Symbols can also be fake if they have the correct alignment, but are rather harder to spot.
|