SpriteOAM memes

This commit is contained in:
Kermalis
2024-12-04 02:47:10 -05:00
parent 0d8af0446c
commit d3652cf59b
20 changed files with 352 additions and 46 deletions

View File

@@ -1,7 +1,7 @@
#ifndef GUARD_MENU_H
#define GUARD_MENU_H
#include "sprite_oam.h"
#include "structs/sprite_oam.h"
#include "structs/str_text.h"
#include "structs/str_position.h"

View File

@@ -0,0 +1,117 @@
#ifndef GUARD_SPRITE_OAM_H
#define GUARD_SPRITE_OAM_H
// size: 0xC
// This is probably used for blue/gold rescue team to convert from GBA OAM to their platform's OAM in AddSprite()
typedef struct unkStruct_2039DB0
{
u16 unk0; // SpriteOAM attrib1 mask
s16 unk2; // SpriteOAM attrib2 mask
u16 unk4; // SpriteOAM attrib3 mask
u16 unk6; // SpriteOAM attrib1 value
u16 unk8; // SpriteOAM attrib2 value
u16 unkA; // SpriteOAM attrib3 value
} unkStruct_2039DB0;
// size: 0x8. Similar to struct OamData but unk6 is not copied to OAM
typedef struct SpriteOAM
{
// y:8 affineMode1:1 affineMode2:1 objMode:2 mosaic:1 bpp:1 shape:2
/* 0x0 */ u16 attrib1;
// x:9 matrixNum:5 size:2
/* 0x2 */ u16 attrib2;
// tileNum:10 priority:2 paletteNum:4
/* 0x4 */ u16 attrib3;
// unk6_0:1 unk6_1:1 unk6_2:1 unk6_3:1 unk6_4:12
u16 unk6;
} SpriteOAM;
// The SpriteOAM struct is handled with macros most likely.
// Most funcs that deal with it look the same and do some weird bit manipulations one by one on the attributes.
// It seems like there is a macro to initialize the structure to some default values...
// attrib1
#define SPRITEOAM_MAX_Y 0xFF
#define SPRITEOAM_SHIFT_Y 0
#define SPRITEOAM_MASK_Y (SPRITEOAM_MAX_Y << SPRITEOAM_SHIFT_Y) // ~ 0xFF00
#define SPRITEOAM_MAX_AFFINEMODE1 1
#define SPRITEOAM_SHIFT_AFFINEMODE1 8
#define SPRITEOAM_MASK_AFFINEMODE1 (SPRITEOAM_MAX_AFFINEMODE1 << SPRITEOAM_SHIFT_AFFINEMODE1) // ~ 0xFEFF
#define SPRITEOAM_MAX_AFFINEMODE2 1
#define SPRITEOAM_SHIFT_AFFINEMODE2 9
#define SPRITEOAM_MASK_AFFINEMODE2 (SPRITEOAM_MAX_AFFINEMODE2 << SPRITEOAM_SHIFT_AFFINEMODE2) // ~ 0xFDFF
#define SPRITEOAM_MAX_OBJMODE 3
#define SPRITEOAM_SHIFT_OBJMODE 10
#define SPRITEOAM_MASK_OBJMODE (SPRITEOAM_MAX_OBJMODE << SPRITEOAM_SHIFT_OBJMODE) // ~ 0xF3FF
#define SPRITEOAM_MAX_MOSAIC 1
#define SPRITEOAM_SHIFT_MOSAIC 12
#define SPRITEOAM_MASK_MOSAIC (SPRITEOAM_MAX_MOSAIC << SPRITEOAM_SHIFT_MOSAIC) // ~ 0xEFFF
#define SPRITEOAM_MAX_BPP 1
#define SPRITEOAM_SHIFT_BPP 13
#define SPRITEOAM_MASK_BPP (SPRITEOAM_MAX_BPP << SPRITEOAM_SHIFT_BPP) // ~ 0xDFFF
#define SPRITEOAM_MAX_SHAPE 3
#define SPRITEOAM_SHIFT_SHAPE 14
#define SPRITEOAM_MASK_SHAPE (SPRITEOAM_MAX_SHAPE << SPRITEOAM_SHIFT_SHAPE) // ~ 0x3FFF
// attrib2
#define SPRITEOAM_MAX_X 0x1FF
#define SPRITEOAM_SHIFT_X 0
#define SPRITEOAM_MASK_X (SPRITEOAM_MAX_X << SPRITEOAM_SHIFT_X) // ~ 0xFE00
#define SPRITEOAM_MAX_MATRIXNUM 31
#define SPRITEOAM_SHIFT_MATRIXNUM 9
#define SPRITEOAM_MASK_MATRIXNUM (SPRITEOAM_MAX_MATRIXNUM << SPRITEOAM_SHIFT_MATRIXNUM) // ~ 0xC1FF
#define SPRITEOAM_MAX_SIZE 3
#define SPRITEOAM_SHIFT_SIZE 14
#define SPRITEOAM_MASK_SIZE (SPRITEOAM_MAX_SIZE << SPRITEOAM_SHIFT_SIZE) // ~ 0x3FFF
// attrib3
#define SPRITEOAM_MAX_TILENUM 0x3FF
#define SPRITEOAM_SHIFT_TILENUM 0
#define SPRITEOAM_MASK_TILENUM (SPRITEOAM_MAX_TILENUM << SPRITEOAM_SHIFT_TILENUM) // ~ 0xFC00
#define SPRITEOAM_MAX_PRIORITY 3
#define SPRITEOAM_SHIFT_PRIORITY 10
#define SPRITEOAM_MASK_PRIORITY (SPRITEOAM_MAX_PRIORITY << SPRITEOAM_SHIFT_PRIORITY) // ~ 0xF3FF
#define SPRITEOAM_MAX_PALETTENUM 15
#define SPRITEOAM_SHIFT_PALETTENUM 12
#define SPRITEOAM_MASK_PALETTENUM (SPRITEOAM_MAX_PALETTENUM << SPRITEOAM_SHIFT_PALETTENUM) // ~ 0xFFF
// unk6
#define SPRITEOAM_MAX_UNK6_0 1
#define SPRITEOAM_SHIFT_UNK6_0 0
#define SPRITEOAM_MASK_UNK6_0 (SPRITEOAM_MAX_UNK6_0 << SPRITEOAM_SHIFT_UNK6_0) // ~ 0xFFFE
#define SPRITEOAM_MAX_UNK6_1 1
#define SPRITEOAM_SHIFT_UNK6_1 1
#define SPRITEOAM_MASK_UNK6_1 (SPRITEOAM_MAX_UNK6_1 << SPRITEOAM_SHIFT_UNK6_1) // ~ 0xFFFD
#define SPRITEOAM_MAX_UNK6_2 1
#define SPRITEOAM_SHIFT_UNK6_2 2
#define SPRITEOAM_MASK_UNK6_2 (SPRITEOAM_MAX_UNK6_2 << SPRITEOAM_SHIFT_UNK6_2) // ~ 0xFFFB
/* Not seen yet
#define SPRITEOAM_MAX_UNK6_3 1
#define SPRITEOAM_SHIFT_UNK6_3 3
#define SPRITEOAM_MASK_UNK6_3 (SPRITEOAM_MAX_UNK6_3 << SPRITEOAM_SHIFT_UNK6_3) // ~ 0xFFF7
*/
// Seems to be the "working" Y coord. Gets copied to attrib1's Y coord in `AddSprite()`.
// kermalis is too lazy to rename it atm since we're still figuring things out
#define SPRITEOAM_MAX_UNK6_4 0xFFF
#define SPRITEOAM_SHIFT_UNK6_4 4
#define SPRITEOAM_MASK_UNK6_4 (SPRITEOAM_MAX_UNK6_4 << SPRITEOAM_SHIFT_UNK6_4) // ~ 0xF
#endif // GUARD_SPRITE_OAM_H

View File

@@ -0,0 +1,293 @@
#ifndef GUARD_SPRITE_OAM_TEMP_H
#define GUARD_SPRITE_OAM_TEMP_H
#include "sprite_oam.h"
// This file is here for temporary decomp notes about SpriteOAM. It can be deleted later when we figure it out.
// SpriteOAM initialization is handled by inlines. Potentially has inlines for other scenarios [for example: AddSprite()]
// There is lots of testing done and evidence that it most likely is not handled by macros.
// Some funcs (such as sub_8039174) can be fakematched with "{} while(0);" contexts and a temp var. Note that it's not a "do {} while(0)" but just a "{} while(0)".
// However, that does not work for most funcs, and does not produce some of the register preloading we get with inlines.
// Also, the inlines I (kermalis) have tested with DO produce the behavior we see in all of the funcs. It just doesn't match 100% yet.
// Most of the special work seems to be done on the first two u16 of SpriteOAM.
// They are usually loaded/stored separately from the second half of the struct.
// There doesn't seem to be a logical reason for SpriteOAM to be split into two substructs, but the evidence points to it.
// AddSprite() still hasn't been matched (I'm writing this Dec 4 2024), because it suggests SpriteOAM is actually two separate substructs.
// "volatile u32/u16" was the first thing we tried, but it produces horrible asm and doesn't make sense for Chunsoft to have used it in this context.
// The main breakthrough came from producing 99% similar asm through attrib1/attrib2 being a substruct.
// Proposed struct definition:
typedef struct SpriteOAM2_Sub1
{
u16 attrib1;
u16 attrib2;
} SpriteOAM2_Sub1;
typedef struct SpriteOAM2_Sub2
{
u16 attrib3;
u16 unk6;
} SpriteOAM2_Sub2;
typedef struct SpriteOAM2
{
SpriteOAM2_Sub1 unk0;
SpriteOAM2_Sub2 unk2;
} SpriteOAM2;
// (It's called SpriteOAM2 temporarily to avoid conflicts with the current one)
// There seem to be 3 ways of handling SpriteOAM that I have found so far.
// #1 - When a SpriteOAM is initialized on the stack, certain inlines set its fields. This produces heavily inefficient asm but that's what they did.
// #2 - When a SpriteOAM is initialized from ram, it uses different inlines that produce way better asm. They're still known to be inlines because of the register preloading.
// #3 - Sometimes neither of the above 2's inlines are used for attrib1. It appears to be a third set of inlines (didn't attempt yet, feel free to).
// For type #3, way more testing needs to be done.
// The thing is, in all 3 scenarios, an identical order of operations is usually applied.
// The asm being nearly identical each time also makes it unlikely to be done manually without some macros or inlines.
// And this is why we haven't "real" matched many (any?) SpriteOAM funcs in the years this decomp has existed.
// Here are some helpful decomp.me links with examples.
// Type #1 - Main func (sub_8013F84) - Initializes a SpriteOAM on the stack [https://decomp.me/scratch/KYLmA]
// Type #2 - Complex fast init func (sub_8052FB8) - Loop func that initializes many SpriteOAM from ram [https://decomp.me/scratch/RX3cp]
// Type #3 - Fast init func (sub_8039174) - Initializes a SpriteOAM from ram [https://decomp.me/scratch/4qs37]
// Type #1 attrib1 inlines:
static inline void SetAttrib1(SpriteOAM2_Sub1 *sub, u32 attrib1)
{
SpriteOAM2_Sub1 nSub = *sub;
nSub.attrib1 = attrib1;
*sub = nSub;
// The only issue with this is the fact that the "ldr" happens before the "0xFFFF0000" mask is loaded.
// The only way we have gotten the mask to load before the "ldr" is with something like this:
// *sub = (SpriteOAM2_Sub){ value, sub->attrib2 };
// However, that produces bad asm. So the solution is to somehow make it a one liner like the above, while still loading from the entire "*sub" somehow.
// This func can also be changed to simply return a SpriteOAM2_Sub1 instead of replacing the value.
// BTW, making a "{} while(0)" macro for this actually folds the constants together unlike this... which is more efficient asm. Not what we want for matching lol.
// Matching this will be a huge deal!
}
static inline void SetAffineMode1(SpriteOAM2 *s, u32 mode1)
{
u32 valueM = mode1 & SPRITEOAM_MAX_AFFINEMODE1;
u32 value = valueM << SPRITEOAM_SHIFT_AFFINEMODE1;
SetAttrib1(&s->unk0, s->unk0.attrib1 & (u16)~SPRITEOAM_MASK_AFFINEMODE1);
SetAttrib1(&s->unk0, s->unk0.attrib1 | value);
}
static inline void SetAffineMode2(SpriteOAM2 *s, u32 mode2)
{
u32 valueM = mode2 & SPRITEOAM_MAX_AFFINEMODE2;
u32 value = valueM << SPRITEOAM_SHIFT_AFFINEMODE2;
SetAttrib1(&s->unk0, s->unk0.attrib1 & (u16)~SPRITEOAM_MASK_AFFINEMODE2);
SetAttrib1(&s->unk0, s->unk0.attrib1 | value);
}
static inline void SetObjMode(SpriteOAM2 *s, u32 mode)
{
u32 valueM = mode & SPRITEOAM_MAX_OBJMODE;
u32 value = valueM << SPRITEOAM_SHIFT_OBJMODE;
SetAttrib1(&s->unk0, s->unk0.attrib1 & (u16)~SPRITEOAM_MASK_OBJMODE);
SetAttrib1(&s->unk0, s->unk0.attrib1 | value);
}
static inline void SetMosaic(SpriteOAM2 *s, u32 mosaic)
{
u32 valueM = mosaic & SPRITEOAM_MAX_MOSAIC;
u32 value = valueM << SPRITEOAM_SHIFT_MOSAIC;
SetAttrib1(&s->unk0, s->unk0.attrib1 & (u16)~SPRITEOAM_MASK_MOSAIC);
SetAttrib1(&s->unk0, s->unk0.attrib1 | value);
}
static inline void SetBPP(SpriteOAM2 *s, u32 bpp)
{
u32 valueM = bpp & SPRITEOAM_MAX_BPP;
u32 value = valueM << SPRITEOAM_SHIFT_BPP;
SetAttrib1(&s->unk0, s->unk0.attrib1 & (u16)~SPRITEOAM_MASK_BPP);
SetAttrib1(&s->unk0, s->unk0.attrib1 | value);
}
static inline void SetShape(SpriteOAM2 *s, u32 shape)
{
u32 valueM = shape & SPRITEOAM_MAX_SHAPE;
u32 value = valueM << SPRITEOAM_SHIFT_SHAPE;
SetAttrib1(&s->unk0, s->unk0.attrib1 & (u16)~SPRITEOAM_MASK_SHAPE);
SetAttrib1(&s->unk0, s->unk0.attrib1 | value);
}
// Type #2 attrib1 inlines:
static inline void FastSetAffineMode1(SpriteOAM2 *s, u32 mode1)
{
u32 value;
value = mode1 & SPRITEOAM_MAX_AFFINEMODE1;
s->unk0.attrib1 &= ~SPRITEOAM_MASK_AFFINEMODE1;
s->unk0.attrib1 |= value << SPRITEOAM_SHIFT_AFFINEMODE1;
}
static inline void FastSetAffineMode2(SpriteOAM2 *s, u32 mode2)
{
u32 value;
value = mode2 & SPRITEOAM_MAX_AFFINEMODE2;
s->unk0.attrib1 &= ~SPRITEOAM_MASK_AFFINEMODE2;
s->unk0.attrib1 |= value << SPRITEOAM_SHIFT_AFFINEMODE2;
}
static inline void FastSetObjMode(SpriteOAM2 *s, u32 mode)
{
u32 value;
value = mode & SPRITEOAM_MAX_OBJMODE;
s->unk0.attrib1 &= ~SPRITEOAM_MASK_OBJMODE;
s->unk0.attrib1 |= value << SPRITEOAM_SHIFT_OBJMODE;
}
static inline void FastSetMosaic(SpriteOAM2 *s, u32 mosaic)
{
u32 value;
value = mosaic & SPRITEOAM_MAX_MOSAIC;
s->unk0.attrib1 &= ~SPRITEOAM_MASK_MOSAIC;
s->unk0.attrib1 |= value << SPRITEOAM_SHIFT_MOSAIC;
}
static inline void FastSetBPP(SpriteOAM2 *s, u32 bpp)
{
u32 value;
value = bpp & SPRITEOAM_MAX_BPP;
s->unk0.attrib1 &= ~SPRITEOAM_MASK_BPP;
s->unk0.attrib1 |= value << SPRITEOAM_SHIFT_BPP;
}
static inline void FastSetShape(SpriteOAM2 *s, u32 shape)
{
u32 value;
value = shape & SPRITEOAM_MAX_SHAPE;
s->unk0.attrib1 &= ~SPRITEOAM_MASK_SHAPE;
s->unk0.attrib1 |= value << SPRITEOAM_SHIFT_SHAPE;
}
// attrib2 inlines:
// These pretty much match in every scenario.
// Trying to macro-ify or make a sub-inline will remove some register preloading we rely on.
static inline void SetX(SpriteOAM2 *s, u32 x)
{
u32 value;
u32 valueS;
value = x & SPRITEOAM_MAX_X;
valueS = value << SPRITEOAM_SHIFT_X;
s->unk0.attrib2 &= ~SPRITEOAM_MASK_X;
s->unk0.attrib2 |= valueS;
}
static inline void SetMatrixNum(SpriteOAM2 *s, u32 matNum)
{
u32 value;
u32 valueS;
value = matNum & SPRITEOAM_MAX_MATRIXNUM;
valueS = value << SPRITEOAM_SHIFT_MATRIXNUM;
s->unk0.attrib2 &= ~SPRITEOAM_MASK_MATRIXNUM;
s->unk0.attrib2 |= valueS;
}
static inline void SetSize(SpriteOAM2 *s, u32 size)
{
u32 value;
u32 valueS;
value = size & SPRITEOAM_MAX_SIZE;
valueS = value << SPRITEOAM_SHIFT_SIZE;
s->unk0.attrib2 &= ~SPRITEOAM_MASK_SIZE;
s->unk0.attrib2 |= valueS;
}
// attrib3 inline:
// For some reason this one is all combined. Also matches pretty much every time.
// It's possible the attrib1 ones are also combined like this, but not the attrib2 ones since they're called out of order or not at all.
// And just like the attrib2 inlines, attempting to macro or inline further results in removed preloading.
static inline void SetAttrib3(SpriteOAM2 *s, u32 tileNum, u32 prio, u32 palNum)
{
u32 value;
u32 valueS;
value = tileNum & SPRITEOAM_MAX_TILENUM;
valueS = value << SPRITEOAM_SHIFT_TILENUM;
s->unk2.attrib3 &= ~SPRITEOAM_MASK_TILENUM;
s->unk2.attrib3 |= valueS;
value = prio & SPRITEOAM_MAX_PRIORITY;
valueS = value << SPRITEOAM_SHIFT_PRIORITY;
s->unk2.attrib3 &= ~SPRITEOAM_MASK_PRIORITY;
s->unk2.attrib3 |= valueS;
value = palNum & SPRITEOAM_MAX_PALETTENUM;
valueS = value << SPRITEOAM_SHIFT_PALETTENUM;
s->unk2.attrib3 &= ~SPRITEOAM_MASK_PALETTENUM;
s->unk2.attrib3 |= valueS;
}
// unk6 inlines:
// Basically same situation as attrib2 inlines. Can't be altered past this or combined, and matches like this.
static inline void SetUnk6_0(SpriteOAM2 *s, u32 part0)
{
u32 value;
u32 valueS;
value = part0 & SPRITEOAM_MAX_UNK6_0;
valueS = value << SPRITEOAM_SHIFT_UNK6_0;
s->unk2.unk6 &= ~SPRITEOAM_MASK_UNK6_0;
s->unk2.unk6 |= valueS;
}
static inline void SetUnk6_1(SpriteOAM2 *s, u32 part1)
{
u32 value;
u32 valueS;
value = part1 & SPRITEOAM_MAX_UNK6_1;
valueS = value << SPRITEOAM_SHIFT_UNK6_1;
s->unk2.unk6 &= ~SPRITEOAM_MASK_UNK6_1;
s->unk2.unk6 |= valueS;
}
static inline void SetUnk6_2(SpriteOAM2 *s, u32 part2)
{
u32 value;
u32 valueS;
value = part2 & SPRITEOAM_MAX_UNK6_2;
valueS = value << SPRITEOAM_SHIFT_UNK6_2;
s->unk2.unk6 &= ~SPRITEOAM_MASK_UNK6_2;
s->unk2.unk6 |= valueS;
}
// Not seen yet
/*static inline void SetUnk6_3(SpriteOAM2 *s, u32 part3)
{
u32 value;
u32 valueS;
value = part3 & SPRITEOAM_MAX_UNK6_3;
valueS = value << SPRITEOAM_SHIFT_UNK6_3;
s->unk2.unk6 &= ~SPRITEOAM_MASK_UNK6_3;
s->unk2.unk6 |= valueS;
}*/
static inline void SetUnk6_4(SpriteOAM2 *s, u32 part4)
{
u32 value;
u32 valueS;
value = part4 & SPRITEOAM_MAX_UNK6_4;
valueS = value << SPRITEOAM_SHIFT_UNK6_4;
s->unk2.unk6 &= ~SPRITEOAM_MASK_UNK6_4;
s->unk2.unk6 |= valueS;
}
#endif // GUARD_SPRITE_OAM_TEMP_H