Tutorial on decompiling objects (and also object_dns documented) (#647)
* Fix existing documentation * Fill out object_decomp.md * First part of object decomp example * Document animations * Tutorial for identifying blob * Naming everything else referenced in the actor * Name all limb display lists * Finish example * Some tips and tricks * Remove TODO * Make merging.md consistent with everything else * Name limbs properly * Rename "bouncing idle" to "walk", since they use that animation to walk towards you if they catch you * Run formatter again * Purge hylian toolbox * Be a bit more precise about vertex naming * Also -> Further * Correct description of how extract_assets works * Format types of data * Add note about palette * Update text about object's C file * Fix typo * one more time -> in more detail * Format render error as code * explain what texture animations are * Standardize on bullet points * Use anon's sugestion for the "How we work with objects" section * Trailing commas for better formatting * Delete undefined_syms reference in object decomp * Add note about root limbs * Remove undefined_syms reference in object_decomp_example * Remove "since" * Explain *how* I changed the texture * Explain what to do if the limb doesn't render anything * Fix some extremely tiny incorrect enum name thing * Explain the object symbol stuff bettter * Add link to ZAPD documentation * Also update documentation * Update actor flags for Dns
@ -1,44 +1,45 @@
|
||||
<Root>
|
||||
<!-- Assets for the King's Chamber Deku Guards -->
|
||||
<File Name="object_dns" Segment="6">
|
||||
<Animation Name="object_dns_Anim_0002A8" Offset="0x2A8" />
|
||||
<Animation Name="object_dns_Anim_000734" Offset="0x734" />
|
||||
<Animation Name="object_dns_Anim_0008F4" Offset="0x8F4" />
|
||||
<Animation Name="object_dns_Anim_000BD8" Offset="0xBD8" />
|
||||
<Animation Name="object_dns_Anim_000D58" Offset="0xD58" />
|
||||
<Animation Name="object_dns_Anim_000FEC" Offset="0xFEC" />
|
||||
<DList Name="object_dns_DL_001640" Offset="0x1640" />
|
||||
<DList Name="object_dns_DL_0016F0" Offset="0x16F0" />
|
||||
<DList Name="object_dns_DL_0017A0" Offset="0x17A0" />
|
||||
<DList Name="object_dns_DL_0018B8" Offset="0x18B8" />
|
||||
<DList Name="object_dns_DL_0019B8" Offset="0x19B8" />
|
||||
<DList Name="object_dns_DL_001A50" Offset="0x1A50" />
|
||||
<DList Name="object_dns_DL_001AD8" Offset="0x1AD8" />
|
||||
<DList Name="object_dns_DL_001B90" Offset="0x1B90" />
|
||||
<DList Name="object_dns_DL_001C20" Offset="0x1C20" />
|
||||
<DList Name="object_dns_DL_001CB0" Offset="0x1CB0" />
|
||||
<DList Name="object_dns_DL_001D40" Offset="0x1D40" />
|
||||
<DList Name="object_dns_DL_001DD8" Offset="0x1DD8" />
|
||||
<Texture Name="object_dns_Tex_001E68" OutName="tex_001E68" Format="rgba16" Width="32" Height="32" Offset="0x1E68" />
|
||||
<Texture Name="object_dns_Tex_002668" OutName="tex_002668" Format="rgba16" Width="16" Height="16" Offset="0x2668" />
|
||||
<Texture Name="object_dns_Tex_002868" OutName="tex_002868" Format="rgba16" Width="8" Height="8" Offset="0x2868" />
|
||||
<Texture Name="object_dns_Tex_0028E8" OutName="tex_0028E8" Format="rgba16" Width="8" Height="8" Offset="0x28E8" />
|
||||
<Texture Name="object_dns_Tex_002968" OutName="tex_002968" Format="rgba16" Width="8" Height="8" Offset="0x2968" />
|
||||
<Texture Name="object_dns_Tex_0029E8" OutName="tex_0029E8" Format="rgba16" Width="8" Height="8" Offset="0x29E8" />
|
||||
<DList Name="object_dns_DL_002C48" Offset="0x2C48" />
|
||||
<Limb Name="object_dns_Standardlimb_002D18" Type="Standard" Offset="0x2D18" />
|
||||
<Limb Name="object_dns_Standardlimb_002D24" Type="Standard" Offset="0x2D24" />
|
||||
<Limb Name="object_dns_Standardlimb_002D30" Type="Standard" Offset="0x2D30" />
|
||||
<Limb Name="object_dns_Standardlimb_002D3C" Type="Standard" Offset="0x2D3C" />
|
||||
<Limb Name="object_dns_Standardlimb_002D48" Type="Standard" Offset="0x2D48" />
|
||||
<Limb Name="object_dns_Standardlimb_002D54" Type="Standard" Offset="0x2D54" />
|
||||
<Limb Name="object_dns_Standardlimb_002D60" Type="Standard" Offset="0x2D60" />
|
||||
<Limb Name="object_dns_Standardlimb_002D6C" Type="Standard" Offset="0x2D6C" />
|
||||
<Limb Name="object_dns_Standardlimb_002D78" Type="Standard" Offset="0x2D78" />
|
||||
<Limb Name="object_dns_Standardlimb_002D84" Type="Standard" Offset="0x2D84" />
|
||||
<Limb Name="object_dns_Standardlimb_002D90" Type="Standard" Offset="0x2D90" />
|
||||
<Limb Name="object_dns_Standardlimb_002D9C" Type="Standard" Offset="0x2D9C" />
|
||||
<Skeleton Name="object_dns_Skel_002DD8" Type="Normal" LimbType="Standard" Offset="0x2DD8" />
|
||||
<Animation Name="object_dns_Anim_003310" Offset="0x3310" />
|
||||
<Animation Name="object_dns_Anim_0034EC" Offset="0x34EC" />
|
||||
<Animation Name="gKingsChamberDekuGuardDanceAnim" Offset="0x2A8" /> <!-- Original name is "dns_dance" -->
|
||||
<Animation Name="gKingsChamberDekuGuardFlipAnim" Offset="0x734" /> <!-- Original name is "dns_dancejump" -->
|
||||
<Animation Name="gKingsChamberDekuGuardSurpriseStartAnim" Offset="0x8F4" /> <!-- Original name is "dns_furueru" ("to shiver") -->
|
||||
<Animation Name="gKingsChamberDekuGuardSurpriseLoopAnim" Offset="0xBD8" />
|
||||
<Animation Name="gKingsChamberDekuGuardRunStartAnim" Offset="0xD58" /> <!-- Original name is "dns_hasiru" ("to run") -->
|
||||
<Animation Name="gKingsChamberDekuGuardRunLoopAnim" Offset="0xFEC" />
|
||||
<DList Name="gKingsChamberDekuGuardRightFootDL" Offset="0x1640" />
|
||||
<DList Name="gKingsChamberDekuGuardLeftFootDL" Offset="0x16F0" />
|
||||
<DList Name="gKingsChamberDekuGuardSnoutDL" Offset="0x17A0" />
|
||||
<DList Name="gKingsChamberDekuGuardHeadDL" Offset="0x18B8" />
|
||||
<DList Name="gKingsChamberDekuGuardStalkDL" Offset="0x19B8" />
|
||||
<DList Name="gKingsChamberDekuGuardEyesDL" Offset="0x1A50" />
|
||||
<DList Name="gKingsChamberDekuGuardTorsoDL" Offset="0x1AD8" />
|
||||
<DList Name="gKingsChamberDekuGuardLeftMustacheDL" Offset="0x1B90" />
|
||||
<DList Name="gKingsChamberDekuGuardRightMustacheDL" Offset="0x1C20" />
|
||||
<DList Name="gKingsChamberDekuGuardLeftLeafDL" Offset="0x1CB0" />
|
||||
<DList Name="gKingsChamberDekuGuardCenterLeafDL" Offset="0x1D40" />
|
||||
<DList Name="gKingsChamberDekuGuardRightLeafDL" Offset="0x1DD8" />
|
||||
<Texture Name="gKingsChamberDekuGuardLeafTex" OutName="kings_chamber_deku_guard_leaf" Format="rgba16" Width="32" Height="32" Offset="0x1E68" />
|
||||
<Texture Name="gKingsChamberDekuGuardBodyTex" OutName="kings_chamber_deku_guard_body" Format="rgba16" Width="16" Height="16" Offset="0x2668" />
|
||||
<Texture Name="gKingsChamberDekuGuardMouthTex" OutName="kings_chamber_deku_guard_mouth" Format="rgba16" Width="8" Height="8" Offset="0x2868" />
|
||||
<Texture Name="gKingsChamberDekuGuardEyeOpenTex" OutName="kings_chamber_deku_guard_eye_open" Format="rgba16" Width="8" Height="8" Offset="0x28E8" />" Format="rgba16" Width="8" Height="8" Offset="0x28E8" />
|
||||
<Texture Name="gKingsChamberDekuGuardEyeHalfTex" OutName="kings_chamber_deku_guard_eye_half" Format="rgba16" Width="8" Height="8" Offset="0x2968" />
|
||||
<Texture Name="gKingsChamberDekuGuardEyeClosedTex" OutName="kings_chamber_deku_guard_eye_closed" Format="rgba16" Width="8" Height="8" Offset="0x29E8" />
|
||||
<DList Name="gKingsChamberDekuGuardDekuFlower" Offset="0x2C48" />
|
||||
<Limb Name="gKingsChamberDekuGuardTorsoLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_TORSO" Offset="0x2D18" />
|
||||
<Limb Name="gKingsChamberDekuGuardHeadLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_HEAD" Offset="0x2D24" />
|
||||
<Limb Name="gKingsChamberDekuGuardStalkLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_STALK" Offset="0x2D30" />
|
||||
<Limb Name="gKingsChamberDekuGuardLeftLeafLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_LEFT_LEAF" Offset="0x2D3C" />
|
||||
<Limb Name="gKingsChamberDekuGuardRightLeafLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_RIGHT_LEAF" Offset="0x2D48" />
|
||||
<Limb Name="gKingsChamberDekuGuardCenterLeafLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_CENTER_LEAF" Offset="0x2D54" />
|
||||
<Limb Name="gKingsChamberDekuGuardSnoutLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_SNOUT" Offset="0x2D60" />
|
||||
<Limb Name="gKingsChamberDekuGuardRightMustacheLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_RIGHT_MUSTACHE" Offset="0x2D6C" />
|
||||
<Limb Name="gKingsChamberDekuGuardLeftMustacheLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_LEFT_MUSTACHE" Offset="0x2D78" />
|
||||
<Limb Name="gKingsChamberDekuGuardEyesLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_EYES" Offset="0x2D84" />
|
||||
<Limb Name="gKingsChamberDekuGuardLeftFootLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_LEFT_FOOT" Offset="0x2D90" />
|
||||
<Limb Name="gKingsChamberDekuGuardRightFootLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_RIGHT_FOOT" Offset="0x2D9C" />
|
||||
<Skeleton Name="gKingsChamberDekuGuardSkel" Type="Normal" LimbType="Standard" LimbNone="KINGS_CHAMBER_DEKU_GUARD_LIMB_NONE" LimbMax="KINGS_CHAMBER_DEKU_GUARD_LIMB_MAX" EnumName="KingsChamberDekuGuardLimbs" Offset="0x2DD8" />
|
||||
<Animation Name="gKingsChamberDekuGuardIdleAnim" Offset="0x3310" /> <!-- Original name is "dns_wait" -->
|
||||
<Animation Name="gKingsChamberDekuGuardWalkAnim" Offset="0x34EC" /> <!-- Original name is "dns_walk" -->
|
||||
</File>
|
||||
</Root>
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Beginning decompilation: the Init function and the Actor struct
|
||||
|
||||
Up: [Contents](contents.md)
|
||||
- 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 EnRecepgirl (the rather forward Mayor's receptionist in the Mayor's residence in East Clock Town) as our example: it is a nice simple NPC with most of the common features of an NPC.
|
||||
|
||||
@ -96,9 +96,9 @@ It is currently divided into six sections as follows:
|
||||
|
||||
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.
|
||||
|
||||
5. `if`'d-out section containing the `InitVars` and a few other common pieces of data. This can be ignored until we import the data.
|
||||
4. `if`'d-out section containing the `InitVars` and a few other common pieces of data. This can be ignored until we import the data.
|
||||
|
||||
4. A set of `extern`s. These refer to the data in the previous section, and, data that comes from other files, usually in the actor's corresponding object file. The latter 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). 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. A set of `extern`s. These refer to the data in the previous section, and, data that comes from other files, usually in the actor's corresponding object file. The latter point to addresses in the ROM where assets are stored (usually collision data, animations or display lists). These can simply be replaced by including the object file (see [Object Decompilation](object_decomp.md) for how this process works).
|
||||
|
||||
6. List of functions. Each `#pragma GLOBAL_ASM` 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.
|
||||
|
||||
|
@ -32,7 +32,9 @@
|
||||
- Fake symbols
|
||||
- Inlining
|
||||
|
||||
## [Object Decompilation](object_decomp.md) (TODO)
|
||||
- [Documenting a decompiled file](documenting.md)
|
||||
|
||||
## [Object Decompilation](object_decomp.md)
|
||||
- Object files
|
||||
- How we decompile objects
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
# Data
|
||||
|
||||
Up: [Contents](contents.md)
|
||||
Previous: [Draw functions](draw_functions.md)
|
||||
- Up: [Contents](contents.md)
|
||||
- Previous: [Draw functions](draw_functions.md)
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Data first](#data-first)
|
||||
- [Extern and data last](#extern-and-data-last)
|
||||
- [Segmented pointers](#segmented-pointers)
|
||||
- [Segmented pointers and object symbols](#segmented-pointers-and-object-symbols)
|
||||
- [Fake symbols](#fake-symbols)
|
||||
- [Inlining](#inlining)
|
||||
|
||||
@ -114,7 +114,7 @@ static s32 D_80C106C8 = 0;
|
||||
That should be everything, and we should now be able to `make` without the data file with no issues.
|
||||
|
||||
|
||||
## Segmented pointers
|
||||
## Segmented pointers and object symbols
|
||||
|
||||
The game has a convenient system that allows it to sometimes effectively use offsets into a file instead of raw memory addresses to reference things. This is done by setting a file address to a *segment*. A segmented address is of the form `0x0XYYYYYY`, where `X` is the segment number. There are 16 available segments, and actors always set segment 6 to their object file, which is a file containing assets (skeleton, animations, textures, etc.) that they use. This is what all those `D_06...` are, and it is also what the entries in `D_80C106B0` are: they are currently raw numbers instead of symbols, though, and we would like to replace them.
|
||||
|
||||
@ -124,29 +124,23 @@ mips-linux-gnu-ld: build/src/overlays/actors/ovl_En_Recepgirl/z_en_recepgirl.o:(
|
||||
````
|
||||
As we'd expect, of course: we didn't fulfil our promise that they were defined elsewhere.)
|
||||
|
||||
This is mitigated by use of the file `undefined_syms.txt`, which feeds the linker the raw addresses to use as the symbol definitions. If you find a segmented address that is not in already externed, `extern` it at the top of the file and add it to the actor's section in undefined_syms:
|
||||
```C
|
||||
extern void* D_0600F8F0;
|
||||
extern void* D_0600FCF0;
|
||||
extern void* D_060100F0;
|
||||
extern void* D_0600FCF0;
|
||||
For actors which have yet to be decompiled, this is mitigated by use of the file `undefined_syms.txt`, which feeds the linker the raw addresses to use as the symbol definitions. However, we want to replace these segmented addresses with proper object symbols whenever possible. In `En_Recepgirl_InitVars`, we can see that this actor uses the object `OBJECT_BG`:
|
||||
```c
|
||||
const ActorInit En_Recepgirl_InitVars = {
|
||||
ACTOR_EN_RECEPGIRL,
|
||||
ACTORCAT_NPC,
|
||||
FLAGS,
|
||||
OBJECT_BG,
|
||||
};
|
||||
```
|
||||
|
||||
static void* D_80C106B0[4] = { &D_0600F8F0, &D_0600FCF0, &D_060100F0, &D_0600FCF0 };
|
||||
```
|
||||
and in undefined_syms.txt:
|
||||
```
|
||||
// ovl_En_Recepgirl
|
||||
D_06000968 = 0x06000968;
|
||||
D_06001384 = 0x06001384;
|
||||
D_06009890 = 0x06009890;
|
||||
D_0600A280 = 0x0600A280;
|
||||
D_0600AD98 = 0x0600AD98;
|
||||
D_0600F8F0 = 0x0600F8F0;
|
||||
D_0600FCF0 = 0x0600FCF0;
|
||||
D_060100F0 = 0x060100F0;
|
||||
D_06011B60 = 0x06011B60;
|
||||
If we open up `assets/objects/object_bg.h`, we can see a bunch of different names corresponding to every asset in the object. You may notice that some of these names look a bit familiar; `object_bg_Tex_00F8F0` seems very close to the segmented address `(void*)0x600F8F0`. This is the proper object symbol for this segmented address, so we should `#include` this header in our actor and use these object symbols like so:
|
||||
```c
|
||||
static void* D_80C106B0[4] = { object_bg_Tex_00F8F0, object_bg_Tex_00FCF0, object_bg_Tex_0100F0, object_bg_Tex_00FCF0 };
|
||||
```
|
||||
|
||||
After replacing every segmented pointer with an object symbol, you should go ahead and delete every segmented pointer associated with this actor from `undefined_syms`.
|
||||
|
||||
We will come back and name these later when we do the object.
|
||||
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Documenting
|
||||
|
||||
Up: [Contents](contents.md)
|
||||
Previous: [Data](data.md)
|
||||
- Up: [Contents](contents.md)
|
||||
- Previous: [Data](data.md)
|
||||
|
||||
Decompilation is only the first step: since the point of this project is to understand the game better than ever before, the code needs documentation. In this document, we will go through the basic stuff that it's good to do for any actor: we will not try to understand every single thing the actor does in full detail, but try to name the functions and variables usefully for a full documentation pass later to take advantage of.
|
||||
|
||||
@ -62,11 +62,7 @@ const ActorInit En_Recepgirl_InitVars = {
|
||||
(ActorFunc)EnRecepgirl_Draw,
|
||||
};
|
||||
|
||||
extern void* D_0600F8F0;
|
||||
extern void* D_0600FCF0;
|
||||
extern void* D_060100F0;
|
||||
|
||||
static void* D_80C106B0[4] = { &D_0600F8F0, &D_0600FCF0, &D_060100F0, &D_0600FCF0 };
|
||||
static void* D_80C106B0[4] = { object_bg_Tex_00F8F0, object_bg_Tex_00FCF0, object_bg_Tex_0100F0, object_bg_Tex_00FCF0 };
|
||||
|
||||
// static InitChainEntry sInitChain[] = {
|
||||
static InitChainEntry D_80C106C0[] = {
|
||||
@ -76,14 +72,6 @@ static InitChainEntry D_80C106C0[] = {
|
||||
|
||||
static s32 D_80C106C8 = 0;
|
||||
|
||||
extern AnimationHeader D_06000968;
|
||||
extern AnimationHeader D_06001384;
|
||||
extern AnimationHeader D_06009890;
|
||||
extern AnimationHeader D_0600A280;
|
||||
extern AnimationHeader D_0600AD98;
|
||||
extern FlexSkeletonHeader D_06011B60;
|
||||
|
||||
|
||||
// #pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Recepgirl/EnRecepgirl_Init.s")
|
||||
void EnRecepgirl_Init(Actor* thisx, GlobalContext* globalCtx) {
|
||||
EnRecepgirl* this = THIS;
|
||||
@ -91,7 +79,7 @@ void EnRecepgirl_Init(Actor* thisx, GlobalContext* globalCtx) {
|
||||
|
||||
Actor_ProcessInitChain(&this->actor, D_80C106C0);
|
||||
ActorShape_Init(&this->actor.shape, -60.0f, NULL, 0.0f);
|
||||
SkelAnime_InitFlex(globalCtx, &this->skelAnime, &D_06011B60, &D_06009890, this->jointTable, this->morphTable, 24);
|
||||
SkelAnime_InitFlex(globalCtx, &this->skelAnime, &object_bg_Skel_011B60, &object_bg_Anim_009890, this->jointTable, this->morphTable, 24);
|
||||
|
||||
if (D_80C106C8 == 0) {
|
||||
for (i = 0; i < 4; i++) {
|
||||
@ -132,8 +120,8 @@ void func_80C100DC(EnRecepgirl *this) {
|
||||
|
||||
// #pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Recepgirl/func_80C10148.s")
|
||||
void func_80C10148(EnRecepgirl *this) {
|
||||
if (this->skelAnime.animation == &D_06001384) {
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &D_0600AD98, 5.0f);
|
||||
if (this->skelAnime.animation == &object_bg_Anim_001384) {
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &object_bg_Anim_00AD98, 5.0f);
|
||||
}
|
||||
this->actionFunc = func_80C1019C;
|
||||
}
|
||||
@ -141,10 +129,10 @@ void func_80C10148(EnRecepgirl *this) {
|
||||
// #pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Recepgirl/func_80C1019C.s")
|
||||
void func_80C1019C(EnRecepgirl* this, GlobalContext* globalCtx) {
|
||||
if (SkelAnime_Update(&this->skelAnime) != 0) {
|
||||
if (this->skelAnime.animation == &D_0600A280) {
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &D_0600AD98, 5.0f);
|
||||
if (this->skelAnime.animation == &object_bg_Anim_00A280) {
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &object_bg_Anim_00AD98, 5.0f);
|
||||
} else {
|
||||
Animation_ChangeTransitionRepeat(&this->skelAnime, &D_06009890, -4.0f);
|
||||
Animation_ChangeTransitionRepeat(&this->skelAnime, &object_bg_Anim_009890, -4.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,7 +152,7 @@ void func_80C1019C(EnRecepgirl* this, GlobalContext* globalCtx) {
|
||||
|
||||
// #pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Recepgirl/func_80C10290.s")
|
||||
void func_80C10290(EnRecepgirl *this) {
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &D_0600A280, -4.0f);
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &object_bg_Anim_00A280, -4.0f);
|
||||
this->actionFunc = func_80C102D4;
|
||||
}
|
||||
|
||||
@ -173,18 +161,18 @@ void func_80C102D4(EnRecepgirl *this, GlobalContext *globalCtx) {
|
||||
u8 temp_v0_2;
|
||||
|
||||
if (SkelAnime_Update(&this->skelAnime) != 0) {
|
||||
if (this->skelAnime.animation == &D_0600A280) {
|
||||
Animation_ChangeDefaultRepeat(&this->skelAnime, &D_06001384);
|
||||
} else if (this->skelAnime.animation == &D_0600AD98) {
|
||||
if (this->skelAnime.animation == &object_bg_Anim_00A280) {
|
||||
Animation_ChangeDefaultRepeat(&this->skelAnime, &object_bg_Anim_001384);
|
||||
} else if (this->skelAnime.animation == &object_bg_Anim_00AD98) {
|
||||
if (this->actor.textId == 0x2ADA) {
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &D_06000968, 10.0f);
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &object_bg_Anim_000968, 10.0f);
|
||||
} else {
|
||||
Animation_ChangeTransitionRepeat(&this->skelAnime, &D_06009890, 10.0f);
|
||||
Animation_ChangeTransitionRepeat(&this->skelAnime, &object_bg_Anim_009890, 10.0f);
|
||||
}
|
||||
} else if (this->actor.textId == 0x2ADA) {
|
||||
Animation_ChangeTransitionRepeat(&this->skelAnime, &D_06009890, 10.0f);
|
||||
Animation_ChangeTransitionRepeat(&this->skelAnime, &object_bg_Anim_009890, 10.0f);
|
||||
} else {
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &D_0600A280, -4.0f);
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &object_bg_Anim_00A280, -4.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,17 +186,17 @@ void func_80C102D4(EnRecepgirl *this, GlobalContext *globalCtx) {
|
||||
if ((temp_v0_2 == 5) && (func_80147624(globalCtx) != 0)) {
|
||||
if (this->actor.textId == 0x2AD9) {
|
||||
Flags_SetSwitch(globalCtx, this->actor.params);
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &D_0600AD98, 10.0f);
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &object_bg_Anim_00AD98, 10.0f);
|
||||
if ((gSaveContext.weekEventReg[63] & 0x80)) {
|
||||
this->actor.textId = 0x2ADF;
|
||||
} else {
|
||||
this->actor.textId = 0x2ADA;
|
||||
}
|
||||
} else if (this->actor.textId == 0x2ADC) {
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &D_0600AD98, 10.0f);
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &object_bg_Anim_00AD98, 10.0f);
|
||||
this->actor.textId = 0x2ADD;
|
||||
} else {
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &D_06000968, 10.0f);
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &object_bg_Anim_000968, 10.0f);
|
||||
if (this->actor.textId == 0x2ADD) {
|
||||
this->actor.textId = 0x2ADE;
|
||||
} else if (this->actor.textId == 0x2ADA) {
|
||||
@ -349,11 +337,9 @@ Go to "Analysis -> Find Dlists" and press OK (the defaults are usually fine). Th
|
||||
|
||||
We will talk about what all these types of data are next time, but for now, all we want to know is what
|
||||
```C
|
||||
extern void* D_0600F8F0;
|
||||
extern void* D_0600FCF0;
|
||||
extern void* D_060100F0;
|
||||
static void* D_80C106B0[4] = { object_bg_Tex_00F8F0, object_bg_Tex_00FCF0, object_bg_Tex_0100F0, object_bg_Tex_00FCF0 };
|
||||
```
|
||||
actually are. We know they are set on segment 8, so we need to find where the skeleton uses them. We know from `extern FlexSkeletonHeader D_06011B60;` that this is at `0x06011B60`, so scroll down to it, right-click on it, and choose "Open in Skeleton Viewer". Pick an animation that we know it uses (sometimes Z64Utils misidentifies other things for animations), such as `extern AnimationHeader D_06000968;`, and you will get this error:
|
||||
actually are. We know they are set on segment 8, so we need to find where the skeleton uses them. We know from `object_bg_Skel_011B60` that this is at `0x06011B60`, so scroll down to it, right-click on it, and choose "Open in Skeleton Viewer". Pick an animation that we know it uses (sometimes Z64Utils misidentifies other things for animations), such as `object_bg_Anim_000968`, and you will get this error:
|
||||
|
||||
![Z64Utils, error when viewing skeleton](images/z64utils_skeleton_error.png)
|
||||
|
||||
@ -389,11 +375,7 @@ But what sort of textures? This is an NPC, so what textures on the model would i
|
||||
|
||||
**N.B.** static data should not be renamed in the assembly or `variables.txt`, since assembly has no notion of file locality and there can be symbol clashes. Therefore it should only be renamed in its respective file, not globally.
|
||||
```C
|
||||
extern void* D_0600F8F0;
|
||||
extern void* D_0600FCF0;
|
||||
extern void* D_060100F0;
|
||||
|
||||
static TexturePtr sEyeTextures[] = { &D_0600F8F0, &D_0600FCF0, &D_060100F0, &D_0600FCF0 };
|
||||
static TexturePtr sEyeTextures[] = { object_bg_Tex_00F8F0, object_bg_Tex_00FCF0, object_bg_Tex_0100F0, object_bg_Tex_00FCF0 };
|
||||
```
|
||||
|
||||
And now it's rather more obvious what
|
||||
@ -461,18 +443,18 @@ Finally, we have to name the rest of the functions. Setup functions are usually
|
||||
|
||||
```C
|
||||
void func_80C10148(EnRecepgirl* this) {
|
||||
if (this->skelAnime.animation == &D_06001384) {
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &D_0600AD98, 5.0f);
|
||||
if (this->skelAnime.animation == &object_bg_Anim_001384) {
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &object_bg_Anim_00AD98, 5.0f);
|
||||
}
|
||||
this->actionFunc = func_80C1019C;
|
||||
}
|
||||
|
||||
void func_80C1019C(EnRecepgirl* this, GlobalContext* globalCtx) {
|
||||
if (SkelAnime_Update(&this->skelAnime) != 0) {
|
||||
if (this->skelAnime.animation == &D_0600A280) {
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &D_0600AD98, 5.0f);
|
||||
if (this->skelAnime.animation == &object_bg_Anim_00A280) {
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &object_bg_Anim_00AD98, 5.0f);
|
||||
} else {
|
||||
Animation_ChangeTransitionRepeat(&this->skelAnime, &D_06009890, -4.0f);
|
||||
Animation_ChangeTransitionRepeat(&this->skelAnime, &object_bg_Anim_009890, -4.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@ -491,7 +473,7 @@ void func_80C1019C(EnRecepgirl* this, GlobalContext* globalCtx) {
|
||||
}
|
||||
|
||||
void func_80C10290(EnRecepgirl* this) {
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &D_0600A280, -4.0f);
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &object_bg_Anim_00A280, -4.0f);
|
||||
this->actionFunc = func_80C102D4;
|
||||
}
|
||||
|
||||
@ -499,18 +481,18 @@ void func_80C102D4(EnRecepgirl* this, GlobalContext* globalCtx) {
|
||||
u8 temp_v0_2;
|
||||
|
||||
if (SkelAnime_Update(&this->skelAnime)) {
|
||||
if (this->skelAnime.animation == &D_0600A280) {
|
||||
Animation_ChangeDefaultRepeat(&this->skelAnime, &D_06001384);
|
||||
} else if (this->skelAnime.animation == &D_0600AD98) {
|
||||
if (this->skelAnime.animation == &object_bg_Anim_00A280) {
|
||||
Animation_ChangeDefaultRepeat(&this->skelAnime, &object_bg_Anim_001384);
|
||||
} else if (this->skelAnime.animation == &object_bg_Anim_00AD98) {
|
||||
if (this->actor.textId == 0x2ADA) { // Mayor's office is on the left (meeting ongoing)
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &D_06000968, 10.0f);
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &object_bg_Anim_000968, 10.0f);
|
||||
} else {
|
||||
Animation_ChangeTransitionRepeat(&this->skelAnime, &D_06009890, 10.0f);
|
||||
Animation_ChangeTransitionRepeat(&this->skelAnime, &object_bg_Anim_009890, 10.0f);
|
||||
}
|
||||
} else if (this->actor.textId == 0x2ADA) { // Mayor's office is on the left (meeting ongoing)
|
||||
Animation_ChangeTransitionRepeat(&this->skelAnime, &D_06009890, 10.0f);
|
||||
Animation_ChangeTransitionRepeat(&this->skelAnime, &object_bg_Anim_009890, 10.0f);
|
||||
} else {
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &D_0600A280, -4.0f);
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &object_bg_Anim_00A280, -4.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@ -521,17 +503,17 @@ void func_80C102D4(EnRecepgirl* this, GlobalContext* globalCtx) {
|
||||
} else if ((temp_v0_2 == 5) && (func_80147624(globalCtx) != 0)) {
|
||||
if (this->actor.textId == 0x2AD9) { // "Welcome..."
|
||||
Flags_SetSwitch(globalCtx, this->actor.params);
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &D_0600AD98, 10.0f);
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &object_bg_Anim_00AD98, 10.0f);
|
||||
if (gSaveContext.weekEventReg[63] & 0x80) { // showed Couple's Mask to meeting
|
||||
this->actor.textId = 0x2ADF; // Mayor's office is on the left (meeting ended)
|
||||
} else {
|
||||
this->actor.textId = 0x2ADA; // Mayor's office is on the left (meeting ongoing)
|
||||
}
|
||||
} else if (this->actor.textId == 0x2ADC) { // hear directions again?
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &D_0600AD98, 10.0f);
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &object_bg_Anim_00AD98, 10.0f);
|
||||
this->actor.textId = 0x2ADD; // "So..."
|
||||
} else {
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &D_06000968, 10.0f);
|
||||
Animation_MorphToPlayOnce(&this->skelAnime, &object_bg_Anim_000968, 10.0f);
|
||||
|
||||
if (this->actor.textId == 0x2ADD) { // "So..."
|
||||
this->actor.textId = 0x2ADE; // Mayor's office is on the left, drawing room on the right
|
||||
@ -568,7 +550,7 @@ We like to make macros for reading an actor's `params` (indeed, this is required
|
||||
} else {
|
||||
Actor_SetScale(&this->dyna.actor, 0.1f);
|
||||
DynaPolyActor_Init(&this->dyna, 1);
|
||||
CollisionHeader_GetVirtual(&D_06001B2C, &colHeader);
|
||||
CollisionHeader_GetVirtual(&object_tree_Colheader_001B2C, &colHeader);
|
||||
this->dyna.bgId = DynaPoly_SetBgActor(globalCtx, &globalCtx->colCtx.dyna, &this->dyna.actor, colHeader);
|
||||
}
|
||||
```
|
||||
@ -588,7 +570,7 @@ Notice that we use `thisx`: this makes the form of every one of these macros the
|
||||
} else {
|
||||
Actor_SetScale(&this->dyna.actor, 0.1f);
|
||||
DynaPolyActor_Init(&this->dyna, 1);
|
||||
CollisionHeader_GetVirtual(&D_06001B2C, &colHeader);
|
||||
CollisionHeader_GetVirtual(&object_tree_Colheader_001B2C, &colHeader);
|
||||
this->dyna.bgId = DynaPoly_SetBgActor(globalCtx, &globalCtx->colCtx.dyna, &this->dyna.actor, colHeader);
|
||||
}
|
||||
```
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Draw functions
|
||||
|
||||
Up: [Contents](contents.md)
|
||||
Previous: [The rest of the functions in the actor](other_functions.md)
|
||||
- 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.
|
||||
|
||||
|
BIN
docs/tutorial/images/broken_texture.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
docs/tutorial/images/custom_texture_in_game.png
Normal file
After Width: | Height: | Size: 230 KiB |
BIN
docs/tutorial/images/dns_custom_texture.png
Normal file
After Width: | Height: | Size: 169 B |
BIN
docs/tutorial/images/z64utils_dns_deku_flower.png
Normal file
After Width: | Height: | Size: 134 KiB |
BIN
docs/tutorial/images/z64utils_dns_display_list.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
docs/tutorial/images/z64utils_dns_head_limb.png
Normal file
After Width: | Height: | Size: 58 KiB |
BIN
docs/tutorial/images/z64utils_dns_limb_dlist.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
docs/tutorial/images/z64utils_dns_skeletonheader.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
docs/tutorial/images/z64utils_open_dns.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
docs/tutorial/images/z64utils_set_segment_8.png
Normal file
After Width: | Height: | Size: 13 KiB |
@ -1,7 +1,7 @@
|
||||
# The merging process
|
||||
|
||||
Up: [Contents](contents.md)
|
||||
Previous: [Documenting](documenting.md)
|
||||
- Up: [Contents](contents.md)
|
||||
- Previous: [Documenting](documenting.md)
|
||||
|
||||
|
||||
## Preparing to PR
|
||||
|
@ -1,14 +1,160 @@
|
||||
# Analysing object files
|
||||
# Object Decompilation
|
||||
|
||||
Up: [Contents](contents.md)
|
||||
Previous: [Documenting](documenting.md)
|
||||
- Up: [Contents](contents.md)
|
||||
- Previous: [Documenting](documenting.md)
|
||||
|
||||
TODO: To be written once automated objects are present.
|
||||
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.
|
||||
|
||||
## Useful tools
|
||||
## 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
|
||||
- Texture Animations (instructions to scroll/cycle textures or change colors)
|
||||
|
||||
## How we work with objects
|
||||
|
||||
These objects are considered assets so they are not included in the repository directly. Instead we use the Zelda Asset Processor for Decompilation (ZAPD) to extract them from the ROM. As input, ZAPD takes an XML file that describes what and how to extract. Each object already has an autogenerated XML created in the `assets/xml/objects` directory, though they are incomplete with just autogenerated names. The goal of object decompilation is to complete these files by identifying any remaining blobs and giving everything proper names.
|
||||
|
||||
## 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
|
||||
|
||||
All objects have had their XML generated in an automated way, so most constituent parts of each object are already identified, but will still need to be named and documented properly. Further, 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 all files that have been updated since the last time it was run. You can also 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
|
||||
|
||||
You'll want to name everything that exists in the XML, but it helps to break it down into separate steps. After each step, extract the single asset again.
|
||||
|
||||
1. First, name every piece of data that is directly accessed by another part of the repo (generally, but not always, actors). For most objects, this includes several of a skeleton, animations, some textures, and collision information.
|
||||
|
||||
2. Then, name the limbs associated with each skeleton. This is usually straightforward, providing Z64Utils can show the skeleton properly. Note that when naming limbs, you should name them *from the object's perspective*, so the "left arm" is the one that is to the *object's* left. As part of this step, you should also replace the autogenerated enum values associated with each limb with an appropriate name. Another thing to note is that some limbs are considered "Root" limbs that are simply parents to other limbs and don't actually render anything themselves. In these cases, you can simply add `Root` to the limb name, e.g., `gGibdoRightLegRootLimb`.
|
||||
|
||||
3. Next, name the display lists that are associated to limbs in the skeleton. Z64Utils can show you which display list is associated with a given limb.
|
||||
|
||||
4. If any non-limb display lists still lack a name, try naming them now.
|
||||
|
||||
5. Lastly, try to name every texture in the object. This can be a bit tricky just by looking at the textures, so it helps to see how they're used in the display lists. You can also try manipulating the textures, recompiling the game, and loading up the resulting ROM to see what your changes look like in-game.
|
||||
|
||||
You'll notice that we didn't mention vertices in this list. This is because, for the most part, vertices are not used outside the object itself, and so are not required to have names. However, some actors have code that manipulates or otherwise interacts with vertices; in these cases, the vertices should be added to the XML and named.
|
||||
|
||||
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`, `TLUT`, `Vtx`, `TexAnim`, etc.), while output texture files are named in `snake_case`. TLUTs (texture look-up tables) should always have their output file end in `tlut`. For more info on which suffixes to use, check out the [ZAPD documentation on extraction XML](https://github.com/zeldaret/mm/blob/master/tools/ZAPD/docs/zapd_extraction_xml_reference.md).
|
||||
|
||||
### Textures
|
||||
|
||||
Since all objects have an autogenerated XML, most textures in the game already have an entry in an XML file. However, some did not get automatically detected, and textures can be 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, and if in doubt, just do height 1 and appropriate width.
|
||||
|
||||
If in doubt, look at completed objects in the repo, and if still in doubt, ask.
|
||||
|
||||
## Tools
|
||||
|
||||
We are very fortunate that several nice tools have been written recently that are excellent for documenting asset files:
|
||||
- [Z64Utils](https://github.com/Random06457/Z64Utils/releases), for looking at displaylists, textures they reference, the skeleton, animations, etc.
|
||||
- [Texture64](https://github.com/queueRAM/Texture64/releases), for looking at textures in all the common N64 formats (needed since Z64Utils cannot interpret textures not explicitly referenced in displaylists currently)
|
||||
|
||||
## 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
|
||||
|
||||
- Change and save the texture. You can simply open the texture's PNG file in most image editors (e.g., Photoshop, GIMP, Paint, etc.) and edit it to your liking, though you'll need to keep the original palette in mind for palettized textures.
|
||||
- Touch the object's main C file (in the same directory)
|
||||
- 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).
|
||||
|
||||
## Tips and Tricks
|
||||
|
||||
### Fixing improper-looking textures
|
||||
|
||||
For CI4 and CI8 textures, you might see improper-looking textures like so:
|
||||
|
||||
![An improper-looking CI8 texture](images/broken_texture.png)
|
||||
|
||||
The reason this happens is because ZAPD couldn't determine the TLUT for the texture, so it couldn't use the proper palette. To fix this, you can supply a `TlutOffset` to the texture like so:
|
||||
```xml
|
||||
<Texture Name="gGiantFaceEyeOpenTex" OutName="giant_face_eye_open" Format="ci8" Width="32" Height="64" Offset="0x5A80" TlutOffset="0x5380" />
|
||||
```
|
||||
|
||||
### Understanding texture animations
|
||||
|
||||
Texture animations are new to Majora's Mask, and they can be pretty tricky to understand. Luckily, there's some extensive documentation on how they're structured [here](https://github.com/zeldaret/mm/blob/master/tools/ZAPD/ZAPD/ZTextureAnimation.cpp). One useful thing to remember is that empty texture animations take the form of `00 00 00 06 00 00 00 00`. The process that automatically generated all the object XMLs sometimes failed to recognize this as an empty texture animation, so it puts it in various blobs or fails to account for it at all.
|
||||
|
||||
Next: [The merging process](merging.md)
|
258
docs/tutorial/object_decomp_example.md
Normal file
@ -0,0 +1,258 @@
|
||||
# Object Decompilation Example
|
||||
|
||||
- Previous: [Object Decompilation](object_decomp.md)
|
||||
|
||||
Let's take a look at `object_dns`, which is a pretty typical NPC object. It's used by one actor: `ovl_En_Dns`.
|
||||
|
||||
## Step 1: Naming the skeleton and limbs
|
||||
|
||||
We already went through the steps of opening an object file in Z64Utils in the [documenting step](documenting.md#z64utils), but we'll do it in more detail here. First, search for the object file, then right-click and select "Open in Object Analyzer". It will ask you to choose a segment: this is the segment that the file is put on, and allows Z64Utils to resolve the segmented addresses it references into symbols. The json already knows it should be segment 6, so just click OK.
|
||||
|
||||
![Opening object_dns in Z64Utils](images/z64utils_open_dns.png)
|
||||
|
||||
In the resulting window, go to "Analysis -> Find Dlists" and press OK (the defaults are usually fine). This will only find display lists, so to find everything else in the obect, do "Analysis -> Analyze Dlists". We're looking for the skeleton, so scroll down to the SkeletonHeader, right-click it, and select "Open in Skeleton Viewer":
|
||||
|
||||
![Finding object_dns's SkeletonHeader in Z64Utils](images/z64utils_dns_skeletonheader.png)
|
||||
|
||||
When you open the Skeleton Viewer, you'll see a list of animations off to the side. Selecting one of them will display an error that says something like `RENDER ERROR AT 0x06001A98! (Could not read 0x80 bytes at address 08000000)`. This is because one of the display lists in the skeleton is expecting something to be set at segment 8. From the actor, we know that it's expecting the eye textures to be loaded into segment 8 like so:
|
||||
```c
|
||||
static TexturePtr D_8092DE1C[] = { &D_060028E8, &D_06002968, &D_060029E8, &D_06002968 };
|
||||
[...]
|
||||
gSPSegment(POLY_OPA_DISP++, 0x08, Lib_SegmentedToVirtual(sEyeTextures[this->eyeIndex]));
|
||||
```
|
||||
|
||||
Let's set segment 8 to be one of the eye textures listed here. Click on "Segments", then for segment 08, click "Edit" and select "Address" as the source. Input "060028E8" as the address, then hit OK. This should result in the window looking as follows:
|
||||
|
||||
![Setting segment 8 to one of the eye textures in Z64Utils](images/z64utils_set_segment_8.png)
|
||||
|
||||
Now that we've gotten around the error, we can see what each limb in the skeleton corresponds to by clicking on it in the Hierarchy. Clicking on any given limb will highlight what part of the model it represents in red. In the below example, this limb is clearly the head:
|
||||
|
||||
![Showing the head limb in Z64Utils](images/z64utils_dns_head_limb.png)
|
||||
|
||||
Note that some limbs don't actually render anything, so sometimes clicking on a limb will not turn anything red; this may indicate a "Root" limb that has no associated display list, or it may indicate something like an eye limb that doesn't have the right textures loaded to display anything in Z64Utils. It may be useful to skip ahead to [Step #5](#step-5-naming-limb-display-lists) to learn how to check if the limb has a display list. If it doesn't have a display list, then it's a "Root" limb that will never be highlighted.
|
||||
|
||||
We can now start naming the skeleton and individual limbs. Since we know this particular skeleton is the King's Chamber Deku Guard, we can name the skeleton `gKingsChamberDekuGuardSkel`. For the LimbNone name, we can call it something like `KINGS_CHAMBER_DEKU_GUARD_LIMB_NONE`, and we can name the LimbMax similarly. For the EnumName, we can name it `KingsChamberDekuGuardLimbs`. For each individual limb, we can name them based on what we see in Z64Utils; just make sure to update both the Name and the EnumName. After naming everything, we have something that looks like this:
|
||||
```xml
|
||||
<Limb Name="gKingsChamberDekuGuardTorsoLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_TORSO" Offset="0x2D18" />
|
||||
<Limb Name="gKingsChamberDekuGuardHeadLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_HEAD" Offset="0x2D24" />
|
||||
<Limb Name="gKingsChamberDekuGuardStalkLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_STALK" Offset="0x2D30" />
|
||||
<Limb Name="gKingsChamberDekuGuardLeftLeafLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_LEFT_LEAF" Offset="0x2D3C" />
|
||||
<Limb Name="gKingsChamberDekuGuardRightLeafLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_RIGHT_LEAF" Offset="0x2D48" />
|
||||
<Limb Name="gKingsChamberDekuGuardCenterLeafLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_CENTER_LEAF" Offset="0x2D54" />
|
||||
<Limb Name="gKingsChamberDekuGuardSnoutLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_SNOUT" Offset="0x2D60" />
|
||||
<Limb Name="gKingsChamberDekuGuardRightMustacheLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_RIGHT_MUSTACHE" Offset="0x2D6C" />
|
||||
<Limb Name="gKingsChamberDekuGuardLeftMustacheLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_LEFT_MUSTACHE" Offset="0x2D78" />
|
||||
<Limb Name="gKingsChamberDekuGuardEyesLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_EYES" Offset="0x2D84" />
|
||||
<Limb Name="gKingsChamberDekuGuardLeftFootLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_LEFT_FOOT" Offset="0x2D90" />
|
||||
<Limb Name="gKingsChamberDekuGuardRightFootLimb" Type="Standard" EnumName="KINGS_CHAMBER_DEKU_GUARD_LIMB_RIGHT_FOOT" Offset="0x2D9C" />
|
||||
<Skeleton Name="gKingsChamberDekuGuardSkel" Type="Normal" LimbType="Standard" LimbNone="KINGS_CHAMBER_DEKU_GUARD_LIMB_NONE" LimbMax="KINGS_CHAMBER_DEKU_GUARD_LIMB_MAX" EnumName="KingsChamberDekuGuardLimbs" Offset="0x2DD8" />
|
||||
```
|
||||
|
||||
Now we can run `./extract_assets.py -s objects/object_dns` to extract the object again, this time with our new names. What can we do with this? Quite a bit actually. In `z_en_dns.h`, we can add this to the top of the file to start using these new names in our code:
|
||||
```c
|
||||
#include "objects/object_dns/object_dns.h"
|
||||
```
|
||||
|
||||
Now, we can redefine the `jointTable` and `morphTable` in terms of the limb enum we defined before, like so:
|
||||
```c
|
||||
/* 0x22A */ Vec3s jointTable[KINGS_CHAMBER_DEKU_GUARD_LIMB_MAX];
|
||||
/* 0x278 */ Vec3s morphTable[KINGS_CHAMBER_DEKU_GUARD_LIMB_MAX];
|
||||
```
|
||||
|
||||
We can also use our new skeleton name and limb enum when initialization the skeleton like so:
|
||||
```c
|
||||
SkelAnime_Init(globalCtx, &this->skelAnime, &gKingsChamberDekuGuardSkel, NULL, this->jointTable, this->morphTable, KINGS_CHAMBER_DEKU_GUARD_LIMB_MAX);
|
||||
```
|
||||
|
||||
Lastly, we can use our limb enum in `EnDns_PostLimbDraw`. Where the code originally had:
|
||||
```c
|
||||
if (limbIndex == 2) {
|
||||
[...]
|
||||
}
|
||||
```
|
||||
|
||||
We can instead write:
|
||||
```c
|
||||
if (limbIndex == KINGS_CHAMBER_DEKU_GUARD_LIMB_HEAD) {
|
||||
[...]
|
||||
}
|
||||
```
|
||||
|
||||
## Step 2: Naming the animations
|
||||
|
||||
Now that we have the skeleton figured out, it's time to name all the animations. In the Skeleton Viewer, you can hit the "play" button on any animation to see what it looks like. Note that some objects have multiple skeletons, and selecting an animation that is associated with a different skeleton than the one you're looking at can cause odd behavior. Try to give each animation a descriptive name based on what it looks like. If you're struggling;
|
||||
- Try viewing the animation in game. In what contexts does this animation play?
|
||||
- Try analyzing the code for the actor to see when the animation is used. Is this animation ever referenced?
|
||||
- If you're still really struggling, Majora's Mask 3D contains the original animation names for the majority of animations in the game. These original names can help you figure out what the developers were originally intending. Explaining how to find these animations in MM3D is outside of the scope of this document, so just ask in Discord if you want to try this.
|
||||
|
||||
After naming the animations, the end result will look something like this:
|
||||
```xml
|
||||
<Animation Name="gKingsChamberDekuGuardDanceAnim" Offset="0x2A8" />
|
||||
<Animation Name="gKingsChamberDekuGuardFlipAnim" Offset="0x734" />
|
||||
<Animation Name="gKingsChamberDekuGuardSurpriseStartAnim" Offset="0x8F4" />
|
||||
<Animation Name="gKingsChamberDekuGuardSurpriseLoopAnim" Offset="0xBD8" />
|
||||
<Animation Name="gKingsChamberDekuGuardRunStartAnim" Offset="0xD58" />
|
||||
<Animation Name="gKingsChamberDekuGuardRunLoopAnim" Offset="0xFEC" />
|
||||
[...]
|
||||
<Animation Name="gKingsChamberDekuGuardIdleAnim" Offset="0x3310" />
|
||||
<Animation Name="gKingsChamberDekuGuardWalkAnim" Offset="0x34EC" />
|
||||
```
|
||||
|
||||
Once again, we can run `./extract_assets.py -s objects/object_dns` to extract the object, and we can update the animation names in `z_en_dns.c` to use our new names like so:
|
||||
```c
|
||||
static AnimationInfoS sAnimations[] = {
|
||||
{ &gKingsChamberDekuGuardIdleAnim, 1.0f, 0, -1, ANIMMODE_LOOP, 0 },
|
||||
{ &gKingsChamberDekuGuardIdleAnim, 1.0f, 0, -1, ANIMMODE_LOOP, -4 },
|
||||
{ &gKingsChamberDekuGuardWalkAnim, 1.0f, 0, -1, ANIMMODE_LOOP, 0 },
|
||||
{ &gKingsChamberDekuGuardWalkAnim, 1.0f, 0, -1, ANIMMODE_LOOP, -4 },
|
||||
{ &gKingsChamberDekuGuardSurpriseStartAnim, 1.0f, 0, -1, ANIMMODE_ONCE, 0 },
|
||||
{ &gKingsChamberDekuGuardSurpriseLoopAnim, 1.0f, 0, -1, ANIMMODE_LOOP, 0 },
|
||||
{ &gKingsChamberDekuGuardRunStartAnim, 1.0f, 0, -1, ANIMMODE_ONCE, 0 },
|
||||
{ &gKingsChamberDekuGuardRunLoopAnim, 1.0f, 0, -1, ANIMMODE_LOOP, 0 },
|
||||
{ &gKingsChamberDekuGuardDanceAnim, 1.0f, 0, -1, ANIMMODE_ONCE, 0 },
|
||||
{ &gKingsChamberDekuGuardFlipAnim, 1.0f, 0, -1, ANIMMODE_ONCE, 0 },
|
||||
};
|
||||
```
|
||||
|
||||
## Step 3: Identifying the blob
|
||||
|
||||
In the XML, you may notice undefined blobs like this:
|
||||
```xml
|
||||
<!-- <Blob Name="object_dns_Blob_0028E8" Size="0x180" Offset="0x28E8" /> -->
|
||||
```
|
||||
|
||||
You might already have an idea as to what this is based on what you've seen before. Recall that the eye textures are referenced in the actor's code like this:
|
||||
```c
|
||||
static TexturePtr D_8092DE1C[] = { &D_060028E8, &D_06002968, &D_060029E8, &D_06002968 };
|
||||
```
|
||||
|
||||
Do you notice how the "28E8" in `D_060028E8` also appears as the Offset in that blob? That's because the blob is just the eye textures; the process for automatically creating the XML wasn't able to figure it out on its own, so we'll need to do it ourselves. But how should we define these textures in the XML? Recall that the eye textures were loaded into segment 8; let's take a look in `object_dns.c` and see if we can find something that uses this segment. This display list has the answer:
|
||||
```c
|
||||
Gfx object_dns_DL_001A50[] = {
|
||||
[...]
|
||||
gsDPLoadTextureBlock(0x08000000, G_IM_FMT_RGBA, G_IM_SIZ_16b, 8, 8, 0, G_TX_NOMIRROR | G_TX_CLAMP, G_TX_NOMIRROR |
|
||||
G_TX_CLAMP, 3, 3, G_TX_NOLOD, G_TX_NOLOD),
|
||||
[...]
|
||||
};
|
||||
```
|
||||
|
||||
Using `0x08000000` with `gsDPLoadTextureBlock` signals that this display list is expecting a texture in segment 8. What kind of texture is it expecting? We can look at the arguments after the `0x08000000`. It's looking for an RBGA16 texture with dimensions of 8x8, so we can define these textures in the XML like so:
|
||||
```xml
|
||||
<Texture Name="object_dns_Tex_0028E8" OutName="tex_0028E8" Format="rgba16" Width="8" Height="8" Offset="0x28E8" />
|
||||
<Texture Name="object_dns_Tex_002968" OutName="tex_002968" Format="rgba16" Width="8" Height="8" Offset="0x2968" />
|
||||
<Texture Name="object_dns_Tex_0029E8" OutName="tex_0029E8" Format="rgba16" Width="8" Height="8" Offset="0x29E8" />
|
||||
```
|
||||
|
||||
Now, we just have to name them. In [Step #1](#step-1-naming-the-skeleton-and-limbs), we set segment 8 to one of the eye textures; we can use that same technique with the other two eye textures to see what they are. Like most NPCs, these various eye textures are used for handling blinking, so we can name them based on how open the eye is:
|
||||
```xml
|
||||
<Texture Name="gKingsChamberDekuGuardEyeOpenTex" OutName="kings_chamber_deku_guard_eye_open" Format="rgba16" Width="8" Height="8" Offset="0x28E8" />
|
||||
<Texture Name="gKingsChamberDekuGuardEyeHalfTex" OutName="kings_chamber_deku_guard_eye_half" Format="rgba16" Width="8" Height="8" Offset="0x2968" />
|
||||
<Texture Name="gKingsChamberDekuGuardEyeClosedTex" OutName="kings_chamber_deku_guard_eye_closed" Format="rgba16" Width="8" Height="8" Offset="0x29E8" />
|
||||
```
|
||||
|
||||
Like with previous steps, we can run `./extract_assets.py -s objects/object_dns` and then update `z_en_dns.c` with our new names:
|
||||
```c
|
||||
static TexturePtr sEyeTextures[] = {
|
||||
gKingsChamberDekuGuardEyeOpenTex,
|
||||
gKingsChamberDekuGuardEyeHalfTex,
|
||||
gKingsChamberDekuGuardEyeClosedTex,
|
||||
gKingsChamberDekuGuardEyeHalfTex,
|
||||
};
|
||||
```
|
||||
|
||||
Note that this step might be tricky to do if multiple things in the actor use the same segment. It's okay to wait to do this until you've named all the display lists in the actor, since that will make it easier to find the display list associated with a given texture.
|
||||
|
||||
## Step #4: Naming anything else in the actor
|
||||
|
||||
For some actors, there may be a few other things left to name that are directly referenced in the actor's code. In our case, there is one display list that we still need to name:
|
||||
```c
|
||||
gSPDisplayList(POLY_OPA_DISP++, &D_06002C48);
|
||||
```
|
||||
|
||||
In Z64Utils, scroll to find this display list, then right-click and select "Open in Dlist Viewer":
|
||||
|
||||
![Opening dlist_00002C48 in Z64Utils](images/z64utils_dns_display_list.png)
|
||||
|
||||
We can see this is the guard's Deku Flower:
|
||||
|
||||
![Showing the guard's Deku Flower in Z64Utils](images/z64utils_dns_deku_flower.png)
|
||||
|
||||
We can name the display list as such in the XML:
|
||||
```xml
|
||||
<DList Name="gKingsChamberDekuGuardDekuFlower" Offset="0x2C48" />
|
||||
```
|
||||
|
||||
Then, like all steps before, we can run `./extract_assets.py -s objects/object_dns` and then update `z_en_dns.c` with our new name:
|
||||
```c
|
||||
gSPDisplayList(POLY_OPA_DISP++, gKingsChamberDekuGuardDekuFlower);
|
||||
```
|
||||
|
||||
## Step #5: Naming limb display lists
|
||||
|
||||
Now that we've named everything that's used externally by the actor, we just need to clean up the rest of the unnamed stuff in the XML. Let's start by naming the display lists for each limb. There are two ways you can determine which display list is associated with a given limb. One way is to click on the limb in Z64Utils; it will tell you what display list is used for that limb:
|
||||
|
||||
![Showing the head limb's display list in Z64Utils](images/z64utils_dns_limb_dlist.png)
|
||||
|
||||
Another way is to simply check `object_dns.c`. Each limb lists its own display list like this:
|
||||
```c
|
||||
StandardLimb gKingsChamberDekuGuardHeadLimb = {
|
||||
{ 0, 1300, 0 }, KINGS_CHAMBER_DEKU_GUARD_LIMB_STALK - 1, KINGS_CHAMBER_DEKU_GUARD_LIMB_LEFT_FOOT - 1,
|
||||
object_dns_DL_0018B8
|
||||
};
|
||||
```
|
||||
|
||||
Either way you go about it, you should be able to name all the limb display lists like so:
|
||||
```xml
|
||||
<DList Name="gKingsChamberDekuGuardRightFootDL" Offset="0x1640" />
|
||||
<DList Name="gKingsChamberDekuGuardLeftFootDL" Offset="0x16F0" />
|
||||
<DList Name="gKingsChamberDekuGuardSnoutDL" Offset="0x17A0" />
|
||||
<DList Name="gKingsChamberDekuGuardHeadDL" Offset="0x18B8" />
|
||||
<DList Name="gKingsChamberDekuGuardStalkDL" Offset="0x19B8" />
|
||||
<DList Name="gKingsChamberDekuGuardEyesDL" Offset="0x1A50" />
|
||||
<DList Name="gKingsChamberDekuGuardTorsoDL" Offset="0x1AD8" />
|
||||
<DList Name="gKingsChamberDekuGuardLeftMustacheDL" Offset="0x1B90" />
|
||||
<DList Name="gKingsChamberDekuGuardRightMustacheDL" Offset="0x1C20" />
|
||||
<DList Name="gKingsChamberDekuGuardLeftLeafDL" Offset="0x1CB0" />
|
||||
<DList Name="gKingsChamberDekuGuardCenterLeafDL" Offset="0x1D40" />
|
||||
<DList Name="gKingsChamberDekuGuardRightLeafDL" Offset="0x1DD8" />
|
||||
```
|
||||
|
||||
Run `./extract_assets.py -s objects/object_dns` once again, since it will help in the next step to have all of our display lists named.
|
||||
|
||||
### Step #6: Naming remaining textures
|
||||
|
||||
With every display list named, it's now a lot easier to name the remaining textures. In the `assets/objects/object_dns/` folder, you can see all the textures in the object as various PNG files. For some of the textures, just looking at them will give you a good idea as to what they should be named. For other textures, it may help to see how the texture is used in the object's display lists. Let's take a look at `object_dns_Tex_002868`, which is only used in one display list:
|
||||
```c
|
||||
Gfx gKingsChamberDekuGuardSnoutDL[] = {
|
||||
[...]
|
||||
gsDPLoadTextureBlock(object_dns_Tex_002868, G_IM_FMT_RGBA, G_IM_SIZ_16b, 8, 8, 0, G_TX_MIRROR | G_TX_CLAMP,
|
||||
G_TX_MIRROR | G_TX_CLAMP, 3, 3, G_TX_NOLOD, G_TX_NOLOD),
|
||||
[...]
|
||||
};
|
||||
```
|
||||
|
||||
Since `tex_002868.rgba16.png` looks like the inside of the Deku Guard's mouth, and it's *only* used in the snout display list, you can probably name this something like `gKingsChamberDekuGuardMouthTex`. But rather than guessing, we can confirm it by editing the texture ourselves and then viewing it in-game. Using an image editor, let's change `tex_002868.rgba16.png` to this:
|
||||
|
||||
![Our custom mouth texture](images/dns_custom_texture.png)
|
||||
|
||||
Now, rebuild the game using [the steps described here](object_decomp.md#building-and-investigative-modding) and look at the guard in-game. You should see something like this:
|
||||
|
||||
![Our custom mouth texture being shown in-game](images/custom_texture_in_game.png)
|
||||
|
||||
This confirms our suspicion that this is indeed the mouth texture, so we can name it as such. We can use similar strategies to name all the other textures like so:
|
||||
```xml
|
||||
<Texture Name="gKingsChamberDekuGuardLeafTex" OutName="kings_chamber_deku_guard_leaf" Format="rgba16" Width="32" Height="32" Offset="0x1E68" />
|
||||
<Texture Name="gKingsChamberDekuGuardBodyTex" OutName="kings_chamber_deku_guard_body" Format="rgba16" Width="16" Height="16" Offset="0x2668" />
|
||||
<Texture Name="gKingsChamberDekuGuardMouthTex" OutName="kings_chamber_deku_guard_mouth" Format="rgba16" Width="8" Height="8" Offset="0x2868" />
|
||||
```
|
||||
|
||||
### Step #7: Finishing up
|
||||
|
||||
If you have any other unnamed assets, now's the time to identify them. Otherwise, finish up the file by putting a comment at the top above the `<File>` node:
|
||||
```xml
|
||||
<Root>
|
||||
<!-- Assets for the King's Chamber Deku Guards -->
|
||||
<File Name="object_dns" Segment="6">
|
||||
```
|
||||
|
||||
And we're done! Hopefully, you found this example helpful when decompiling your own objects.
|
@ -1,7 +1,7 @@
|
||||
# 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)
|
||||
- Up: [Contents](contents.md)
|
||||
- Previous: [Beginning decompilation: the Init function and the Actor struct](beginning_decomp.md)
|
||||
|
||||
## Now what?
|
||||
|
||||
|
@ -5,9 +5,8 @@
|
||||
*/
|
||||
|
||||
#include "z_en_dns.h"
|
||||
#include "objects/object_dns/object_dns.h"
|
||||
|
||||
#define FLAGS 0x00000019
|
||||
#define FLAGS (ACTOR_FLAG_1 | ACTOR_FLAG_8 | ACTOR_FLAG_10)
|
||||
|
||||
#define THIS ((EnDns*)thisx)
|
||||
|
||||
@ -20,6 +19,20 @@ void func_8092D330(EnDns* this, GlobalContext* globalCtx);
|
||||
void EnDns_DoNothing(EnDns* this, GlobalContext* globalCtx);
|
||||
void func_8092D4D8(EnDns* this, GlobalContext* globalCtx);
|
||||
|
||||
typedef enum {
|
||||
/* 0 */ EN_DNS_ANIMATION_IDLE_1,
|
||||
/* 1 */ EN_DNS_ANIMATION_IDLE_2,
|
||||
/* 2 */ EN_DNS_ANIMATION_WALK_1,
|
||||
/* 3 */ EN_DNS_ANIMATION_WALK_2,
|
||||
/* 4 */ EN_DNS_ANIMATION_SURPRISE_START,
|
||||
/* 5 */ EN_DNS_ANIMATION_SURPRISE_LOOP,
|
||||
/* 6 */ EN_DNS_ANIMATION_RUN_START,
|
||||
/* 7 */ EN_DNS_ANIMATION_RUN_LOOP,
|
||||
/* 8 */ EN_DNS_ANIMATION_DANCE,
|
||||
/* 9 */ EN_DNS_ANIMATION_FLIP,
|
||||
/* 10 */ EN_DNS_ANIMATION_MAX,
|
||||
} EnDnsAnimationIndex;
|
||||
|
||||
static s32 D_8092DCB0[] = {
|
||||
0x00172000, 0x050E082F, 0x0C100E08, 0x200C1000, 0x00172000, 0x050E0830, 0x0C100E08, 0x210C1000,
|
||||
0x00172000, 0x050E0831, 0x0C100E08, 0x220C1000, 0x00172000, 0x050E0832, 0x0C100E08, 0x230C1000,
|
||||
@ -61,22 +74,23 @@ static ColliderCylinderInit sCylinderInit = {
|
||||
static CollisionCheckInfoInit2 sColChkInfoInit = { 1, 0, 0, 0, MASS_IMMOVABLE };
|
||||
|
||||
static AnimationInfoS sAnimations[] = {
|
||||
{ &object_dns_Anim_003310, 1.0f, 0, -1, ANIMMODE_LOOP, 0 },
|
||||
{ &object_dns_Anim_003310, 1.0f, 0, -1, ANIMMODE_LOOP, -4 },
|
||||
{ &object_dns_Anim_0034EC, 1.0f, 0, -1, ANIMMODE_LOOP, 0 },
|
||||
{ &object_dns_Anim_0034EC, 1.0f, 0, -1, ANIMMODE_LOOP, -4 },
|
||||
{ &object_dns_Anim_0008F4, 1.0f, 0, -1, ANIMMODE_ONCE, 0 },
|
||||
{ &object_dns_Anim_000BD8, 1.0f, 0, -1, ANIMMODE_LOOP, 0 },
|
||||
{ &object_dns_Anim_000D58, 1.0f, 0, -1, ANIMMODE_ONCE, 0 },
|
||||
{ &object_dns_Anim_000FEC, 1.0f, 0, -1, ANIMMODE_LOOP, 0 },
|
||||
{ &object_dns_Anim_0002A8, 1.0f, 0, -1, ANIMMODE_ONCE, 0 },
|
||||
{ &object_dns_Anim_000734, 1.0f, 0, -1, ANIMMODE_ONCE, 0 },
|
||||
{ &gKingsChamberDekuGuardIdleAnim, 1.0f, 0, -1, ANIMMODE_LOOP, 0 },
|
||||
{ &gKingsChamberDekuGuardIdleAnim, 1.0f, 0, -1, ANIMMODE_LOOP, -4 },
|
||||
{ &gKingsChamberDekuGuardWalkAnim, 1.0f, 0, -1, ANIMMODE_LOOP, 0 },
|
||||
{ &gKingsChamberDekuGuardWalkAnim, 1.0f, 0, -1, ANIMMODE_LOOP, -4 },
|
||||
{ &gKingsChamberDekuGuardSurpriseStartAnim, 1.0f, 0, -1, ANIMMODE_ONCE, 0 },
|
||||
{ &gKingsChamberDekuGuardSurpriseLoopAnim, 1.0f, 0, -1, ANIMMODE_LOOP, 0 },
|
||||
{ &gKingsChamberDekuGuardRunStartAnim, 1.0f, 0, -1, ANIMMODE_ONCE, 0 },
|
||||
{ &gKingsChamberDekuGuardRunLoopAnim, 1.0f, 0, -1, ANIMMODE_LOOP, 0 },
|
||||
{ &gKingsChamberDekuGuardDanceAnim, 1.0f, 0, -1, ANIMMODE_ONCE, 0 },
|
||||
{ &gKingsChamberDekuGuardFlipAnim, 1.0f, 0, -1, ANIMMODE_ONCE, 0 },
|
||||
};
|
||||
|
||||
void func_8092C5C0(EnDns* this) {
|
||||
s32 pad;
|
||||
|
||||
if (((this->unk_2F8 == 2) || (this->unk_2F8 == 3) || (this->unk_2F8 == 6) || (this->unk_2F8 == 7)) &&
|
||||
if (((this->animationIndex == EN_DNS_ANIMATION_WALK_1) || (this->animationIndex == EN_DNS_ANIMATION_WALK_2) ||
|
||||
(this->animationIndex == EN_DNS_ANIMATION_RUN_START) || (this->animationIndex == EN_DNS_ANIMATION_RUN_LOOP)) &&
|
||||
(Animation_OnFrame(&this->skelAnime, 0.0f) || Animation_OnFrame(&this->skelAnime, 3.0f))) {
|
||||
Actor_PlaySfxAtPos(&this->actor, NA_SE_EN_NUTS_WALK);
|
||||
}
|
||||
@ -87,28 +101,30 @@ s32 func_8092C63C(EnDns* this, s32 arg1) {
|
||||
s32 ret = false;
|
||||
|
||||
switch (arg1) {
|
||||
case 0:
|
||||
case 1:
|
||||
if ((this->unk_2F8 != 0) && (this->unk_2F8 != 1)) {
|
||||
case EN_DNS_ANIMATION_IDLE_1:
|
||||
case EN_DNS_ANIMATION_IDLE_2:
|
||||
if ((this->animationIndex != EN_DNS_ANIMATION_IDLE_1) &&
|
||||
(this->animationIndex != EN_DNS_ANIMATION_IDLE_2)) {
|
||||
phi_v1 = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
case 3:
|
||||
if ((this->unk_2F8 != 2) && (this->unk_2F8 != 3)) {
|
||||
case EN_DNS_ANIMATION_WALK_1:
|
||||
case EN_DNS_ANIMATION_WALK_2:
|
||||
if ((this->animationIndex != EN_DNS_ANIMATION_WALK_1) &&
|
||||
(this->animationIndex != EN_DNS_ANIMATION_WALK_2)) {
|
||||
phi_v1 = true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (this->unk_2F8 != arg1) {
|
||||
if (this->animationIndex != arg1) {
|
||||
phi_v1 = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (phi_v1) {
|
||||
this->unk_2F8 = arg1;
|
||||
this->animationIndex = arg1;
|
||||
ret = SubS_ChangeAnimationByInfoS(&this->skelAnime, sAnimations, arg1);
|
||||
}
|
||||
|
||||
@ -154,11 +170,11 @@ void func_8092C86C(EnDns* this, GlobalContext* globalCtx) {
|
||||
}
|
||||
|
||||
void func_8092C934(EnDns* this) {
|
||||
if ((this->unk_2C6 & 0x40) && (DECR(this->unk_2DE) == 0)) {
|
||||
this->unk_2E0++;
|
||||
if (this->unk_2E0 >= 4) {
|
||||
this->unk_2DE = Rand_S16Offset(30, 30);
|
||||
this->unk_2E0 = 0;
|
||||
if ((this->unk_2C6 & 0x40) && (DECR(this->blinkTimer) == 0)) {
|
||||
this->eyeIndex++;
|
||||
if (this->eyeIndex >= 4) {
|
||||
this->blinkTimer = Rand_S16Offset(30, 30);
|
||||
this->eyeIndex = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -220,7 +236,7 @@ s32 func_8092CAD0(EnDns* this, GlobalContext* globalCtx) {
|
||||
this->unk_2F0 = 0.0f;
|
||||
if (this->unk_2D2 != 0) {
|
||||
this->unk_2F0 = this->skelAnime.curFrame;
|
||||
func_8092C63C(this, 2);
|
||||
func_8092C63C(this, EN_DNS_ANIMATION_WALK_1);
|
||||
}
|
||||
this->unk_2DA = this->actor.world.rot.y;
|
||||
}
|
||||
@ -238,14 +254,14 @@ s32 func_8092CB98(EnDns* this, GlobalContext* globalCtx) {
|
||||
if (globalCtx->csCtx.state != 0) {
|
||||
if (!(this->unk_2C6 & 0x80)) {
|
||||
this->unk_2C8 = func_8092CA74(this);
|
||||
this->actor.flags &= ~0x1;
|
||||
this->actor.flags &= ~ACTOR_FLAG_1;
|
||||
func_8013AED4(&this->unk_2C6, 0, 7);
|
||||
this->unk_2C6 |= 0x80;
|
||||
this->unk_1D8 = 0xFF;
|
||||
}
|
||||
phi_v1 = 1;
|
||||
} else if (this->unk_2C6 & 0x80) {
|
||||
this->actor.flags |= 1;
|
||||
this->actor.flags |= ACTOR_FLAG_1;
|
||||
func_8013AED4(&this->unk_2C6, 3, 7);
|
||||
this->unk_2C6 &= ~0x80;
|
||||
}
|
||||
@ -290,7 +306,7 @@ s32 func_8092CCEC(EnDns* this, GlobalContext* globalCtx) {
|
||||
}
|
||||
|
||||
s32 func_8092CE38(EnDns* this) {
|
||||
static s32 D_8092DE00[] = { 8, 8, 9 };
|
||||
static s32 D_8092DE00[] = { EN_DNS_ANIMATION_DANCE, EN_DNS_ANIMATION_DANCE, EN_DNS_ANIMATION_FLIP };
|
||||
s16 frame;
|
||||
s32 pad;
|
||||
Vec3f sp2C;
|
||||
@ -369,7 +385,7 @@ void func_8092D108(EnDns* this, GlobalContext* globalCtx) {
|
||||
Matrix_Scale(this->actor.scale.x, this->actor.scale.y, this->actor.scale.z, MTXMODE_APPLY);
|
||||
|
||||
gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(globalCtx->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
|
||||
gSPDisplayList(POLY_OPA_DISP++, object_dns_DL_002C48);
|
||||
gSPDisplayList(POLY_OPA_DISP++, gKingsChamberDekuGuardDekuFlower);
|
||||
|
||||
CLOSE_DISPS(globalCtx->state.gfxCtx);
|
||||
}
|
||||
@ -390,11 +406,11 @@ void func_8092D1B8(EnDns* this, GlobalContext* globalCtx) {
|
||||
play_sound(NA_SE_SY_FOUND);
|
||||
gSaveContext.eventInf[1] |= 0x20;
|
||||
this->unk_2F4 = func_8092CCEC;
|
||||
func_8092C63C(this, 2);
|
||||
func_8092C63C(this, EN_DNS_ANIMATION_WALK_1);
|
||||
this->actionFunc = EnDns_DoNothing;
|
||||
} else if (gSaveContext.eventInf[1] & 0x40) {
|
||||
func_8092CCEC(this, globalCtx);
|
||||
func_8092C63C(this, 2);
|
||||
func_8092C63C(this, EN_DNS_ANIMATION_WALK_1);
|
||||
this->actionFunc = func_8092D330;
|
||||
}
|
||||
Math_ApproachS(&this->actor.shape.rot.y, sp22, 3, 0x2AA8);
|
||||
@ -436,7 +452,7 @@ void func_8092D4D8(EnDns* this, GlobalContext* globalCtx) {
|
||||
|
||||
if (ENDNS_GET_4000(&this->actor) && (this->unk_2D2 == 0)) {
|
||||
if (func_8092CE38(this)) {
|
||||
func_8092C63C(this, 2);
|
||||
func_8092C63C(this, EN_DNS_ANIMATION_WALK_1);
|
||||
}
|
||||
} else if (func_8010BF58(&this->actor, globalCtx, this->unk_1E0, this->unk_2F4, &this->unk_1DC)) {
|
||||
func_8013AED4(&this->unk_2C6, 3, 7);
|
||||
@ -445,7 +461,7 @@ void func_8092D4D8(EnDns* this, GlobalContext* globalCtx) {
|
||||
if (!(gSaveContext.eventInf[1] & 0x20)) {
|
||||
this->skelAnime.curFrame = this->unk_2F0;
|
||||
this->actor.world.rot.y = this->unk_2DA;
|
||||
func_8092C63C(this, 8);
|
||||
func_8092C63C(this, EN_DNS_ANIMATION_DANCE);
|
||||
}
|
||||
this->unk_2CC = 0;
|
||||
this->unk_2CE = 0;
|
||||
@ -458,7 +474,12 @@ void func_8092D4D8(EnDns* this, GlobalContext* globalCtx) {
|
||||
}
|
||||
|
||||
void func_8092D5E8(EnDns* this, GlobalContext* globalCtx) {
|
||||
static s32 D_8092DE0C[] = { 0, 0, 4, 6 };
|
||||
static s32 D_8092DE0C[] = {
|
||||
EN_DNS_ANIMATION_IDLE_1,
|
||||
EN_DNS_ANIMATION_IDLE_1,
|
||||
EN_DNS_ANIMATION_SURPRISE_START,
|
||||
EN_DNS_ANIMATION_RUN_START,
|
||||
};
|
||||
u32 temp_v0;
|
||||
u32 temp_v1;
|
||||
|
||||
@ -470,9 +491,10 @@ void func_8092D5E8(EnDns* this, GlobalContext* globalCtx) {
|
||||
this->unk_1D8 = temp_v1;
|
||||
}
|
||||
|
||||
if (((this->unk_2F8 == 4) || (this->unk_2F8 == 6)) &&
|
||||
if (((this->animationIndex == EN_DNS_ANIMATION_SURPRISE_START) ||
|
||||
(this->animationIndex == EN_DNS_ANIMATION_RUN_START)) &&
|
||||
Animation_OnFrame(&this->skelAnime, this->skelAnime.endFrame)) {
|
||||
func_8092C63C(this, this->unk_2F8 + 1);
|
||||
func_8092C63C(this, this->animationIndex + 1);
|
||||
}
|
||||
|
||||
func_800EDF24(&this->actor, globalCtx, temp_v0);
|
||||
@ -488,9 +510,10 @@ void EnDns_Init(Actor* thisx, GlobalContext* globalCtx) {
|
||||
}
|
||||
|
||||
ActorShape_Init(&this->actor.shape, 0.0f, NULL, 18.0f);
|
||||
SkelAnime_Init(globalCtx, &this->skelAnime, &object_dns_Skel_002DD8, NULL, this->jointTable, this->morphTable, 13);
|
||||
this->unk_2F8 = -1;
|
||||
func_8092C63C(this, 2);
|
||||
SkelAnime_Init(globalCtx, &this->skelAnime, &gKingsChamberDekuGuardSkel, NULL, this->jointTable, this->morphTable,
|
||||
KINGS_CHAMBER_DEKU_GUARD_LIMB_MAX);
|
||||
this->animationIndex = -1;
|
||||
func_8092C63C(this, EN_DNS_ANIMATION_WALK_1);
|
||||
Collider_InitAndSetCylinder(globalCtx, &this->collider, &this->actor, &sCylinderInit);
|
||||
CollisionCheck_SetInfo2(&this->actor.colChkInfo, DamageTable_Get(0x16), &sColChkInfoInit);
|
||||
Actor_SetScale(&this->actor, 0.01f);
|
||||
@ -590,7 +613,7 @@ void EnDns_PostLimbDraw(GlobalContext* globalCtx, s32 limbIndex, Gfx** dList, Ve
|
||||
}
|
||||
}
|
||||
|
||||
if (limbIndex == 2) {
|
||||
if (limbIndex == KINGS_CHAMBER_DEKU_GUARD_LIMB_HEAD) {
|
||||
func_8092D954(this->unk_2CC, this->unk_2CE + this->actor.shape.rot.y, &this->unk_218, &this->unk_224, phi_v1,
|
||||
phi_v0);
|
||||
Matrix_InsertTranslation(this->unk_218.x, this->unk_218.y, this->unk_218.z, MTXMODE_NEW);
|
||||
@ -609,15 +632,19 @@ void EnDns_PostLimbDraw(GlobalContext* globalCtx, s32 limbIndex, Gfx** dList, Ve
|
||||
}
|
||||
|
||||
void EnDns_Draw(Actor* thisx, GlobalContext* globalCtx) {
|
||||
static TexturePtr D_8092DE1C[] = { object_dns_Tex_0028E8, object_dns_Tex_002968, object_dns_Tex_0029E8,
|
||||
object_dns_Tex_002968 };
|
||||
static TexturePtr sEyeTextures[] = {
|
||||
gKingsChamberDekuGuardEyeOpenTex,
|
||||
gKingsChamberDekuGuardEyeHalfTex,
|
||||
gKingsChamberDekuGuardEyeClosedTex,
|
||||
gKingsChamberDekuGuardEyeHalfTex,
|
||||
};
|
||||
EnDns* this = THIS;
|
||||
|
||||
OPEN_DISPS(globalCtx->state.gfxCtx);
|
||||
|
||||
func_8012C28C(globalCtx->state.gfxCtx);
|
||||
|
||||
gSPSegment(POLY_OPA_DISP++, 0x08, Lib_SegmentedToVirtual(D_8092DE1C[this->unk_2E0]));
|
||||
gSPSegment(POLY_OPA_DISP++, 0x08, Lib_SegmentedToVirtual(sEyeTextures[this->eyeIndex]));
|
||||
gDPPipeSync(POLY_OPA_DISP++);
|
||||
|
||||
SkelAnime_DrawOpa(globalCtx, this->skelAnime.skeleton, this->skelAnime.jointTable, EnDns_OverrideLimbDraw,
|
||||
|
@ -2,6 +2,7 @@
|
||||
#define Z_EN_DNS_H
|
||||
|
||||
#include "global.h"
|
||||
#include "objects/object_dns/object_dns.h"
|
||||
|
||||
struct EnDns;
|
||||
|
||||
@ -31,8 +32,8 @@ typedef struct EnDns {
|
||||
/* 0x1E4 */ Gfx* unk_1E4[13];
|
||||
/* 0x218 */ Vec3f unk_218;
|
||||
/* 0x224 */ Vec3s unk_224;
|
||||
/* 0x22A */ Vec3s jointTable[13];
|
||||
/* 0x278 */ Vec3s morphTable[13];
|
||||
/* 0x22A */ Vec3s jointTable[KINGS_CHAMBER_DEKU_GUARD_LIMB_MAX];
|
||||
/* 0x278 */ Vec3s morphTable[KINGS_CHAMBER_DEKU_GUARD_LIMB_MAX];
|
||||
/* 0x2C6 */ u16 unk_2C6;
|
||||
/* 0x2C8 */ u16 unk_2C8;
|
||||
/* 0x2CA */ UNK_TYPE1 unk_2CA[0x2];
|
||||
@ -45,14 +46,14 @@ typedef struct EnDns {
|
||||
/* 0x2D8 */ s16 unk_2D8;
|
||||
/* 0x2DA */ s16 unk_2DA;
|
||||
/* 0x2DC */ s16 unk_2DC;
|
||||
/* 0x2DE */ s16 unk_2DE;
|
||||
/* 0x2E0 */ s16 unk_2E0;
|
||||
/* 0x2DE */ s16 blinkTimer;
|
||||
/* 0x2E0 */ s16 eyeIndex;
|
||||
/* 0x2E4 */ f32 unk_2E4;
|
||||
/* 0x2E8 */ UNK_TYPE1 unk_2E8[0x4];
|
||||
/* 0x2EC */ f32 unk_2EC;
|
||||
/* 0x2F0 */ f32 unk_2F0;
|
||||
/* 0x2F4 */ EnDnsFunc unk_2F4;
|
||||
/* 0x2F8 */ s32 unk_2F8;
|
||||
/* 0x2F8 */ s32 animationIndex;
|
||||
/* 0x2FC */ s32 unk_2FC;
|
||||
} EnDns; // size = 0x300
|
||||
|
||||
|