1
0
mirror of https://github.com/zeldaret/oot.git synced 2024-09-21 12:54:51 +00:00
oot/docs/tutorial/draw_functions.md
fig02 2e6279bc8e
PlayState Rename (#1231)
* global context -> play

* fix PlayState* PlayState
2022-05-21 14:23:43 -04:00

20 KiB

Draw functions

Up: Contents Previous: The rest of the functions in the actor

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

EnJj_Draw(Actor* thisx, PlayState* play);

As in Init, Destroy and Update, it is much more convenient to feed mips2c the fake prototype

EnJj_Draw(EnJj* this, PlayState* play);

so that it fills out the struct fields from the actuar actor; we then put a

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:

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);
    func_800943C8(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

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.

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:

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

SEGMENTED_TO_VIRTUAL(D_80A88CFC[this->unk_30E]);

and the whole macro is

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

Graph_OpenDisps(&sp3C, temp_a1, (const char *) "../z_en_jj.c", 0x36F);

by

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

void EnJj_Draw(Actor *thisx, PlayState *play) {
    EnJj *this = THIS;

    OPEN_DISPS(play->state.gfxCtx, "../z_en_jj.c", 879);
    func_800943C8(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

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);
    func_80093D18(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:

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

$ 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

gSPSegment(POLY_OPA_DISP++, 0x08, SEGMENTED_TO_VIRTUAL(D_809F5EA0[this->unk_268]));

Next,

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,

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

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

temp_v1 = D_809F5E4C[this->unk_26A - 1];

and then

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,

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

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

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:

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:

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,

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:

gSPDisplayList(POLY_OPA_DISP++, D_06001B00);

Putting this all together

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);
    func_80093D18(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

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

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

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

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

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.