summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorKurausukun <lord.uber1@gmail.com>2021-06-17 22:09:48 -0400
committerKurausukun <lord.uber1@gmail.com>2021-06-17 22:09:48 -0400
commit8b59909ac5eb6e3540aeb78396943d57a9702e4d (patch)
tree3ea51f01171cc1bd6444ab4a4b833cf09eb0463c /src
parent780da6bfd08b5401a7e11f1e0d38e377f2fab3dd (diff)
remove gflib
Diffstat (limited to 'src')
-rw-r--r--src/bg.c1248
-rw-r--r--src/blit.c209
-rw-r--r--src/dma3_manager.c183
-rw-r--r--src/gpu_regs.c195
-rw-r--r--src/io_reg.c36
-rw-r--r--src/malloc.c210
-rw-r--r--src/sprite.c1775
-rw-r--r--src/string_util.c781
-rw-r--r--src/text.c1808
-rw-r--r--src/window.c721
10 files changed, 7166 insertions, 0 deletions
diff --git a/src/bg.c b/src/bg.c
new file mode 100644
index 000000000..ec7c2113b
--- /dev/null
+++ b/src/bg.c
@@ -0,0 +1,1248 @@
+#include <limits.h>
+#include "global.h"
+#include "bg.h"
+#include "dma3.h"
+#include "gpu_regs.h"
+
+#define DISPCNT_ALL_BG_AND_MODE_BITS (DISPCNT_BG_ALL_ON | 0x7)
+
+struct BgControl
+{
+ struct BgConfig {
+ u8 visible:1;
+ u8 unknown_1:1;
+ u8 screenSize:2;
+ u8 priority:2;
+ u8 mosaic:1;
+ u8 wraparound:1;
+
+ u8 charBaseIndex:2;
+ u8 mapBaseIndex:5;
+ u8 paletteMode:1;
+
+ u8 unknown_2; // Assigned to but never read
+ u8 unknown_3; // Assigned to but never read
+ } configs[NUM_BACKGROUNDS];
+
+ u16 bgVisibilityAndMode;
+};
+
+struct BgConfig2
+{
+ u32 baseTile:10;
+ u32 basePalette:4;
+ u32 unk_3:18;
+
+ void* tilemap;
+ s32 bg_x;
+ s32 bg_y;
+};
+
+static struct BgControl sGpuBgConfigs;
+static struct BgConfig2 sGpuBgConfigs2[NUM_BACKGROUNDS];
+static u32 sDmaBusyBitfield[NUM_BACKGROUNDS];
+
+u32 gUnneededFireRedVariable;
+
+static const struct BgConfig sZeroedBgControlStruct = { 0 };
+
+void ResetBgs(void)
+{
+ ResetBgControlStructs();
+ sGpuBgConfigs.bgVisibilityAndMode = 0;
+ SetTextModeAndHideBgs();
+}
+
+static void SetBgModeInternal(u8 bgMode)
+{
+ sGpuBgConfigs.bgVisibilityAndMode &= ~0x7;
+ sGpuBgConfigs.bgVisibilityAndMode |= bgMode;
+}
+
+u8 GetBgMode(void)
+{
+ return sGpuBgConfigs.bgVisibilityAndMode & 0x7;
+}
+
+void ResetBgControlStructs(void)
+{
+ int i;
+
+ for (i = 0; i < NUM_BACKGROUNDS; i++)
+ {
+ sGpuBgConfigs.configs[i] = sZeroedBgControlStruct;
+ }
+}
+
+void Unused_ResetBgControlStruct(u8 bg)
+{
+ if (!IsInvalidBg(bg))
+ {
+ sGpuBgConfigs.configs[bg] = sZeroedBgControlStruct;
+ }
+}
+
+enum
+{
+ BG_CTRL_ATTR_VISIBLE = 1,
+ BG_CTRL_ATTR_CHARBASEINDEX = 2,
+ BG_CTRL_ATTR_MAPBASEINDEX = 3,
+ BG_CTRL_ATTR_SCREENSIZE = 4,
+ BG_CTRL_ATTR_PALETTEMODE = 5,
+ BG_CTRL_ATTR_PRIORITY = 6,
+ BG_CTRL_ATTR_MOSAIC = 7,
+ BG_CTRL_ATTR_WRAPAROUND = 8,
+};
+
+static void SetBgControlAttributes(u8 bg, u8 charBaseIndex, u8 mapBaseIndex, u8 screenSize, u8 paletteMode, u8 priority, u8 mosaic, u8 wraparound)
+{
+ if (!IsInvalidBg(bg))
+ {
+ if (charBaseIndex != 0xFF)
+ {
+ sGpuBgConfigs.configs[bg].charBaseIndex = charBaseIndex;
+ }
+
+ if (mapBaseIndex != 0xFF)
+ {
+ sGpuBgConfigs.configs[bg].mapBaseIndex = mapBaseIndex;
+ }
+
+ if (screenSize != 0xFF)
+ {
+ sGpuBgConfigs.configs[bg].screenSize = screenSize;
+ }
+
+ if (paletteMode != 0xFF)
+ {
+ sGpuBgConfigs.configs[bg].paletteMode = paletteMode;
+ }
+
+ if (priority != 0xFF)
+ {
+ sGpuBgConfigs.configs[bg].priority = priority;
+ }
+
+ if (mosaic != 0xFF)
+ {
+ sGpuBgConfigs.configs[bg].mosaic = mosaic;
+ }
+
+ if (wraparound != 0xFF)
+ {
+ sGpuBgConfigs.configs[bg].wraparound = wraparound;
+ }
+
+ sGpuBgConfigs.configs[bg].unknown_2 = 0;
+ sGpuBgConfigs.configs[bg].unknown_3 = 0;
+
+ sGpuBgConfigs.configs[bg].visible = 1;
+ }
+}
+
+static u16 GetBgControlAttribute(u8 bg, u8 attributeId)
+{
+ if (!IsInvalidBg(bg) && sGpuBgConfigs.configs[bg].visible)
+ {
+ switch (attributeId)
+ {
+ case BG_CTRL_ATTR_VISIBLE:
+ return sGpuBgConfigs.configs[bg].visible;
+ case BG_CTRL_ATTR_CHARBASEINDEX:
+ return sGpuBgConfigs.configs[bg].charBaseIndex;
+ case BG_CTRL_ATTR_MAPBASEINDEX:
+ return sGpuBgConfigs.configs[bg].mapBaseIndex;
+ case BG_CTRL_ATTR_SCREENSIZE:
+ return sGpuBgConfigs.configs[bg].screenSize;
+ case BG_CTRL_ATTR_PALETTEMODE:
+ return sGpuBgConfigs.configs[bg].paletteMode;
+ case BG_CTRL_ATTR_PRIORITY:
+ return sGpuBgConfigs.configs[bg].priority;
+ case BG_CTRL_ATTR_MOSAIC:
+ return sGpuBgConfigs.configs[bg].mosaic;
+ case BG_CTRL_ATTR_WRAPAROUND:
+ return sGpuBgConfigs.configs[bg].wraparound;
+ }
+ }
+
+ return 0xFF;
+}
+
+u8 LoadBgVram(u8 bg, const void *src, u16 size, u16 destOffset, u8 mode)
+{
+ u16 offset;
+ s8 cursor;
+
+ if (IsInvalidBg(bg) || !sGpuBgConfigs.configs[bg].visible)
+ return -1;
+
+ switch (mode)
+ {
+ case 0x1:
+ offset = sGpuBgConfigs.configs[bg].charBaseIndex * BG_CHAR_SIZE;
+ offset = destOffset + offset;
+ cursor = RequestDma3Copy(src, (void*)(offset + BG_VRAM), size, 0);
+ if (cursor == -1)
+ return -1;
+ break;
+ case 0x2:
+ offset = sGpuBgConfigs.configs[bg].mapBaseIndex * BG_SCREEN_SIZE;
+ offset = destOffset + offset;
+ cursor = RequestDma3Copy(src, (void*)(offset + BG_VRAM), size, 0);
+ if (cursor == -1)
+ return -1;
+ break;
+ default:
+ cursor = -1;
+ break;
+ }
+
+ return cursor;
+}
+
+static void ShowBgInternal(u8 bg)
+{
+ u16 value;
+ if (!IsInvalidBg(bg) && sGpuBgConfigs.configs[bg].visible)
+ {
+ value = sGpuBgConfigs.configs[bg].priority |
+ (sGpuBgConfigs.configs[bg].charBaseIndex << 2) |
+ (sGpuBgConfigs.configs[bg].mosaic << 6) |
+ (sGpuBgConfigs.configs[bg].paletteMode << 7) |
+ (sGpuBgConfigs.configs[bg].mapBaseIndex << 8) |
+ (sGpuBgConfigs.configs[bg].wraparound << 13) |
+ (sGpuBgConfigs.configs[bg].screenSize << 14);
+
+ SetGpuReg((bg << 1) + REG_OFFSET_BG0CNT, value);
+
+ sGpuBgConfigs.bgVisibilityAndMode |= 1 << (bg + 8);
+ sGpuBgConfigs.bgVisibilityAndMode &= DISPCNT_ALL_BG_AND_MODE_BITS;
+ }
+}
+
+static void HideBgInternal(u8 bg)
+{
+ if (!IsInvalidBg(bg))
+ {
+ sGpuBgConfigs.bgVisibilityAndMode &= ~(1 << (bg + 8));
+ sGpuBgConfigs.bgVisibilityAndMode &= DISPCNT_ALL_BG_AND_MODE_BITS;
+ }
+}
+
+static void SyncBgVisibilityAndMode(void)
+{
+ SetGpuReg(REG_OFFSET_DISPCNT, (GetGpuReg(REG_OFFSET_DISPCNT) & ~DISPCNT_ALL_BG_AND_MODE_BITS) | sGpuBgConfigs.bgVisibilityAndMode);
+}
+
+void SetTextModeAndHideBgs(void)
+{
+ SetGpuReg(REG_OFFSET_DISPCNT, GetGpuReg(REG_OFFSET_DISPCNT) & ~DISPCNT_ALL_BG_AND_MODE_BITS);
+}
+
+static void SetBgAffineInternal(u8 bg, s32 srcCenterX, s32 srcCenterY, s16 dispCenterX, s16 dispCenterY, s16 scaleX, s16 scaleY, u16 rotationAngle)
+{
+ struct BgAffineSrcData src;
+ struct BgAffineDstData dest;
+
+ switch (sGpuBgConfigs.bgVisibilityAndMode & 0x7)
+ {
+ default:
+ case 0:
+ return;
+ case 1:
+ if (bg != 2)
+ return;
+ break;
+ case 2:
+ if (bg != 2 && bg != 3)
+ return;
+ break;
+ }
+
+ src.texX = srcCenterX;
+ src.texY = srcCenterY;
+ src.scrX = dispCenterX;
+ src.scrY = dispCenterY;
+ src.sx = scaleX;
+ src.sy = scaleY;
+ src.alpha = rotationAngle;
+
+ BgAffineSet(&src, &dest, 1);
+
+ SetGpuReg(REG_OFFSET_BG2PA, dest.pa);
+ SetGpuReg(REG_OFFSET_BG2PB, dest.pb);
+ SetGpuReg(REG_OFFSET_BG2PC, dest.pc);
+ SetGpuReg(REG_OFFSET_BG2PD, dest.pd);
+ SetGpuReg(REG_OFFSET_BG2PA, dest.pa);
+ SetGpuReg(REG_OFFSET_BG2X_L, (s16)(dest.dx));
+ SetGpuReg(REG_OFFSET_BG2X_H, (s16)(dest.dx >> 16));
+ SetGpuReg(REG_OFFSET_BG2Y_L, (s16)(dest.dy));
+ SetGpuReg(REG_OFFSET_BG2Y_H, (s16)(dest.dy >> 16));
+}
+
+bool8 IsInvalidBg(u8 bg)
+{
+ if (bg >= NUM_BACKGROUNDS)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+int DummiedOutFireRedLeafGreenTileAllocFunc(int a1, int a2, int a3, int a4)
+{
+ return 0;
+}
+
+void ResetBgsAndClearDma3BusyFlags(u32 leftoverFireRedLeafGreenVariable)
+{
+ int i;
+ ResetBgs();
+
+ for (i = 0; i < NUM_BACKGROUNDS; i++)
+ {
+ sDmaBusyBitfield[i] = 0;
+ }
+
+ gUnneededFireRedVariable = leftoverFireRedLeafGreenVariable;
+}
+
+void InitBgsFromTemplates(u8 bgMode, const struct BgTemplate *templates, u8 numTemplates)
+{
+ int i;
+ u8 bg;
+
+ SetBgModeInternal(bgMode);
+ ResetBgControlStructs();
+
+ for (i = 0; i < numTemplates; i++)
+ {
+ bg = templates[i].bg;
+ if (bg < NUM_BACKGROUNDS)
+ {
+ SetBgControlAttributes(bg,
+ templates[i].charBaseIndex,
+ templates[i].mapBaseIndex,
+ templates[i].screenSize,
+ templates[i].paletteMode,
+ templates[i].priority,
+ 0,
+ 0);
+
+ sGpuBgConfigs2[bg].baseTile = templates[i].baseTile;
+ sGpuBgConfigs2[bg].basePalette = 0;
+ sGpuBgConfigs2[bg].unk_3 = 0;
+
+ sGpuBgConfigs2[bg].tilemap = NULL;
+ sGpuBgConfigs2[bg].bg_x = 0;
+ sGpuBgConfigs2[bg].bg_y = 0;
+ }
+ }
+}
+
+void InitBgFromTemplate(const struct BgTemplate *template)
+{
+ u8 bg = template->bg;
+
+ if (bg < NUM_BACKGROUNDS)
+ {
+ SetBgControlAttributes(bg,
+ template->charBaseIndex,
+ template->mapBaseIndex,
+ template->screenSize,
+ template->paletteMode,
+ template->priority,
+ 0,
+ 0);
+
+ sGpuBgConfigs2[bg].baseTile = template->baseTile;
+ sGpuBgConfigs2[bg].basePalette = 0;
+ sGpuBgConfigs2[bg].unk_3 = 0;
+
+ sGpuBgConfigs2[bg].tilemap = NULL;
+ sGpuBgConfigs2[bg].bg_x = 0;
+ sGpuBgConfigs2[bg].bg_y = 0;
+ }
+}
+
+void SetBgMode(u8 bgMode)
+{
+ SetBgModeInternal(bgMode);
+}
+
+u16 LoadBgTiles(u8 bg, const void* src, u16 size, u16 destOffset)
+{
+ u16 tileOffset;
+ u8 cursor;
+
+ if (GetBgControlAttribute(bg, BG_CTRL_ATTR_PALETTEMODE) == 0)
+ {
+ tileOffset = (sGpuBgConfigs2[bg].baseTile + destOffset) * 0x20;
+ }
+ else
+ {
+ tileOffset = (sGpuBgConfigs2[bg].baseTile + destOffset) * 0x40;
+ }
+
+ cursor = LoadBgVram(bg, src, size, tileOffset, DISPCNT_MODE_1);
+
+ if (cursor == 0xFF)
+ {
+ return -1;
+ }
+
+ sDmaBusyBitfield[cursor / 0x20] |= (1 << (cursor % 0x20));
+
+ if (gUnneededFireRedVariable == 1)
+ {
+ DummiedOutFireRedLeafGreenTileAllocFunc(bg, tileOffset / 0x20, size / 0x20, 1);
+ }
+
+ return cursor;
+}
+
+u16 LoadBgTilemap(u8 bg, const void *src, u16 size, u16 destOffset)
+{
+ u8 cursor = LoadBgVram(bg, src, size, destOffset * 2, DISPCNT_MODE_2);
+
+ if (cursor == 0xFF)
+ {
+ return -1;
+ }
+
+ sDmaBusyBitfield[cursor / 0x20] |= (1 << (cursor % 0x20));
+
+ return cursor;
+}
+
+u16 Unused_LoadBgPalette(u8 bg, const void *src, u16 size, u16 destOffset)
+{
+ s8 cursor;
+
+ if (!IsInvalidBg32(bg))
+ {
+ u16 paletteOffset = (sGpuBgConfigs2[bg].basePalette * 0x20) + (destOffset * 2);
+ cursor = RequestDma3Copy(src, (void*)(paletteOffset + BG_PLTT), size, 0);
+
+ if (cursor == -1)
+ {
+ return -1;
+ }
+ }
+ else
+ {
+ return -1;
+ }
+
+ sDmaBusyBitfield[cursor / 0x20] |= (1 << (cursor % 0x20));
+
+ return (u8)cursor;
+}
+
+bool8 IsDma3ManagerBusyWithBgCopy(void)
+{
+ int i;
+
+ for (i = 0; i < 0x80; i++)
+ {
+ u8 div = i / 0x20;
+ u8 mod = i % 0x20;
+
+ if ((sDmaBusyBitfield[div] & (1 << mod)))
+ {
+ s8 reqSpace = CheckForSpaceForDma3Request(i);
+ if (reqSpace == -1)
+ {
+ return TRUE;
+ }
+
+ sDmaBusyBitfield[div] &= ~(1 << mod);
+ }
+ }
+
+ return FALSE;
+}
+
+void ShowBg(u8 bg)
+{
+ ShowBgInternal(bg);
+ SyncBgVisibilityAndMode();
+}
+
+void HideBg(u8 bg)
+{
+ HideBgInternal(bg);
+ SyncBgVisibilityAndMode();
+}
+
+void SetBgAttribute(u8 bg, u8 attributeId, u8 value)
+{
+ switch (attributeId)
+ {
+ case BG_ATTR_CHARBASEINDEX:
+ SetBgControlAttributes(bg, value, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
+ break;
+ case BG_ATTR_MAPBASEINDEX:
+ SetBgControlAttributes(bg, 0xFF, value, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
+ break;
+ case BG_ATTR_SCREENSIZE:
+ SetBgControlAttributes(bg, 0xFF, 0xFF, value, 0xFF, 0xFF, 0xFF, 0xFF);
+ break;
+ case BG_ATTR_PALETTEMODE:
+ SetBgControlAttributes(bg, 0xFF, 0xFF, 0xFF, value, 0xFF, 0xFF, 0xFF);
+ break;
+ case BG_ATTR_PRIORITY:
+ SetBgControlAttributes(bg, 0xFF, 0xFF, 0xFF, 0xFF, value, 0xFF, 0xFF);
+ break;
+ case BG_ATTR_MOSAIC:
+ SetBgControlAttributes(bg, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, value, 0xFF);
+ break;
+ case BG_ATTR_WRAPAROUND:
+ SetBgControlAttributes(bg, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, value);
+ break;
+ }
+}
+
+u16 GetBgAttribute(u8 bg, u8 attributeId)
+{
+ switch (attributeId)
+ {
+ case BG_ATTR_CHARBASEINDEX:
+ return GetBgControlAttribute(bg, BG_CTRL_ATTR_CHARBASEINDEX);
+ case BG_ATTR_MAPBASEINDEX:
+ return GetBgControlAttribute(bg, BG_CTRL_ATTR_MAPBASEINDEX);
+ case BG_ATTR_SCREENSIZE:
+ return GetBgControlAttribute(bg, BG_CTRL_ATTR_SCREENSIZE);
+ case BG_ATTR_PALETTEMODE:
+ return GetBgControlAttribute(bg, BG_CTRL_ATTR_PALETTEMODE);
+ case BG_ATTR_PRIORITY:
+ return GetBgControlAttribute(bg, BG_CTRL_ATTR_PRIORITY);
+ case BG_ATTR_MOSAIC:
+ return GetBgControlAttribute(bg, BG_CTRL_ATTR_MOSAIC);
+ case BG_ATTR_WRAPAROUND:
+ return GetBgControlAttribute(bg, BG_CTRL_ATTR_WRAPAROUND);
+ case BG_ATTR_METRIC:
+ switch (GetBgType(bg))
+ {
+ case 0:
+ return GetBgMetricTextMode(bg, 0) * 0x800;
+ case 1:
+ return GetBgMetricAffineMode(bg, 0) * 0x100;
+ default:
+ return 0;
+ }
+ case BG_ATTR_TYPE:
+ return GetBgType(bg);
+ case BG_ATTR_BASETILE:
+ return sGpuBgConfigs2[bg].baseTile;
+ default:
+ return -1;
+ }
+}
+
+s32 ChangeBgX(u8 bg, s32 value, u8 op)
+{
+ u8 mode;
+ u16 temp1;
+ u16 temp2;
+
+ if (IsInvalidBg32(bg) || !GetBgControlAttribute(bg, BG_CTRL_ATTR_VISIBLE))
+ {
+ return -1;
+ }
+
+ switch (op)
+ {
+ case 0:
+ default:
+ sGpuBgConfigs2[bg].bg_x = value;
+ break;
+ case 1:
+ sGpuBgConfigs2[bg].bg_x += value;
+ break;
+ case 2:
+ sGpuBgConfigs2[bg].bg_x -= value;
+ break;
+ }
+
+ mode = GetBgMode();
+
+ switch (bg)
+ {
+ case 0:
+ temp1 = sGpuBgConfigs2[0].bg_x >> 0x8;
+ SetGpuReg(REG_OFFSET_BG0HOFS, temp1);
+ break;
+ case 1:
+ temp1 = sGpuBgConfigs2[1].bg_x >> 0x8;
+ SetGpuReg(REG_OFFSET_BG1HOFS, temp1);
+ break;
+ case 2:
+ if (mode == 0)
+ {
+ temp1 = sGpuBgConfigs2[2].bg_x >> 0x8;
+ SetGpuReg(REG_OFFSET_BG2HOFS, temp1);
+ }
+ else
+ {
+ temp1 = sGpuBgConfigs2[2].bg_x >> 0x10;
+ temp2 = sGpuBgConfigs2[2].bg_x & 0xFFFF;
+ SetGpuReg(REG_OFFSET_BG2X_H, temp1);
+ SetGpuReg(REG_OFFSET_BG2X_L, temp2);
+ }
+ break;
+ case 3:
+ if (mode == 0)
+ {
+ temp1 = sGpuBgConfigs2[3].bg_x >> 0x8;
+ SetGpuReg(REG_OFFSET_BG3HOFS, temp1);
+ }
+ else if (mode == 2)
+ {
+ temp1 = sGpuBgConfigs2[3].bg_x >> 0x10;
+ temp2 = sGpuBgConfigs2[3].bg_x & 0xFFFF;
+ SetGpuReg(REG_OFFSET_BG3X_H, temp1);
+ SetGpuReg(REG_OFFSET_BG3X_L, temp2);
+ }
+ break;
+ }
+
+ return sGpuBgConfigs2[bg].bg_x;
+}
+
+s32 GetBgX(u8 bg)
+{
+ if (IsInvalidBg32(bg))
+ return -1;
+ else if (!GetBgControlAttribute(bg, BG_CTRL_ATTR_VISIBLE))
+ return -1;
+ else
+ return sGpuBgConfigs2[bg].bg_x;
+}
+
+s32 ChangeBgY(u8 bg, s32 value, u8 op)
+{
+ u8 mode;
+ u16 temp1;
+ u16 temp2;
+
+ if (IsInvalidBg32(bg) || !GetBgControlAttribute(bg, BG_CTRL_ATTR_VISIBLE))
+ {
+ return -1;
+ }
+
+ switch (op)
+ {
+ case 0:
+ default:
+ sGpuBgConfigs2[bg].bg_y = value;
+ break;
+ case 1:
+ sGpuBgConfigs2[bg].bg_y += value;
+ break;
+ case 2:
+ sGpuBgConfigs2[bg].bg_y -= value;
+ break;
+ }
+
+ mode = GetBgMode();
+
+ switch (bg)
+ {
+ case 0:
+ temp1 = sGpuBgConfigs2[0].bg_y >> 0x8;
+ SetGpuReg(REG_OFFSET_BG0VOFS, temp1);
+ break;
+ case 1:
+ temp1 = sGpuBgConfigs2[1].bg_y >> 0x8;
+ SetGpuReg(REG_OFFSET_BG1VOFS, temp1);
+ break;
+ case 2:
+ if (mode == 0)
+ {
+ temp1 = sGpuBgConfigs2[2].bg_y >> 0x8;
+ SetGpuReg(REG_OFFSET_BG2VOFS, temp1);
+ }
+ else
+ {
+ temp1 = sGpuBgConfigs2[2].bg_y >> 0x10;
+ temp2 = sGpuBgConfigs2[2].bg_y & 0xFFFF;
+ SetGpuReg(REG_OFFSET_BG2Y_H, temp1);
+ SetGpuReg(REG_OFFSET_BG2Y_L, temp2);
+ }
+ break;
+ case 3:
+ if (mode == 0)
+ {
+ temp1 = sGpuBgConfigs2[3].bg_y >> 0x8;
+ SetGpuReg(REG_OFFSET_BG3VOFS, temp1);
+ }
+ else if (mode == 2)
+ {
+ temp1 = sGpuBgConfigs2[3].bg_y >> 0x10;
+ temp2 = sGpuBgConfigs2[3].bg_y & 0xFFFF;
+ SetGpuReg(REG_OFFSET_BG3Y_H, temp1);
+ SetGpuReg(REG_OFFSET_BG3Y_L, temp2);
+ }
+ break;
+ }
+
+ return sGpuBgConfigs2[bg].bg_y;
+}
+
+s32 ChangeBgY_ScreenOff(u8 bg, s32 value, u8 op)
+{
+ u8 mode;
+ u16 temp1;
+ u16 temp2;
+
+ if (IsInvalidBg32(bg) || !GetBgControlAttribute(bg, BG_CTRL_ATTR_VISIBLE))
+ {
+ return -1;
+ }
+
+ switch (op)
+ {
+ case 0:
+ default:
+ sGpuBgConfigs2[bg].bg_y = value;
+ break;
+ case 1:
+ sGpuBgConfigs2[bg].bg_y += value;
+ break;
+ case 2:
+ sGpuBgConfigs2[bg].bg_y -= value;
+ break;
+ }
+
+ mode = GetBgMode();
+
+ switch (bg)
+ {
+ case 0:
+ temp1 = sGpuBgConfigs2[0].bg_y >> 0x8;
+ SetGpuReg_ForcedBlank(REG_OFFSET_BG0VOFS, temp1);
+ break;
+ case 1:
+ temp1 = sGpuBgConfigs2[1].bg_y >> 0x8;
+ SetGpuReg_ForcedBlank(REG_OFFSET_BG1VOFS, temp1);
+ break;
+ case 2:
+ if (mode == 0)
+ {
+ temp1 = sGpuBgConfigs2[2].bg_y >> 0x8;
+ SetGpuReg_ForcedBlank(REG_OFFSET_BG2VOFS, temp1);
+
+ }
+ else
+ {
+ temp1 = sGpuBgConfigs2[2].bg_y >> 0x10;
+ temp2 = sGpuBgConfigs2[2].bg_y & 0xFFFF;
+ SetGpuReg_ForcedBlank(REG_OFFSET_BG2Y_H, temp1);
+ SetGpuReg_ForcedBlank(REG_OFFSET_BG2Y_L, temp2);
+ }
+ break;
+ case 3:
+ if (mode == 0)
+ {
+ temp1 = sGpuBgConfigs2[3].bg_y >> 0x8;
+ SetGpuReg_ForcedBlank(REG_OFFSET_BG3VOFS, temp1);
+ }
+ else if (mode == 2)
+ {
+ temp1 = sGpuBgConfigs2[3].bg_y >> 0x10;
+ temp2 = sGpuBgConfigs2[3].bg_y & 0xFFFF;
+ SetGpuReg_ForcedBlank(REG_OFFSET_BG3Y_H, temp1);
+ SetGpuReg_ForcedBlank(REG_OFFSET_BG3Y_L, temp2);
+ }
+ break;
+ }
+
+ return sGpuBgConfigs2[bg].bg_y;
+}
+
+s32 GetBgY(u8 bg)
+{
+ if (IsInvalidBg32(bg))
+ return -1;
+ else if (!GetBgControlAttribute(bg, BG_CTRL_ATTR_VISIBLE))
+ return -1;
+ else
+ return sGpuBgConfigs2[bg].bg_y;
+}
+
+void SetBgAffine(u8 bg, s32 srcCenterX, s32 srcCenterY, s16 dispCenterX, s16 dispCenterY, s16 scaleX, s16 scaleY, u16 rotationAngle)
+{
+ SetBgAffineInternal(bg, srcCenterX, srcCenterY, dispCenterX, dispCenterY, scaleX, scaleY, rotationAngle);
+}
+
+u8 Unused_AdjustBgMosaic(u8 a1, u8 a2)
+{
+ u16 result = GetGpuReg(REG_OFFSET_MOSAIC);
+ s16 test1 = result & 0xF;
+ s16 test2 = (result >> 4) & 0xF;
+
+ result &= 0xFF00;
+
+ switch (a2)
+ {
+ case 0:
+ default:
+ test1 = a1 & 0xF;
+ test2 = a1 >> 0x4;
+ break;
+ case 1:
+ test1 = a1 & 0xF;
+ break;
+ case 2:
+ if ((test1 + a1) > 0xF)
+ {
+ test1 = 0xF;
+ }
+ else
+ {
+ test1 += a1;
+ }
+ break;
+ case 3:
+ if ((test1 - a1) < 0)
+ {
+ test1 = 0x0;
+ }
+ else
+ {
+ test1 -= a1;
+ }
+ break;
+ case 4:
+ test2 = a1 & 0xF;
+ break;
+ case 5:
+ if ((test2 + a1) > 0xF)
+ {
+ test2 = 0xF;
+ }
+ else
+ {
+ test2 += a1;
+ }
+ break;
+ case 6:
+ if ((test2 - a1) < 0)
+ {
+ test2 = 0x0;
+ }
+ else
+ {
+ test2 -= a1;
+ }
+ break;
+ }
+
+ result |= ((test2 << 0x4) & 0xF0);
+ result |= (test1 & 0xF);
+
+ SetGpuReg(REG_OFFSET_MOSAIC, result);
+
+ return result;
+}
+
+void SetBgTilemapBuffer(u8 bg, void *tilemap)
+{
+ if (!IsInvalidBg32(bg) && GetBgControlAttribute(bg, BG_CTRL_ATTR_VISIBLE))
+ {
+ sGpuBgConfigs2[bg].tilemap = tilemap;
+ }
+}
+
+void UnsetBgTilemapBuffer(u8 bg)
+{
+ if (!IsInvalidBg32(bg) && GetBgControlAttribute(bg, BG_CTRL_ATTR_VISIBLE))
+ {
+ sGpuBgConfigs2[bg].tilemap = NULL;
+ }
+}
+
+void* GetBgTilemapBuffer(u8 bg)
+{
+ if (IsInvalidBg32(bg))
+ return NULL;
+ else if (!GetBgControlAttribute(bg, BG_CTRL_ATTR_VISIBLE))
+ return NULL;
+ else
+ return sGpuBgConfigs2[bg].tilemap;
+}
+
+void CopyToBgTilemapBuffer(u8 bg, const void *src, u16 mode, u16 destOffset)
+{
+ if (!IsInvalidBg32(bg) && !IsTileMapOutsideWram(bg))
+ {
+ if (mode != 0)
+ CpuCopy16(src, (void *)(sGpuBgConfigs2[bg].tilemap + (destOffset * 2)), mode);
+ else
+ LZ77UnCompWram(src, (void *)(sGpuBgConfigs2[bg].tilemap + (destOffset * 2)));
+ }
+}
+
+void CopyBgTilemapBufferToVram(u8 bg)
+{
+ u16 sizeToLoad;
+
+ if (!IsInvalidBg32(bg) && !IsTileMapOutsideWram(bg))
+ {
+ switch (GetBgType(bg))
+ {
+ case 0:
+ sizeToLoad = GetBgMetricTextMode(bg, 0) * 0x800;
+ break;
+ case 1:
+ sizeToLoad = GetBgMetricAffineMode(bg, 0) * 0x100;
+ break;
+ default:
+ sizeToLoad = 0;
+ break;
+ }
+ LoadBgVram(bg, sGpuBgConfigs2[bg].tilemap, sizeToLoad, 0, 2);
+ }
+}
+
+void CopyToBgTilemapBufferRect(u8 bg, const void* src, u8 destX, u8 destY, u8 width, u8 height)
+{
+ u16 destX16;
+ u16 destY16;
+ u16 mode;
+
+ if (!IsInvalidBg32(bg) && !IsTileMapOutsideWram(bg))
+ {
+ switch (GetBgType(bg))
+ {
+ case 0:
+ {
+ const u16 * srcCopy = src;
+ for (destY16 = destY; destY16 < (destY + height); destY16++)
+ {
+ for (destX16 = destX; destX16 < (destX + width); destX16++)
+ {
+ ((u16*)sGpuBgConfigs2[bg].tilemap)[((destY16 * 0x20) + destX16)] = *srcCopy++;
+ }
+ }
+ break;
+ }
+ case 1:
+ {
+ const u8 * srcCopy = src;
+ mode = GetBgMetricAffineMode(bg, 0x1);
+ for (destY16 = destY; destY16 < (destY + height); destY16++)
+ {
+ for (destX16 = destX; destX16 < (destX + width); destX16++)
+ {
+ ((u8*)sGpuBgConfigs2[bg].tilemap)[((destY16 * mode) + destX16)] = *srcCopy++;
+ }
+ }
+ break;
+ }
+ }
+ }
+}
+
+void CopyToBgTilemapBufferRect_ChangePalette(u8 bg, const void *src, u8 destX, u8 destY, u8 rectWidth, u8 rectHeight, u8 palette)
+{
+ CopyRectToBgTilemapBufferRect(bg, src, 0, 0, rectWidth, rectHeight, destX, destY, rectWidth, rectHeight, palette, 0, 0);
+}
+
+void CopyRectToBgTilemapBufferRect(u8 bg, const void *src, u8 srcX, u8 srcY, u8 srcWidth, u8 unused, u8 srcHeight, u8 destX, u8 destY, u8 rectWidth, u8 rectHeight, s16 palette1, s16 tileOffset)
+{
+ u16 screenWidth, screenHeight, screenSize;
+ u16 var;
+ const void *srcPtr;
+ u16 i, j;
+
+ if (!IsInvalidBg32(bg) && !IsTileMapOutsideWram(bg))
+ {
+ screenSize = GetBgControlAttribute(bg, BG_CTRL_ATTR_SCREENSIZE);
+ screenWidth = GetBgMetricTextMode(bg, 0x1) * 0x20;
+ screenHeight = GetBgMetricTextMode(bg, 0x2) * 0x20;
+ switch (GetBgType(bg))
+ {
+ case 0:
+ srcPtr = src + ((srcY * srcWidth) + srcX) * 2;
+ for (i = destX; i < (destX + rectWidth); i++)
+ {
+ for (j = srcHeight; j < (srcHeight + destY); j++)
+ {
+ u16 index = GetTileMapIndexFromCoords(j, i, screenSize, screenWidth, screenHeight);
+ CopyTileMapEntry(srcPtr, sGpuBgConfigs2[bg].tilemap + (index * 2), rectHeight, palette1, tileOffset);
+ srcPtr += 2;
+ }
+ srcPtr += (srcWidth - destY) * 2;
+ }
+ break;
+ case 1:
+ srcPtr = src + ((srcY * srcWidth) + srcX);
+ var = GetBgMetricAffineMode(bg, 0x1);
+ for (i = destX; i < (destX + rectWidth); i++)
+ {
+ for (j = srcHeight; j < (srcHeight + destY); j++)
+ {
+ *(u8*)(sGpuBgConfigs2[bg].tilemap + ((var * i) + j)) = *(u8*)(srcPtr) + palette1;
+ srcPtr++;
+ }
+ srcPtr += (srcWidth - destY);
+ }
+ break;
+ }
+ }
+}
+
+void FillBgTilemapBufferRect_Palette0(u8 bg, u16 tileNum, u8 x, u8 y, u8 width, u8 height)
+{
+ u16 x16;
+ u16 y16;
+ u16 mode;
+
+ if (!IsInvalidBg32(bg) && !IsTileMapOutsideWram(bg))
+ {
+ switch (GetBgType(bg))
+ {
+ case 0:
+ for (y16 = y; y16 < (y + height); y16++)
+ {
+ for (x16 = x; x16 < (x + width); x16++)
+ {
+ ((u16*)sGpuBgConfigs2[bg].tilemap)[((y16 * 0x20) + x16)] = tileNum;
+ }
+ }
+ break;
+ case 1:
+ mode = GetBgMetricAffineMode(bg, 0x1);
+ for (y16 = y; y16 < (y + height); y16++)
+ {
+ for (x16 = x; x16 < (x + width); x16++)
+ {
+ ((u8*)sGpuBgConfigs2[bg].tilemap)[((y16 * mode) + x16)] = tileNum;
+ }
+ }
+ break;
+ }
+ }
+}
+
+void FillBgTilemapBufferRect(u8 bg, u16 tileNum, u8 x, u8 y, u8 width, u8 height, u8 palette)
+{
+ WriteSequenceToBgTilemapBuffer(bg, tileNum, x, y, width, height, palette, 0);
+}
+
+void WriteSequenceToBgTilemapBuffer(u8 bg, u16 firstTileNum, u8 x, u8 y, u8 width, u8 height, u8 paletteSlot, s16 tileNumDelta)
+{
+ u16 mode;
+ u16 mode2;
+ u16 attribute;
+ u16 mode3;
+ u16 x16, y16;
+
+ if (!IsInvalidBg32(bg) && !IsTileMapOutsideWram(bg))
+ {
+ attribute = GetBgControlAttribute(bg, BG_CTRL_ATTR_SCREENSIZE);
+ mode = GetBgMetricTextMode(bg, 0x1) * 0x20;
+ mode2 = GetBgMetricTextMode(bg, 0x2) * 0x20;
+ switch (GetBgType(bg))
+ {
+ case 0:
+ for (y16 = y; y16 < (y + height); y16++)
+ {
+ for (x16 = x; x16 < (x + width); x16++)
+ {
+ CopyTileMapEntry(&firstTileNum, &((u16*)sGpuBgConfigs2[bg].tilemap)[(u16)GetTileMapIndexFromCoords(x16, y16, attribute, mode, mode2)], paletteSlot, 0, 0);
+ firstTileNum = (firstTileNum & (METATILE_COLLISION_MASK | METATILE_ELEVATION_MASK)) + ((firstTileNum + tileNumDelta) & METATILE_ID_MASK);
+ }
+ }
+ break;
+ case 1:
+ mode3 = GetBgMetricAffineMode(bg, 0x1);
+ for (y16 = y; y16 < (y + height); y16++)
+ {
+ for (x16 = x; x16 < (x + width); x16++)
+ {
+ ((u8*)sGpuBgConfigs2[bg].tilemap)[(y16 * mode3) + x16] = firstTileNum;
+ firstTileNum = (firstTileNum & (METATILE_COLLISION_MASK | METATILE_ELEVATION_MASK)) + ((firstTileNum + tileNumDelta) & METATILE_ID_MASK);
+ }
+ }
+ break;
+ }
+ }
+}
+
+u16 GetBgMetricTextMode(u8 bg, u8 whichMetric)
+{
+ u8 screenSize = GetBgControlAttribute(bg, BG_CTRL_ATTR_SCREENSIZE);
+
+ switch (whichMetric)
+ {
+ case 0:
+ switch (screenSize)
+ {
+ case 0:
+ return 1;
+ case 1:
+ case 2:
+ return 2;
+ case 3:
+ return 4;
+ }
+ break;
+ case 1:
+ switch (screenSize)
+ {
+ case 0:
+ return 1;
+ case 1:
+ return 2;
+ case 2:
+ return 1;
+ case 3:
+ return 2;
+ }
+ break;
+ case 2:
+ switch (screenSize)
+ {
+ case 0:
+ case 1:
+ return 1;
+ case 2:
+ case 3:
+ return 2;
+ }
+ break;
+ }
+ return 0;
+}
+
+u32 GetBgMetricAffineMode(u8 bg, u8 whichMetric)
+{
+ u8 screenSize = GetBgControlAttribute(bg, BG_CTRL_ATTR_SCREENSIZE);
+
+ switch (whichMetric)
+ {
+ case 0:
+ switch (screenSize)
+ {
+ case 0:
+ return 0x1;
+ case 1:
+ return 0x4;
+ case 2:
+ return 0x10;
+ case 3:
+ return 0x40;
+ }
+ break;
+ case 1:
+ case 2:
+ return 0x10 << screenSize;
+ }
+ return 0;
+}
+
+u32 GetTileMapIndexFromCoords(s32 x, s32 y, s32 screenSize, u32 screenWidth, u32 screenHeight)
+{
+ x = x & (screenWidth - 1);
+ y = y & (screenHeight - 1);
+
+ switch (screenSize)
+ {
+ case 0:
+ case 2:
+ break;
+ case 3:
+ if (y >= 0x20)
+ y += 0x20;
+ case 1:
+ if (x >= 0x20)
+ {
+ x -= 0x20;
+ y += 0x20;
+ }
+ break;
+ }
+ return (y * 0x20) + x;
+}
+
+void CopyTileMapEntry(const u16 *src, u16 *dest, s32 palette1, s32 tileOffset, s32 palette2)
+{
+ u16 var;
+
+ switch (palette1)
+ {
+ case 0 ... 15:
+ var = ((*src + tileOffset) & 0xFFF) + ((palette1 + palette2) << 12);
+ break;
+ case 16:
+ var = *dest;
+ var &= 0xFC00;
+ var += palette2 << 12;
+ var |= (*src + tileOffset) & 0x3FF;
+ break;
+ default:
+ case 17 ... INT_MAX:
+ var = *src + tileOffset + (palette2 << 12);
+ break;
+ }
+ *dest = var;
+}
+
+u32 GetBgType(u8 bg)
+{
+ u8 mode = GetBgMode();
+
+ switch (bg)
+ {
+ case 0:
+ case 1:
+ switch (mode)
+ {
+ case 0:
+ case 1:
+ return 0;
+ }
+ break;
+ case 2:
+ switch (mode)
+ {
+ case 0:
+ return 0;
+ case 1:
+ case 2:
+ return 1;
+ }
+ break;
+ case 3:
+ switch (mode)
+ {
+ case 0:
+ return 0;
+ case 2:
+ return 1;
+ }
+ break;
+ }
+
+ return 0xFFFF;
+}
+
+bool32 IsInvalidBg32(u8 bg)
+{
+ if (bg >= NUM_BACKGROUNDS)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+bool32 IsTileMapOutsideWram(u8 bg)
+{
+ if (sGpuBgConfigs2[bg].tilemap > (void*)IWRAM_END)
+ return TRUE;
+ else if (sGpuBgConfigs2[bg].tilemap == NULL)
+ return TRUE;
+ else
+ return FALSE;
+}
diff --git a/src/blit.c b/src/blit.c
new file mode 100644
index 000000000..bdbb2e2fd
--- /dev/null
+++ b/src/blit.c
@@ -0,0 +1,209 @@
+#include "global.h"
+#include "blit.h"
+
+void BlitBitmapRect4BitWithoutColorKey(const struct Bitmap *src, struct Bitmap *dst, u16 srcX, u16 srcY, u16 dstX, u16 dstY, u16 width, u16 height)
+{
+ BlitBitmapRect4Bit(src, dst, srcX, srcY, dstX, dstY, width, height, 0xFF);
+}
+
+void BlitBitmapRect4Bit(const struct Bitmap *src, struct Bitmap *dst, u16 srcX, u16 srcY, u16 dstX, u16 dstY, u16 width, u16 height, u8 colorKey)
+{
+ s32 xEnd;
+ s32 yEnd;
+ s32 multiplierSrcY;
+ s32 multiplierDstY;
+ s32 loopSrcY, loopDstY;
+ s32 loopSrcX, loopDstX;
+ const u8 *pixelsSrc;
+ u8 *pixelsDst;
+ s32 toOrr;
+ s32 toAnd;
+ s32 toShift;
+
+ if (dst->width - dstX < width)
+ xEnd = (dst->width - dstX) + srcX;
+ else
+ xEnd = srcX + width;
+
+ if (dst->height - dstY < height)
+ yEnd = (dst->height - dstY) + srcY;
+ else
+ yEnd = height + srcY;
+
+ multiplierSrcY = (src->width + (src->width & 7)) >> 3;
+ multiplierDstY = (dst->width + (dst->width & 7)) >> 3;
+
+ if (colorKey == 0xFF)
+ {
+ for (loopSrcY = srcY, loopDstY = dstY; loopSrcY < yEnd; loopSrcY++, loopDstY++)
+ {
+ for (loopSrcX = srcX, loopDstX = dstX; loopSrcX < xEnd; loopSrcX++, loopDstX++)
+ {
+ pixelsSrc = src->pixels + ((loopSrcX >> 1) & 3) + ((loopSrcX >> 3) << 5) + (((loopSrcY >> 3) * multiplierSrcY) << 5) + ((u32)(loopSrcY << 0x1d) >> 0x1B);
+ pixelsDst = dst->pixels + ((loopDstX >> 1) & 3) + ((loopDstX >> 3) << 5) + (((loopDstY >> 3) * multiplierDstY) << 5) + ((u32)(loopDstY << 0x1d) >> 0x1B);
+ toOrr = ((*pixelsSrc >> ((loopSrcX & 1) << 2)) & 0xF);
+ toShift = ((loopDstX & 1) << 2);
+ toOrr <<= toShift;
+ toAnd = 0xF0 >> (toShift);
+ *pixelsDst = toOrr | (*pixelsDst & toAnd);
+ }
+ }
+ }
+ else
+ {
+ for (loopSrcY = srcY, loopDstY = dstY; loopSrcY < yEnd; loopSrcY++, loopDstY++)
+ {
+ for (loopSrcX = srcX, loopDstX = dstX; loopSrcX < xEnd; loopSrcX++, loopDstX++)
+ {
+ pixelsSrc = src->pixels + ((loopSrcX >> 1) & 3) + ((loopSrcX >> 3) << 5) + (((loopSrcY >> 3) * multiplierSrcY) << 5) + ((u32)(loopSrcY << 0x1d) >> 0x1B);
+ pixelsDst = dst->pixels + ((loopDstX >> 1) & 3) + ((loopDstX >> 3) << 5) + (((loopDstY >> 3) * multiplierDstY) << 5) + ((u32)(loopDstY << 0x1d) >> 0x1B);
+ toOrr = ((*pixelsSrc >> ((loopSrcX & 1) << 2)) & 0xF);
+ if (toOrr != colorKey)
+ {
+ toShift = ((loopDstX & 1) << 2);
+ toOrr <<= toShift;
+ toAnd = 0xF0 >> (toShift);
+ *pixelsDst = toOrr | (*pixelsDst & toAnd);
+ }
+ }
+ }
+ }
+}
+
+void FillBitmapRect4Bit(struct Bitmap *surface, u16 x, u16 y, u16 width, u16 height, u8 fillValue)
+{
+ s32 xEnd;
+ s32 yEnd;
+ s32 multiplierY;
+ s32 loopX, loopY;
+ u8 toOrr1, toOrr2;
+
+ xEnd = x + width;
+ if (xEnd > surface->width)
+ xEnd = surface->width;
+
+ yEnd = y + height;
+ if (yEnd > surface->height)
+ yEnd = surface->height;
+
+ multiplierY = (surface->width + (surface->width & 7)) >> 3;
+ toOrr1 = fillValue << 4;
+ toOrr2 = fillValue & 0xF;
+
+ for (loopY = y; loopY < yEnd; loopY++)
+ {
+ for (loopX = x; loopX < xEnd; loopX++)
+ {
+ u8 *pixels = surface->pixels + ((loopX >> 1) & 3) + ((loopX >> 3) << 5) + (((loopY >> 3) * multiplierY) << 5) + ((u32)(loopY << 0x1d) >> 0x1B);
+ if ((loopX << 0x1F) != 0)
+ *pixels = toOrr1 | (*pixels & 0xF);
+ else
+ *pixels = toOrr2 | (*pixels & 0xF0);
+ }
+ }
+}
+
+void BlitBitmapRect4BitTo8Bit(const struct Bitmap *src, struct Bitmap *dst, u16 srcX, u16 srcY, u16 dstX, u16 dstY, u16 width, u16 height, u8 colorKey, u8 paletteOffset)
+{
+ s32 palOffsetBits;
+ s32 xEnd;
+ s32 yEnd;
+ s32 multiplierSrcY;
+ s32 multiplierDstY;
+ s32 loopSrcY, loopDstY;
+ s32 loopSrcX, loopDstX;
+ const u8 *pixelsSrc;
+ u8 *pixelsDst;
+ s32 colorKeyBits;
+
+ palOffsetBits = (u32)(paletteOffset << 0x1C) >> 0x18;
+ colorKeyBits = (u32)(colorKey << 0x1C) >> 0x18;
+
+ if (dst->width - dstX < width)
+ xEnd = (dst->width - dstX) + srcX;
+ else
+ xEnd = width + srcX;
+
+ if (dst->height - dstY < height)
+ yEnd = (srcY + dst->height) - dstY;
+ else
+ yEnd = srcY + height;
+
+ multiplierSrcY = (src->width + (src->width & 7)) >> 3;
+ multiplierDstY = (dst->width + (dst->width & 7)) >> 3;
+
+ if (colorKey == 0xFF)
+ {
+ for (loopSrcY = srcY, loopDstY = dstY; loopSrcY < yEnd; loopSrcY++, loopDstY++)
+ {
+ pixelsSrc = src->pixels + ((srcX >> 1) & 3) + ((srcX >> 3) << 5) + (((loopSrcY >> 3) * multiplierSrcY) << 5) + ((u32)(loopSrcY << 0x1d) >> 0x1b);
+ for (loopSrcX = srcX, loopDstX = dstX; loopSrcX < xEnd; loopSrcX++, loopDstX++)
+ {
+ pixelsDst = dst->pixels + (loopDstX & 7) + ((loopDstX >> 3) << 6) + (((loopDstY >> 3) * multiplierDstY) << 6) + ((u32)(loopDstY << 0x1d) >> 0x1a);
+ if (loopSrcX & 1)
+ {
+ *pixelsDst = palOffsetBits + (*pixelsSrc >> 4);
+ }
+ else
+ {
+ pixelsSrc = src->pixels + ((loopSrcX >> 1) & 3) + ((loopSrcX >> 3) << 5) + (((loopSrcY >> 3) * multiplierSrcY) << 5) + ((u32)(loopSrcY << 0x1d) >> 0x1b);
+ *pixelsDst = palOffsetBits + (*pixelsSrc & 0xF);
+ }
+ }
+ }
+ }
+ else
+ {
+ for (loopSrcY = srcY, loopDstY = dstY; loopSrcY < yEnd; loopSrcY++, loopDstY++)
+ {
+ pixelsSrc = src->pixels + ((srcX >> 1) & 3) + ((srcX >> 3) << 5) + (((loopSrcY >> 3) * multiplierSrcY) << 5) + ((u32)(loopSrcY << 0x1d) >> 0x1b);
+ for (loopSrcX = srcX, loopDstX = dstX; loopSrcX < xEnd; loopSrcX++, loopDstX++)
+ {
+ if (loopSrcX & 1)
+ {
+ if ((*pixelsSrc & 0xF0) != colorKeyBits)
+ {
+ pixelsDst = dst->pixels + (loopDstX & 7) + ((loopDstX >> 3) << 6) + (((loopDstY >> 3) * multiplierDstY) << 6) + ((u32)(loopDstY << 0x1d) >> 0x1a);
+ *pixelsDst = palOffsetBits + (*pixelsSrc >> 4);
+ }
+ }
+ else
+ {
+ pixelsSrc = src->pixels + ((loopSrcX >> 1) & 3) + ((loopSrcX >> 3) << 5) + (((loopSrcY >> 3) * multiplierSrcY) << 5) + ((u32)(loopSrcY << 0x1d) >> 0x1b);
+ if ((*pixelsSrc & 0xF) != colorKey)
+ {
+ pixelsDst = dst->pixels + (loopDstX & 7) + ((loopDstX >> 3) << 6) + (((loopDstY >> 3) * multiplierDstY) << 6) + ((u32)(loopDstY << 0x1d) >> 0x1a);
+ *pixelsDst = palOffsetBits + (*pixelsSrc & 0xF);
+ }
+ }
+ }
+ }
+ }
+}
+
+void FillBitmapRect8Bit(struct Bitmap *surface, u16 x, u16 y, u16 width, u16 height, u8 fillValue)
+{
+ s32 xEnd;
+ s32 yEnd;
+ s32 multiplierY;
+ s32 loopX, loopY;
+
+ xEnd = x + width;
+ if (xEnd > surface->width)
+ xEnd = surface->width;
+
+ yEnd = y + height;
+ if (yEnd > surface->height)
+ yEnd = surface->height;
+
+ multiplierY = (surface->width + (surface->width & 7)) >> 3;
+
+ for (loopY = y; loopY < yEnd; loopY++)
+ {
+ for (loopX = x; loopX < xEnd; loopX++)
+ {
+ u8 *pixels = surface->pixels + (loopX & 7) + ((loopX >> 3) << 6) + (((loopY >> 3) * multiplierY) << 6) + ((u32)(loopY << 0x1d) >> 0x1a);
+ *pixels = fillValue;
+ }
+ }
+}
diff --git a/src/dma3_manager.c b/src/dma3_manager.c
new file mode 100644
index 000000000..d774efe8c
--- /dev/null
+++ b/src/dma3_manager.c
@@ -0,0 +1,183 @@
+#include "global.h"
+#include "dma3.h"
+
+#define MAX_DMA_REQUESTS 128
+
+#define DMA_REQUEST_COPY32 1
+#define DMA_REQUEST_FILL32 2
+#define DMA_REQUEST_COPY16 3
+#define DMA_REQUEST_FILL16 4
+
+struct Dma3Request
+{
+ const u8 *src;
+ u8 *dest;
+ u16 size;
+ u16 mode;
+ u32 value;
+};
+
+static struct Dma3Request sDma3Requests[MAX_DMA_REQUESTS];
+
+static vbool8 sDma3ManagerLocked;
+static u8 sDma3RequestCursor;
+
+void ClearDma3Requests(void)
+{
+ int i;
+
+ sDma3ManagerLocked = TRUE;
+ sDma3RequestCursor = 0;
+
+ for (i = 0; i < MAX_DMA_REQUESTS; i++)
+ {
+ sDma3Requests[i].size = 0;
+ sDma3Requests[i].src = NULL;
+ sDma3Requests[i].dest = NULL;
+ }
+
+ sDma3ManagerLocked = FALSE;
+}
+
+void ProcessDma3Requests(void)
+{
+ u16 bytesTransferred;
+
+ if (sDma3ManagerLocked)
+ return;
+
+ bytesTransferred = 0;
+
+ // as long as there are DMA requests to process (unless size or vblank is an issue), do not exit
+ while (sDma3Requests[sDma3RequestCursor].size != 0)
+ {
+ bytesTransferred += sDma3Requests[sDma3RequestCursor].size;
+
+ if (bytesTransferred > 40 * 1024)
+ return; // don't transfer more than 40 KiB
+ if (*(u8 *)REG_ADDR_VCOUNT > 224)
+ return; // we're about to leave vblank, stop
+
+ switch (sDma3Requests[sDma3RequestCursor].mode)
+ {
+ case DMA_REQUEST_COPY32: // regular 32-bit copy
+ Dma3CopyLarge32_(sDma3Requests[sDma3RequestCursor].src,
+ sDma3Requests[sDma3RequestCursor].dest,
+ sDma3Requests[sDma3RequestCursor].size);
+ break;
+ case DMA_REQUEST_FILL32: // repeat a single 32-bit value across RAM
+ Dma3FillLarge32_(sDma3Requests[sDma3RequestCursor].value,
+ sDma3Requests[sDma3RequestCursor].dest,
+ sDma3Requests[sDma3RequestCursor].size);
+ break;
+ case DMA_REQUEST_COPY16: // regular 16-bit copy
+ Dma3CopyLarge16_(sDma3Requests[sDma3RequestCursor].src,
+ sDma3Requests[sDma3RequestCursor].dest,
+ sDma3Requests[sDma3RequestCursor].size);
+ break;
+ case DMA_REQUEST_FILL16: // repeat a single 16-bit value across RAM
+ Dma3FillLarge16_(sDma3Requests[sDma3RequestCursor].value,
+ sDma3Requests[sDma3RequestCursor].dest,
+ sDma3Requests[sDma3RequestCursor].size);
+ break;
+ }
+
+ // Free the request
+ sDma3Requests[sDma3RequestCursor].src = NULL;
+ sDma3Requests[sDma3RequestCursor].dest = NULL;
+ sDma3Requests[sDma3RequestCursor].size = 0;
+ sDma3Requests[sDma3RequestCursor].mode = 0;
+ sDma3Requests[sDma3RequestCursor].value = 0;
+ sDma3RequestCursor++;
+
+ if (sDma3RequestCursor >= MAX_DMA_REQUESTS) // loop back to the first DMA request
+ sDma3RequestCursor = 0;
+ }
+}
+
+s16 RequestDma3Copy(const void *src, void *dest, u16 size, u8 mode)
+{
+ int cursor;
+ int i = 0;
+
+ sDma3ManagerLocked = TRUE;
+ cursor = sDma3RequestCursor;
+
+ while (i < MAX_DMA_REQUESTS)
+ {
+ if (sDma3Requests[cursor].size == 0) // an empty request was found.
+ {
+ sDma3Requests[cursor].src = src;
+ sDma3Requests[cursor].dest = dest;
+ sDma3Requests[cursor].size = size;
+
+ if (mode == 1)
+ sDma3Requests[cursor].mode = DMA_REQUEST_COPY32;
+ else
+ sDma3Requests[cursor].mode = DMA_REQUEST_COPY16;
+
+ sDma3ManagerLocked = FALSE;
+ return cursor;
+ }
+ if (++cursor >= MAX_DMA_REQUESTS) // loop back to start.
+ cursor = 0;
+ i++;
+ }
+ sDma3ManagerLocked = FALSE;
+ return -1; // no free DMA request was found
+}
+
+s16 RequestDma3Fill(s32 value, void *dest, u16 size, u8 mode)
+{
+ int cursor;
+ int i = 0;
+
+ cursor = sDma3RequestCursor;
+ sDma3ManagerLocked = TRUE;
+
+ while (i < MAX_DMA_REQUESTS)
+ {
+ if (sDma3Requests[cursor].size == 0) // an empty request was found.
+ {
+ sDma3Requests[cursor].dest = dest;
+ sDma3Requests[cursor].size = size;
+ sDma3Requests[cursor].mode = mode;
+ sDma3Requests[cursor].value = value;
+
+ if(mode == 1)
+ sDma3Requests[cursor].mode = DMA_REQUEST_FILL32;
+ else
+ sDma3Requests[cursor].mode = DMA_REQUEST_FILL16;
+
+ sDma3ManagerLocked = FALSE;
+ return cursor;
+ }
+ if (++cursor >= MAX_DMA_REQUESTS) // loop back to start.
+ cursor = 0;
+ i++;
+ }
+ sDma3ManagerLocked = FALSE;
+ return -1; // no free DMA request was found
+}
+
+s16 CheckForSpaceForDma3Request(s16 index)
+{
+ int i = 0;
+
+ if (index == -1) // check if all requests are free
+ {
+ while (i < MAX_DMA_REQUESTS)
+ {
+ if (sDma3Requests[i].size != 0)
+ return -1;
+ i++;
+ }
+ return 0;
+ }
+ else // check the specified request
+ {
+ if (sDma3Requests[index].size != 0)
+ return -1;
+ return 0;
+ }
+}
diff --git a/src/gpu_regs.c b/src/gpu_regs.c
new file mode 100644
index 000000000..3bcc4fd93
--- /dev/null
+++ b/src/gpu_regs.c
@@ -0,0 +1,195 @@
+#include "global.h"
+#include "gpu_regs.h"
+
+#define GPU_REG_BUF_SIZE 0x60
+
+#define GPU_REG_BUF(offset) (*(u16 *)(&sGpuRegBuffer[offset]))
+#define GPU_REG(offset) (*(vu16 *)(REG_BASE + offset))
+
+#define EMPTY_SLOT 0xFF
+
+static u8 sGpuRegBuffer[GPU_REG_BUF_SIZE];
+static u8 sGpuRegWaitingList[GPU_REG_BUF_SIZE];
+static volatile bool8 sGpuRegBufferLocked;
+static volatile bool8 sShouldSyncRegIE;
+static vu16 sRegIE;
+
+static void CopyBufferedValueToGpuReg(u8 regOffset);
+static void SyncRegIE(void);
+static void UpdateRegDispstatIntrBits(u16 regIE);
+
+void InitGpuRegManager(void)
+{
+ s32 i;
+
+ for (i = 0; i < GPU_REG_BUF_SIZE; i++)
+ {
+ sGpuRegBuffer[i] = 0;
+ sGpuRegWaitingList[i] = EMPTY_SLOT;
+ }
+
+ sGpuRegBufferLocked = FALSE;
+ sShouldSyncRegIE = FALSE;
+ sRegIE = 0;
+}
+
+static void CopyBufferedValueToGpuReg(u8 regOffset)
+{
+ if (regOffset == REG_OFFSET_DISPSTAT)
+ {
+ REG_DISPSTAT &= ~(DISPSTAT_HBLANK_INTR | DISPSTAT_VBLANK_INTR);
+ REG_DISPSTAT |= GPU_REG_BUF(REG_OFFSET_DISPSTAT);
+ }
+ else
+ {
+ GPU_REG(regOffset) = GPU_REG_BUF(regOffset);
+ }
+}
+
+void CopyBufferedValuesToGpuRegs(void)
+{
+ if (!sGpuRegBufferLocked)
+ {
+ s32 i;
+
+ for (i = 0; i < GPU_REG_BUF_SIZE; i++)
+ {
+ u8 regOffset = sGpuRegWaitingList[i];
+ if (regOffset == EMPTY_SLOT)
+ return;
+ CopyBufferedValueToGpuReg(regOffset);
+ sGpuRegWaitingList[i] = EMPTY_SLOT;
+ }
+ }
+}
+
+void SetGpuReg(u8 regOffset, u16 value)
+{
+ if (regOffset < GPU_REG_BUF_SIZE)
+ {
+ u16 vcount;
+
+ GPU_REG_BUF(regOffset) = value;
+ vcount = REG_VCOUNT & 0xFF;
+
+ if ((vcount >= 161 && vcount <= 225) || (REG_DISPCNT & DISPCNT_FORCED_BLANK))
+ {
+ CopyBufferedValueToGpuReg(regOffset);
+ }
+ else
+ {
+ s32 i;
+
+ sGpuRegBufferLocked = TRUE;
+
+ for (i = 0; i < GPU_REG_BUF_SIZE && sGpuRegWaitingList[i] != EMPTY_SLOT; i++)
+ {
+ if (sGpuRegWaitingList[i] == regOffset)
+ {
+ sGpuRegBufferLocked = FALSE;
+ return;
+ }
+ }
+
+ sGpuRegWaitingList[i] = regOffset;
+ sGpuRegBufferLocked = FALSE;
+ }
+ }
+}
+
+void SetGpuReg_ForcedBlank(u8 regOffset, u16 value)
+{
+ if (regOffset < GPU_REG_BUF_SIZE)
+ {
+ GPU_REG_BUF(regOffset) = value;
+
+ if (REG_DISPCNT & DISPCNT_FORCED_BLANK)
+ {
+ CopyBufferedValueToGpuReg(regOffset);
+ }
+ else
+ {
+ s32 i;
+
+ sGpuRegBufferLocked = TRUE;
+
+ for (i = 0; i < GPU_REG_BUF_SIZE && sGpuRegWaitingList[i] != EMPTY_SLOT; i++)
+ {
+ if (sGpuRegWaitingList[i] == regOffset)
+ {
+ sGpuRegBufferLocked = FALSE;
+ return;
+ }
+ }
+
+ sGpuRegWaitingList[i] = regOffset;
+ sGpuRegBufferLocked = FALSE;
+ }
+ }
+}
+
+u16 GetGpuReg(u8 regOffset)
+{
+ if (regOffset == REG_OFFSET_DISPSTAT)
+ return REG_DISPSTAT;
+
+ if (regOffset == REG_OFFSET_VCOUNT)
+ return REG_VCOUNT;
+
+ return GPU_REG_BUF(regOffset);
+}
+
+void SetGpuRegBits(u8 regOffset, u16 mask)
+{
+ u16 regValue = GPU_REG_BUF(regOffset);
+ SetGpuReg(regOffset, regValue | mask);
+}
+
+void ClearGpuRegBits(u8 regOffset, u16 mask)
+{
+ u16 regValue = GPU_REG_BUF(regOffset);
+ SetGpuReg(regOffset, regValue & ~mask);
+}
+
+static void SyncRegIE(void)
+{
+ if (sShouldSyncRegIE)
+ {
+ u16 temp = REG_IME;
+ REG_IME = 0;
+ REG_IE = sRegIE;
+ REG_IME = temp;
+ sShouldSyncRegIE = FALSE;
+ }
+}
+
+void EnableInterrupts(u16 mask)
+{
+ sRegIE |= mask;
+ sShouldSyncRegIE = TRUE;
+ SyncRegIE();
+ UpdateRegDispstatIntrBits(sRegIE);
+}
+
+void DisableInterrupts(u16 mask)
+{
+ sRegIE &= ~mask;
+ sShouldSyncRegIE = TRUE;
+ SyncRegIE();
+ UpdateRegDispstatIntrBits(sRegIE);
+}
+
+static void UpdateRegDispstatIntrBits(u16 regIE)
+{
+ u16 oldValue = GetGpuReg(REG_OFFSET_DISPSTAT) & (DISPSTAT_HBLANK_INTR | DISPSTAT_VBLANK_INTR);
+ u16 newValue = 0;
+
+ if (regIE & INTR_FLAG_VBLANK)
+ newValue |= DISPSTAT_VBLANK_INTR;
+
+ if (regIE & INTR_FLAG_HBLANK)
+ newValue |= DISPSTAT_HBLANK_INTR;
+
+ if (oldValue != newValue)
+ SetGpuReg(REG_OFFSET_DISPSTAT, newValue);
+}
diff --git a/src/io_reg.c b/src/io_reg.c
new file mode 100644
index 000000000..44364349d
--- /dev/null
+++ b/src/io_reg.c
@@ -0,0 +1,36 @@
+#include "global.h"
+#include "io_reg.h"
+#include "gba/io_reg.h"
+
+static const u32 sUnused[] = {
+ 0,
+ 0,
+ (1 << 26) | (1 << 3),
+ (1 << 26) | (1 << 3) | (1 << 1),
+ (1 << 26) | (1 << 3) | (1 << 2),
+ (1 << 26) | (1 << 3) | (1 << 2) | (1 << 1),
+ (1 << 26) | (1 << 4),
+ (1 << 26) | (1 << 4) | (1 << 2),
+ (1 << 26) | (1 << 4) | (1 << 3),
+ (1 << 26) | (1 << 4) | (1 << 3) | (1 << 2),
+ (1 << 26) | (1 << 4) | (1 << 1),
+ (1 << 26) | (1 << 4) | (1 << 2) | (1 << 1),
+ (1 << 26) | (1 << 4) | (1 << 3) | (1 << 1),
+ (1 << 26) | (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1),
+ (1 << 25) | (1 << 8),
+ (1 << 27) | (1 << 10),
+};
+
+const u16 gOverworldBackgroundLayerFlags[] = {
+ BLDCNT_TGT2_BG0,
+ BLDCNT_TGT2_BG1,
+ BLDCNT_TGT2_BG2,
+ BLDCNT_TGT2_BG3,
+};
+
+const u16 gOrbEffectBackgroundLayerFlags[] = {
+ BLDCNT_TGT1_BG0,
+ BLDCNT_TGT1_BG1,
+ BLDCNT_TGT1_BG2,
+ BLDCNT_TGT1_BG3,
+};
diff --git a/src/malloc.c b/src/malloc.c
new file mode 100644
index 000000000..38fc8939e
--- /dev/null
+++ b/src/malloc.c
@@ -0,0 +1,210 @@
+#include "global.h"
+
+static void *sHeapStart;
+static u32 sHeapSize;
+static u32 sFiller; // needed to align dma3_manager.o(.bss)
+
+#define MALLOC_SYSTEM_ID 0xA3A3
+
+struct MemBlock {
+ // Whether this block is currently allocated.
+ bool16 flag;
+
+ // Magic number used for error checking. Should equal MALLOC_SYSTEM_ID.
+ u16 magic;
+
+ // Size of the block (not including this header struct).
+ u32 size;
+
+ // Previous block pointer. Equals sHeapStart if this is the first block.
+ struct MemBlock *prev;
+
+ // Next block pointer. Equals sHeapStart if this is the last block.
+ struct MemBlock *next;
+
+ // Data in the memory block. (Arrays of length 0 are a GNU extension.)
+ u8 data[0];
+};
+
+void PutMemBlockHeader(void *block, struct MemBlock *prev, struct MemBlock *next, u32 size)
+{
+ struct MemBlock *header = (struct MemBlock *)block;
+
+ header->flag = FALSE;
+ header->magic = MALLOC_SYSTEM_ID;
+ header->size = size;
+ header->prev = prev;
+ header->next = next;
+}
+
+void PutFirstMemBlockHeader(void *block, u32 size)
+{
+ PutMemBlockHeader(block, (struct MemBlock *)block, (struct MemBlock *)block, size - sizeof(struct MemBlock));
+}
+
+void *AllocInternal(void *heapStart, u32 size)
+{
+ struct MemBlock *pos = (struct MemBlock *)heapStart;
+ struct MemBlock *head = pos;
+ struct MemBlock *splitBlock;
+ u32 foundBlockSize;
+
+ // Alignment
+ if (size & 3)
+ size = 4 * ((size / 4) + 1);
+
+ for (;;) {
+ // Loop through the blocks looking for unused block that's big enough.
+
+ if (!pos->flag) {
+ foundBlockSize = pos->size;
+
+ if (foundBlockSize >= size) {
+ if (foundBlockSize - size < 2 * sizeof(struct MemBlock)) {
+ // The block isn't much bigger than the requested size,
+ // so just use it.
+ pos->flag = TRUE;
+ } else {
+ // The block is significantly bigger than the requested
+ // size, so split the rest into a separate block.
+ foundBlockSize -= sizeof(struct MemBlock);
+ foundBlockSize -= size;
+
+ splitBlock = (struct MemBlock *)(pos->data + size);
+
+ pos->flag = TRUE;
+ pos->size = size;
+
+ PutMemBlockHeader(splitBlock, pos, pos->next, foundBlockSize);
+
+ pos->next = splitBlock;
+
+ if (splitBlock->next != head)
+ splitBlock->next->prev = splitBlock;
+ }
+
+ return pos->data;
+ }
+ }
+
+ if (pos->next == head)
+ return NULL;
+
+ pos = pos->next;
+ }
+}
+
+void FreeInternal(void *heapStart, void *pointer)
+{
+ if (pointer) {
+ struct MemBlock *head = (struct MemBlock *)heapStart;
+ struct MemBlock *block = (struct MemBlock *)((u8 *)pointer - sizeof(struct MemBlock));
+ block->flag = FALSE;
+
+ // If the freed block isn't the last one, merge with the next block
+ // if it's not in use.
+ if (block->next != head) {
+ if (!block->next->flag) {
+ block->size += sizeof(struct MemBlock) + block->next->size;
+ block->next->magic = 0;
+ block->next = block->next->next;
+ if (block->next != head)
+ block->next->prev = block;
+ }
+ }
+
+ // If the freed block isn't the first one, merge with the previous block
+ // if it's not in use.
+ if (block != head) {
+ if (!block->prev->flag) {
+ block->prev->next = block->next;
+
+ if (block->next != head)
+ block->next->prev = block->prev;
+
+ block->magic = 0;
+ block->prev->size += sizeof(struct MemBlock) + block->size;
+ }
+ }
+ }
+}
+
+void *AllocZeroedInternal(void *heapStart, u32 size)
+{
+ void *mem = AllocInternal(heapStart, size);
+
+ if (mem != NULL) {
+ if (size & 3)
+ size = 4 * ((size / 4) + 1);
+
+ CpuFill32(0, mem, size);
+ }
+
+ return mem;
+}
+
+bool32 CheckMemBlockInternal(void *heapStart, void *pointer)
+{
+ struct MemBlock *head = (struct MemBlock *)heapStart;
+ struct MemBlock *block = (struct MemBlock *)((u8 *)pointer - sizeof(struct MemBlock));
+
+ if (block->magic != MALLOC_SYSTEM_ID)
+ return FALSE;
+
+ if (block->next->magic != MALLOC_SYSTEM_ID)
+ return FALSE;
+
+ if (block->next != head && block->next->prev != block)
+ return FALSE;
+
+ if (block->prev->magic != MALLOC_SYSTEM_ID)
+ return FALSE;
+
+ if (block->prev != head && block->prev->next != block)
+ return FALSE;
+
+ if (block->next != head && block->next != (struct MemBlock *)(block->data + block->size))
+ return FALSE;
+
+ return TRUE;
+}
+
+void InitHeap(void *heapStart, u32 heapSize)
+{
+ sHeapStart = heapStart;
+ sHeapSize = heapSize;
+ PutFirstMemBlockHeader(heapStart, heapSize);
+}
+
+void *Alloc(u32 size)
+{
+ return AllocInternal(sHeapStart, size);
+}
+
+void *AllocZeroed(u32 size)
+{
+ return AllocZeroedInternal(sHeapStart, size);
+}
+
+void Free(void *pointer)
+{
+ FreeInternal(sHeapStart, pointer);
+}
+
+bool32 CheckMemBlock(void *pointer)
+{
+ return CheckMemBlockInternal(sHeapStart, pointer);
+}
+
+bool32 CheckHeap()
+{
+ struct MemBlock *pos = (struct MemBlock *)sHeapStart;
+
+ do {
+ if (!CheckMemBlockInternal(sHeapStart, pos->data))
+ return FALSE;
+ pos = pos->next;
+ } while (pos != (struct MemBlock *)sHeapStart);
+
+ return TRUE;
+}
diff --git a/src/sprite.c b/src/sprite.c
new file mode 100644
index 000000000..f97ecc712
--- /dev/null
+++ b/src/sprite.c
@@ -0,0 +1,1775 @@
+#include "global.h"
+#include "sprite.h"
+#include "main.h"
+#include "palette.h"
+
+#define MAX_SPRITE_COPY_REQUESTS 64
+
+#define OAM_MATRIX_COUNT 32
+
+#define SET_SPRITE_TILE_RANGE(index, start, count) \
+{ \
+ sSpriteTileRanges[index * 2] = start; \
+ (sSpriteTileRanges + 1)[index * 2] = count; \
+}
+
+#define ALLOC_SPRITE_TILE(n) \
+{ \
+ sSpriteTileAllocBitmap[(n) / 8] |= (1 << ((n) % 8)); \
+}
+
+#define FREE_SPRITE_TILE(n) \
+{ \
+ sSpriteTileAllocBitmap[(n) / 8] &= ~(1 << ((n) % 8)); \
+}
+
+#define SPRITE_TILE_IS_ALLOCATED(n) ((sSpriteTileAllocBitmap[(n) / 8] >> ((n) % 8)) & 1)
+
+
+struct SpriteCopyRequest
+{
+ const u8 *src;
+ u8 *dest;
+ u16 size;
+};
+
+struct OamDimensions32
+{
+ s32 width;
+ s32 height;
+};
+
+struct OamDimensions
+{
+ s8 width;
+ s8 height;
+};
+
+static void UpdateOamCoords(void);
+static void BuildSpritePriorities(void);
+static void SortSprites(void);
+static void CopyMatricesToOamBuffer(void);
+static void AddSpritesToOamBuffer(void);
+static u8 CreateSpriteAt(u8 index, const struct SpriteTemplate *template, s16 x, s16 y, u8 subpriority);
+static void ResetOamMatrices(void);
+static void ResetSprite(struct Sprite *sprite);
+static s16 AllocSpriteTiles(u16 tileCount);
+static void RequestSpriteFrameImageCopy(u16 index, u16 tileNum, const struct SpriteFrameImage *images);
+static void ResetAllSprites(void);
+static void BeginAnim(struct Sprite *sprite);
+static void ContinueAnim(struct Sprite *sprite);
+static void AnimCmd_frame(struct Sprite *sprite);
+static void AnimCmd_end(struct Sprite *sprite);
+static void AnimCmd_jump(struct Sprite *sprite);
+static void AnimCmd_loop(struct Sprite *sprite);
+static void BeginAnimLoop(struct Sprite *sprite);
+static void ContinueAnimLoop(struct Sprite *sprite);
+static void JumpToTopOfAnimLoop(struct Sprite *sprite);
+static void BeginAffineAnim(struct Sprite *sprite);
+static void ContinueAffineAnim(struct Sprite *sprite);
+static void AffineAnimDelay(u8 matrixNum, struct Sprite *sprite);
+static void AffineAnimCmd_loop(u8 matrixNum, struct Sprite *sprite);
+static void BeginAffineAnimLoop(u8 matrixNum, struct Sprite *sprite);
+static void ContinueAffineAnimLoop(u8 matrixNum, struct Sprite *sprite);
+static void JumpToTopOfAffineAnimLoop(u8 matrixNum, struct Sprite *sprite);
+static void AffineAnimCmd_jump(u8 matrixNum, struct Sprite *sprite);
+static void AffineAnimCmd_end(u8 matrixNum, struct Sprite *sprite);
+static void AffineAnimCmd_frame(u8 matrixNum, struct Sprite *sprite);
+static void CopyOamMatrix(u8 destMatrixIndex, struct OamMatrix *srcMatrix);
+static u8 GetSpriteMatrixNum(struct Sprite *sprite);
+static void SetSpriteOamFlipBits(struct Sprite *sprite, u8 hFlip, u8 vFlip);
+static void AffineAnimStateRestartAnim(u8 matrixNum);
+static void AffineAnimStateStartAnim(u8 matrixNum, u8 animNum);
+static void AffineAnimStateReset(u8 matrixNum);
+static void ApplyAffineAnimFrameAbsolute(u8 matrixNum, struct AffineAnimFrameCmd *frameCmd);
+static void DecrementAnimDelayCounter(struct Sprite *sprite);
+static bool8 DecrementAffineAnimDelayCounter(struct Sprite *sprite, u8 matrixNum);
+static void ApplyAffineAnimFrameRelativeAndUpdateMatrix(u8 matrixNum, struct AffineAnimFrameCmd *frameCmd);
+static s16 ConvertScaleParam(s16 scale);
+static void GetAffineAnimFrame(u8 matrixNum, struct Sprite *sprite, struct AffineAnimFrameCmd *frameCmd);
+static void ApplyAffineAnimFrame(u8 matrixNum, struct AffineAnimFrameCmd *frameCmd);
+static u8 IndexOfSpriteTileTag(u16 tag);
+static void AllocSpriteTileRange(u16 tag, u16 start, u16 count);
+static void DoLoadSpritePalette(const u16 *src, u16 paletteOffset);
+static void obj_update_pos2(struct Sprite* sprite, s32 a1, s32 a2);
+
+typedef void (*AnimFunc)(struct Sprite *);
+typedef void (*AnimCmdFunc)(struct Sprite *);
+typedef void (*AffineAnimCmdFunc)(u8 matrixNum, struct Sprite *);
+
+#define DUMMY_OAM_DATA \
+{ \
+ .y = 160, \
+ .affineMode = 0, \
+ .objMode = 0, \
+ .mosaic = 0, \
+ .bpp = 0, \
+ .shape = SPRITE_SHAPE(8x8), \
+ .x = 304, \
+ .matrixNum = 0, \
+ .size = SPRITE_SIZE(8x8), \
+ .tileNum = 0, \
+ .priority = 3, /* lowest priority */ \
+ .paletteNum = 0, \
+ .affineParam = 0 \
+}
+
+#define ANIM_END 0xFFFF
+#define AFFINE_ANIM_END 0x7FFF
+
+// forward declarations
+const union AnimCmd * const gDummySpriteAnimTable[];
+const union AffineAnimCmd * const gDummySpriteAffineAnimTable[];
+const struct SpriteTemplate gDummySpriteTemplate;
+
+// Unreferenced data. Also unreferenced in R/S.
+static const u8 sUnknownData[24] =
+{
+ 0x01, 0x04, 0x10, 0x40,
+ 0x02, 0x04, 0x08, 0x20,
+ 0x02, 0x04, 0x08, 0x20,
+ 0x01, 0x04, 0x10, 0x40,
+ 0x02, 0x04, 0x08, 0x20,
+ 0x02, 0x04, 0x08, 0x20,
+};
+
+static const u8 sCenterToCornerVecTable[3][4][2] =
+{
+ { // square
+ { -4, -4 },
+ { -8, -8 },
+ { -16, -16 },
+ { -32, -32 },
+ },
+ { // horizontal rectangle
+ { -8, -4 },
+ { -16, -4 },
+ { -16, -8 },
+ { -32, -16 },
+ },
+ { // vertical rectangle
+ { -4, -8 },
+ { -4, -16 },
+ { -8, -16 },
+ { -16, -32 },
+ },
+};
+
+static const struct Sprite sDummySprite =
+{
+ .oam = DUMMY_OAM_DATA,
+ .anims = gDummySpriteAnimTable,
+ .images = NULL,
+ .affineAnims = gDummySpriteAffineAnimTable,
+ .template = &gDummySpriteTemplate,
+ .subspriteTables = NULL,
+ .callback = SpriteCallbackDummy,
+ .pos1 = { 304, 160 },
+ .pos2 = { 0, 0 },
+ .centerToCornerVecX = 0,
+ .centerToCornerVecY = 0,
+ .animNum = 0,
+ .animCmdIndex = 0,
+ .animDelayCounter = 0,
+ .animPaused = 0,
+ .affineAnimPaused = 0,
+ .animLoopCounter = 0,
+ .data = {0, 0, 0, 0, 0, 0, 0},
+ .inUse = 0,
+ .coordOffsetEnabled = 0,
+ .invisible = FALSE,
+ .flags_3 = 0,
+ .flags_4 = 0,
+ .flags_5 = 0,
+ .flags_6 = 0,
+ .flags_7 = 0,
+ .hFlip = 0,
+ .vFlip = 0,
+ .animBeginning = 0,
+ .affineAnimBeginning = 0,
+ .animEnded = 0,
+ .affineAnimEnded = 0,
+ .usingSheet = 0,
+ .flags_f = 0,
+ .sheetTileStart = 0,
+ .subspriteTableNum = 0,
+ .subspriteMode = 0,
+ .subpriority = 0xFF
+};
+
+const struct OamData gDummyOamData = DUMMY_OAM_DATA;
+
+static const union AnimCmd sDummyAnim = { ANIM_END };
+
+const union AnimCmd * const gDummySpriteAnimTable[] = { &sDummyAnim };
+
+static const union AffineAnimCmd sDummyAffineAnim = { AFFINE_ANIM_END };
+
+const union AffineAnimCmd * const gDummySpriteAffineAnimTable[] = { &sDummyAffineAnim };
+
+const struct SpriteTemplate gDummySpriteTemplate =
+{
+ .tileTag = 0,
+ .paletteTag = 0xFFFF,
+ .oam = &gDummyOamData,
+ .anims = gDummySpriteAnimTable,
+ .images = NULL,
+ .affineAnims = gDummySpriteAffineAnimTable,
+ .callback = SpriteCallbackDummy
+};
+
+static const AnimFunc sAnimFuncs[] =
+{
+ ContinueAnim,
+ BeginAnim,
+};
+
+static const AnimFunc sAffineAnimFuncs[] =
+{
+ ContinueAffineAnim,
+ BeginAffineAnim,
+};
+
+static const AnimCmdFunc sAnimCmdFuncs[] =
+{
+ AnimCmd_loop,
+ AnimCmd_jump,
+ AnimCmd_end,
+ AnimCmd_frame,
+};
+
+static const AffineAnimCmdFunc sAffineAnimCmdFuncs[] =
+{
+ AffineAnimCmd_loop,
+ AffineAnimCmd_jump,
+ AffineAnimCmd_end,
+ AffineAnimCmd_frame,
+};
+
+static const struct OamDimensions32 sOamDimensions32[3][4] =
+{
+ [ST_OAM_SQUARE] =
+ {
+ [SPRITE_SIZE(8x8)] = { 8, 8 },
+ [SPRITE_SIZE(16x16)] = { 16, 16 },
+ [SPRITE_SIZE(32x32)] = { 32, 32 },
+ [SPRITE_SIZE(64x64)] = { 64, 64 },
+ },
+ [ST_OAM_H_RECTANGLE] =
+ {
+ [SPRITE_SIZE(16x8)] = { 16, 8 },
+ [SPRITE_SIZE(32x8)] = { 32, 8 },
+ [SPRITE_SIZE(32x16)] = { 32, 16 },
+ [SPRITE_SIZE(64x32)] = { 64, 32 },
+ },
+ [ST_OAM_V_RECTANGLE] =
+ {
+ [SPRITE_SIZE(8x16)] = { 8, 16 },
+ [SPRITE_SIZE(8x32)] = { 8, 32 },
+ [SPRITE_SIZE(16x32)] = { 16, 32 },
+ [SPRITE_SIZE(32x64)] = { 32, 64 },
+ },
+};
+
+static const struct OamDimensions sOamDimensions[3][4] =
+{
+ [ST_OAM_SQUARE] =
+ {
+ [SPRITE_SIZE(8x8)] = { 8, 8 },
+ [SPRITE_SIZE(16x16)] = { 16, 16 },
+ [SPRITE_SIZE(32x32)] = { 32, 32 },
+ [SPRITE_SIZE(64x64)] = { 64, 64 },
+ },
+ [ST_OAM_H_RECTANGLE] =
+ {
+ [SPRITE_SIZE(16x8)] = { 16, 8 },
+ [SPRITE_SIZE(32x8)] = { 32, 8 },
+ [SPRITE_SIZE(32x16)] = { 32, 16 },
+ [SPRITE_SIZE(64x32)] = { 64, 32 },
+ },
+ [ST_OAM_V_RECTANGLE] =
+ {
+ [SPRITE_SIZE(8x16)] = { 8, 16 },
+ [SPRITE_SIZE(8x32)] = { 8, 32 },
+ [SPRITE_SIZE(16x32)] = { 16, 32 },
+ [SPRITE_SIZE(32x64)] = { 32, 64 },
+ },
+};
+
+// iwram bss
+static u16 sSpriteTileRangeTags[MAX_SPRITES];
+static u16 sSpriteTileRanges[MAX_SPRITES * 2];
+static struct AffineAnimState sAffineAnimStates[OAM_MATRIX_COUNT];
+static u16 sSpritePaletteTags[16];
+
+// iwram common
+u32 gOamMatrixAllocBitmap;
+u8 gReservedSpritePaletteCount;
+
+EWRAM_DATA struct Sprite gSprites[MAX_SPRITES + 1] = {0};
+EWRAM_DATA static u16 sSpritePriorities[MAX_SPRITES] = {0};
+EWRAM_DATA static u8 sSpriteOrder[MAX_SPRITES] = {0};
+EWRAM_DATA static bool8 sShouldProcessSpriteCopyRequests = 0;
+EWRAM_DATA static u8 sSpriteCopyRequestCount = 0;
+EWRAM_DATA static struct SpriteCopyRequest sSpriteCopyRequests[MAX_SPRITES] = {0};
+EWRAM_DATA u8 gOamLimit = 0;
+EWRAM_DATA u16 gReservedSpriteTileCount = 0;
+EWRAM_DATA static u8 sSpriteTileAllocBitmap[128] = {0};
+EWRAM_DATA s16 gSpriteCoordOffsetX = 0;
+EWRAM_DATA s16 gSpriteCoordOffsetY = 0;
+EWRAM_DATA struct OamMatrix gOamMatrices[OAM_MATRIX_COUNT] = {0};
+EWRAM_DATA bool8 gAffineAnimsDisabled = FALSE;
+
+void ResetSpriteData(void)
+{
+ ResetOamRange(0, 128);
+ ResetAllSprites();
+ ClearSpriteCopyRequests();
+ ResetAffineAnimData();
+ FreeSpriteTileRanges();
+ gOamLimit = 64;
+ gReservedSpriteTileCount = 0;
+ AllocSpriteTiles(0);
+ gSpriteCoordOffsetX = 0;
+ gSpriteCoordOffsetY = 0;
+}
+
+void AnimateSprites(void)
+{
+ u8 i;
+ for (i = 0; i < MAX_SPRITES; i++)
+ {
+ struct Sprite *sprite = &gSprites[i];
+
+ if (sprite->inUse)
+ {
+ sprite->callback(sprite);
+
+ if (sprite->inUse)
+ AnimateSprite(sprite);
+ }
+ }
+}
+
+void BuildOamBuffer(void)
+{
+ u8 temp;
+ UpdateOamCoords();
+ BuildSpritePriorities();
+ SortSprites();
+ temp = gMain.oamLoadDisabled;
+ gMain.oamLoadDisabled = TRUE;
+ AddSpritesToOamBuffer();
+ CopyMatricesToOamBuffer();
+ gMain.oamLoadDisabled = temp;
+ sShouldProcessSpriteCopyRequests = TRUE;
+}
+
+void UpdateOamCoords(void)
+{
+ u8 i;
+ for (i = 0; i < MAX_SPRITES; i++)
+ {
+ struct Sprite *sprite = &gSprites[i];
+ if (sprite->inUse && !sprite->invisible)
+ {
+ if (sprite->coordOffsetEnabled)
+ {
+ sprite->oam.x = sprite->pos1.x + sprite->pos2.x + sprite->centerToCornerVecX + gSpriteCoordOffsetX;
+ sprite->oam.y = sprite->pos1.y + sprite->pos2.y + sprite->centerToCornerVecY + gSpriteCoordOffsetY;
+ }
+ else
+ {
+ sprite->oam.x = sprite->pos1.x + sprite->pos2.x + sprite->centerToCornerVecX;
+ sprite->oam.y = sprite->pos1.y + sprite->pos2.y + sprite->centerToCornerVecY;
+ }
+ }
+ }
+}
+
+void BuildSpritePriorities(void)
+{
+ u16 i;
+ for (i = 0; i < MAX_SPRITES; i++)
+ {
+ struct Sprite *sprite = &gSprites[i];
+ u16 priority = sprite->subpriority | (sprite->oam.priority << 8);
+ sSpritePriorities[i] = priority;
+ }
+}
+
+void SortSprites(void)
+{
+ u8 i;
+ for (i = 1; i < MAX_SPRITES; i++)
+ {
+ u8 j = i;
+ struct Sprite *sprite1 = &gSprites[sSpriteOrder[i - 1]];
+ struct Sprite *sprite2 = &gSprites[sSpriteOrder[i]];
+ u16 sprite1Priority = sSpritePriorities[sSpriteOrder[i - 1]];
+ u16 sprite2Priority = sSpritePriorities[sSpriteOrder[i]];
+ s16 sprite1Y = sprite1->oam.y;
+ s16 sprite2Y = sprite2->oam.y;
+
+ if (sprite1Y >= DISPLAY_HEIGHT)
+ sprite1Y = sprite1Y - 256;
+
+ if (sprite2Y >= DISPLAY_HEIGHT)
+ sprite2Y = sprite2Y - 256;
+
+ if (sprite1->oam.affineMode == ST_OAM_AFFINE_DOUBLE
+ && sprite1->oam.size == ST_OAM_SIZE_3)
+ {
+ u32 shape = sprite1->oam.shape;
+ if (shape == ST_OAM_SQUARE || shape == ST_OAM_V_RECTANGLE)
+ {
+ if (sprite1Y > 128)
+ sprite1Y = sprite1Y - 256;
+ }
+ }
+
+ if (sprite2->oam.affineMode == ST_OAM_AFFINE_DOUBLE
+ && sprite2->oam.size == ST_OAM_SIZE_3)
+ {
+ u32 shape = sprite2->oam.shape;
+ if (shape == ST_OAM_SQUARE || shape == ST_OAM_V_RECTANGLE)
+ {
+ if (sprite2Y > 128)
+ sprite2Y = sprite2Y - 256;
+ }
+ }
+
+ while (j > 0
+ && ((sprite1Priority > sprite2Priority)
+ || (sprite1Priority == sprite2Priority && sprite1Y < sprite2Y)))
+ {
+ u8 temp = sSpriteOrder[j];
+ sSpriteOrder[j] = sSpriteOrder[j - 1];
+ sSpriteOrder[j - 1] = temp;
+
+ // UB: If j equals 1, then j-- makes j equal 0.
+ // Then, sSpriteOrder[-1] gets accessed below.
+ // Although this doesn't result in a bug in the ROM,
+ // the behavior is undefined.
+ j--;
+#ifdef UBFIX
+ if (j == 0)
+ break;
+#endif
+
+ sprite1 = &gSprites[sSpriteOrder[j - 1]];
+ sprite2 = &gSprites[sSpriteOrder[j]];
+ sprite1Priority = sSpritePriorities[sSpriteOrder[j - 1]];
+ sprite2Priority = sSpritePriorities[sSpriteOrder[j]];
+ sprite1Y = sprite1->oam.y;
+ sprite2Y = sprite2->oam.y;
+
+ if (sprite1Y >= DISPLAY_HEIGHT)
+ sprite1Y = sprite1Y - 256;
+
+ if (sprite2Y >= DISPLAY_HEIGHT)
+ sprite2Y = sprite2Y - 256;
+
+ if (sprite1->oam.affineMode == ST_OAM_AFFINE_DOUBLE
+ && sprite1->oam.size == ST_OAM_SIZE_3)
+ {
+ u32 shape = sprite1->oam.shape;
+ if (shape == ST_OAM_SQUARE || shape == ST_OAM_V_RECTANGLE)
+ {
+ if (sprite1Y > 128)
+ sprite1Y = sprite1Y - 256;
+ }
+ }
+
+ if (sprite2->oam.affineMode == ST_OAM_AFFINE_DOUBLE
+ && sprite2->oam.size == ST_OAM_SIZE_3)
+ {
+ u32 shape = sprite2->oam.shape;
+ if (shape == ST_OAM_SQUARE || shape == ST_OAM_V_RECTANGLE)
+ {
+ if (sprite2Y > 128)
+ sprite2Y = sprite2Y - 256;
+ }
+ }
+ }
+ }
+}
+
+void CopyMatricesToOamBuffer(void)
+{
+ u8 i;
+ for (i = 0; i < OAM_MATRIX_COUNT; i++)
+ {
+ u32 base = 4 * i;
+ gMain.oamBuffer[base + 0].affineParam = gOamMatrices[i].a;
+ gMain.oamBuffer[base + 1].affineParam = gOamMatrices[i].b;
+ gMain.oamBuffer[base + 2].affineParam = gOamMatrices[i].c;
+ gMain.oamBuffer[base + 3].affineParam = gOamMatrices[i].d;
+ }
+}
+
+void AddSpritesToOamBuffer(void)
+{
+ u8 i = 0;
+ u8 oamIndex = 0;
+
+ while (i < MAX_SPRITES)
+ {
+ struct Sprite *sprite = &gSprites[sSpriteOrder[i]];
+ if (sprite->inUse && !sprite->invisible && AddSpriteToOamBuffer(sprite, &oamIndex))
+ return;
+ i++;
+ }
+
+ while (oamIndex < gOamLimit)
+ {
+ gMain.oamBuffer[oamIndex] = gDummyOamData;
+ oamIndex++;
+ }
+}
+
+u8 CreateSprite(const struct SpriteTemplate *template, s16 x, s16 y, u8 subpriority)
+{
+ u8 i;
+
+ for (i = 0; i < MAX_SPRITES; i++)
+ if (!gSprites[i].inUse)
+ return CreateSpriteAt(i, template, x, y, subpriority);
+
+ return MAX_SPRITES;
+}
+
+u8 CreateSpriteAtEnd(const struct SpriteTemplate *template, s16 x, s16 y, u8 subpriority)
+{
+ s16 i;
+
+ for (i = MAX_SPRITES - 1; i > -1; i--)
+ if (!gSprites[i].inUse)
+ return CreateSpriteAt(i, template, x, y, subpriority);
+
+ return MAX_SPRITES;
+}
+
+u8 CreateInvisibleSprite(void (*callback)(struct Sprite *))
+{
+ u8 index = CreateSprite(&gDummySpriteTemplate, 0, 0, 31);
+
+ if (index == MAX_SPRITES)
+ {
+ return MAX_SPRITES;
+ }
+ else
+ {
+ gSprites[index].invisible = TRUE;
+ gSprites[index].callback = callback;
+ return index;
+ }
+}
+
+u8 CreateSpriteAt(u8 index, const struct SpriteTemplate *template, s16 x, s16 y, u8 subpriority)
+{
+ struct Sprite *sprite = &gSprites[index];
+
+ ResetSprite(sprite);
+
+ sprite->inUse = TRUE;
+ sprite->animBeginning = TRUE;
+ sprite->affineAnimBeginning = TRUE;
+ sprite->usingSheet = TRUE;
+
+ sprite->subpriority = subpriority;
+ sprite->oam = *template->oam;
+ sprite->anims = template->anims;
+ sprite->affineAnims = template->affineAnims;
+ sprite->template = template;
+ sprite->callback = template->callback;
+ sprite->pos1.x = x;
+ sprite->pos1.y = y;
+
+ CalcCenterToCornerVec(sprite, sprite->oam.shape, sprite->oam.size, sprite->oam.affineMode);
+
+ if (template->tileTag == 0xFFFF)
+ {
+ s16 tileNum;
+ sprite->images = template->images;
+ tileNum = AllocSpriteTiles((u8)(sprite->images->size / TILE_SIZE_4BPP));
+ if (tileNum == -1)
+ {
+ ResetSprite(sprite);
+ return MAX_SPRITES;
+ }
+ sprite->oam.tileNum = tileNum;
+ sprite->usingSheet = FALSE;
+ sprite->sheetTileStart = 0;
+ }
+ else
+ {
+ sprite->sheetTileStart = GetSpriteTileStartByTag(template->tileTag);
+ SetSpriteSheetFrameTileNum(sprite);
+ }
+
+ if (sprite->oam.affineMode & ST_OAM_AFFINE_ON_MASK)
+ InitSpriteAffineAnim(sprite);
+
+ if (template->paletteTag != 0xFFFF)
+ sprite->oam.paletteNum = IndexOfSpritePaletteTag(template->paletteTag);
+
+ return index;
+}
+
+u8 CreateSpriteAndAnimate(const struct SpriteTemplate *template, s16 x, s16 y, u8 subpriority)
+{
+ u8 i;
+
+ for (i = 0; i < MAX_SPRITES; i++)
+ {
+ struct Sprite *sprite = &gSprites[i];
+
+ if (!gSprites[i].inUse)
+ {
+ u8 index = CreateSpriteAt(i, template, x, y, subpriority);
+
+ if (index == MAX_SPRITES)
+ return MAX_SPRITES;
+
+ gSprites[i].callback(sprite);
+
+ if (gSprites[i].inUse)
+ AnimateSprite(sprite);
+
+ return index;
+ }
+ }
+
+ return MAX_SPRITES;
+}
+
+void DestroySprite(struct Sprite *sprite)
+{
+ if (sprite->inUse)
+ {
+ if (!sprite->usingSheet)
+ {
+ u16 i;
+ u16 tileEnd = (sprite->images->size / TILE_SIZE_4BPP) + sprite->oam.tileNum;
+ for (i = sprite->oam.tileNum; i < tileEnd; i++)
+ FREE_SPRITE_TILE(i);
+ }
+ ResetSprite(sprite);
+ }
+}
+
+void ResetOamRange(u8 a, u8 b)
+{
+ u8 i;
+
+ for (i = a; i < b; i++)
+ {
+ gMain.oamBuffer[i] = *(struct OamData *)&gDummyOamData;
+ }
+}
+
+void LoadOam(void)
+{
+ if (!gMain.oamLoadDisabled)
+ CpuCopy32(gMain.oamBuffer, (void *)OAM, sizeof(gMain.oamBuffer));
+}
+
+void ClearSpriteCopyRequests(void)
+{
+ u8 i;
+
+ sShouldProcessSpriteCopyRequests = FALSE;
+ sSpriteCopyRequestCount = 0;
+
+ for (i = 0; i < MAX_SPRITE_COPY_REQUESTS; i++)
+ {
+ sSpriteCopyRequests[i].src = 0;
+ sSpriteCopyRequests[i].dest = 0;
+ sSpriteCopyRequests[i].size = 0;
+ }
+}
+
+void ResetOamMatrices(void)
+{
+ u8 i;
+ for (i = 0; i < OAM_MATRIX_COUNT; i++)
+ {
+ // set to identity matrix
+ gOamMatrices[i].a = 0x0100;
+ gOamMatrices[i].b = 0x0000;
+ gOamMatrices[i].c = 0x0000;
+ gOamMatrices[i].d = 0x0100;
+ }
+}
+
+void SetOamMatrix(u8 matrixNum, u16 a, u16 b, u16 c, u16 d)
+{
+ gOamMatrices[matrixNum].a = a;
+ gOamMatrices[matrixNum].b = b;
+ gOamMatrices[matrixNum].c = c;
+ gOamMatrices[matrixNum].d = d;
+}
+
+void ResetSprite(struct Sprite *sprite)
+{
+ *sprite = sDummySprite;
+}
+
+void CalcCenterToCornerVec(struct Sprite *sprite, u8 shape, u8 size, u8 affineMode)
+{
+ u8 x = sCenterToCornerVecTable[shape][size][0];
+ u8 y = sCenterToCornerVecTable[shape][size][1];
+
+ if (affineMode & ST_OAM_AFFINE_DOUBLE_MASK)
+ {
+ x *= 2;
+ y *= 2;
+ }
+
+ sprite->centerToCornerVecX = x;
+ sprite->centerToCornerVecY = y;
+}
+
+s16 AllocSpriteTiles(u16 tileCount)
+{
+ u16 i;
+ s16 start;
+ u16 numTilesFound;
+
+ if (tileCount == 0)
+ {
+ // Free all unreserved tiles if the tile count is 0.
+ for (i = gReservedSpriteTileCount; i < TOTAL_OBJ_TILE_COUNT; i++)
+ FREE_SPRITE_TILE(i);
+
+ return 0;
+ }
+
+ i = gReservedSpriteTileCount;
+
+ for (;;)
+ {
+ while (SPRITE_TILE_IS_ALLOCATED(i))
+ {
+ i++;
+
+ if (i == TOTAL_OBJ_TILE_COUNT)
+ return -1;
+ }
+
+ start = i;
+ numTilesFound = 1;
+
+ while (numTilesFound != tileCount)
+ {
+ i++;
+
+ if (i == TOTAL_OBJ_TILE_COUNT)
+ return -1;
+
+ if (!SPRITE_TILE_IS_ALLOCATED(i))
+ numTilesFound++;
+ else
+ break;
+ }
+
+ if (numTilesFound == tileCount)
+ break;
+ }
+
+ for (i = start; i < tileCount + start; i++)
+ ALLOC_SPRITE_TILE(i);
+
+ return start;
+}
+
+u8 SpriteTileAllocBitmapOp(u16 bit, u8 op)
+{
+ u8 index = bit / 8;
+ u8 shift = bit % 8;
+ u8 val = bit % 8;
+ u8 retVal = 0;
+
+ if (op == 0)
+ {
+ val = ~(1 << val);
+ sSpriteTileAllocBitmap[index] &= val;
+ }
+ else if (op == 1)
+ {
+ val = (1 << val);
+ sSpriteTileAllocBitmap[index] |= val;
+ }
+ else
+ {
+ retVal = 1 << shift;
+ retVal &= sSpriteTileAllocBitmap[index];
+ }
+
+ return retVal;
+}
+
+void SpriteCallbackDummy(struct Sprite *sprite)
+{
+}
+
+void ProcessSpriteCopyRequests(void)
+{
+ if (sShouldProcessSpriteCopyRequests)
+ {
+ u8 i = 0;
+
+ while (sSpriteCopyRequestCount > 0)
+ {
+ CpuCopy16(sSpriteCopyRequests[i].src, sSpriteCopyRequests[i].dest, sSpriteCopyRequests[i].size);
+ sSpriteCopyRequestCount--;
+ i++;
+ }
+
+ sShouldProcessSpriteCopyRequests = FALSE;
+ }
+}
+
+void RequestSpriteFrameImageCopy(u16 index, u16 tileNum, const struct SpriteFrameImage *images)
+{
+ if (sSpriteCopyRequestCount < MAX_SPRITE_COPY_REQUESTS)
+ {
+ sSpriteCopyRequests[sSpriteCopyRequestCount].src = images[index].data;
+ sSpriteCopyRequests[sSpriteCopyRequestCount].dest = (u8 *)OBJ_VRAM0 + TILE_SIZE_4BPP * tileNum;
+ sSpriteCopyRequests[sSpriteCopyRequestCount].size = images[index].size;
+ sSpriteCopyRequestCount++;
+ }
+}
+
+void RequestSpriteCopy(const u8 *src, u8 *dest, u16 size)
+{
+ if (sSpriteCopyRequestCount < MAX_SPRITE_COPY_REQUESTS)
+ {
+ sSpriteCopyRequests[sSpriteCopyRequestCount].src = src;
+ sSpriteCopyRequests[sSpriteCopyRequestCount].dest = dest;
+ sSpriteCopyRequests[sSpriteCopyRequestCount].size = size;
+ sSpriteCopyRequestCount++;
+ }
+}
+
+void CopyFromSprites(u8 *dest)
+{
+ u32 i;
+ u8 *src = (u8 *)gSprites;
+ for (i = 0; i < sizeof(struct Sprite) * MAX_SPRITES; i++)
+ {
+ *dest = *src;
+ dest++;
+ src++;
+ }
+}
+
+void CopyToSprites(u8 *src)
+{
+ u32 i;
+ u8 *dest = (u8 *)gSprites;
+ for (i = 0; i < sizeof(struct Sprite) * MAX_SPRITES; i++)
+ {
+ *dest = *src;
+ src++;
+ dest++;
+ }
+}
+
+void ResetAllSprites(void)
+{
+ u8 i;
+
+ for (i = 0; i < MAX_SPRITES; i++)
+ {
+ ResetSprite(&gSprites[i]);
+ sSpriteOrder[i] = i;
+ }
+
+ ResetSprite(&gSprites[i]);
+}
+
+// UB: template pointer may point to freed temporary storage
+void FreeSpriteTiles(struct Sprite *sprite)
+{
+ if (sprite->template->tileTag != 0xFFFF)
+ FreeSpriteTilesByTag(sprite->template->tileTag);
+}
+
+// UB: template pointer may point to freed temporary storage
+void FreeSpritePalette(struct Sprite *sprite)
+{
+ FreeSpritePaletteByTag(sprite->template->paletteTag);
+}
+
+void FreeSpriteOamMatrix(struct Sprite *sprite)
+{
+ if (sprite->oam.affineMode & ST_OAM_AFFINE_ON_MASK)
+ {
+ FreeOamMatrix(sprite->oam.matrixNum);
+ sprite->oam.affineMode = ST_OAM_AFFINE_OFF;
+ }
+}
+
+void DestroySpriteAndFreeResources(struct Sprite *sprite)
+{
+ FreeSpriteTiles(sprite);
+ FreeSpritePalette(sprite);
+ FreeSpriteOamMatrix(sprite);
+ DestroySprite(sprite);
+}
+
+void AnimateSprite(struct Sprite *sprite)
+{
+ sAnimFuncs[sprite->animBeginning](sprite);
+
+ if (!gAffineAnimsDisabled)
+ sAffineAnimFuncs[sprite->affineAnimBeginning](sprite);
+}
+
+void BeginAnim(struct Sprite *sprite)
+{
+ s16 imageValue;
+ u8 duration;
+ u8 hFlip;
+ u8 vFlip;
+
+ sprite->animCmdIndex = 0;
+ sprite->animEnded = FALSE;
+ sprite->animLoopCounter = 0;
+ imageValue = sprite->anims[sprite->animNum][sprite->animCmdIndex].frame.imageValue;
+
+ if (imageValue != -1)
+ {
+ sprite->animBeginning = FALSE;
+ duration = sprite->anims[sprite->animNum][sprite->animCmdIndex].frame.duration;
+ hFlip = sprite->anims[sprite->animNum][sprite->animCmdIndex].frame.hFlip;
+ vFlip = sprite->anims[sprite->animNum][sprite->animCmdIndex].frame.vFlip;
+
+ if (duration)
+ duration--;
+
+ sprite->animDelayCounter = duration;
+
+ if (!(sprite->oam.affineMode & ST_OAM_AFFINE_ON_MASK))
+ SetSpriteOamFlipBits(sprite, hFlip, vFlip);
+
+ if (sprite->usingSheet)
+ sprite->oam.tileNum = sprite->sheetTileStart + imageValue;
+ else
+ RequestSpriteFrameImageCopy(imageValue, sprite->oam.tileNum, sprite->images);
+ }
+}
+
+void ContinueAnim(struct Sprite *sprite)
+{
+ if (sprite->animDelayCounter)
+ {
+ u8 hFlip;
+ u8 vFlip;
+ DecrementAnimDelayCounter(sprite);
+ hFlip = sprite->anims[sprite->animNum][sprite->animCmdIndex].frame.hFlip;
+ vFlip = sprite->anims[sprite->animNum][sprite->animCmdIndex].frame.vFlip;
+ if (!(sprite->oam.affineMode & ST_OAM_AFFINE_ON_MASK))
+ SetSpriteOamFlipBits(sprite, hFlip, vFlip);
+ }
+ else if (!sprite->animPaused)
+ {
+ s16 type;
+ s16 funcIndex;
+ sprite->animCmdIndex++;
+ type = sprite->anims[sprite->animNum][sprite->animCmdIndex].type;
+ funcIndex = 3;
+ if (type < 0)
+ funcIndex = type + 3;
+ sAnimCmdFuncs[funcIndex](sprite);
+ }
+}
+
+void AnimCmd_frame(struct Sprite *sprite)
+{
+ s16 imageValue;
+ u8 duration;
+ u8 hFlip;
+ u8 vFlip;
+
+ imageValue = sprite->anims[sprite->animNum][sprite->animCmdIndex].frame.imageValue;
+ duration = sprite->anims[sprite->animNum][sprite->animCmdIndex].frame.duration;
+ hFlip = sprite->anims[sprite->animNum][sprite->animCmdIndex].frame.hFlip;
+ vFlip = sprite->anims[sprite->animNum][sprite->animCmdIndex].frame.vFlip;
+
+ if (duration)
+ duration--;
+
+ sprite->animDelayCounter = duration;
+
+ if (!(sprite->oam.affineMode & ST_OAM_AFFINE_ON_MASK))
+ SetSpriteOamFlipBits(sprite, hFlip, vFlip);
+
+ if (sprite->usingSheet)
+ sprite->oam.tileNum = sprite->sheetTileStart + imageValue;
+ else
+ RequestSpriteFrameImageCopy(imageValue, sprite->oam.tileNum, sprite->images);
+}
+
+void AnimCmd_end(struct Sprite *sprite)
+{
+ sprite->animCmdIndex--;
+ sprite->animEnded = TRUE;
+}
+
+void AnimCmd_jump(struct Sprite *sprite)
+{
+ s16 imageValue;
+ u8 duration;
+ u8 hFlip;
+ u8 vFlip;
+
+ sprite->animCmdIndex = sprite->anims[sprite->animNum][sprite->animCmdIndex].jump.target;
+
+ imageValue = sprite->anims[sprite->animNum][sprite->animCmdIndex].frame.imageValue;
+ duration = sprite->anims[sprite->animNum][sprite->animCmdIndex].frame.duration;
+ hFlip = sprite->anims[sprite->animNum][sprite->animCmdIndex].frame.hFlip;
+ vFlip = sprite->anims[sprite->animNum][sprite->animCmdIndex].frame.vFlip;
+
+ if (duration)
+ duration--;
+
+ sprite->animDelayCounter = duration;
+
+ if (!(sprite->oam.affineMode & ST_OAM_AFFINE_ON_MASK))
+ SetSpriteOamFlipBits(sprite, hFlip, vFlip);
+
+ if (sprite->usingSheet)
+ sprite->oam.tileNum = sprite->sheetTileStart + imageValue;
+ else
+ RequestSpriteFrameImageCopy(imageValue, sprite->oam.tileNum, sprite->images);
+}
+
+void AnimCmd_loop(struct Sprite *sprite)
+{
+ if (sprite->animLoopCounter)
+ ContinueAnimLoop(sprite);
+ else
+ BeginAnimLoop(sprite);
+}
+
+void BeginAnimLoop(struct Sprite *sprite)
+{
+ sprite->animLoopCounter = sprite->anims[sprite->animNum][sprite->animCmdIndex].loop.count;
+ JumpToTopOfAnimLoop(sprite);
+ ContinueAnim(sprite);
+}
+
+void ContinueAnimLoop(struct Sprite *sprite)
+{
+ sprite->animLoopCounter--;
+ JumpToTopOfAnimLoop(sprite);
+ ContinueAnim(sprite);
+}
+
+void JumpToTopOfAnimLoop(struct Sprite *sprite)
+{
+ if (sprite->animLoopCounter)
+ {
+ sprite->animCmdIndex--;
+
+ while (sprite->anims[sprite->animNum][sprite->animCmdIndex - 1].type != -3)
+ {
+ if (sprite->animCmdIndex == 0)
+ break;
+ sprite->animCmdIndex--;
+ }
+
+ sprite->animCmdIndex--;
+ }
+}
+
+void BeginAffineAnim(struct Sprite *sprite)
+{
+ if ((sprite->oam.affineMode & ST_OAM_AFFINE_ON_MASK) && sprite->affineAnims[0][0].type != 32767)
+ {
+ struct AffineAnimFrameCmd frameCmd;
+ u8 matrixNum = GetSpriteMatrixNum(sprite);
+ AffineAnimStateRestartAnim(matrixNum);
+ GetAffineAnimFrame(matrixNum, sprite, &frameCmd);
+ sprite->affineAnimBeginning = FALSE;
+ sprite->affineAnimEnded = FALSE;
+ ApplyAffineAnimFrame(matrixNum, &frameCmd);
+ sAffineAnimStates[matrixNum].delayCounter = frameCmd.duration;
+ if (sprite->flags_f)
+ obj_update_pos2(sprite, sprite->data[6], sprite->data[7]);
+ }
+}
+
+void ContinueAffineAnim(struct Sprite *sprite)
+{
+ if (sprite->oam.affineMode & ST_OAM_AFFINE_ON_MASK)
+ {
+ u8 matrixNum = GetSpriteMatrixNum(sprite);
+
+ if (sAffineAnimStates[matrixNum].delayCounter)
+ AffineAnimDelay(matrixNum, sprite);
+ else if (sprite->affineAnimPaused)
+ return;
+ else
+ {
+ s16 type;
+ s16 funcIndex;
+ sAffineAnimStates[matrixNum].animCmdIndex++;
+ type = sprite->affineAnims[sAffineAnimStates[matrixNum].animNum][sAffineAnimStates[matrixNum].animCmdIndex].type;
+ funcIndex = 3;
+ if (type >= 32765)
+ funcIndex = type - 32765;
+ sAffineAnimCmdFuncs[funcIndex](matrixNum, sprite);
+ }
+ if (sprite->flags_f)
+ obj_update_pos2(sprite, sprite->data[6], sprite->data[7]);
+ }
+}
+
+void AffineAnimDelay(u8 matrixNum, struct Sprite *sprite)
+{
+ if (!DecrementAffineAnimDelayCounter(sprite, matrixNum))
+ {
+ struct AffineAnimFrameCmd frameCmd;
+ GetAffineAnimFrame(matrixNum, sprite, &frameCmd);
+ ApplyAffineAnimFrameRelativeAndUpdateMatrix(matrixNum, &frameCmd);
+ }
+}
+
+void AffineAnimCmd_loop(u8 matrixNum, struct Sprite *sprite)
+{
+ if (sAffineAnimStates[matrixNum].loopCounter)
+ ContinueAffineAnimLoop(matrixNum, sprite);
+ else
+ BeginAffineAnimLoop(matrixNum, sprite);
+}
+
+void BeginAffineAnimLoop(u8 matrixNum, struct Sprite *sprite)
+{
+ sAffineAnimStates[matrixNum].loopCounter = sprite->affineAnims[sAffineAnimStates[matrixNum].animNum][sAffineAnimStates[matrixNum].animCmdIndex].loop.count;
+ JumpToTopOfAffineAnimLoop(matrixNum, sprite);
+ ContinueAffineAnim(sprite);
+}
+
+void ContinueAffineAnimLoop(u8 matrixNum, struct Sprite *sprite)
+{
+ sAffineAnimStates[matrixNum].loopCounter--;
+ JumpToTopOfAffineAnimLoop(matrixNum, sprite);
+ ContinueAffineAnim(sprite);
+}
+
+void JumpToTopOfAffineAnimLoop(u8 matrixNum, struct Sprite *sprite)
+{
+ if (sAffineAnimStates[matrixNum].loopCounter)
+ {
+ sAffineAnimStates[matrixNum].animCmdIndex--;
+
+ while (sprite->affineAnims[sAffineAnimStates[matrixNum].animNum][sAffineAnimStates[matrixNum].animCmdIndex - 1].type != 32765)
+ {
+ if (sAffineAnimStates[matrixNum].animCmdIndex == 0)
+ break;
+ sAffineAnimStates[matrixNum].animCmdIndex--;
+ }
+
+ sAffineAnimStates[matrixNum].animCmdIndex--;
+ }
+}
+
+void AffineAnimCmd_jump(u8 matrixNum, struct Sprite *sprite)
+{
+ struct AffineAnimFrameCmd frameCmd;
+ sAffineAnimStates[matrixNum].animCmdIndex = sprite->affineAnims[sAffineAnimStates[matrixNum].animNum][sAffineAnimStates[matrixNum].animCmdIndex].jump.target;
+ GetAffineAnimFrame(matrixNum, sprite, &frameCmd);
+ ApplyAffineAnimFrame(matrixNum, &frameCmd);
+ sAffineAnimStates[matrixNum].delayCounter = frameCmd.duration;
+}
+
+void AffineAnimCmd_end(u8 matrixNum, struct Sprite *sprite)
+{
+ struct AffineAnimFrameCmd dummyFrameCmd = {0};
+ sprite->affineAnimEnded = TRUE;
+ sAffineAnimStates[matrixNum].animCmdIndex--;
+ ApplyAffineAnimFrameRelativeAndUpdateMatrix(matrixNum, &dummyFrameCmd);
+}
+
+void AffineAnimCmd_frame(u8 matrixNum, struct Sprite *sprite)
+{
+ struct AffineAnimFrameCmd frameCmd;
+ GetAffineAnimFrame(matrixNum, sprite, &frameCmd);
+ ApplyAffineAnimFrame(matrixNum, &frameCmd);
+ sAffineAnimStates[matrixNum].delayCounter = frameCmd.duration;
+}
+
+void CopyOamMatrix(u8 destMatrixIndex, struct OamMatrix *srcMatrix)
+{
+ gOamMatrices[destMatrixIndex].a = srcMatrix->a;
+ gOamMatrices[destMatrixIndex].b = srcMatrix->b;
+ gOamMatrices[destMatrixIndex].c = srcMatrix->c;
+ gOamMatrices[destMatrixIndex].d = srcMatrix->d;
+}
+
+u8 GetSpriteMatrixNum(struct Sprite *sprite)
+{
+ u8 matrixNum = 0;
+ if (sprite->oam.affineMode & ST_OAM_AFFINE_ON_MASK)
+ matrixNum = sprite->oam.matrixNum;
+ return matrixNum;
+}
+
+void sub_8007E18(struct Sprite* sprite, s16 a2, s16 a3)
+{
+ sprite->data[6] = a2;
+ sprite->data[7] = a3;
+ sprite->flags_f = 1;
+}
+
+s32 sub_8007E28(s32 a0, s32 a1, s32 a2)
+{
+ s32 subResult, var1;
+
+ subResult = a1 - a0;
+ if (subResult < 0)
+ var1 = -(subResult) >> 9;
+ else
+ var1 = -(subResult >> 9);
+ return a2 - ((u32)(a2 * a1) / (u32)(a0) + var1);
+}
+
+void obj_update_pos2(struct Sprite *sprite, s32 a1, s32 a2)
+{
+ s32 var0, var1, var2;
+
+ u32 matrixNum = sprite->oam.matrixNum;
+ if (a1 != 0x800)
+ {
+ var0 = sOamDimensions32[sprite->oam.shape][sprite->oam.size].width;
+ var1 = var0 << 8;
+ var2 = (var0 << 16) / gOamMatrices[matrixNum].a;
+ sprite->pos2.x = sub_8007E28(var1, var2, a1);
+ }
+ if (a2 != 0x800)
+ {
+ var0 = sOamDimensions32[sprite->oam.shape][sprite->oam.size].height;
+ var1 = var0 << 8;
+ var2 = (var0 << 16) / gOamMatrices[matrixNum].d;
+ sprite->pos2.y = sub_8007E28(var1, var2, a2);
+ }
+}
+
+void SetSpriteOamFlipBits(struct Sprite *sprite, u8 hFlip, u8 vFlip)
+{
+ sprite->oam.matrixNum &= 0x7;
+ sprite->oam.matrixNum |= (((hFlip ^ sprite->hFlip) & 1) << 3);
+ sprite->oam.matrixNum |= (((vFlip ^ sprite->vFlip) & 1) << 4);
+}
+
+void AffineAnimStateRestartAnim(u8 matrixNum)
+{
+ sAffineAnimStates[matrixNum].animCmdIndex = 0;
+ sAffineAnimStates[matrixNum].delayCounter = 0;
+ sAffineAnimStates[matrixNum].loopCounter = 0;
+}
+
+void AffineAnimStateStartAnim(u8 matrixNum, u8 animNum)
+{
+ sAffineAnimStates[matrixNum].animNum = animNum;
+ sAffineAnimStates[matrixNum].animCmdIndex = 0;
+ sAffineAnimStates[matrixNum].delayCounter = 0;
+ sAffineAnimStates[matrixNum].loopCounter = 0;
+ sAffineAnimStates[matrixNum].xScale = 0x0100;
+ sAffineAnimStates[matrixNum].yScale = 0x0100;
+ sAffineAnimStates[matrixNum].rotation = 0;
+}
+
+void AffineAnimStateReset(u8 matrixNum)
+{
+ sAffineAnimStates[matrixNum].animNum = 0;
+ sAffineAnimStates[matrixNum].animCmdIndex = 0;
+ sAffineAnimStates[matrixNum].delayCounter = 0;
+ sAffineAnimStates[matrixNum].loopCounter = 0;
+ sAffineAnimStates[matrixNum].xScale = 0x0100;
+ sAffineAnimStates[matrixNum].yScale = 0x0100;
+ sAffineAnimStates[matrixNum].rotation = 0;
+}
+
+void ApplyAffineAnimFrameAbsolute(u8 matrixNum, struct AffineAnimFrameCmd *frameCmd)
+{
+ sAffineAnimStates[matrixNum].xScale = frameCmd->xScale;
+ sAffineAnimStates[matrixNum].yScale = frameCmd->yScale;
+ sAffineAnimStates[matrixNum].rotation = frameCmd->rotation << 8;
+}
+
+void DecrementAnimDelayCounter(struct Sprite *sprite)
+{
+ if (!sprite->animPaused)
+ sprite->animDelayCounter--;
+}
+
+bool8 DecrementAffineAnimDelayCounter(struct Sprite *sprite, u8 matrixNum)
+{
+ if (!sprite->affineAnimPaused)
+ --sAffineAnimStates[matrixNum].delayCounter;
+ return sprite->affineAnimPaused;
+}
+
+void ApplyAffineAnimFrameRelativeAndUpdateMatrix(u8 matrixNum, struct AffineAnimFrameCmd *frameCmd)
+{
+ struct ObjAffineSrcData srcData;
+ struct OamMatrix matrix;
+ sAffineAnimStates[matrixNum].xScale += frameCmd->xScale;
+ sAffineAnimStates[matrixNum].yScale += frameCmd->yScale;
+ sAffineAnimStates[matrixNum].rotation = (sAffineAnimStates[matrixNum].rotation + (frameCmd->rotation << 8)) & ~0xFF;
+ srcData.xScale = ConvertScaleParam(sAffineAnimStates[matrixNum].xScale);
+ srcData.yScale = ConvertScaleParam(sAffineAnimStates[matrixNum].yScale);
+ srcData.rotation = sAffineAnimStates[matrixNum].rotation;
+ ObjAffineSet(&srcData, &matrix, 1, 2);
+ CopyOamMatrix(matrixNum, &matrix);
+}
+
+s16 ConvertScaleParam(s16 scale)
+{
+ s32 val = 0x10000;
+ return SAFE_DIV(val, scale);
+}
+
+void GetAffineAnimFrame(u8 matrixNum, struct Sprite *sprite, struct AffineAnimFrameCmd *frameCmd)
+{
+ frameCmd->xScale = sprite->affineAnims[sAffineAnimStates[matrixNum].animNum][sAffineAnimStates[matrixNum].animCmdIndex].frame.xScale;
+ frameCmd->yScale = sprite->affineAnims[sAffineAnimStates[matrixNum].animNum][sAffineAnimStates[matrixNum].animCmdIndex].frame.yScale;
+ frameCmd->rotation = sprite->affineAnims[sAffineAnimStates[matrixNum].animNum][sAffineAnimStates[matrixNum].animCmdIndex].frame.rotation;
+ frameCmd->duration = sprite->affineAnims[sAffineAnimStates[matrixNum].animNum][sAffineAnimStates[matrixNum].animCmdIndex].frame.duration;
+}
+
+void ApplyAffineAnimFrame(u8 matrixNum, struct AffineAnimFrameCmd *frameCmd)
+{
+ struct AffineAnimFrameCmd dummyFrameCmd = {0};
+
+ if (frameCmd->duration)
+ {
+ frameCmd->duration--;
+ ApplyAffineAnimFrameRelativeAndUpdateMatrix(matrixNum, frameCmd);
+ }
+ else
+ {
+ ApplyAffineAnimFrameAbsolute(matrixNum, frameCmd);
+ ApplyAffineAnimFrameRelativeAndUpdateMatrix(matrixNum, &dummyFrameCmd);
+ }
+}
+
+void StartSpriteAnim(struct Sprite *sprite, u8 animNum)
+{
+ sprite->animNum = animNum;
+ sprite->animBeginning = TRUE;
+ sprite->animEnded = FALSE;
+}
+
+void StartSpriteAnimIfDifferent(struct Sprite *sprite, u8 animNum)
+{
+ if (sprite->animNum != animNum)
+ StartSpriteAnim(sprite, animNum);
+}
+
+void SeekSpriteAnim(struct Sprite *sprite, u8 animCmdIndex)
+{
+ u8 temp = sprite->animPaused;
+ sprite->animCmdIndex = animCmdIndex - 1;
+ sprite->animDelayCounter = 0;
+ sprite->animBeginning = FALSE;
+ sprite->animEnded = FALSE;
+ sprite->animPaused = FALSE;
+ ContinueAnim(sprite);
+ if (sprite->animDelayCounter)
+ sprite->animDelayCounter++;
+ sprite->animPaused = temp;
+}
+
+void StartSpriteAffineAnim(struct Sprite *sprite, u8 animNum)
+{
+ u8 matrixNum = GetSpriteMatrixNum(sprite);
+ AffineAnimStateStartAnim(matrixNum, animNum);
+ sprite->affineAnimBeginning = TRUE;
+ sprite->affineAnimEnded = FALSE;
+}
+
+void StartSpriteAffineAnimIfDifferent(struct Sprite *sprite, u8 animNum)
+{
+ u8 matrixNum = GetSpriteMatrixNum(sprite);
+ if (sAffineAnimStates[matrixNum].animNum != animNum)
+ StartSpriteAffineAnim(sprite, animNum);
+}
+
+void ChangeSpriteAffineAnim(struct Sprite *sprite, u8 animNum)
+{
+ u8 matrixNum = GetSpriteMatrixNum(sprite);
+ sAffineAnimStates[matrixNum].animNum = animNum;
+ sprite->affineAnimBeginning = TRUE;
+ sprite->affineAnimEnded = FALSE;
+}
+
+void ChangeSpriteAffineAnimIfDifferent(struct Sprite *sprite, u8 animNum)
+{
+ u8 matrixNum = GetSpriteMatrixNum(sprite);
+ if (sAffineAnimStates[matrixNum].animNum != animNum)
+ ChangeSpriteAffineAnim(sprite, animNum);
+}
+
+void SetSpriteSheetFrameTileNum(struct Sprite *sprite)
+{
+ if (sprite->usingSheet)
+ {
+ s16 tileOffset = sprite->anims[sprite->animNum][sprite->animCmdIndex].frame.imageValue;
+ if (tileOffset < 0)
+ tileOffset = 0;
+ sprite->oam.tileNum = sprite->sheetTileStart + tileOffset;
+ }
+}
+
+void ResetAffineAnimData(void)
+{
+ u8 i;
+
+ gAffineAnimsDisabled = FALSE;
+ gOamMatrixAllocBitmap = 0;
+
+ ResetOamMatrices();
+
+ for (i = 0; i < OAM_MATRIX_COUNT; i++)
+ AffineAnimStateReset(i);
+}
+
+u8 AllocOamMatrix(void)
+{
+ u8 i = 0;
+ u32 bit = 1;
+ u32 bitmap = gOamMatrixAllocBitmap;
+
+ while (i < OAM_MATRIX_COUNT)
+ {
+ if (!(bitmap & bit))
+ {
+ gOamMatrixAllocBitmap |= bit;
+ return i;
+ }
+
+ i++;
+ bit <<= 1;
+ }
+
+ return 0xFF;
+}
+
+void FreeOamMatrix(u8 matrixNum)
+{
+ u8 i = 0;
+ u32 bit = 1;
+
+ while (i < matrixNum)
+ {
+ i++;
+ bit <<= 1;
+ }
+
+ gOamMatrixAllocBitmap &= ~bit;
+ SetOamMatrix(matrixNum, 0x100, 0, 0, 0x100);
+}
+
+void InitSpriteAffineAnim(struct Sprite *sprite)
+{
+ u8 matrixNum = AllocOamMatrix();
+ if (matrixNum != 0xFF)
+ {
+ CalcCenterToCornerVec(sprite, sprite->oam.shape, sprite->oam.size, sprite->oam.affineMode);
+ sprite->oam.matrixNum = matrixNum;
+ sprite->affineAnimBeginning = TRUE;
+ AffineAnimStateReset(matrixNum);
+ }
+}
+
+void SetOamMatrixRotationScaling(u8 matrixNum, s16 xScale, s16 yScale, u16 rotation)
+{
+ struct ObjAffineSrcData srcData;
+ struct OamMatrix matrix;
+ srcData.xScale = ConvertScaleParam(xScale);
+ srcData.yScale = ConvertScaleParam(yScale);
+ srcData.rotation = rotation;
+ ObjAffineSet(&srcData, &matrix, 1, 2);
+ CopyOamMatrix(matrixNum, &matrix);
+}
+
+u16 LoadSpriteSheet(const struct SpriteSheet *sheet)
+{
+ s16 tileStart = AllocSpriteTiles(sheet->size / TILE_SIZE_4BPP);
+
+ if (tileStart < 0)
+ {
+ return 0;
+ }
+ else
+ {
+ AllocSpriteTileRange(sheet->tag, (u16)tileStart, sheet->size / TILE_SIZE_4BPP);
+ CpuCopy16(sheet->data, (u8 *)OBJ_VRAM0 + TILE_SIZE_4BPP * tileStart, sheet->size);
+ return (u16)tileStart;
+ }
+}
+
+void LoadSpriteSheets(const struct SpriteSheet *sheets)
+{
+ u8 i;
+ for (i = 0; sheets[i].data != NULL; i++)
+ LoadSpriteSheet(&sheets[i]);
+}
+
+void FreeSpriteTilesByTag(u16 tag)
+{
+ u8 index = IndexOfSpriteTileTag(tag);
+ if (index != 0xFF)
+ {
+ u16 i;
+ u16 *rangeStarts;
+ u16 *rangeCounts;
+ u16 start;
+ u16 count;
+ rangeStarts = sSpriteTileRanges;
+ start = rangeStarts[index * 2];
+ rangeCounts = sSpriteTileRanges + 1;
+ count = rangeCounts[index * 2];
+
+ for (i = start; i < start + count; i++)
+ FREE_SPRITE_TILE(i);
+
+ sSpriteTileRangeTags[index] = 0xFFFF;
+ }
+}
+
+void FreeSpriteTileRanges(void)
+{
+ u8 i;
+
+ for (i = 0; i < MAX_SPRITES; i++)
+ {
+ sSpriteTileRangeTags[i] = 0xFFFF;
+ SET_SPRITE_TILE_RANGE(i, 0, 0);
+ }
+}
+
+u16 GetSpriteTileStartByTag(u16 tag)
+{
+ u8 index = IndexOfSpriteTileTag(tag);
+ if (index == 0xFF)
+ return 0xFFFF;
+ return sSpriteTileRanges[index * 2];
+}
+
+u8 IndexOfSpriteTileTag(u16 tag)
+{
+ u8 i;
+
+ for (i = 0; i < MAX_SPRITES; i++)
+ if (sSpriteTileRangeTags[i] == tag)
+ return i;
+
+ return 0xFF;
+}
+
+u16 GetSpriteTileTagByTileStart(u16 start)
+{
+ u8 i;
+
+ for (i = 0; i < MAX_SPRITES; i++)
+ {
+ if (sSpriteTileRangeTags[i] != 0xFFFF && sSpriteTileRanges[i * 2] == start)
+ return sSpriteTileRangeTags[i];
+ }
+
+ return 0xFFFF;
+}
+
+void AllocSpriteTileRange(u16 tag, u16 start, u16 count)
+{
+ u8 freeIndex = IndexOfSpriteTileTag(0xFFFF);
+ sSpriteTileRangeTags[freeIndex] = tag;
+ SET_SPRITE_TILE_RANGE(freeIndex, start, count);
+}
+
+void FreeAllSpritePalettes(void)
+{
+ u8 i;
+ gReservedSpritePaletteCount = 0;
+ for (i = 0; i < 16; i++)
+ sSpritePaletteTags[i] = 0xFFFF;
+}
+
+u8 LoadSpritePalette(const struct SpritePalette *palette)
+{
+ u8 index = IndexOfSpritePaletteTag(palette->tag);
+
+ if (index != 0xFF)
+ return index;
+
+ index = IndexOfSpritePaletteTag(0xFFFF);
+
+ if (index == 0xFF)
+ {
+ return 0xFF;
+ }
+ else
+ {
+ sSpritePaletteTags[index] = palette->tag;
+ DoLoadSpritePalette(palette->data, index * 16);
+ return index;
+ }
+}
+
+void LoadSpritePalettes(const struct SpritePalette *palettes)
+{
+ u8 i;
+ for (i = 0; palettes[i].data != NULL; i++)
+ if (LoadSpritePalette(&palettes[i]) == 0xFF)
+ break;
+}
+
+void DoLoadSpritePalette(const u16 *src, u16 paletteOffset)
+{
+ LoadPalette(src, paletteOffset + 0x100, 32);
+}
+
+u8 AllocSpritePalette(u16 tag)
+{
+ u8 index = IndexOfSpritePaletteTag(0xFFFF);
+ if (index == 0xFF)
+ {
+ return 0xFF;
+ }
+ else
+ {
+ sSpritePaletteTags[index] = tag;
+ return index;
+ }
+}
+
+u8 IndexOfSpritePaletteTag(u16 tag)
+{
+ u8 i;
+ for (i = gReservedSpritePaletteCount; i < 16; i++)
+ if (sSpritePaletteTags[i] == tag)
+ return i;
+
+ return 0xFF;
+}
+
+u16 GetSpritePaletteTagByPaletteNum(u8 paletteNum)
+{
+ return sSpritePaletteTags[paletteNum];
+}
+
+void FreeSpritePaletteByTag(u16 tag)
+{
+ u8 index = IndexOfSpritePaletteTag(tag);
+ if (index != 0xFF)
+ sSpritePaletteTags[index] = 0xFFFF;
+}
+
+void SetSubspriteTables(struct Sprite *sprite, const struct SubspriteTable *subspriteTables)
+{
+ sprite->subspriteTables = subspriteTables;
+ sprite->subspriteTableNum = 0;
+ sprite->subspriteMode = SUBSPRITES_ON;
+}
+
+bool8 AddSpriteToOamBuffer(struct Sprite *sprite, u8 *oamIndex)
+{
+ if (*oamIndex >= gOamLimit)
+ return 1;
+
+ if (!sprite->subspriteTables || sprite->subspriteMode == SUBSPRITES_OFF)
+ {
+ gMain.oamBuffer[*oamIndex] = sprite->oam;
+ (*oamIndex)++;
+ return 0;
+ }
+ else
+ {
+ return AddSubspritesToOamBuffer(sprite, &gMain.oamBuffer[*oamIndex], oamIndex);
+ }
+}
+
+bool8 AddSubspritesToOamBuffer(struct Sprite *sprite, struct OamData *destOam, u8 *oamIndex)
+{
+ const struct SubspriteTable *subspriteTable;
+ struct OamData *oam;
+
+ if (*oamIndex >= gOamLimit)
+ return 1;
+
+ subspriteTable = &sprite->subspriteTables[sprite->subspriteTableNum];
+ oam = &sprite->oam;
+
+ if (!subspriteTable || !subspriteTable->subsprites)
+ {
+ *destOam = *oam;
+ (*oamIndex)++;
+ return 0;
+ }
+ else
+ {
+ u16 tileNum;
+ u16 baseX;
+ u16 baseY;
+ u8 subspriteCount;
+ u8 hFlip;
+ u8 vFlip;
+ u8 i;
+
+ tileNum = oam->tileNum;
+ subspriteCount = subspriteTable->subspriteCount;
+ hFlip = ((s32)oam->matrixNum >> 3) & 1;
+ vFlip = ((s32)oam->matrixNum >> 4) & 1;
+ baseX = oam->x - sprite->centerToCornerVecX;
+ baseY = oam->y - sprite->centerToCornerVecY;
+
+ for (i = 0; i < subspriteCount; i++, (*oamIndex)++)
+ {
+ u16 x;
+ u16 y;
+
+ if (*oamIndex >= gOamLimit)
+ return 1;
+
+ x = subspriteTable->subsprites[i].x;
+ y = subspriteTable->subsprites[i].y;
+
+ if (hFlip)
+ {
+ s8 width = sOamDimensions[subspriteTable->subsprites[i].shape][subspriteTable->subsprites[i].size].width;
+ s16 right = x;
+ right += width;
+ x = right;
+ x = ~x + 1;
+ }
+
+ if (vFlip)
+ {
+ s8 height = sOamDimensions[subspriteTable->subsprites[i].shape][subspriteTable->subsprites[i].size].height;
+ s16 bottom = y;
+ bottom += height;
+ y = bottom;
+ y = ~y + 1;
+ }
+
+ destOam[i] = *oam;
+ destOam[i].shape = subspriteTable->subsprites[i].shape;
+ destOam[i].size = subspriteTable->subsprites[i].size;
+ destOam[i].x = (s16)baseX + (s16)x;
+ destOam[i].y = baseY + y;
+ destOam[i].tileNum = tileNum + subspriteTable->subsprites[i].tileOffset;
+
+ if (sprite->subspriteMode != SUBSPRITES_IGNORE_PRIORITY)
+ destOam[i].priority = subspriteTable->subsprites[i].priority;
+ }
+ }
+
+ return 0;
+}
diff --git a/src/string_util.c b/src/string_util.c
new file mode 100644
index 000000000..92f8eea5a
--- /dev/null
+++ b/src/string_util.c
@@ -0,0 +1,781 @@
+#include "global.h"
+#include "string_util.h"
+#include "text.h"
+#include "strings.h"
+
+EWRAM_DATA u8 gStringVar1[0x100] = {0};
+EWRAM_DATA u8 gStringVar2[0x100] = {0};
+EWRAM_DATA u8 gStringVar3[0x100] = {0};
+EWRAM_DATA u8 gStringVar4[0x3E8] = {0};
+EWRAM_DATA static u8 sUnknownStringVar[16] = {0};
+
+static const u8 sDigits[] = __("0123456789ABCDEF");
+
+static const s32 sPowersOfTen[] =
+{
+ 1,
+ 10,
+ 100,
+ 1000,
+ 10000,
+ 100000,
+ 1000000,
+ 10000000,
+ 100000000,
+ 1000000000,
+};
+
+u8 *StringCopy10(u8 *dest, const u8 *src)
+{
+ u8 i;
+ u32 limit = 10;
+
+ for (i = 0; i < limit; i++)
+ {
+ dest[i] = src[i];
+
+ if (dest[i] == EOS)
+ return &dest[i];
+ }
+
+ dest[i] = EOS;
+ return &dest[i];
+}
+
+u8 *StringGetEnd10(u8 *str)
+{
+ u8 i;
+ u32 limit = 10;
+
+ for (i = 0; i < limit; i++)
+ if (str[i] == EOS)
+ return &str[i];
+
+ str[i] = EOS;
+ return &str[i];
+}
+
+u8 *StringCopy7(u8 *dest, const u8 *src)
+{
+ s32 i;
+ s32 limit = 7;
+
+ for (i = 0; i < limit; i++)
+ {
+ dest[i] = src[i];
+
+ if (dest[i] == EOS)
+ return &dest[i];
+ }
+
+ dest[i] = EOS;
+ return &dest[i];
+}
+
+u8 *StringCopy(u8 *dest, const u8 *src)
+{
+ while (*src != EOS)
+ {
+ *dest = *src;
+ dest++;
+ src++;
+ }
+
+ *dest = EOS;
+ return dest;
+}
+
+u8 *StringAppend(u8 *dest, const u8 *src)
+{
+ while (*dest != EOS)
+ dest++;
+
+ return StringCopy(dest, src);
+}
+
+u8 *StringCopyN(u8 *dest, const u8 *src, u8 n)
+{
+ u16 i;
+
+ for (i = 0; i < n; i++)
+ dest[i] = src[i];
+
+ return &dest[n];
+}
+
+u8 *StringAppendN(u8 *dest, const u8 *src, u8 n)
+{
+ while (*dest != EOS)
+ dest++;
+
+ return StringCopyN(dest, src, n);
+}
+
+u16 StringLength(const u8 *str)
+{
+ u16 length = 0;
+
+ while (str[length] != EOS)
+ length++;
+
+ return length;
+}
+
+s32 StringCompare(const u8 *str1, const u8 *str2)
+{
+ while (*str1 == *str2)
+ {
+ if (*str1 == EOS)
+ return 0;
+ str1++;
+ str2++;
+ }
+
+ return *str1 - *str2;
+}
+
+s32 StringCompareN(const u8 *str1, const u8 *str2, u32 n)
+{
+ while (*str1 == *str2)
+ {
+ if (*str1 == EOS)
+ return 0;
+ str1++;
+ str2++;
+ if (--n == 0)
+ return 0;
+ }
+
+ return *str1 - *str2;
+}
+
+bool8 IsStringLengthAtLeast(const u8 *str, s32 n)
+{
+ u8 i;
+
+ for (i = 0; i < n; i++)
+ if (str[i] && str[i] != EOS)
+ return TRUE;
+
+ return FALSE;
+}
+
+u8 *ConvertIntToDecimalStringN(u8 *dest, s32 value, enum StringConvertMode mode, u8 n)
+{
+ enum { WAITING_FOR_NONZERO_DIGIT, WRITING_DIGITS, WRITING_SPACES } state;
+ s32 powerOfTen;
+ s32 largestPowerOfTen = sPowersOfTen[n - 1];
+
+ state = WAITING_FOR_NONZERO_DIGIT;
+
+ if (mode == STR_CONV_MODE_RIGHT_ALIGN)
+ state = WRITING_SPACES;
+
+ if (mode == STR_CONV_MODE_LEADING_ZEROS)
+ state = WRITING_DIGITS;
+
+ for (powerOfTen = largestPowerOfTen; powerOfTen > 0; powerOfTen /= 10)
+ {
+ u8 c;
+ u16 digit = value / powerOfTen;
+ s32 temp = value - (powerOfTen * digit);
+
+ if (state == WRITING_DIGITS)
+ {
+ u8 *out = dest++;
+
+ if (digit <= 9)
+ c = sDigits[digit];
+ else
+ c = CHAR_QUESTION_MARK;
+
+ *out = c;
+ }
+ else if (digit != 0 || powerOfTen == 1)
+ {
+ u8 *out;
+ state = WRITING_DIGITS;
+ out = dest++;
+
+ if (digit <= 9)
+ c = sDigits[digit];
+ else
+ c = CHAR_QUESTION_MARK;
+
+ *out = c;
+ }
+ else if (state == WRITING_SPACES)
+ {
+ *dest++ = 0x77;
+ }
+
+ value = temp;
+ }
+
+ *dest = EOS;
+ return dest;
+}
+
+u8 *ConvertUIntToDecimalStringN(u8 *dest, u32 value, enum StringConvertMode mode, u8 n)
+{
+ enum { WAITING_FOR_NONZERO_DIGIT, WRITING_DIGITS, WRITING_SPACES } state;
+ s32 powerOfTen;
+ s32 largestPowerOfTen = sPowersOfTen[n - 1];
+
+ state = WAITING_FOR_NONZERO_DIGIT;
+
+ if (mode == STR_CONV_MODE_RIGHT_ALIGN)
+ state = WRITING_SPACES;
+
+ if (mode == STR_CONV_MODE_LEADING_ZEROS)
+ state = WRITING_DIGITS;
+
+ for (powerOfTen = largestPowerOfTen; powerOfTen > 0; powerOfTen /= 10)
+ {
+ u8 c;
+ u16 digit = value / powerOfTen;
+ u32 temp = value - (powerOfTen * digit);
+
+ if (state == WRITING_DIGITS)
+ {
+ u8 *out = dest++;
+
+ if (digit <= 9)
+ c = sDigits[digit];
+ else
+ c = CHAR_QUESTION_MARK;
+
+ *out = c;
+ }
+ else if (digit != 0 || powerOfTen == 1)
+ {
+ u8 *out;
+ state = WRITING_DIGITS;
+ out = dest++;
+
+ if (digit <= 9)
+ c = sDigits[digit];
+ else
+ c = CHAR_QUESTION_MARK;
+
+ *out = c;
+ }
+ else if (state == WRITING_SPACES)
+ {
+ *dest++ = 0x77;
+ }
+
+ value = temp;
+ }
+
+ *dest = EOS;
+ return dest;
+}
+
+u8 *ConvertIntToHexStringN(u8 *dest, s32 value, enum StringConvertMode mode, u8 n)
+{
+ enum { WAITING_FOR_NONZERO_DIGIT, WRITING_DIGITS, WRITING_SPACES } state;
+ u8 i;
+ s32 powerOfSixteen;
+ s32 largestPowerOfSixteen = 1;
+
+ for (i = 1; i < n; i++)
+ largestPowerOfSixteen *= 16;
+
+ state = WAITING_FOR_NONZERO_DIGIT;
+
+ if (mode == STR_CONV_MODE_RIGHT_ALIGN)
+ state = WRITING_SPACES;
+
+ if (mode == STR_CONV_MODE_LEADING_ZEROS)
+ state = WRITING_DIGITS;
+
+ for (powerOfSixteen = largestPowerOfSixteen; powerOfSixteen > 0; powerOfSixteen /= 16)
+ {
+ u8 c;
+ u32 digit = value / powerOfSixteen;
+ s32 temp = value % powerOfSixteen;
+
+ if (state == WRITING_DIGITS)
+ {
+ char *out = dest++;
+
+ if (digit <= 0xF)
+ c = sDigits[digit];
+ else
+ c = CHAR_QUESTION_MARK;
+
+ *out = c;
+ }
+ else if (digit != 0 || powerOfSixteen == 1)
+ {
+ char *out;
+ state = WRITING_DIGITS;
+ out = dest++;
+
+ if (digit <= 0xF)
+ c = sDigits[digit];
+ else
+ c = CHAR_QUESTION_MARK;
+
+ *out = c;
+ }
+ else if (state == WRITING_SPACES)
+ {
+ *dest++ = 0x77;
+ }
+
+ value = temp;
+ }
+
+ *dest = EOS;
+ return dest;
+}
+
+u8 *StringExpandPlaceholders(u8 *dest, const u8 *src)
+{
+ for (;;)
+ {
+ u8 c = *src++;
+ u8 placeholderId;
+ const u8 *expandedString;
+
+ switch (c)
+ {
+ case PLACEHOLDER_BEGIN:
+ placeholderId = *src++;
+ expandedString = GetExpandedPlaceholder(placeholderId);
+ dest = StringExpandPlaceholders(dest, expandedString);
+ break;
+ case EXT_CTRL_CODE_BEGIN:
+ *dest++ = c;
+ c = *src++;
+ *dest++ = c;
+
+ switch (c)
+ {
+ case EXT_CTRL_CODE_RESET_SIZE:
+ case EXT_CTRL_CODE_PAUSE_UNTIL_PRESS:
+ case EXT_CTRL_CODE_FILL_WINDOW:
+ case EXT_CTRL_CODE_JPN:
+ case EXT_CTRL_CODE_ENG:
+ case EXT_CTRL_CODE_PAUSE_MUSIC:
+ case EXT_CTRL_CODE_RESUME_MUSIC:
+ break;
+ case EXT_CTRL_CODE_COLOR_HIGHLIGHT_SHADOW:
+ *dest++ = *src++;
+ case EXT_CTRL_CODE_PLAY_BGM:
+ *dest++ = *src++;
+ default:
+ *dest++ = *src++;
+ }
+ break;
+ case EOS:
+ *dest = EOS;
+ return dest;
+ case CHAR_PROMPT_SCROLL:
+ case CHAR_PROMPT_CLEAR:
+ case CHAR_NEWLINE:
+ default:
+ *dest++ = c;
+ }
+ }
+}
+
+u8 *StringBraille(u8 *dest, const u8 *src)
+{
+ const u8 setBrailleFont[] = {
+ EXT_CTRL_CODE_BEGIN,
+ EXT_CTRL_CODE_SIZE,
+ 6,
+ EOS
+ };
+ const u8 gotoLine2[] = {
+ CHAR_NEWLINE,
+ EXT_CTRL_CODE_BEGIN,
+ EXT_CTRL_CODE_SHIFT_DOWN,
+ 2,
+ EOS
+ };
+
+ dest = StringCopy(dest, setBrailleFont);
+
+ for (;;)
+ {
+ u8 c = *src++;
+
+ switch (c)
+ {
+ case EOS:
+ *dest = c;
+ return dest;
+ case CHAR_NEWLINE:
+ dest = StringCopy(dest, gotoLine2);
+ break;
+ default:
+ *dest++ = c;
+ *dest++ = c + 0x40;
+ break;
+ }
+ }
+}
+
+static const u8 *ExpandPlaceholder_UnknownStringVar(void)
+{
+ return sUnknownStringVar;
+}
+
+static const u8 *ExpandPlaceholder_PlayerName(void)
+{
+ return gSaveBlock2Ptr->playerName;
+}
+
+static const u8 *ExpandPlaceholder_StringVar1(void)
+{
+ return gStringVar1;
+}
+
+static const u8 *ExpandPlaceholder_StringVar2(void)
+{
+ return gStringVar2;
+}
+
+static const u8 *ExpandPlaceholder_StringVar3(void)
+{
+ return gStringVar3;
+}
+
+static const u8 *ExpandPlaceholder_KunChan(void)
+{
+ if (gSaveBlock2Ptr->playerGender == MALE)
+ return gText_ExpandedPlaceholder_Kun;
+ else
+ return gText_ExpandedPlaceholder_Chan;
+}
+
+static const u8 *ExpandPlaceholder_RivalName(void)
+{
+ if (gSaveBlock2Ptr->playerGender == MALE)
+ return gText_ExpandedPlaceholder_May;
+ else
+ return gText_ExpandedPlaceholder_Brendan;
+}
+
+static const u8 *ExpandPlaceholder_Version(void)
+{
+ return gText_ExpandedPlaceholder_Emerald;
+}
+
+static const u8 *ExpandPlaceholder_Aqua(void)
+{
+ return gText_ExpandedPlaceholder_Aqua;
+}
+
+static const u8 *ExpandPlaceholder_Magma(void)
+{
+ return gText_ExpandedPlaceholder_Magma;
+}
+
+static const u8 *ExpandPlaceholder_Archie(void)
+{
+ return gText_ExpandedPlaceholder_Archie;
+}
+
+static const u8 *ExpandPlaceholder_Maxie(void)
+{
+ return gText_ExpandedPlaceholder_Maxie;
+}
+
+static const u8 *ExpandPlaceholder_Kyogre(void)
+{
+ return gText_ExpandedPlaceholder_Kyogre;
+}
+
+static const u8 *ExpandPlaceholder_Groudon(void)
+{
+ return gText_ExpandedPlaceholder_Groudon;
+}
+
+const u8 *GetExpandedPlaceholder(u32 id)
+{
+ typedef const u8 *(*ExpandPlaceholderFunc)(void);
+
+ static const ExpandPlaceholderFunc funcs[] =
+ {
+ [PLACEHOLDER_ID_UNKNOWN] = ExpandPlaceholder_UnknownStringVar,
+ [PLACEHOLDER_ID_PLAYER] = ExpandPlaceholder_PlayerName,
+ [PLACEHOLDER_ID_STRING_VAR_1] = ExpandPlaceholder_StringVar1,
+ [PLACEHOLDER_ID_STRING_VAR_2] = ExpandPlaceholder_StringVar2,
+ [PLACEHOLDER_ID_STRING_VAR_3] = ExpandPlaceholder_StringVar3,
+ [PLACEHOLDER_ID_KUN] = ExpandPlaceholder_KunChan,
+ [PLACEHOLDER_ID_RIVAL] = ExpandPlaceholder_RivalName,
+ [PLACEHOLDER_ID_VERSION] = ExpandPlaceholder_Version,
+ [PLACEHOLDER_ID_AQUA] = ExpandPlaceholder_Aqua,
+ [PLACEHOLDER_ID_MAGMA] = ExpandPlaceholder_Magma,
+ [PLACEHOLDER_ID_ARCHIE] = ExpandPlaceholder_Archie,
+ [PLACEHOLDER_ID_MAXIE] = ExpandPlaceholder_Maxie,
+ [PLACEHOLDER_ID_KYOGRE] = ExpandPlaceholder_Kyogre,
+ [PLACEHOLDER_ID_GROUDON] = ExpandPlaceholder_Groudon,
+ };
+
+ if (id >= ARRAY_COUNT(funcs))
+ return gText_ExpandedPlaceholder_Empty;
+ else
+ return funcs[id]();
+}
+
+u8 *StringFill(u8 *dest, u8 c, u16 n)
+{
+ u16 i;
+
+ for (i = 0; i < n; i++)
+ *dest++ = c;
+
+ *dest = EOS;
+ return dest;
+}
+
+u8 *StringCopyPadded(u8 *dest, const u8 *src, u8 c, u16 n)
+{
+ while (*src != EOS)
+ {
+ *dest++ = *src++;
+
+ if (n)
+ n--;
+ }
+
+ n--;
+
+ while (n != (u16)-1)
+ {
+ *dest++ = c;
+ n--;
+ }
+
+ *dest = EOS;
+ return dest;
+}
+
+u8 *StringFillWithTerminator(u8 *dest, u16 n)
+{
+ return StringFill(dest, EOS, n);
+}
+
+u8 *StringCopyN_Multibyte(u8 *dest, u8 *src, u32 n)
+{
+ u32 i;
+
+ for (i = n - 1; i != (u32)-1; i--)
+ {
+ if (*src == EOS)
+ {
+ break;
+ }
+ else
+ {
+ *dest++ = *src++;
+ if (*(src - 1) == CHAR_EXTRA_SYMBOL)
+ *dest++ = *src++;
+ }
+ }
+
+ *dest = EOS;
+ return dest;
+}
+
+u32 StringLength_Multibyte(const u8 *str)
+{
+ u32 length = 0;
+
+ while (*str != EOS)
+ {
+ if (*str == CHAR_EXTRA_SYMBOL)
+ str++;
+ str++;
+ length++;
+ }
+
+ return length;
+}
+
+u8 *WriteColorChangeControlCode(u8 *dest, u32 colorType, u8 color)
+{
+ *dest = EXT_CTRL_CODE_BEGIN;
+ dest++;
+
+ switch (colorType)
+ {
+ case 0:
+ *dest = EXT_CTRL_CODE_COLOR;
+ dest++;
+ break;
+ case 1:
+ *dest = EXT_CTRL_CODE_SHADOW;
+ dest++;
+ break;
+ case 2:
+ *dest = EXT_CTRL_CODE_HIGHLIGHT;
+ dest++;
+ break;
+ }
+
+ *dest = color;
+ dest++;
+ *dest = EOS;
+ return dest;
+}
+
+bool32 IsStringJapanese(u8 *str)
+{
+ while (*str != EOS)
+ {
+ if (*str < CHAR_0)
+ if (*str != CHAR_SPACE)
+ return TRUE;
+ str++;
+ }
+
+ return FALSE;
+}
+
+bool32 sub_800924C(u8 *str, s32 n)
+{
+ s32 i;
+
+ for (i = 0; *str != EOS && i < n; i++)
+ {
+ if (*str < CHAR_0)
+ if (*str != CHAR_SPACE)
+ return TRUE;
+ str++;
+ }
+
+ return FALSE;
+}
+
+u8 GetExtCtrlCodeLength(u8 code)
+{
+ static const u8 lengths[] =
+ {
+ [0] = 1,
+ [EXT_CTRL_CODE_COLOR] = 2,
+ [EXT_CTRL_CODE_HIGHLIGHT] = 2,
+ [EXT_CTRL_CODE_SHADOW] = 2,
+ [EXT_CTRL_CODE_COLOR_HIGHLIGHT_SHADOW] = 4,
+ [EXT_CTRL_CODE_PALETTE] = 2,
+ [EXT_CTRL_CODE_SIZE] = 2,
+ [EXT_CTRL_CODE_RESET_SIZE] = 1,
+ [EXT_CTRL_CODE_PAUSE] = 2,
+ [EXT_CTRL_CODE_PAUSE_UNTIL_PRESS] = 1,
+ [EXT_CTRL_CODE_WAIT_SE] = 1,
+ [EXT_CTRL_CODE_PLAY_BGM] = 3,
+ [EXT_CTRL_CODE_ESCAPE] = 2,
+ [EXT_CTRL_CODE_SHIFT_TEXT] = 2,
+ [EXT_CTRL_CODE_SHIFT_DOWN] = 2,
+ [EXT_CTRL_CODE_FILL_WINDOW] = 1,
+ [EXT_CTRL_CODE_PLAY_SE] = 3,
+ [EXT_CTRL_CODE_CLEAR] = 2,
+ [EXT_CTRL_CODE_SKIP] = 2,
+ [EXT_CTRL_CODE_CLEAR_TO] = 2,
+ [EXT_CTRL_CODE_MIN_LETTER_SPACING] = 2,
+ [EXT_CTRL_CODE_JPN] = 1,
+ [EXT_CTRL_CODE_ENG] = 1,
+ [EXT_CTRL_CODE_PAUSE_MUSIC] = 1,
+ [EXT_CTRL_CODE_RESUME_MUSIC] = 1,
+ };
+
+ u8 length = 0;
+ if (code < ARRAY_COUNT(lengths))
+ length = lengths[code];
+ return length;
+}
+
+static const u8 *SkipExtCtrlCode(const u8 *s)
+{
+ while (*s == EXT_CTRL_CODE_BEGIN)
+ {
+ s++;
+ s += GetExtCtrlCodeLength(*s);
+ }
+
+ return s;
+}
+
+s32 StringCompareWithoutExtCtrlCodes(const u8 *str1, const u8 *str2)
+{
+ s32 retVal = 0;
+
+ while (1)
+ {
+ str1 = SkipExtCtrlCode(str1);
+ str2 = SkipExtCtrlCode(str2);
+
+ if (*str1 > *str2)
+ break;
+
+ if (*str1 < *str2)
+ {
+ retVal = -1;
+ if (*str2 == EOS)
+ retVal = 1;
+ }
+
+ if (*str1 == EOS)
+ return retVal;
+
+ str1++;
+ str2++;
+ }
+
+ retVal = 1;
+
+ if (*str1 == EOS)
+ retVal = -1;
+
+ return retVal;
+}
+
+void ConvertInternationalString(u8 *s, u8 language)
+{
+ if (language == LANGUAGE_JAPANESE)
+ {
+ u8 i;
+
+ StripExtCtrlCodes(s);
+ i = StringLength(s);
+ s[i++] = EXT_CTRL_CODE_BEGIN;
+ s[i++] = EXT_CTRL_CODE_ENG;
+ s[i++] = EOS;
+
+ i--;
+
+ while (i != (u8)-1)
+ {
+ s[i + 2] = s[i];
+ i--;
+ }
+
+ s[0] = EXT_CTRL_CODE_BEGIN;
+ s[1] = EXT_CTRL_CODE_JPN;
+ }
+}
+
+void StripExtCtrlCodes(u8 *str)
+{
+ u16 srcIndex = 0;
+ u16 destIndex = 0;
+ while (str[srcIndex] != EOS)
+ {
+ if (str[srcIndex] == EXT_CTRL_CODE_BEGIN)
+ {
+ srcIndex++;
+ srcIndex += GetExtCtrlCodeLength(str[srcIndex]);
+ }
+ else
+ {
+ str[destIndex++] = str[srcIndex++];
+ }
+ }
+ str[destIndex] = EOS;
+}
diff --git a/src/text.c b/src/text.c
new file mode 100644
index 000000000..eb993c421
--- /dev/null
+++ b/src/text.c
@@ -0,0 +1,1808 @@
+#include "global.h"
+#include "battle.h"
+#include "main.h"
+#include "m4a.h"
+#include "palette.h"
+#include "sound.h"
+#include "constants/songs.h"
+#include "string_util.h"
+#include "window.h"
+#include "text.h"
+#include "blit.h"
+#include "menu.h"
+#include "dynamic_placeholder_text_util.h"
+
+EWRAM_DATA struct TextPrinter gTempTextPrinter = {0};
+EWRAM_DATA struct TextPrinter gTextPrinters[NUM_TEXT_PRINTERS] = {0};
+
+static u16 gFontHalfRowLookupTable[0x51];
+static u16 gLastTextBgColor;
+static u16 gLastTextFgColor;
+static u16 gLastTextShadowColor;
+
+const struct FontInfo *gFonts;
+u8 gDisableTextPrinters;
+struct TextGlyph gCurGlyph;
+TextFlags gTextFlags;
+
+const u8 gFontHalfRowOffsets[] =
+{
+ 0x00, 0x01, 0x02, 0x00, 0x03, 0x04, 0x05, 0x03, 0x06, 0x07, 0x08, 0x06, 0x00, 0x01, 0x02, 0x00,
+ 0x09, 0x0A, 0x0B, 0x09, 0x0C, 0x0D, 0x0E, 0x0C, 0x0F, 0x10, 0x11, 0x0F, 0x09, 0x0A, 0x0B, 0x09,
+ 0x12, 0x13, 0x14, 0x12, 0x15, 0x16, 0x17, 0x15, 0x18, 0x19, 0x1A, 0x18, 0x12, 0x13, 0x14, 0x12,
+ 0x00, 0x01, 0x02, 0x00, 0x03, 0x04, 0x05, 0x03, 0x06, 0x07, 0x08, 0x06, 0x00, 0x01, 0x02, 0x00,
+ 0x1B, 0x1C, 0x1D, 0x1B, 0x1E, 0x1F, 0x20, 0x1E, 0x21, 0x22, 0x23, 0x21, 0x1B, 0x1C, 0x1D, 0x1B,
+ 0x24, 0x25, 0x26, 0x24, 0x27, 0x28, 0x29, 0x27, 0x2A, 0x2B, 0x2C, 0x2A, 0x24, 0x25, 0x26, 0x24,
+ 0x2D, 0x2E, 0x2F, 0x2D, 0x30, 0x31, 0x32, 0x30, 0x33, 0x34, 0x35, 0x33, 0x2D, 0x2E, 0x2F, 0x2D,
+ 0x1B, 0x1C, 0x1D, 0x1B, 0x1E, 0x1F, 0x20, 0x1E, 0x21, 0x22, 0x23, 0x21, 0x1B, 0x1C, 0x1D, 0x1B,
+ 0x36, 0x37, 0x38, 0x36, 0x39, 0x3A, 0x3B, 0x39, 0x3C, 0x3D, 0x3E, 0x3C, 0x36, 0x37, 0x38, 0x36,
+ 0x3F, 0x40, 0x41, 0x3F, 0x42, 0x43, 0x44, 0x42, 0x45, 0x46, 0x47, 0x45, 0x3F, 0x40, 0x41, 0x3F,
+ 0x48, 0x49, 0x4A, 0x48, 0x4B, 0x4C, 0x4D, 0x4B, 0x4E, 0x4F, 0x50, 0x4E, 0x48, 0x49, 0x4A, 0x48,
+ 0x36, 0x37, 0x38, 0x36, 0x39, 0x3A, 0x3B, 0x39, 0x3C, 0x3D, 0x3E, 0x3C, 0x36, 0x37, 0x38, 0x36,
+ 0x00, 0x01, 0x02, 0x00, 0x03, 0x04, 0x05, 0x03, 0x06, 0x07, 0x08, 0x06, 0x00, 0x01, 0x02, 0x00,
+ 0x09, 0x0A, 0x0B, 0x09, 0x0C, 0x0D, 0x0E, 0x0C, 0x0F, 0x10, 0x11, 0x0F, 0x09, 0x0A, 0x0B, 0x09,
+ 0x12, 0x13, 0x14, 0x12, 0x15, 0x16, 0x17, 0x15, 0x18, 0x19, 0x1A, 0x18, 0x12, 0x13, 0x14, 0x12,
+ 0x00, 0x01, 0x02, 0x00, 0x03, 0x04, 0x05, 0x03, 0x06, 0x07, 0x08, 0x06, 0x00, 0x01, 0x02, 0x00
+};
+
+const u8 gDownArrowTiles[] = INCBIN_U8("graphics/fonts/down_arrow.4bpp");
+const u8 gDarkDownArrowTiles[] = INCBIN_U8("graphics/fonts/down_arrow_RS.4bpp");
+const u8 gUnusedFRLGBlankedDownArrow[] = INCBIN_U8("graphics/fonts/unused_frlg_blanked_down_arrow.4bpp");
+const u8 gUnusedFRLGDownArrow[] = INCBIN_U8("graphics/fonts/unused_frlg_down_arrow.4bpp");
+const u8 gDownArrowYCoords[] = { 0x0, 0x1, 0x2, 0x1 };
+const u8 gWindowVerticalScrollSpeeds[] = { 0x1, 0x2, 0x4, 0x0 };
+
+const struct GlyphWidthFunc gGlyphWidthFuncs[] =
+{
+ { 0x0, GetGlyphWidthFont0 },
+ { 0x1, GetGlyphWidthFont1 },
+ { 0x2, GetGlyphWidthFont2 },
+ { 0x3, GetGlyphWidthFont2 },
+ { 0x4, GetGlyphWidthFont2 },
+ { 0x5, GetGlyphWidthFont2 },
+ { 0x6, GetGlyphWidthFont6 },
+ { 0x7, GetGlyphWidthFont7 },
+ { 0x8, GetGlyphWidthFont8 }
+};
+
+const struct KeypadIcon gKeypadIcons[] =
+{
+ [CHAR_A_BUTTON] = { 0x0, 0x8, 0xC },
+ [CHAR_B_BUTTON] = { 0x1, 0x8, 0xC },
+ [CHAR_L_BUTTON] = { 0x2, 0x10, 0xC },
+ [CHAR_R_BUTTON] = { 0x4, 0x10, 0xC },
+ [CHAR_START_BUTTON] = { 0x6, 0x18, 0xC },
+ [CHAR_SELECT_BUTTON] = { 0x9, 0x18, 0xC },
+ [CHAR_DPAD_UP] = { 0xC, 0x8, 0xC },
+ [CHAR_DPAD_DOWN] = { 0xD, 0x8, 0xC },
+ [CHAR_DPAD_LEFT] = { 0xE, 0x8, 0xC },
+ [CHAR_DPAD_RIGHT] = { 0xF, 0x8, 0xC },
+ [CHAR_DPAD_UPDOWN] = { 0x20, 0x8, 0xC },
+ [CHAR_DPAD_LEFTRIGHT] = { 0x21, 0x8, 0xC },
+ [CHAR_DPAD_NONE] = { 0x22, 0x8, 0xC }
+};
+
+const u8 gKeypadIconTiles[] = INCBIN_U8("graphics/fonts/keypad_icons.4bpp");
+
+const struct FontInfo gFontInfos[] =
+{
+ { Font0Func, 0x5, 0xC, 0x0, 0x0, 0x0, 0x2, 0x1, 0x3 },
+ { Font1Func, 0x6, 0x10, 0x0, 0x0, 0x0, 0x2, 0x1, 0x3 },
+ { Font2Func, 0x6, 0xE, 0x0, 0x0, 0x0, 0x2, 0x1, 0x3 },
+ { Font3Func, 0x6, 0xE, 0x0, 0x0, 0x0, 0x2, 0x1, 0x3 },
+ { Font4Func, 0x6, 0xE, 0x0, 0x0, 0x0, 0x2, 0x1, 0x3 },
+ { Font5Func, 0x6, 0xE, 0x0, 0x0, 0x0, 0x2, 0x1, 0x3 },
+ { Font6Func, 0x8, 0x10, 0x0, 0x8, 0x0, 0x2, 0x1, 0x3 },
+ { Font7Func, 0x5, 0x10, 0x0, 0x0, 0x0, 0x2, 0x1, 0x3 },
+ { Font8Func, 0x5, 0x8, 0x0, 0x0, 0x0, 0x2, 0x1, 0x3 },
+ { NULL, 0x8, 0x8, 0x0, 0x0, 0x0, 0x1, 0x2, 0xF }
+};
+
+const u8 gMenuCursorDimensions[][2] =
+{
+ { 0x8, 0xC },
+ { 0x8, 0xF },
+ { 0x8, 0xE },
+ { 0x8, 0xE },
+ { 0x8, 0xE },
+ { 0x8, 0xE },
+ { 0x8, 0x10 },
+ { 0x8, 0xF },
+ { 0x8, 0x8 },
+ { 0x0, 0x0 }
+};
+
+const u16 gFont9JapaneseGlyphs[] = INCBIN_U16("graphics/fonts/font9.hwjpnfont");
+
+extern const u16 gFont8LatinGlyphs[];
+extern const u8 gFont8LatinGlyphWidths[];
+extern const u16 gFont0LatinGlyphs[];
+extern const u8 gFont0LatinGlyphWidths[];
+extern const u16 gFont7LatinGlyphs[];
+extern const u8 gFont7LatinGlyphWidths[];
+extern const u16 gFont2LatinGlyphs[];
+extern const u8 gFont2LatinGlyphWidths[];
+extern const u16 gFont1LatinGlyphs[];
+extern const u8 gFont1LatinGlyphWidths[];
+extern const u16 gFont0JapaneseGlyphs[];
+extern const u16 gFont1JapaneseGlyphs[];
+extern const u16 gFont2JapaneseGlyphs[];
+extern const u8 gFont2JapaneseGlyphWidths[];
+
+void SetFontsPointer(const struct FontInfo *fonts)
+{
+ gFonts = fonts;
+}
+
+void DeactivateAllTextPrinters(void)
+{
+ int printer;
+ for (printer = 0; printer < NUM_TEXT_PRINTERS; ++printer)
+ gTextPrinters[printer].active = 0;
+}
+
+u16 AddTextPrinterParameterized(u8 windowId, u8 fontId, const u8 *str, u8 x, u8 y, u8 speed, void (*callback)(struct TextPrinterTemplate *, u16))
+{
+ struct TextPrinterTemplate printerTemplate;
+
+ printerTemplate.currentChar = str;
+ printerTemplate.windowId = windowId;
+ printerTemplate.fontId = fontId;
+ printerTemplate.x = x;
+ printerTemplate.y = y;
+ printerTemplate.currentX = x;
+ printerTemplate.currentY = y;
+ printerTemplate.letterSpacing = gFonts[fontId].letterSpacing;
+ printerTemplate.lineSpacing = gFonts[fontId].lineSpacing;
+ printerTemplate.unk = gFonts[fontId].unk;
+ printerTemplate.fgColor = gFonts[fontId].fgColor;
+ printerTemplate.bgColor = gFonts[fontId].bgColor;
+ printerTemplate.shadowColor = gFonts[fontId].shadowColor;
+ return AddTextPrinter(&printerTemplate, speed, callback);
+}
+
+bool16 AddTextPrinter(struct TextPrinterTemplate *printerTemplate, u8 speed, void (*callback)(struct TextPrinterTemplate *, u16))
+{
+ int i;
+ u16 j;
+
+ if (!gFonts)
+ return FALSE;
+
+ gTempTextPrinter.active = 1;
+ gTempTextPrinter.state = 0;
+ gTempTextPrinter.textSpeed = speed;
+ gTempTextPrinter.delayCounter = 0;
+ gTempTextPrinter.scrollDistance = 0;
+
+ for (i = 0; i < 7; i++)
+ {
+ gTempTextPrinter.subStructFields[i] = 0;
+ }
+
+ gTempTextPrinter.printerTemplate = *printerTemplate;
+ gTempTextPrinter.callback = callback;
+ gTempTextPrinter.minLetterSpacing = 0;
+ gTempTextPrinter.japanese = 0;
+
+ GenerateFontHalfRowLookupTable(printerTemplate->fgColor, printerTemplate->bgColor, printerTemplate->shadowColor);
+ if (speed != TEXT_SPEED_FF && speed != 0)
+ {
+ --gTempTextPrinter.textSpeed;
+ gTextPrinters[printerTemplate->windowId] = gTempTextPrinter;
+ }
+ else
+ {
+ gTempTextPrinter.textSpeed = 0;
+ for (j = 0; j < 0x400; ++j)
+ {
+ if (RenderFont(&gTempTextPrinter) == 1)
+ break;
+ }
+
+ if (speed != TEXT_SPEED_FF)
+ CopyWindowToVram(gTempTextPrinter.printerTemplate.windowId, 2);
+ gTextPrinters[printerTemplate->windowId].active = 0;
+ }
+ gDisableTextPrinters = 0;
+ return TRUE;
+}
+
+void RunTextPrinters(void)
+{
+ int i;
+
+ if (gDisableTextPrinters == 0)
+ {
+ for (i = 0; i < NUM_TEXT_PRINTERS; ++i)
+ {
+ if (gTextPrinters[i].active)
+ {
+ u16 temp = RenderFont(&gTextPrinters[i]);
+ switch (temp)
+ {
+ case 0:
+ CopyWindowToVram(gTextPrinters[i].printerTemplate.windowId, 2);
+ case 3:
+ if (gTextPrinters[i].callback != 0)
+ gTextPrinters[i].callback(&gTextPrinters[i].printerTemplate, temp);
+ break;
+ case 1:
+ gTextPrinters[i].active = 0;
+ break;
+ }
+ }
+ }
+ }
+}
+
+bool16 IsTextPrinterActive(u8 id)
+{
+ return gTextPrinters[id].active;
+}
+
+u32 RenderFont(struct TextPrinter *textPrinter)
+{
+ u32 ret;
+ while (TRUE)
+ {
+ ret = gFonts[textPrinter->printerTemplate.fontId].fontFunction(textPrinter);
+ if (ret != 2)
+ return ret;
+ }
+}
+
+void GenerateFontHalfRowLookupTable(u8 fgColor, u8 bgColor, u8 shadowColor)
+{
+ u32 fg12, bg12, shadow12;
+ u32 temp;
+
+ u16 *current = gFontHalfRowLookupTable;
+
+ gLastTextBgColor = bgColor;
+ gLastTextFgColor = fgColor;
+ gLastTextShadowColor = shadowColor;
+
+ bg12 = bgColor << 12;
+ fg12 = fgColor << 12;
+ shadow12 = shadowColor << 12;
+
+ temp = (bgColor << 8) | (bgColor << 4) | bgColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (fgColor << 8) | (bgColor << 4) | bgColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (shadowColor << 8) | (bgColor << 4) | bgColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (bgColor << 8) | (fgColor << 4) | bgColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (fgColor << 8) | (fgColor << 4) | bgColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (shadowColor << 8) | (fgColor << 4) | bgColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (bgColor << 8) | (shadowColor << 4) | bgColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (fgColor << 8) | (shadowColor << 4) | bgColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (shadowColor << 8) | (shadowColor << 4) | bgColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (bgColor << 8) | (bgColor << 4) | fgColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (fgColor << 8) | (bgColor << 4) | fgColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (shadowColor << 8) | (bgColor << 4) | fgColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (bgColor << 8) | (fgColor << 4) | fgColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (fgColor << 8) | (fgColor << 4) | fgColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (shadowColor << 8) | (fgColor << 4) | fgColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (bgColor << 8) | (shadowColor << 4) | fgColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (fgColor << 8) | (shadowColor << 4) | fgColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (shadowColor << 8) | (shadowColor << 4) | fgColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (bgColor << 8) | (bgColor << 4) | shadowColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (fgColor << 8) | (bgColor << 4) | shadowColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (shadowColor << 8) | (bgColor << 4) | shadowColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (bgColor << 8) | (fgColor << 4) | shadowColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (fgColor << 8) | (fgColor << 4) | shadowColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (shadowColor << 8) | (fgColor << 4) | shadowColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (bgColor << 8) | (shadowColor << 4) | shadowColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (fgColor << 8) | (shadowColor << 4) | shadowColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+
+ temp = (shadowColor << 8) | (shadowColor << 4) | shadowColor;
+ *(current++) = (bg12) | temp;
+ *(current++) = (fg12) | temp;
+ *(current++) = (shadow12) | temp;
+}
+
+void SaveTextColors(u8 *fgColor, u8 *bgColor, u8 *shadowColor)
+{
+ *bgColor = gLastTextBgColor;
+ *fgColor = gLastTextFgColor;
+ *shadowColor = gLastTextShadowColor;
+}
+
+void RestoreTextColors(u8 *fgColor, u8 *bgColor, u8 *shadowColor)
+{
+ GenerateFontHalfRowLookupTable(*fgColor, *bgColor, *shadowColor);
+}
+
+void DecompressGlyphTile(const void *src_, void *dest_)
+{
+ u32 temp;
+ const u16 *src = src_;
+ u32 *dest = dest_;
+
+ temp = *(src++);
+ *(dest)++ = ((gFontHalfRowLookupTable[gFontHalfRowOffsets[temp & 0xFF]]) << 16) | (gFontHalfRowLookupTable[gFontHalfRowOffsets[temp >> 8]]);
+
+ temp = *(src++);
+ *(dest++) = ((gFontHalfRowLookupTable[gFontHalfRowOffsets[temp & 0xFF]]) << 16) | (gFontHalfRowLookupTable[gFontHalfRowOffsets[temp >> 8]]);
+
+ temp = *(src++);
+ *(dest++) = ((gFontHalfRowLookupTable[gFontHalfRowOffsets[temp & 0xFF]]) << 16) | (gFontHalfRowLookupTable[gFontHalfRowOffsets[temp >> 8]]);
+
+ temp = *(src++);
+ *(dest++) = ((gFontHalfRowLookupTable[gFontHalfRowOffsets[temp & 0xFF]]) << 16) | (gFontHalfRowLookupTable[gFontHalfRowOffsets[temp >> 8]]);
+
+ temp = *(src++);
+ *(dest++) = ((gFontHalfRowLookupTable[gFontHalfRowOffsets[temp & 0xFF]]) << 16) | (gFontHalfRowLookupTable[gFontHalfRowOffsets[temp >> 8]]);
+
+ temp = *(src++);
+ *(dest++) = ((gFontHalfRowLookupTable[gFontHalfRowOffsets[temp & 0xFF]]) << 16) | (gFontHalfRowLookupTable[gFontHalfRowOffsets[temp >> 8]]);
+
+ temp = *(src++);
+ *(dest++) = ((gFontHalfRowLookupTable[gFontHalfRowOffsets[temp & 0xFF]]) << 16) | (gFontHalfRowLookupTable[gFontHalfRowOffsets[temp >> 8]]);
+
+ temp = *(src++);
+ *(dest++) = ((gFontHalfRowLookupTable[gFontHalfRowOffsets[temp & 0xFF]]) << 16) | (gFontHalfRowLookupTable[gFontHalfRowOffsets[temp >> 8]]);
+}
+
+u8 GetLastTextColor(u8 colorType)
+{
+ switch (colorType)
+ {
+ case 0:
+ return gLastTextFgColor;
+ case 2:
+ return gLastTextBgColor;
+ case 1:
+ return gLastTextShadowColor;
+ default:
+ return 0;
+ }
+}
+
+inline static void GLYPH_COPY(u8 *windowTiles, u32 widthOffset, u32 j, u32 i, u32 *glyphPixels, s32 width, s32 height)
+{
+ u32 xAdd, yAdd, pixelData, bits, toOrr, dummyX;
+ u8 *dst;
+
+ xAdd = j + width;
+ yAdd = i + height;
+ dummyX = j;
+ for (; i < yAdd; i++)
+ {
+ pixelData = *glyphPixels++;
+ for (j = dummyX; j < xAdd; j++)
+ {
+ if ((toOrr = pixelData & 0xF))
+ {
+ dst = windowTiles + ((j / 8) * 32) + ((j % 8) / 2) + ((i / 8) * widthOffset) + ((i % 8) * 4);
+ bits = ((j & 1) * 4);
+ *dst = (toOrr << bits) | (*dst & (0xF0 >> bits));
+ }
+ pixelData >>= 4;
+ }
+ }
+}
+
+void CopyGlyphToWindow(struct TextPrinter *textPrinter)
+{
+ struct Window *window;
+ struct WindowTemplate *template;
+ u32 *glyphPixels;
+ u32 currX, currY, widthOffset;
+ s32 glyphWidth, glyphHeight;
+ u8 *windowTiles;
+
+ window = &gWindows[textPrinter->printerTemplate.windowId];
+ template = &window->window;
+
+ if ((glyphWidth = (template->width * 8) - textPrinter->printerTemplate.currentX) > gCurGlyph.width)
+ glyphWidth = gCurGlyph.width;
+
+ if ((glyphHeight = (template->height * 8) - textPrinter->printerTemplate.currentY) > gCurGlyph.height)
+ glyphHeight = gCurGlyph.height;
+
+ currX = textPrinter->printerTemplate.currentX;
+ currY = textPrinter->printerTemplate.currentY;
+ glyphPixels = gCurGlyph.gfxBufferTop;
+ windowTiles = window->tileData;
+ widthOffset = template->width * 32;
+
+ if (glyphWidth < 9)
+ {
+ if (glyphHeight < 9)
+ {
+ GLYPH_COPY(windowTiles, widthOffset, currX, currY, glyphPixels, glyphWidth, glyphHeight);
+ }
+ else
+ {
+ GLYPH_COPY(windowTiles, widthOffset, currX, currY, glyphPixels, glyphWidth, 8);
+ GLYPH_COPY(windowTiles, widthOffset, currX, currY + 8, glyphPixels + 16, glyphWidth, glyphHeight - 8);
+ }
+ }
+ else
+ {
+ if (glyphHeight < 9)
+ {
+ GLYPH_COPY(windowTiles, widthOffset, currX, currY, glyphPixels, 8, glyphHeight);
+ GLYPH_COPY(windowTiles, widthOffset, currX + 8, currY, glyphPixels + 8, glyphWidth - 8, glyphHeight);
+ }
+ else
+ {
+ GLYPH_COPY(windowTiles, widthOffset, currX, currY, glyphPixels, 8, 8);
+ GLYPH_COPY(windowTiles, widthOffset, currX + 8, currY, glyphPixels + 8, glyphWidth - 8, 8);
+ GLYPH_COPY(windowTiles, widthOffset, currX, currY + 8, glyphPixels + 16, 8, glyphHeight - 8);
+ GLYPH_COPY(windowTiles, widthOffset, currX + 8, currY + 8, glyphPixels + 24, glyphWidth - 8, glyphHeight - 8);
+ }
+ }
+}
+
+void ClearTextSpan(struct TextPrinter *textPrinter, u32 width)
+{
+ struct Window *window;
+ struct Bitmap pixels_data;
+ struct TextGlyph *glyph;
+ u8* glyphHeight;
+
+ if (gLastTextBgColor != 0)
+ {
+ window = &gWindows[textPrinter->printerTemplate.windowId];
+ pixels_data.pixels = window->tileData;
+ pixels_data.width = window->window.width << 3;
+ pixels_data.height = window->window.height << 3;
+
+ glyph = &gCurGlyph;
+ glyphHeight = &glyph->height;
+
+ FillBitmapRect4Bit(
+ &pixels_data,
+ textPrinter->printerTemplate.currentX,
+ textPrinter->printerTemplate.currentY,
+ width,
+ *glyphHeight,
+ gLastTextBgColor);
+ }
+}
+
+u16 Font0Func(struct TextPrinter *textPrinter)
+{
+ struct TextPrinterSubStruct *subStruct = (struct TextPrinterSubStruct *)(&textPrinter->subStructFields);
+
+ if (subStruct->hasGlyphIdBeenSet == FALSE)
+ {
+ subStruct->glyphId = 0;
+ subStruct->hasGlyphIdBeenSet = TRUE;
+ }
+ return RenderText(textPrinter);
+}
+
+u16 Font1Func(struct TextPrinter *textPrinter)
+{
+ struct TextPrinterSubStruct *subStruct = (struct TextPrinterSubStruct *)(&textPrinter->subStructFields);
+
+ if (subStruct->hasGlyphIdBeenSet == FALSE)
+ {
+ subStruct->glyphId = 1;
+ subStruct->hasGlyphIdBeenSet = TRUE;
+ }
+ return RenderText(textPrinter);
+}
+
+u16 Font2Func(struct TextPrinter *textPrinter)
+{
+ struct TextPrinterSubStruct *subStruct = (struct TextPrinterSubStruct *)(&textPrinter->subStructFields);
+
+ if (subStruct->hasGlyphIdBeenSet == FALSE)
+ {
+ subStruct->glyphId = 2;
+ subStruct->hasGlyphIdBeenSet = TRUE;
+ }
+ return RenderText(textPrinter);
+}
+
+u16 Font3Func(struct TextPrinter *textPrinter)
+{
+ struct TextPrinterSubStruct *subStruct = (struct TextPrinterSubStruct *)(&textPrinter->subStructFields);
+
+ if (subStruct->hasGlyphIdBeenSet == FALSE)
+ {
+ subStruct->glyphId = 3;
+ subStruct->hasGlyphIdBeenSet = TRUE;
+ }
+ return RenderText(textPrinter);
+}
+
+u16 Font4Func(struct TextPrinter *textPrinter)
+{
+ struct TextPrinterSubStruct *subStruct = (struct TextPrinterSubStruct *)(&textPrinter->subStructFields);
+
+ if (subStruct->hasGlyphIdBeenSet == FALSE)
+ {
+ subStruct->glyphId = 4;
+ subStruct->hasGlyphIdBeenSet = TRUE;
+ }
+ return RenderText(textPrinter);
+}
+
+u16 Font5Func(struct TextPrinter *textPrinter)
+{
+ struct TextPrinterSubStruct *subStruct = (struct TextPrinterSubStruct *)(&textPrinter->subStructFields);
+
+ if (subStruct->hasGlyphIdBeenSet == FALSE)
+ {
+ subStruct->glyphId = 5;
+ subStruct->hasGlyphIdBeenSet = TRUE;
+ }
+ return RenderText(textPrinter);
+}
+
+u16 Font7Func(struct TextPrinter *textPrinter)
+{
+ struct TextPrinterSubStruct *subStruct = (struct TextPrinterSubStruct *)(&textPrinter->subStructFields);
+
+ if (subStruct->hasGlyphIdBeenSet == FALSE)
+ {
+ subStruct->glyphId = 7;
+ subStruct->hasGlyphIdBeenSet = TRUE;
+ }
+ return RenderText(textPrinter);
+}
+
+u16 Font8Func(struct TextPrinter *textPrinter)
+{
+ struct TextPrinterSubStruct *subStruct = (struct TextPrinterSubStruct *)(&textPrinter->subStructFields);
+
+ if (subStruct->hasGlyphIdBeenSet == FALSE)
+ {
+ subStruct->glyphId = 8;
+ subStruct->hasGlyphIdBeenSet = TRUE;
+ }
+ return RenderText(textPrinter);
+}
+
+void TextPrinterInitDownArrowCounters(struct TextPrinter *textPrinter)
+{
+ struct TextPrinterSubStruct *subStruct = (struct TextPrinterSubStruct *)(&textPrinter->subStructFields);
+
+ if (gTextFlags.autoScroll == 1)
+ {
+ subStruct->autoScrollDelay = 0;
+ }
+ else
+ {
+ subStruct->downArrowYPosIdx = 0;
+ subStruct->downArrowDelay = 0;
+ }
+}
+
+void TextPrinterDrawDownArrow(struct TextPrinter *textPrinter)
+{
+ struct TextPrinterSubStruct *subStruct = (struct TextPrinterSubStruct *)(&textPrinter->subStructFields);
+ const u8 *arrowTiles;
+
+ if (gTextFlags.autoScroll == 0)
+ {
+ if (subStruct->downArrowDelay != 0)
+ {
+ subStruct->downArrowDelay--;
+ }
+ else
+ {
+ FillWindowPixelRect(
+ textPrinter->printerTemplate.windowId,
+ textPrinter->printerTemplate.bgColor << 4 | textPrinter->printerTemplate.bgColor,
+ textPrinter->printerTemplate.currentX,
+ textPrinter->printerTemplate.currentY,
+ 8,
+ 16);
+
+ switch (gTextFlags.useAlternateDownArrow)
+ {
+ case FALSE:
+ default:
+ arrowTiles = gDownArrowTiles;
+ break;
+ case TRUE:
+ arrowTiles = gDarkDownArrowTiles;
+ break;
+ }
+
+ BlitBitmapRectToWindow(
+ textPrinter->printerTemplate.windowId,
+ arrowTiles,
+ 0,
+ gDownArrowYCoords[subStruct->downArrowYPosIdx],
+ 8,
+ 16,
+ textPrinter->printerTemplate.currentX,
+ textPrinter->printerTemplate.currentY,
+ 8,
+ 16);
+ CopyWindowToVram(textPrinter->printerTemplate.windowId, 2);
+
+ subStruct->downArrowDelay = 8;
+ subStruct->downArrowYPosIdx++;
+ }
+ }
+}
+
+void TextPrinterClearDownArrow(struct TextPrinter *textPrinter)
+{
+ FillWindowPixelRect(
+ textPrinter->printerTemplate.windowId,
+ textPrinter->printerTemplate.bgColor << 4 | textPrinter->printerTemplate.bgColor,
+ textPrinter->printerTemplate.currentX,
+ textPrinter->printerTemplate.currentY,
+ 8,
+ 16);
+ CopyWindowToVram(textPrinter->printerTemplate.windowId, 2);
+}
+
+bool8 TextPrinterWaitAutoMode(struct TextPrinter *textPrinter)
+{
+ struct TextPrinterSubStruct *subStruct = (struct TextPrinterSubStruct *)(&textPrinter->subStructFields);
+
+ if (subStruct->autoScrollDelay == 49)
+ {
+ return TRUE;
+ }
+ else
+ {
+ subStruct->autoScrollDelay++;
+ return FALSE;
+ }
+}
+
+bool16 TextPrinterWaitWithDownArrow(struct TextPrinter *textPrinter)
+{
+ bool8 result = FALSE;
+ if (gTextFlags.autoScroll != 0)
+ {
+ result = TextPrinterWaitAutoMode(textPrinter);
+ }
+ else
+ {
+ TextPrinterDrawDownArrow(textPrinter);
+ if (JOY_NEW(A_BUTTON | B_BUTTON))
+ {
+ result = TRUE;
+ PlaySE(SE_SELECT);
+ }
+ }
+ return result;
+}
+
+bool16 TextPrinterWait(struct TextPrinter *textPrinter)
+{
+ bool16 result = FALSE;
+ if (gTextFlags.autoScroll != 0)
+ {
+ result = TextPrinterWaitAutoMode(textPrinter);
+ }
+ else
+ {
+ if (JOY_NEW(A_BUTTON | B_BUTTON))
+ {
+ result = TRUE;
+ PlaySE(SE_SELECT);
+ }
+ }
+ return result;
+}
+
+void DrawDownArrow(u8 windowId, u16 x, u16 y, u8 bgColor, bool8 drawArrow, u8 *counter, u8 *yCoordIndex)
+{
+ const u8 *arrowTiles;
+
+ if (*counter != 0)
+ {
+ --*counter;
+ }
+ else
+ {
+ FillWindowPixelRect(windowId, (bgColor << 4) | bgColor, x, y, 0x8, 0x10);
+ if (drawArrow == 0)
+ {
+ switch (gTextFlags.useAlternateDownArrow)
+ {
+ case 0:
+ default:
+ arrowTiles = gDownArrowTiles;
+ break;
+ case 1:
+ arrowTiles = gDarkDownArrowTiles;
+ break;
+ }
+
+ BlitBitmapRectToWindow(
+ windowId,
+ arrowTiles,
+ 0,
+ gDownArrowYCoords[*yCoordIndex & 3],
+ 0x8,
+ 0x10,
+ x,
+ y - 2,
+ 0x8,
+ 0x10);
+ CopyWindowToVram(windowId, 0x2);
+ *counter = 8;
+ ++*yCoordIndex;
+ }
+ }
+}
+
+u16 RenderText(struct TextPrinter *textPrinter)
+{
+ struct TextPrinterSubStruct *subStruct = (struct TextPrinterSubStruct *)(&textPrinter->subStructFields);
+ u16 currChar;
+ s32 width;
+ s32 widthHelper;
+
+ switch (textPrinter->state)
+ {
+ case 0:
+ if ((JOY_HELD(A_BUTTON | B_BUTTON)) && subStruct->hasPrintBeenSpedUp)
+ textPrinter->delayCounter = 0;
+
+ if (textPrinter->delayCounter && textPrinter->textSpeed)
+ {
+ textPrinter->delayCounter--;
+ if (gTextFlags.canABSpeedUpPrint && (JOY_NEW(A_BUTTON | B_BUTTON)))
+ {
+ subStruct->hasPrintBeenSpedUp = TRUE;
+ textPrinter->delayCounter = 0;
+ }
+ return 3;
+ }
+
+ if (!(gBattleTypeFlags & BATTLE_TYPE_RECORDED) && gTextFlags.autoScroll)
+ textPrinter->delayCounter = 3;
+ else
+ textPrinter->delayCounter = textPrinter->textSpeed;
+
+ currChar = *textPrinter->printerTemplate.currentChar;
+ textPrinter->printerTemplate.currentChar++;
+
+ switch (currChar)
+ {
+ case CHAR_NEWLINE:
+ textPrinter->printerTemplate.currentX = textPrinter->printerTemplate.x;
+ textPrinter->printerTemplate.currentY += (gFonts[textPrinter->printerTemplate.fontId].maxLetterHeight + textPrinter->printerTemplate.lineSpacing);
+ return 2;
+ case PLACEHOLDER_BEGIN:
+ textPrinter->printerTemplate.currentChar++;
+ return 2;
+ case EXT_CTRL_CODE_BEGIN:
+ currChar = *textPrinter->printerTemplate.currentChar;
+ textPrinter->printerTemplate.currentChar++;
+ switch (currChar)
+ {
+ case EXT_CTRL_CODE_COLOR:
+ textPrinter->printerTemplate.fgColor = *textPrinter->printerTemplate.currentChar;
+ textPrinter->printerTemplate.currentChar++;
+ GenerateFontHalfRowLookupTable(textPrinter->printerTemplate.fgColor, textPrinter->printerTemplate.bgColor, textPrinter->printerTemplate.shadowColor);
+ return 2;
+ case EXT_CTRL_CODE_HIGHLIGHT:
+ textPrinter->printerTemplate.bgColor = *textPrinter->printerTemplate.currentChar;
+ textPrinter->printerTemplate.currentChar++;
+ GenerateFontHalfRowLookupTable(textPrinter->printerTemplate.fgColor, textPrinter->printerTemplate.bgColor, textPrinter->printerTemplate.shadowColor);
+ return 2;
+ case EXT_CTRL_CODE_SHADOW:
+ textPrinter->printerTemplate.shadowColor = *textPrinter->printerTemplate.currentChar;
+ textPrinter->printerTemplate.currentChar++;
+ GenerateFontHalfRowLookupTable(textPrinter->printerTemplate.fgColor, textPrinter->printerTemplate.bgColor, textPrinter->printerTemplate.shadowColor);
+ return 2;
+ case EXT_CTRL_CODE_COLOR_HIGHLIGHT_SHADOW:
+ textPrinter->printerTemplate.fgColor = *textPrinter->printerTemplate.currentChar;
+ textPrinter->printerTemplate.currentChar++;
+ textPrinter->printerTemplate.bgColor = *textPrinter->printerTemplate.currentChar;
+ textPrinter->printerTemplate.currentChar++;
+ textPrinter->printerTemplate.shadowColor = *textPrinter->printerTemplate.currentChar;
+ textPrinter->printerTemplate.currentChar++;
+ GenerateFontHalfRowLookupTable(textPrinter->printerTemplate.fgColor, textPrinter->printerTemplate.bgColor, textPrinter->printerTemplate.shadowColor);
+ return 2;
+ case EXT_CTRL_CODE_PALETTE:
+ textPrinter->printerTemplate.currentChar++;
+ return 2;
+ case EXT_CTRL_CODE_SIZE:
+ subStruct->glyphId = *textPrinter->printerTemplate.currentChar;
+ textPrinter->printerTemplate.currentChar++;
+ return 2;
+ case EXT_CTRL_CODE_RESET_SIZE:
+ return 2;
+ case EXT_CTRL_CODE_PAUSE:
+ textPrinter->delayCounter = *textPrinter->printerTemplate.currentChar;
+ textPrinter->printerTemplate.currentChar++;
+ textPrinter->state = 6;
+ return 2;
+ case EXT_CTRL_CODE_PAUSE_UNTIL_PRESS:
+ textPrinter->state = 1;
+ if (gTextFlags.autoScroll)
+ subStruct->autoScrollDelay = 0;
+ return 3;
+ case EXT_CTRL_CODE_WAIT_SE:
+ textPrinter->state = 5;
+ return 3;
+ case EXT_CTRL_CODE_PLAY_BGM:
+ currChar = *textPrinter->printerTemplate.currentChar;
+ textPrinter->printerTemplate.currentChar++;
+ currChar |= *textPrinter->printerTemplate.currentChar << 8;
+ textPrinter->printerTemplate.currentChar++;
+ PlayBGM(currChar);
+ return 2;
+ case EXT_CTRL_CODE_ESCAPE:
+ currChar = *textPrinter->printerTemplate.currentChar | 0x100;
+ textPrinter->printerTemplate.currentChar++;
+ break;
+ case EXT_CTRL_CODE_PLAY_SE:
+ currChar = *textPrinter->printerTemplate.currentChar;
+ textPrinter->printerTemplate.currentChar++;
+ currChar |= (*textPrinter->printerTemplate.currentChar << 8);
+ textPrinter->printerTemplate.currentChar++;
+ PlaySE(currChar);
+ return 2;
+ case EXT_CTRL_CODE_SHIFT_TEXT:
+ textPrinter->printerTemplate.currentX = textPrinter->printerTemplate.x + *textPrinter->printerTemplate.currentChar;
+ textPrinter->printerTemplate.currentChar++;
+ return 2;
+ case EXT_CTRL_CODE_SHIFT_DOWN:
+ textPrinter->printerTemplate.currentY = textPrinter->printerTemplate.y + *textPrinter->printerTemplate.currentChar;
+ textPrinter->printerTemplate.currentChar++;
+ return 2;
+ case EXT_CTRL_CODE_FILL_WINDOW:
+ FillWindowPixelBuffer(textPrinter->printerTemplate.windowId, PIXEL_FILL(textPrinter->printerTemplate.bgColor));
+ textPrinter->printerTemplate.currentX = textPrinter->printerTemplate.x;
+ textPrinter->printerTemplate.currentY = textPrinter->printerTemplate.y;
+ return 2;
+ case EXT_CTRL_CODE_PAUSE_MUSIC:
+ m4aMPlayStop(&gMPlayInfo_BGM);
+ return 2;
+ case EXT_CTRL_CODE_RESUME_MUSIC:
+ m4aMPlayContinue(&gMPlayInfo_BGM);
+ return 2;
+ case EXT_CTRL_CODE_CLEAR:
+ width = *textPrinter->printerTemplate.currentChar;
+ textPrinter->printerTemplate.currentChar++;
+ if (width > 0)
+ {
+ ClearTextSpan(textPrinter, width);
+ textPrinter->printerTemplate.currentX += width;
+ return 0;
+ }
+ return 2;
+ case EXT_CTRL_CODE_SKIP:
+ textPrinter->printerTemplate.currentX = *textPrinter->printerTemplate.currentChar + textPrinter->printerTemplate.x;
+ textPrinter->printerTemplate.currentChar++;
+ return 2;
+ case EXT_CTRL_CODE_CLEAR_TO:
+ {
+ widthHelper = *textPrinter->printerTemplate.currentChar;
+ widthHelper += textPrinter->printerTemplate.x;
+ textPrinter->printerTemplate.currentChar++;
+ width = widthHelper - textPrinter->printerTemplate.currentX;
+ if (width > 0)
+ {
+ ClearTextSpan(textPrinter, width);
+ textPrinter->printerTemplate.currentX += width;
+ return 0;
+ }
+ }
+ return 2;
+ case EXT_CTRL_CODE_MIN_LETTER_SPACING:
+ textPrinter->minLetterSpacing = *textPrinter->printerTemplate.currentChar++;
+ return 2;
+ case EXT_CTRL_CODE_JPN:
+ textPrinter->japanese = 1;
+ return 2;
+ case EXT_CTRL_CODE_ENG:
+ textPrinter->japanese = 0;
+ return 2;
+ }
+ break;
+ case CHAR_PROMPT_CLEAR:
+ textPrinter->state = 2;
+ TextPrinterInitDownArrowCounters(textPrinter);
+ return 3;
+ case CHAR_PROMPT_SCROLL:
+ textPrinter->state = 3;
+ TextPrinterInitDownArrowCounters(textPrinter);
+ return 3;
+ case CHAR_EXTRA_SYMBOL:
+ currChar = *textPrinter->printerTemplate.currentChar | 0x100;
+ textPrinter->printerTemplate.currentChar++;
+ break;
+ case CHAR_KEYPAD_ICON:
+ currChar = *textPrinter->printerTemplate.currentChar++;
+ gCurGlyph.width = DrawKeypadIcon(textPrinter->printerTemplate.windowId, currChar, textPrinter->printerTemplate.currentX, textPrinter->printerTemplate.currentY);
+ textPrinter->printerTemplate.currentX += gCurGlyph.width + textPrinter->printerTemplate.letterSpacing;
+ return 0;
+ case EOS:
+ return 1;
+ }
+
+ switch (subStruct->glyphId)
+ {
+ case 0:
+ DecompressGlyphFont0(currChar, textPrinter->japanese);
+ break;
+ case 1:
+ DecompressGlyphFont1(currChar, textPrinter->japanese);
+ break;
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ DecompressGlyphFont2(currChar, textPrinter->japanese);
+ break;
+ case 7:
+ DecompressGlyphFont7(currChar, textPrinter->japanese);
+ break;
+ case 8:
+ DecompressGlyphFont8(currChar, textPrinter->japanese);
+ break;
+ case 6:
+ break;
+ }
+
+ CopyGlyphToWindow(textPrinter);
+
+ if (textPrinter->minLetterSpacing)
+ {
+ textPrinter->printerTemplate.currentX += gCurGlyph.width;
+ width = textPrinter->minLetterSpacing - gCurGlyph.width;
+ if (width > 0)
+ {
+ ClearTextSpan(textPrinter, width);
+ textPrinter->printerTemplate.currentX += width;
+ }
+ }
+ else
+ {
+ if (textPrinter->japanese)
+ textPrinter->printerTemplate.currentX += (gCurGlyph.width + textPrinter->printerTemplate.letterSpacing);
+ else
+ textPrinter->printerTemplate.currentX += gCurGlyph.width;
+ }
+ return 0;
+ case 1:
+ if (TextPrinterWait(textPrinter))
+ textPrinter->state = 0;
+ return 3;
+ case 2:
+ if (TextPrinterWaitWithDownArrow(textPrinter))
+ {
+ FillWindowPixelBuffer(textPrinter->printerTemplate.windowId, PIXEL_FILL(textPrinter->printerTemplate.bgColor));
+ textPrinter->printerTemplate.currentX = textPrinter->printerTemplate.x;
+ textPrinter->printerTemplate.currentY = textPrinter->printerTemplate.y;
+ textPrinter->state = 0;
+ }
+ return 3;
+ case 3:
+ if (TextPrinterWaitWithDownArrow(textPrinter))
+ {
+ TextPrinterClearDownArrow(textPrinter);
+ textPrinter->scrollDistance = gFonts[textPrinter->printerTemplate.fontId].maxLetterHeight + textPrinter->printerTemplate.lineSpacing;
+ textPrinter->printerTemplate.currentX = textPrinter->printerTemplate.x;
+ textPrinter->state = 4;
+ }
+ return 3;
+ case 4:
+ if (textPrinter->scrollDistance)
+ {
+ int scrollSpeed = GetPlayerTextSpeed();
+ int speed = gWindowVerticalScrollSpeeds[scrollSpeed];
+ if (textPrinter->scrollDistance < speed)
+ {
+ ScrollWindow(textPrinter->printerTemplate.windowId, 0, textPrinter->scrollDistance, PIXEL_FILL(textPrinter->printerTemplate.bgColor));
+ textPrinter->scrollDistance = 0;
+ }
+ else
+ {
+ ScrollWindow(textPrinter->printerTemplate.windowId, 0, speed, PIXEL_FILL(textPrinter->printerTemplate.bgColor));
+ textPrinter->scrollDistance -= speed;
+ }
+ CopyWindowToVram(textPrinter->printerTemplate.windowId, 2);
+ }
+ else
+ {
+ textPrinter->state = 0;
+ }
+ return 3;
+ case 5:
+ if (!IsSEPlaying())
+ textPrinter->state = 0;
+ return 3;
+ case 6:
+ if (textPrinter->delayCounter != 0)
+ textPrinter->delayCounter--;
+ else
+ textPrinter->state = 0;
+ return 3;
+ }
+
+ return 1;
+}
+
+u32 GetStringWidthFixedWidthFont(const u8 *str, u8 fontId, u8 letterSpacing)
+{
+ int i;
+ u8 width;
+ int temp;
+ int temp2;
+ u8 line;
+ int strPos;
+ u8 lineWidths[8];
+ const u8 *strLocal;
+
+ for (i = 0; i < 8; i++)
+ {
+ lineWidths[i] = 0;
+ }
+
+ width = 0;
+ line = 0;
+ strLocal = str;
+ strPos = 0;
+
+ do
+ {
+ temp = strLocal[strPos++];
+ switch (temp)
+ {
+ case CHAR_NEWLINE:
+ case EOS:
+ lineWidths[line] = width;
+ width = 0;
+ line++;
+ break;
+ case EXT_CTRL_CODE_BEGIN:
+ temp2 = strLocal[strPos++];
+ switch (temp2)
+ {
+ case EXT_CTRL_CODE_COLOR_HIGHLIGHT_SHADOW:
+ ++strPos;
+ case EXT_CTRL_CODE_PLAY_BGM:
+ case EXT_CTRL_CODE_PLAY_SE:
+ ++strPos;
+ case EXT_CTRL_CODE_COLOR:
+ case EXT_CTRL_CODE_HIGHLIGHT:
+ case EXT_CTRL_CODE_SHADOW:
+ case EXT_CTRL_CODE_PALETTE:
+ case EXT_CTRL_CODE_SIZE:
+ case EXT_CTRL_CODE_PAUSE:
+ case EXT_CTRL_CODE_ESCAPE:
+ case EXT_CTRL_CODE_SHIFT_TEXT:
+ case EXT_CTRL_CODE_SHIFT_DOWN:
+ case EXT_CTRL_CODE_CLEAR:
+ case EXT_CTRL_CODE_SKIP:
+ case EXT_CTRL_CODE_CLEAR_TO:
+ case EXT_CTRL_CODE_MIN_LETTER_SPACING:
+ ++strPos;
+ break;
+ case EXT_CTRL_CODE_RESET_SIZE:
+ case EXT_CTRL_CODE_PAUSE_UNTIL_PRESS:
+ case EXT_CTRL_CODE_WAIT_SE:
+ case EXT_CTRL_CODE_FILL_WINDOW:
+ case EXT_CTRL_CODE_JPN:
+ case EXT_CTRL_CODE_ENG:
+ default:
+ break;
+ }
+ break;
+ case CHAR_DYNAMIC:
+ case PLACEHOLDER_BEGIN:
+ ++strPos;
+ break;
+ case CHAR_PROMPT_SCROLL:
+ case CHAR_PROMPT_CLEAR:
+ break;
+ case CHAR_KEYPAD_ICON:
+ case CHAR_EXTRA_SYMBOL:
+ ++strPos;
+ default:
+ ++width;
+ break;
+ }
+ } while (temp != EOS);
+
+ for (width = 0, strPos = 0; strPos < 8; ++strPos)
+ {
+ if (width < lineWidths[strPos])
+ width = lineWidths[strPos];
+ }
+
+ return (u8)(GetFontAttribute(fontId, FONTATTR_MAX_LETTER_WIDTH) + letterSpacing) * width;
+}
+
+u32 (*GetFontWidthFunc(u8 glyphId))(u16, bool32)
+{
+ u32 i;
+
+ for (i = 0; i < 9; ++i)
+ {
+ if (glyphId == gGlyphWidthFuncs[i].fontId)
+ return gGlyphWidthFuncs[i].func;
+ }
+
+ return NULL;
+}
+
+s32 GetStringWidth(u8 fontId, const u8 *str, s16 letterSpacing)
+{
+ bool8 isJapanese;
+ int minGlyphWidth;
+ u32 (*func)(u16 glyphId, bool32 isJapanese);
+ int localLetterSpacing;
+ u32 lineWidth;
+ const u8 *bufferPointer;
+ int glyphWidth;
+ s32 width;
+
+ isJapanese = 0;
+ minGlyphWidth = 0;
+
+ func = GetFontWidthFunc(fontId);
+ if (func == NULL)
+ return 0;
+
+ if (letterSpacing == -1)
+ localLetterSpacing = GetFontAttribute(fontId, FONTATTR_LETTER_SPACING);
+ else
+ localLetterSpacing = letterSpacing;
+
+ width = 0;
+ lineWidth = 0;
+ bufferPointer = 0;
+
+ while (*str != EOS)
+ {
+ switch (*str)
+ {
+ case CHAR_NEWLINE:
+ if (lineWidth > width)
+ width = lineWidth;
+ lineWidth = 0;
+ break;
+ case PLACEHOLDER_BEGIN:
+ switch (*++str)
+ {
+ case PLACEHOLDER_ID_STRING_VAR_1:
+ bufferPointer = gStringVar1;
+ break;
+ case PLACEHOLDER_ID_STRING_VAR_2:
+ bufferPointer = gStringVar2;
+ break;
+ case PLACEHOLDER_ID_STRING_VAR_3:
+ bufferPointer = gStringVar3;
+ break;
+ default:
+ return 0;
+ }
+ case CHAR_DYNAMIC:
+ if (bufferPointer == NULL)
+ bufferPointer = DynamicPlaceholderTextUtil_GetPlaceholderPtr(*++str);
+ while (*bufferPointer != EOS)
+ {
+ glyphWidth = func(*bufferPointer++, isJapanese);
+ if (minGlyphWidth > 0)
+ {
+ if (glyphWidth < minGlyphWidth)
+ glyphWidth = minGlyphWidth;
+ lineWidth += glyphWidth;
+ }
+ else
+ {
+ lineWidth += glyphWidth;
+ if (isJapanese && str[1] != EOS)
+ lineWidth += localLetterSpacing;
+ }
+ }
+ bufferPointer = 0;
+ break;
+ case EXT_CTRL_CODE_BEGIN:
+ switch (*++str)
+ {
+ case EXT_CTRL_CODE_COLOR_HIGHLIGHT_SHADOW:
+ ++str;
+ case EXT_CTRL_CODE_PLAY_BGM:
+ case EXT_CTRL_CODE_PLAY_SE:
+ ++str;
+ case EXT_CTRL_CODE_COLOR:
+ case EXT_CTRL_CODE_HIGHLIGHT:
+ case EXT_CTRL_CODE_SHADOW:
+ case EXT_CTRL_CODE_PALETTE:
+ case EXT_CTRL_CODE_PAUSE:
+ case EXT_CTRL_CODE_ESCAPE:
+ case EXT_CTRL_CODE_SHIFT_TEXT:
+ case EXT_CTRL_CODE_SHIFT_DOWN:
+ ++str;
+ break;
+ case EXT_CTRL_CODE_SIZE:
+ func = GetFontWidthFunc(*++str);
+ if (func == NULL)
+ return 0;
+ if (letterSpacing == -1)
+ localLetterSpacing = GetFontAttribute(*str, FONTATTR_LETTER_SPACING);
+ break;
+ case EXT_CTRL_CODE_CLEAR:
+ glyphWidth = *++str;
+ lineWidth += glyphWidth;
+ break;
+ case EXT_CTRL_CODE_SKIP:
+ lineWidth = *++str;
+ break;
+ case EXT_CTRL_CODE_CLEAR_TO:
+ if (*++str > lineWidth)
+ lineWidth = *str;
+ break;
+ case EXT_CTRL_CODE_MIN_LETTER_SPACING:
+ minGlyphWidth = *++str;
+ break;
+ case EXT_CTRL_CODE_JPN:
+ isJapanese = 1;
+ break;
+ case EXT_CTRL_CODE_ENG:
+ isJapanese = 0;
+ break;
+ case EXT_CTRL_CODE_RESET_SIZE:
+ case EXT_CTRL_CODE_PAUSE_UNTIL_PRESS:
+ case EXT_CTRL_CODE_WAIT_SE:
+ case EXT_CTRL_CODE_FILL_WINDOW:
+ default:
+ break;
+ }
+ break;
+ case CHAR_KEYPAD_ICON:
+ case CHAR_EXTRA_SYMBOL:
+ if (*str == CHAR_EXTRA_SYMBOL)
+ glyphWidth = func(*++str | 0x100, isJapanese);
+ else
+ glyphWidth = GetKeypadIconWidth(*++str);
+
+ if (minGlyphWidth > 0)
+ {
+ if (glyphWidth < minGlyphWidth)
+ glyphWidth = minGlyphWidth;
+ lineWidth += glyphWidth;
+ }
+ else
+ {
+ lineWidth += glyphWidth;
+ if (isJapanese && str[1] != EOS)
+ lineWidth += localLetterSpacing;
+ }
+ break;
+ case CHAR_PROMPT_SCROLL:
+ case CHAR_PROMPT_CLEAR:
+ break;
+ default:
+ glyphWidth = func(*str, isJapanese);
+ if (minGlyphWidth > 0)
+ {
+ if (glyphWidth < minGlyphWidth)
+ glyphWidth = minGlyphWidth;
+ lineWidth += glyphWidth;
+ }
+ else
+ {
+ lineWidth += glyphWidth;
+ if (isJapanese && str[1] != EOS)
+ lineWidth += localLetterSpacing;
+ }
+ break;
+ }
+ ++str;
+ }
+
+ if (lineWidth > width)
+ return lineWidth;
+ return width;
+}
+
+u8 RenderTextFont9(u8 *pixels, u8 fontId, u8 *str)
+{
+ u8 shadowColor;
+ u8 *strLocal;
+ int strPos;
+ int temp;
+ int temp2;
+ u8 colorBackup[3];
+ u8 fgColor;
+ u8 bgColor;
+
+ SaveTextColors(&colorBackup[0], &colorBackup[1], &colorBackup[2]);
+
+ fgColor = TEXT_COLOR_WHITE;
+ bgColor = TEXT_COLOR_TRANSPARENT;
+ shadowColor = TEXT_COLOR_LIGHT_GRAY;
+
+ GenerateFontHalfRowLookupTable(TEXT_COLOR_WHITE, TEXT_COLOR_TRANSPARENT, TEXT_COLOR_LIGHT_GRAY);
+ strLocal = str;
+ strPos = 0;
+
+ do
+ {
+ temp = strLocal[strPos++];
+ switch (temp)
+ {
+ case EXT_CTRL_CODE_BEGIN:
+ temp2 = strLocal[strPos++];
+ switch (temp2)
+ {
+ case EXT_CTRL_CODE_COLOR_HIGHLIGHT_SHADOW:
+ fgColor = strLocal[strPos++];
+ bgColor = strLocal[strPos++];
+ shadowColor = strLocal[strPos++];
+ GenerateFontHalfRowLookupTable(fgColor, bgColor, shadowColor);
+ continue;
+ case EXT_CTRL_CODE_COLOR:
+ fgColor = strLocal[strPos++];
+ GenerateFontHalfRowLookupTable(fgColor, bgColor, shadowColor);
+ continue;
+ case EXT_CTRL_CODE_HIGHLIGHT:
+ bgColor = strLocal[strPos++];
+ GenerateFontHalfRowLookupTable(fgColor, bgColor, shadowColor);
+ continue;
+ case EXT_CTRL_CODE_SHADOW:
+ shadowColor = strLocal[strPos++];
+ GenerateFontHalfRowLookupTable(fgColor, bgColor, shadowColor);
+ continue;
+ case EXT_CTRL_CODE_SIZE:
+ fontId = strLocal[strPos++];
+ break;
+ case EXT_CTRL_CODE_PLAY_BGM:
+ case EXT_CTRL_CODE_PLAY_SE:
+ ++strPos;
+ case EXT_CTRL_CODE_PALETTE:
+ case EXT_CTRL_CODE_PAUSE:
+ case EXT_CTRL_CODE_ESCAPE:
+ case EXT_CTRL_CODE_SHIFT_TEXT:
+ case EXT_CTRL_CODE_SHIFT_DOWN:
+ case EXT_CTRL_CODE_CLEAR:
+ case EXT_CTRL_CODE_SKIP:
+ case EXT_CTRL_CODE_CLEAR_TO:
+ case EXT_CTRL_CODE_MIN_LETTER_SPACING:
+ ++strPos;
+ break;
+ case EXT_CTRL_CODE_RESET_SIZE:
+ case EXT_CTRL_CODE_PAUSE_UNTIL_PRESS:
+ case EXT_CTRL_CODE_WAIT_SE:
+ case EXT_CTRL_CODE_FILL_WINDOW:
+ case EXT_CTRL_CODE_JPN:
+ case EXT_CTRL_CODE_ENG:
+ default:
+ continue;
+ }
+ break;
+ case CHAR_DYNAMIC:
+ case CHAR_KEYPAD_ICON:
+ case CHAR_EXTRA_SYMBOL:
+ case PLACEHOLDER_BEGIN:
+ ++strPos;
+ break;
+ case CHAR_PROMPT_SCROLL:
+ case CHAR_PROMPT_CLEAR:
+ case CHAR_NEWLINE:
+ case EOS:
+ break;
+ default:
+ switch (fontId)
+ {
+ case 9:
+ DecompressGlyphFont9(temp);
+ break;
+ case 1:
+ default:
+ DecompressGlyphFont1(temp, 1);
+ break;
+ }
+ CpuCopy32(gCurGlyph.gfxBufferTop, pixels, 0x20);
+ CpuCopy32(gCurGlyph.gfxBufferBottom, pixels + 0x20, 0x20);
+ pixels += 0x40;
+ break;
+ }
+ }
+ while (temp != EOS);
+
+ RestoreTextColors(&colorBackup[0], &colorBackup[1], &colorBackup[2]);
+ return 1;
+}
+
+u8 DrawKeypadIcon(u8 windowId, u8 keypadIconId, u16 x, u16 y)
+{
+ BlitBitmapRectToWindow(
+ windowId,
+ gKeypadIconTiles + (gKeypadIcons[keypadIconId].tileOffset * 0x20),
+ 0,
+ 0,
+ 0x80,
+ 0x80,
+ x,
+ y,
+ gKeypadIcons[keypadIconId].width,
+ gKeypadIcons[keypadIconId].height);
+ return gKeypadIcons[keypadIconId].width;
+}
+
+u8 GetKeypadIconTileOffset(u8 keypadIconId)
+{
+ return gKeypadIcons[keypadIconId].tileOffset;
+}
+
+u8 GetKeypadIconWidth(u8 keypadIconId)
+{
+ return gKeypadIcons[keypadIconId].width;
+}
+
+u8 GetKeypadIconHeight(u8 keypadIconId)
+{
+ return gKeypadIcons[keypadIconId].height;
+}
+
+void SetDefaultFontsPointer(void)
+{
+ SetFontsPointer(&gFontInfos[0]);
+}
+
+u8 GetFontAttribute(u8 fontId, u8 attributeId)
+{
+ int result = 0;
+ switch (attributeId)
+ {
+ case FONTATTR_MAX_LETTER_WIDTH:
+ result = gFontInfos[fontId].maxLetterWidth;
+ break;
+ case FONTATTR_MAX_LETTER_HEIGHT:
+ result = gFontInfos[fontId].maxLetterHeight;
+ break;
+ case FONTATTR_LETTER_SPACING:
+ result = gFontInfos[fontId].letterSpacing;
+ break;
+ case FONTATTR_LINE_SPACING:
+ result = gFontInfos[fontId].lineSpacing;
+ break;
+ case FONTATTR_UNKNOWN:
+ result = gFontInfos[fontId].unk;
+ break;
+ case FONTATTR_COLOR_FOREGROUND:
+ result = gFontInfos[fontId].fgColor;
+ break;
+ case FONTATTR_COLOR_BACKGROUND:
+ result = gFontInfos[fontId].bgColor;
+ break;
+ case FONTATTR_COLOR_SHADOW:
+ result = gFontInfos[fontId].shadowColor;
+ break;
+ }
+ return result;
+}
+
+u8 GetMenuCursorDimensionByFont(u8 fontId, u8 whichDimension)
+{
+ return gMenuCursorDimensions[fontId][whichDimension];
+}
+
+void DecompressGlyphFont0(u16 glyphId, bool32 isJapanese)
+{
+ const u16* glyphs;
+
+ if (isJapanese == 1)
+ {
+ glyphs = gFont0JapaneseGlyphs + (0x100 * (glyphId >> 0x4)) + (0x8 * (glyphId & 0xF));
+ DecompressGlyphTile(glyphs, gCurGlyph.gfxBufferTop);
+ DecompressGlyphTile(glyphs + 0x80, gCurGlyph.gfxBufferBottom);
+ gCurGlyph.width = 8;
+ gCurGlyph.height = 12;
+ }
+ else
+ {
+ glyphs = gFont0LatinGlyphs + (0x20 * glyphId);
+ gCurGlyph.width = gFont0LatinGlyphWidths[glyphId];
+
+ if (gCurGlyph.width <= 8)
+ {
+ DecompressGlyphTile(glyphs, gCurGlyph.gfxBufferTop);
+ DecompressGlyphTile(glyphs + 0x10, gCurGlyph.gfxBufferBottom);
+ }
+ else
+ {
+ DecompressGlyphTile(glyphs, gCurGlyph.gfxBufferTop);
+ DecompressGlyphTile(glyphs + 0x8, gCurGlyph.gfxBufferTop + 8);
+ DecompressGlyphTile(glyphs + 0x10, gCurGlyph.gfxBufferBottom);
+ DecompressGlyphTile(glyphs + 0x18, gCurGlyph.gfxBufferBottom + 8);
+ }
+
+ gCurGlyph.height = 13;
+ }
+}
+
+u32 GetGlyphWidthFont0(u16 glyphId, bool32 isJapanese)
+{
+ if (isJapanese == TRUE)
+ return 8;
+ else
+ return gFont0LatinGlyphWidths[glyphId];
+}
+
+void DecompressGlyphFont7(u16 glyphId, bool32 isJapanese)
+{
+ const u16* glyphs;
+
+ if (isJapanese == TRUE)
+ {
+ glyphs = gFont1JapaneseGlyphs + (0x100 * (glyphId >> 0x4)) + (0x8 * (glyphId % 0x10));
+ DecompressGlyphTile(glyphs, gCurGlyph.gfxBufferTop);
+ DecompressGlyphTile(glyphs + 0x80, gCurGlyph.gfxBufferBottom);
+ gCurGlyph.width = 8;
+ gCurGlyph.height = 15;
+ }
+ else
+ {
+ glyphs = gFont7LatinGlyphs + (0x20 * glyphId);
+ gCurGlyph.width = gFont7LatinGlyphWidths[glyphId];
+
+ if (gCurGlyph.width <= 8)
+ {
+ DecompressGlyphTile(glyphs, gCurGlyph.gfxBufferTop);
+ DecompressGlyphTile(glyphs + 0x10, gCurGlyph.gfxBufferBottom);
+ }
+ else
+ {
+ DecompressGlyphTile(glyphs, gCurGlyph.gfxBufferTop);
+ DecompressGlyphTile(glyphs + 0x8, gCurGlyph.gfxBufferTop + 8);
+ DecompressGlyphTile(glyphs + 0x10, gCurGlyph.gfxBufferBottom);
+ DecompressGlyphTile(glyphs + 0x18, gCurGlyph.gfxBufferBottom + 8);
+ }
+
+ gCurGlyph.height = 15;
+ }
+}
+
+u32 GetGlyphWidthFont7(u16 glyphId, bool32 isJapanese)
+{
+ if (isJapanese == TRUE)
+ return 8;
+ else
+ return gFont7LatinGlyphWidths[glyphId];
+}
+
+void DecompressGlyphFont8(u16 glyphId, bool32 isJapanese)
+{
+ const u16* glyphs;
+
+ if (isJapanese == TRUE)
+ {
+ glyphs = gFont0JapaneseGlyphs + (0x100 * (glyphId >> 0x4)) + (0x8 * (glyphId & 0xF));
+ DecompressGlyphTile(glyphs, gCurGlyph.gfxBufferTop);
+ DecompressGlyphTile(glyphs + 0x80, gCurGlyph.gfxBufferBottom);
+ gCurGlyph.width = 8;
+ gCurGlyph.height = 12;
+ }
+ else
+ {
+ glyphs = gFont8LatinGlyphs + (0x20 * glyphId);
+ gCurGlyph.width = gFont8LatinGlyphWidths[glyphId];
+
+ if (gCurGlyph.width <= 8)
+ {
+ DecompressGlyphTile(glyphs, gCurGlyph.gfxBufferTop);
+ DecompressGlyphTile(glyphs + 0x10, gCurGlyph.gfxBufferBottom);
+ }
+ else
+ {
+ DecompressGlyphTile(glyphs, gCurGlyph.gfxBufferTop);
+ DecompressGlyphTile(glyphs + 0x8, gCurGlyph.gfxBufferTop + 8);
+ DecompressGlyphTile(glyphs + 0x10, gCurGlyph.gfxBufferBottom);
+ DecompressGlyphTile(glyphs + 0x18, gCurGlyph.gfxBufferBottom + 8);
+ }
+
+ gCurGlyph.height = 12;
+ }
+}
+
+u32 GetGlyphWidthFont8(u16 glyphId, bool32 isJapanese)
+{
+ if (isJapanese == TRUE)
+ return 8;
+ else
+ return gFont8LatinGlyphWidths[glyphId];
+}
+
+void DecompressGlyphFont2(u16 glyphId, bool32 isJapanese)
+{
+ const u16* glyphs;
+
+ if (isJapanese == TRUE)
+ {
+ glyphs = gFont2JapaneseGlyphs + (0x100 * (glyphId >> 0x3)) + (0x10 * (glyphId & 0x7));
+ DecompressGlyphTile(glyphs, gCurGlyph.gfxBufferTop);
+ DecompressGlyphTile(glyphs + 0x8, gCurGlyph.gfxBufferTop + 8);
+ DecompressGlyphTile(glyphs + 0x80, gCurGlyph.gfxBufferBottom); // gCurGlyph + 0x20
+ DecompressGlyphTile(glyphs + 0x88, gCurGlyph.gfxBufferBottom + 8); // gCurGlyph + 0x60
+ gCurGlyph.width = gFont2JapaneseGlyphWidths[glyphId];
+ gCurGlyph.height = 14;
+ }
+ else
+ {
+ glyphs = gFont2LatinGlyphs + (0x20 * glyphId);
+ gCurGlyph.width = gFont2LatinGlyphWidths[glyphId];
+
+ if (gCurGlyph.width <= 8)
+ {
+ DecompressGlyphTile(glyphs, gCurGlyph.gfxBufferTop);
+ DecompressGlyphTile(glyphs + 0x10, gCurGlyph.gfxBufferBottom);
+ }
+ else
+ {
+ DecompressGlyphTile(glyphs, gCurGlyph.gfxBufferTop);
+ DecompressGlyphTile(glyphs + 0x8, gCurGlyph.gfxBufferTop + 8);
+ DecompressGlyphTile(glyphs + 0x10, gCurGlyph.gfxBufferBottom);
+ DecompressGlyphTile(glyphs + 0x18, gCurGlyph.gfxBufferBottom + 8);
+ }
+
+ gCurGlyph.height = 14;
+ }
+}
+
+u32 GetGlyphWidthFont2(u16 glyphId, bool32 isJapanese)
+{
+ if (isJapanese == TRUE)
+ return gFont2JapaneseGlyphWidths[glyphId];
+ else
+ return gFont2LatinGlyphWidths[glyphId];
+}
+
+void DecompressGlyphFont1(u16 glyphId, bool32 isJapanese)
+{
+ const u16* glyphs;
+
+ if (isJapanese == TRUE)
+ {
+ glyphs = gFont1JapaneseGlyphs + (0x100 * (glyphId >> 0x4)) + (0x8 * (glyphId % 0x10));
+ DecompressGlyphTile(glyphs, gCurGlyph.gfxBufferTop);
+ DecompressGlyphTile(glyphs + 0x80, gCurGlyph.gfxBufferBottom);
+ gCurGlyph.width = 8;
+ gCurGlyph.height = 15;
+ }
+ else
+ {
+ glyphs = gFont1LatinGlyphs + (0x20 * glyphId);
+ gCurGlyph.width = gFont1LatinGlyphWidths[glyphId];
+
+ if (gCurGlyph.width <= 8)
+ {
+ DecompressGlyphTile(glyphs, gCurGlyph.gfxBufferTop);
+ DecompressGlyphTile(glyphs + 0x10, gCurGlyph.gfxBufferBottom);
+ }
+ else
+ {
+ DecompressGlyphTile(glyphs, gCurGlyph.gfxBufferTop);
+ DecompressGlyphTile(glyphs + 0x8, gCurGlyph.gfxBufferTop + 8);
+ DecompressGlyphTile(glyphs + 0x10, gCurGlyph.gfxBufferBottom);
+ DecompressGlyphTile(glyphs + 0x18, gCurGlyph.gfxBufferBottom + 8);
+ }
+
+ gCurGlyph.height = 15;
+ }
+}
+
+u32 GetGlyphWidthFont1(u16 glyphId, bool32 isJapanese)
+{
+ if (isJapanese == TRUE)
+ return 8;
+ else
+ return gFont1LatinGlyphWidths[glyphId];
+}
+
+void DecompressGlyphFont9(u16 glyphId)
+{
+ const u16* glyphs;
+
+ glyphs = gFont9JapaneseGlyphs + (0x100 * (glyphId >> 4)) + (0x8 * (glyphId & 0xF));
+ DecompressGlyphTile(glyphs, gCurGlyph.gfxBufferTop);
+ DecompressGlyphTile(glyphs + 0x80, gCurGlyph.gfxBufferBottom);
+ gCurGlyph.width = 8;
+ gCurGlyph.height = 12;
+}
diff --git a/src/window.c b/src/window.c
new file mode 100644
index 000000000..b03b513da
--- /dev/null
+++ b/src/window.c
@@ -0,0 +1,721 @@
+#include "global.h"
+#include "window.h"
+#include "malloc.h"
+#include "bg.h"
+#include "blit.h"
+
+u32 gUnusedWindowVar1;
+u32 gUnusedWindowVar2;
+// This global is set to 0 and never changed.
+u8 gTransparentTileNumber;
+u32 gUnusedWindowVar3;
+void *gWindowBgTilemapBuffers[NUM_BACKGROUNDS];
+extern u32 gUnneededFireRedVariable;
+
+#define WINDOWS_MAX 32
+
+EWRAM_DATA struct Window gWindows[WINDOWS_MAX] = {0};
+EWRAM_DATA static struct Window* sWindowPtr = NULL;
+EWRAM_DATA static u16 sWindowSize = 0;
+
+static u8 GetNumActiveWindowsOnBg(u8 bgId);
+static u8 GetNumActiveWindowsOnBg8Bit(u8 bgId);
+
+static const struct WindowTemplate sDummyWindowTemplate = DUMMY_WIN_TEMPLATE;
+
+static void DummyWindowBgTilemap(void)
+{
+
+}
+
+bool16 InitWindows(const struct WindowTemplate *templates)
+{
+ int i;
+ void *bgTilemapBuffer;
+ int j;
+ u8 bgLayer;
+ u16 attrib;
+ u8* allocatedTilemapBuffer;
+ int allocatedBaseBlock;
+
+ for (i = 0; i < NUM_BACKGROUNDS; ++i)
+ {
+ bgTilemapBuffer = GetBgTilemapBuffer(i);
+ if (bgTilemapBuffer != NULL)
+ gWindowBgTilemapBuffers[i] = DummyWindowBgTilemap;
+ else
+ gWindowBgTilemapBuffers[i] = bgTilemapBuffer;
+ }
+
+ for (i = 0; i < WINDOWS_MAX; ++i)
+ {
+ gWindows[i].window = sDummyWindowTemplate;
+ gWindows[i].tileData = NULL;
+ }
+
+ for (i = 0, allocatedBaseBlock = 0, bgLayer = templates[i].bg; bgLayer != 0xFF && i < WINDOWS_MAX; ++i, bgLayer = templates[i].bg)
+ {
+ if (gUnneededFireRedVariable == 1)
+ {
+ allocatedBaseBlock = DummiedOutFireRedLeafGreenTileAllocFunc(bgLayer, 0, templates[i].width * templates[i].height, 0);
+ if (allocatedBaseBlock == -1)
+ return FALSE;
+ }
+
+ if (gWindowBgTilemapBuffers[bgLayer] == NULL)
+ {
+ attrib = GetBgAttribute(bgLayer, BG_ATTR_METRIC);
+
+ if (attrib != 0xFFFF)
+ {
+ allocatedTilemapBuffer = AllocZeroed(attrib);
+
+ if (allocatedTilemapBuffer == NULL)
+ {
+ FreeAllWindowBuffers();
+ return FALSE;
+ }
+
+ for (j = 0; j < attrib; ++j)
+ allocatedTilemapBuffer[j] = 0;
+
+ gWindowBgTilemapBuffers[bgLayer] = allocatedTilemapBuffer;
+ SetBgTilemapBuffer(bgLayer, allocatedTilemapBuffer);
+ }
+ }
+
+ allocatedTilemapBuffer = AllocZeroed((u16)(32 * (templates[i].width * templates[i].height)));
+
+ if (allocatedTilemapBuffer == NULL)
+ {
+ if ((GetNumActiveWindowsOnBg(bgLayer) == 0) && (gWindowBgTilemapBuffers[bgLayer] != DummyWindowBgTilemap))
+ {
+ Free(gWindowBgTilemapBuffers[bgLayer]);
+ gWindowBgTilemapBuffers[bgLayer] = allocatedTilemapBuffer;
+ }
+
+ return FALSE;
+ }
+
+ gWindows[i].tileData = allocatedTilemapBuffer;
+ gWindows[i].window = templates[i];
+
+ if (gUnneededFireRedVariable == 1)
+ {
+ gWindows[i].window.baseBlock = allocatedBaseBlock;
+ DummiedOutFireRedLeafGreenTileAllocFunc(bgLayer, allocatedBaseBlock, templates[i].width * templates[i].height, 1);
+ }
+ }
+
+ gTransparentTileNumber = 0;
+ return TRUE;
+}
+
+u16 AddWindow(const struct WindowTemplate *template)
+{
+ u16 win;
+ u8 bgLayer;
+ int allocatedBaseBlock;
+ u16 attrib;
+ u8 *allocatedTilemapBuffer;
+ int i;
+
+ for (win = 0; win < WINDOWS_MAX; ++win)
+ {
+ if ((bgLayer = gWindows[win].window.bg) == 0xFF)
+ break;
+ }
+
+ if (win == WINDOWS_MAX)
+ return WINDOW_NONE;
+
+ bgLayer = template->bg;
+ allocatedBaseBlock = 0;
+
+ if (gUnneededFireRedVariable == 1)
+ {
+ allocatedBaseBlock = DummiedOutFireRedLeafGreenTileAllocFunc(bgLayer, 0, template->width * template->height, 0);
+
+ if (allocatedBaseBlock == -1)
+ return WINDOW_NONE;
+ }
+
+ if (gWindowBgTilemapBuffers[bgLayer] == NULL)
+ {
+ attrib = GetBgAttribute(bgLayer, BG_ATTR_METRIC);
+
+ if (attrib != 0xFFFF)
+ {
+ allocatedTilemapBuffer = AllocZeroed(attrib);
+
+ if (allocatedTilemapBuffer == NULL)
+ return WINDOW_NONE;
+
+ for (i = 0; i < attrib; ++i)
+ allocatedTilemapBuffer[i] = 0;
+
+ gWindowBgTilemapBuffers[bgLayer] = allocatedTilemapBuffer;
+ SetBgTilemapBuffer(bgLayer, allocatedTilemapBuffer);
+ }
+ }
+
+ allocatedTilemapBuffer = AllocZeroed((u16)(32 * (template->width * template->height)));
+
+ if (allocatedTilemapBuffer == NULL)
+ {
+ if ((GetNumActiveWindowsOnBg(bgLayer) == 0) && (gWindowBgTilemapBuffers[bgLayer] != DummyWindowBgTilemap))
+ {
+ Free(gWindowBgTilemapBuffers[bgLayer]);
+ gWindowBgTilemapBuffers[bgLayer] = allocatedTilemapBuffer;
+ }
+ return WINDOW_NONE;
+ }
+
+ gWindows[win].tileData = allocatedTilemapBuffer;
+ gWindows[win].window = *template;
+
+ if (gUnneededFireRedVariable == 1)
+ {
+ gWindows[win].window.baseBlock = allocatedBaseBlock;
+ DummiedOutFireRedLeafGreenTileAllocFunc(bgLayer, allocatedBaseBlock, gWindows[win].window.width * gWindows[win].window.height, 1);
+ }
+
+ return win;
+}
+
+int AddWindowWithoutTileMap(const struct WindowTemplate *template)
+{
+ u16 win;
+ u8 bgLayer;
+ int allocatedBaseBlock;
+
+ for (win = 0; win < WINDOWS_MAX; ++win)
+ {
+ if (gWindows[win].window.bg == 0xFF)
+ break;
+ }
+
+ if (win == WINDOWS_MAX)
+ return WINDOW_NONE;
+
+ bgLayer = template->bg;
+ allocatedBaseBlock = 0;
+
+ if (gUnneededFireRedVariable == 1)
+ {
+ allocatedBaseBlock = DummiedOutFireRedLeafGreenTileAllocFunc(bgLayer, 0, template->width * template->height, 0);
+
+ if (allocatedBaseBlock == -1)
+ return WINDOW_NONE;
+ }
+
+ gWindows[win].window = *template;
+
+ if (gUnneededFireRedVariable == 1)
+ {
+ gWindows[win].window.baseBlock = allocatedBaseBlock;
+ DummiedOutFireRedLeafGreenTileAllocFunc(bgLayer, allocatedBaseBlock, gWindows[win].window.width * gWindows[win].window.height, 1);
+ }
+
+ return win;
+}
+
+void RemoveWindow(u8 windowId)
+{
+ u8 bgLayer = gWindows[windowId].window.bg;
+
+ if (gUnneededFireRedVariable == 1)
+ {
+ DummiedOutFireRedLeafGreenTileAllocFunc(bgLayer, gWindows[windowId].window.baseBlock, gWindows[windowId].window.width * gWindows[windowId].window.height, 2);
+ }
+
+ gWindows[windowId].window = sDummyWindowTemplate;
+
+ if (GetNumActiveWindowsOnBg(bgLayer) == 0)
+ {
+ if (gWindowBgTilemapBuffers[bgLayer] != DummyWindowBgTilemap)
+ {
+ Free(gWindowBgTilemapBuffers[bgLayer]);
+ gWindowBgTilemapBuffers[bgLayer] = NULL;
+ }
+ }
+
+ if (gWindows[windowId].tileData != NULL)
+ {
+ Free(gWindows[windowId].tileData);
+ gWindows[windowId].tileData = NULL;
+ }
+}
+
+void FreeAllWindowBuffers(void)
+{
+ int i;
+
+ for (i = 0; i < NUM_BACKGROUNDS; ++i)
+ {
+ if (gWindowBgTilemapBuffers[i] != NULL && gWindowBgTilemapBuffers[i] != DummyWindowBgTilemap)
+ {
+ Free(gWindowBgTilemapBuffers[i]);
+ gWindowBgTilemapBuffers[i] = NULL;
+ }
+ }
+
+ for (i = 0; i < WINDOWS_MAX; ++i)
+ {
+ if (gWindows[i].tileData != NULL)
+ {
+ Free(gWindows[i].tileData);
+ gWindows[i].tileData = NULL;
+ }
+ }
+}
+
+void CopyWindowToVram(u8 windowId, u8 mode)
+{
+ struct Window windowLocal = gWindows[windowId];
+ u16 windowSize = 32 * (windowLocal.window.width * windowLocal.window.height);
+
+ switch (mode)
+ {
+ case 1:
+ CopyBgTilemapBufferToVram(windowLocal.window.bg);
+ break;
+ case 2:
+ LoadBgTiles(windowLocal.window.bg, windowLocal.tileData, windowSize, windowLocal.window.baseBlock);
+ break;
+ case 3:
+ LoadBgTiles(windowLocal.window.bg, windowLocal.tileData, windowSize, windowLocal.window.baseBlock);
+ CopyBgTilemapBufferToVram(windowLocal.window.bg);
+ break;
+ }
+}
+
+void CopyWindowRectToVram(u32 windowId, u32 mode, u32 x, u32 y, u32 w, u32 h)
+{
+ struct Window windowLocal;
+ int rectSize;
+ int rectPos;
+
+ if (w != 0 && h != 0)
+ {
+ windowLocal = gWindows[windowId];
+
+ rectSize = ((h - 1) * windowLocal.window.width);
+ rectSize += (windowLocal.window.width - x);
+ rectSize -= (windowLocal.window.width - (x + w));
+ rectSize *= 32;
+
+ rectPos = (y * windowLocal.window.width) + x;
+
+ switch (mode)
+ {
+ case 1:
+ CopyBgTilemapBufferToVram(windowLocal.window.bg);
+ break;
+ case 2:
+ LoadBgTiles(windowLocal.window.bg, windowLocal.tileData + (rectPos * 32), rectSize, windowLocal.window.baseBlock + rectPos);
+ break;
+ case 3:
+ LoadBgTiles(windowLocal.window.bg, windowLocal.tileData + (rectPos * 32), rectSize, windowLocal.window.baseBlock + rectPos);
+ CopyBgTilemapBufferToVram(windowLocal.window.bg);
+ break;
+ }
+ }
+}
+
+void PutWindowTilemap(u8 windowId)
+{
+ struct Window windowLocal = gWindows[windowId];
+
+ WriteSequenceToBgTilemapBuffer(
+ windowLocal.window.bg,
+ GetBgAttribute(windowLocal.window.bg, BG_ATTR_BASETILE) + windowLocal.window.baseBlock,
+ windowLocal.window.tilemapLeft,
+ windowLocal.window.tilemapTop,
+ windowLocal.window.width,
+ windowLocal.window.height,
+ windowLocal.window.paletteNum,
+ 1);
+}
+
+void PutWindowRectTilemapOverridePalette(u8 windowId, u8 x, u8 y, u8 width, u8 height, u8 palette)
+{
+ struct Window windowLocal = gWindows[windowId];
+ u16 currentRow = windowLocal.window.baseBlock + (y * windowLocal.window.width) + x + GetBgAttribute(windowLocal.window.bg, BG_ATTR_BASETILE);
+ int i;
+
+ for (i = 0; i < height; ++i)
+ {
+ WriteSequenceToBgTilemapBuffer(
+ windowLocal.window.bg,
+ currentRow,
+ windowLocal.window.tilemapLeft + x,
+ windowLocal.window.tilemapTop + y + i,
+ width,
+ 1,
+ palette,
+ 1);
+
+ currentRow += windowLocal.window.width;
+ }
+}
+
+// Fills a window with transparent tiles.
+void ClearWindowTilemap(u8 windowId)
+{
+ struct Window windowLocal = gWindows[windowId];
+
+ FillBgTilemapBufferRect(
+ windowLocal.window.bg,
+ gTransparentTileNumber,
+ windowLocal.window.tilemapLeft,
+ windowLocal.window.tilemapTop,
+ windowLocal.window.width,
+ windowLocal.window.height,
+ windowLocal.window.paletteNum);
+}
+
+void PutWindowRectTilemap(u8 windowId, u8 x, u8 y, u8 width, u8 height)
+{
+ struct Window windowLocal = gWindows[windowId];
+ u16 currentRow = windowLocal.window.baseBlock + (y * windowLocal.window.width) + x + GetBgAttribute(windowLocal.window.bg, BG_ATTR_BASETILE);
+ int i;
+
+ for (i = 0; i < height; ++i)
+ {
+ WriteSequenceToBgTilemapBuffer(
+ windowLocal.window.bg,
+ currentRow,
+ windowLocal.window.tilemapLeft + x,
+ windowLocal.window.tilemapTop + y + i,
+ width,
+ 1,
+ windowLocal.window.paletteNum,
+ 1);
+
+ currentRow += windowLocal.window.width;
+ }
+}
+
+void BlitBitmapToWindow(u8 windowId, const u8 *pixels, u16 x, u16 y, u16 width, u16 height)
+{
+ BlitBitmapRectToWindow(windowId, pixels, 0, 0, width, height, x, y, width, height);
+}
+
+void BlitBitmapRectToWindow(u8 windowId, const u8 *pixels, u16 srcX, u16 srcY, u16 srcWidth, int srcHeight, u16 destX, u16 destY, u16 rectWidth, u16 rectHeight)
+{
+ struct Bitmap sourceRect;
+ struct Bitmap destRect;
+
+ sourceRect.pixels = (u8*)pixels;
+ sourceRect.width = srcWidth;
+ sourceRect.height = srcHeight;
+
+ destRect.pixels = gWindows[windowId].tileData;
+ destRect.width = 8 * gWindows[windowId].window.width;
+ destRect.height = 8 * gWindows[windowId].window.height;
+
+ BlitBitmapRect4Bit(&sourceRect, &destRect, srcX, srcY, destX, destY, rectWidth, rectHeight, 0);
+}
+
+static void BlitBitmapRectToWindowWithColorKey(u8 windowId, const u8 *pixels, u16 srcX, u16 srcY, u16 srcWidth, int srcHeight, u16 destX, u16 destY, u16 rectWidth, u16 rectHeight, u8 colorKey)
+{
+ struct Bitmap sourceRect;
+ struct Bitmap destRect;
+
+ sourceRect.pixels = (u8*)pixels;
+ sourceRect.width = srcWidth;
+ sourceRect.height = srcHeight;
+
+ destRect.pixels = gWindows[windowId].tileData;
+ destRect.width = 8 * gWindows[windowId].window.width;
+ destRect.height = 8 * gWindows[windowId].window.height;
+
+ BlitBitmapRect4Bit(&sourceRect, &destRect, srcX, srcY, destX, destY, rectWidth, rectHeight, colorKey);
+}
+
+void FillWindowPixelRect(u8 windowId, u8 fillValue, u16 x, u16 y, u16 width, u16 height)
+{
+ struct Bitmap pixelRect;
+
+ pixelRect.pixels = gWindows[windowId].tileData;
+ pixelRect.width = 8 * gWindows[windowId].window.width;
+ pixelRect.height = 8 * gWindows[windowId].window.height;
+
+ FillBitmapRect4Bit(&pixelRect, x, y, width, height, fillValue);
+}
+
+void CopyToWindowPixelBuffer(u8 windowId, const void *src, u16 size, u16 tileOffset)
+{
+ if (size != 0)
+ CpuCopy16(src, gWindows[windowId].tileData + (32 * tileOffset), size);
+ else
+ LZ77UnCompWram(src, gWindows[windowId].tileData + (32 * tileOffset));
+}
+
+// Sets all pixels within the window to the fillValue color.
+void FillWindowPixelBuffer(u8 windowId, u8 fillValue)
+{
+ int fillSize = gWindows[windowId].window.width * gWindows[windowId].window.height;
+ CpuFastFill8(fillValue, gWindows[windowId].tileData, 32 * fillSize);
+}
+
+#define MOVE_TILES_DOWN(a) \
+{ \
+ destOffset = i + (a); \
+ srcOffset = i + (((width * (distanceLoop & ~7)) | (distanceLoop & 7)) * 4); \
+ if (srcOffset < size) \
+ *(u32*)(tileData + destOffset) = *(u32*)(tileData + srcOffset); \
+ else \
+ *(u32*)(tileData + destOffset) = fillValue32; \
+ distanceLoop++; \
+}
+
+#define MOVE_TILES_UP(a) \
+{ \
+ destOffset = i + (a); \
+ srcOffset = i + (((width * (distanceLoop & ~7)) | (distanceLoop & 7)) * 4); \
+ if (srcOffset < size) \
+ *(u32*)(tileData - destOffset) = *(u32*)(tileData - srcOffset); \
+ else \
+ *(u32*)(tileData - destOffset) = fillValue32; \
+ distanceLoop++; \
+}
+
+void ScrollWindow(u8 windowId, u8 direction, u8 distance, u8 fillValue)
+{
+ struct WindowTemplate window = gWindows[windowId].window;
+ u8 *tileData = gWindows[windowId].tileData;
+ u32 fillValue32 = (fillValue << 24) | (fillValue << 16) | (fillValue << 8) | fillValue;
+ s32 size = window.height * window.width * 32;
+ u32 width = window.width;
+ s32 i;
+ s32 srcOffset, destOffset;
+ u32 distanceLoop;
+
+ switch (direction)
+ {
+ case 0:
+ for (i = 0; i < size; i += 32)
+ {
+ distanceLoop = distance;
+ MOVE_TILES_DOWN(0)
+ MOVE_TILES_DOWN(4)
+ MOVE_TILES_DOWN(8)
+ MOVE_TILES_DOWN(12)
+ MOVE_TILES_DOWN(16)
+ MOVE_TILES_DOWN(20)
+ MOVE_TILES_DOWN(24)
+ MOVE_TILES_DOWN(28)
+ }
+ break;
+ case 1:
+ tileData += size - 4;
+ for (i = 0; i < size; i += 32)
+ {
+ distanceLoop = distance;
+ MOVE_TILES_UP(0)
+ MOVE_TILES_UP(4)
+ MOVE_TILES_UP(8)
+ MOVE_TILES_UP(12)
+ MOVE_TILES_UP(16)
+ MOVE_TILES_UP(20)
+ MOVE_TILES_UP(24)
+ MOVE_TILES_UP(28)
+ }
+ break;
+ case 2:
+ break;
+ }
+}
+
+void CallWindowFunction(u8 windowId, void ( *func)(u8, u8, u8, u8, u8, u8))
+{
+ struct WindowTemplate window = gWindows[windowId].window;
+ func(window.bg, window.tilemapLeft, window.tilemapTop, window.width, window.height, window.paletteNum);
+}
+
+bool8 SetWindowAttribute(u8 windowId, u8 attributeId, u32 value)
+{
+ switch (attributeId)
+ {
+ case WINDOW_TILEMAP_LEFT:
+ gWindows[windowId].window.tilemapLeft = value;
+ return FALSE;
+ case WINDOW_TILEMAP_TOP:
+ gWindows[windowId].window.tilemapTop = value;
+ return FALSE;
+ case WINDOW_PALETTE_NUM:
+ gWindows[windowId].window.paletteNum = value;
+ return FALSE;
+ case WINDOW_BASE_BLOCK:
+ gWindows[windowId].window.baseBlock = value;
+ return FALSE;
+ case WINDOW_TILE_DATA:
+ gWindows[windowId].tileData = (u8*)(value);
+ return TRUE;
+ case WINDOW_BG:
+ case WINDOW_WIDTH:
+ case WINDOW_HEIGHT:
+ default:
+ return TRUE;
+ }
+}
+
+u32 GetWindowAttribute(u8 windowId, u8 attributeId)
+{
+ switch (attributeId)
+ {
+ case WINDOW_BG:
+ return gWindows[windowId].window.bg;
+ case WINDOW_TILEMAP_LEFT:
+ return gWindows[windowId].window.tilemapLeft;
+ case WINDOW_TILEMAP_TOP:
+ return gWindows[windowId].window.tilemapTop;
+ case WINDOW_WIDTH:
+ return gWindows[windowId].window.width;
+ case WINDOW_HEIGHT:
+ return gWindows[windowId].window.height;
+ case WINDOW_PALETTE_NUM:
+ return gWindows[windowId].window.paletteNum;
+ case WINDOW_BASE_BLOCK:
+ return gWindows[windowId].window.baseBlock;
+ case WINDOW_TILE_DATA:
+ return (u32)(gWindows[windowId].tileData);
+ default:
+ return 0;
+ }
+}
+
+static u8 GetNumActiveWindowsOnBg(u8 bgId)
+{
+ u8 windowsNum = 0;
+ s32 i;
+ for (i = 0; i < WINDOWS_MAX; i++)
+ {
+ if (gWindows[i].window.bg == bgId)
+ windowsNum++;
+ }
+ return windowsNum;
+}
+
+static void DummyWindowBgTilemap8Bit(void)
+{
+
+}
+
+u16 AddWindow8Bit(const struct WindowTemplate *template)
+{
+ u16 windowId;
+ u8* memAddress;
+ u8 bgLayer;
+
+ for (windowId = 0; windowId < WINDOWS_MAX; windowId++)
+ {
+ if (gWindows[windowId].window.bg == 0xFF)
+ break;
+ }
+ if (windowId == WINDOWS_MAX)
+ return WINDOW_NONE;
+ bgLayer = template->bg;
+ if (gWindowBgTilemapBuffers[bgLayer] == NULL)
+ {
+ u16 attribute = GetBgAttribute(bgLayer, BG_ATTR_METRIC);
+ if (attribute != 0xFFFF)
+ {
+ s32 i;
+ memAddress = Alloc(attribute);
+ if (memAddress == NULL)
+ return WINDOW_NONE;
+ for (i = 0; i < attribute; i++) // if we're going to zero out the memory anyway, why not call AllocZeroed?
+ memAddress[i] = 0;
+ gWindowBgTilemapBuffers[bgLayer] = memAddress;
+ SetBgTilemapBuffer(bgLayer, memAddress);
+ }
+ }
+ memAddress = Alloc((u16)(64 * (template->width * template->height)));
+ if (memAddress == NULL)
+ {
+ if (GetNumActiveWindowsOnBg8Bit(bgLayer) == 0 && gWindowBgTilemapBuffers[bgLayer] != DummyWindowBgTilemap8Bit)
+ {
+ Free(gWindowBgTilemapBuffers[bgLayer]);
+ gWindowBgTilemapBuffers[bgLayer] = NULL;
+ }
+ return WINDOW_NONE;
+ }
+ else
+ {
+ gWindows[windowId].tileData = memAddress;
+ gWindows[windowId].window = *template;
+ return windowId;
+ }
+}
+
+void FillWindowPixelBuffer8Bit(u8 windowId, u8 fillValue)
+{
+ s32 i;
+ s32 size;
+
+ size = (u16)(64 * (gWindows[windowId].window.width * gWindows[windowId].window.height));
+ for (i = 0; i < size; i++)
+ gWindows[windowId].tileData[i] = fillValue;
+}
+
+void FillWindowPixelRect8Bit(u8 windowId, u8 fillValue, u16 x, u16 y, u16 width, u16 height)
+{
+ struct Bitmap pixelRect;
+
+ pixelRect.pixels = gWindows[windowId].tileData;
+ pixelRect.width = 8 * gWindows[windowId].window.width;
+ pixelRect.height = 8 * gWindows[windowId].window.height;
+
+ FillBitmapRect8Bit(&pixelRect, x, y, width, height, fillValue);
+}
+
+void BlitBitmapRectToWindow4BitTo8Bit(u8 windowId, const u8 *pixels, u16 srcX, u16 srcY, u16 srcWidth, int srcHeight, u16 destX, u16 destY, u16 rectWidth, u16 rectHeight, u8 paletteNum)
+{
+ struct Bitmap sourceRect;
+ struct Bitmap destRect;
+
+ sourceRect.pixels = (u8*) pixels;
+ sourceRect.width = srcWidth;
+ sourceRect.height = srcHeight;
+
+ destRect.pixels = gWindows[windowId].tileData;
+ destRect.width = 8 * gWindows[windowId].window.width;
+ destRect.height = 8 * gWindows[windowId].window.height;
+
+ BlitBitmapRect4BitTo8Bit(&sourceRect, &destRect, srcX, srcY, destX, destY, rectWidth, rectHeight, 0, paletteNum);
+}
+
+void CopyWindowToVram8Bit(u8 windowId, u8 mode)
+{
+ sWindowPtr = &gWindows[windowId];
+ sWindowSize = 64 * (sWindowPtr->window.width * sWindowPtr->window.height);
+
+ switch (mode)
+ {
+ case 1:
+ CopyBgTilemapBufferToVram(sWindowPtr->window.bg);
+ break;
+ case 2:
+ LoadBgTiles(sWindowPtr->window.bg, sWindowPtr->tileData, sWindowSize, sWindowPtr->window.baseBlock);
+ break;
+ case 3:
+ LoadBgTiles(sWindowPtr->window.bg, sWindowPtr->tileData, sWindowSize, sWindowPtr->window.baseBlock);
+ CopyBgTilemapBufferToVram(sWindowPtr->window.bg);
+ break;
+ }
+}
+
+static u8 GetNumActiveWindowsOnBg8Bit(u8 bgId)
+{
+ u8 windowsNum = 0;
+ s32 i;
+ for (i = 0; i < WINDOWS_MAX; i++)
+ {
+ if (gWindows[i].window.bg == bgId)
+ windowsNum++;
+ }
+ return windowsNum;
+}