summaryrefslogtreecommitdiff
path: root/src/engine
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine')
-rw-r--r--src/engine/blend_palette.c20
-rw-r--r--src/engine/cable_club.c905
-rw-r--r--src/engine/clear_save_data_menu.c177
-rw-r--r--src/engine/clock.c90
-rw-r--r--src/engine/decompress.c112
-rw-r--r--src/engine/link.c1821
-rw-r--r--src/engine/load_save.c168
-rw-r--r--src/engine/main.c363
-rw-r--r--src/engine/main_menu.c1667
-rw-r--r--src/engine/menu.c871
-rw-r--r--src/engine/menu_cursor.c793
-rw-r--r--src/engine/mystery_event_menu.c341
-rw-r--r--src/engine/mystery_event_script.c462
-rw-r--r--src/engine/name_string_util.c40
-rw-r--r--src/engine/naming_screen.c2038
-rw-r--r--src/engine/option_menu.c579
-rw-r--r--src/engine/palette.c834
-rw-r--r--src/engine/play_time.c73
-rw-r--r--src/engine/record_mixing.c1085
-rw-r--r--src/engine/reset_rtc_screen.c496
-rw-r--r--src/engine/rng.c18
-rw-r--r--src/engine/rtc.c349
-rw-r--r--src/engine/save.c797
-rw-r--r--src/engine/save_failed_screen.c310
-rw-r--r--src/engine/save_menu_util.c148
-rw-r--r--src/engine/script.c368
-rw-r--r--src/engine/sound.c570
-rw-r--r--src/engine/sound_check_menu.c2199
-rw-r--r--src/engine/sprite.c1802
-rw-r--r--src/engine/string_util.c561
-rw-r--r--src/engine/task.c209
-rw-r--r--src/engine/text.c4324
-rw-r--r--src/engine/text_window.c184
-rw-r--r--src/engine/tileset_anim.c630
-rw-r--r--src/engine/time_events.c118
-rw-r--r--src/engine/trade.c89
-rw-r--r--src/engine/trainer_card.c1569
-rw-r--r--src/engine/trig.c549
-rw-r--r--src/engine/util.c525
39 files changed, 28254 insertions, 0 deletions
diff --git a/src/engine/blend_palette.c b/src/engine/blend_palette.c
new file mode 100644
index 000000000..843c50ac1
--- /dev/null
+++ b/src/engine/blend_palette.c
@@ -0,0 +1,20 @@
+#include "global.h"
+#include "blend_palette.h"
+#include "palette.h"
+
+void BlendPalette(u16 palOffset, u16 numEntries, u8 coeff, u16 blendColor)
+{
+ u16 i;
+ for (i = 0; i < numEntries; i++)
+ {
+ u16 index = i + palOffset;
+ struct PlttData *data1 = (struct PlttData *)&gPlttBufferUnfaded[index];
+ s8 r = data1->r;
+ s8 g = data1->g;
+ s8 b = data1->b;
+ struct PlttData *data2 = (struct PlttData *)&blendColor;
+ gPlttBufferFaded[index] = ((r + (((data2->r - r) * coeff) >> 4)) << 0)
+ | ((g + (((data2->g - g) * coeff) >> 4)) << 5)
+ | ((b + (((data2->b - b) * coeff) >> 4)) << 10);
+ }
+}
diff --git a/src/engine/cable_club.c b/src/engine/cable_club.c
new file mode 100644
index 000000000..7a85f2b6c
--- /dev/null
+++ b/src/engine/cable_club.c
@@ -0,0 +1,905 @@
+#include "global.h"
+#include "battle.h"
+#include "battle_records.h"
+#include "cable_club.h"
+#include "field_message_box.h"
+#include "field_weather.h"
+#include "link.h"
+#include "load_save.h"
+#include "m4a.h"
+#include "main.h"
+#include "menu.h"
+#include "palette.h"
+#include "record_mixing.h"
+#include "overworld.h"
+#include "script.h"
+#include "script_pokemon_80C4.h"
+#include "songs.h"
+#include "sound.h"
+#include "start_menu.h"
+#include "string_util.h"
+#include "strings2.h"
+#include "task.h"
+#include "text.h"
+#include "trainer_card.h"
+
+extern u16 gScriptResult;
+extern struct TrainerCard gTrainerCards[4];
+extern u8 gUnknown_03004860;
+extern u8 gFieldLinkPlayerCount;
+extern u16 gSpecialVar_0x8004;
+extern u16 gSpecialVar_0x8005;
+extern u16 gSpecialVar_0x8006;
+extern u16 gBattleTypeFlags;
+extern const u8 gUnknown_081A4932[];
+extern const u8 gUnknown_081A4975[];
+extern const u8 gUnknown_081A49B6[];
+extern const u8 gUnknown_081A490C[];
+extern const u8* const gTrainerCardColorNames[];
+extern struct
+{
+ u8 field0;
+ u8 field1;
+} gUnknown_020297D8;
+
+static void sub_8082F20(u8 taskId);
+static void sub_8082F68(u8 taskId);
+static void sub_8082FEC(u8 taskId);
+static void sub_808303C(u8 taskId);
+static void sub_80830E4(u8 taskId);
+static void sub_8083188(u8 taskId);
+static void sub_8083288(u8 taskId);
+static void sub_8083314(u8 taskId);
+static void sub_80833C4(u8 taskId);
+static void sub_80833EC(u8 taskId);
+static void sub_8083418(u8 taskId);
+static bool8 sub_8083444(u8 taskId);
+static void sub_808353C(u8 taskId);
+static void sub_8083710(u8 taskId);
+static void sub_8083760(u8 taskId);
+static void sub_80837B4(u8 taskId);
+static void sub_80837EC(u8 taskId);
+static void sub_808382C(u8 taskId);
+static void sub_8083958(void);
+static void sub_80839DC(u8 taskId);
+static void sub_8083AAC(u8 taskId);
+static void sub_8083B44(u8 taskId);
+static void sub_8083B6C(void);
+static void sub_8083CA4(u8 taskId);
+
+extern void sub_80831F8(u8 taskId);
+extern void call_map_music_set_to_zero(void);
+extern void sub_810FEFC(void);
+extern void sub_8047CD8(void);
+extern void sub_805559C(void);
+extern void sub_8055574(void);
+extern s32 sub_80554F8(void);
+extern void sub_805465C(void);
+
+static void sub_8082CD4(u8 arg0, u8 arg1)
+{
+ if (FindTaskIdByFunc(sub_8082F20) == 0xFF)
+ {
+ u8 taskId = CreateTask(sub_8082F20, 80);
+
+ gTasks[taskId].data[1] = arg0;
+ gTasks[taskId].data[2] = arg1;
+ }
+}
+
+static void sub_8082D18(u32 value)
+{
+ ConvertIntToDecimalStringN(gStringVar1, value, STR_CONV_MODE_LEFT_ALIGN, 1);
+ MenuDrawTextWindow(18, 10, 28, 13);
+ sub_8072BD8(gOtherText_PLink, 19, 11, 72);
+}
+
+static void sub_8082D4C()
+{
+ MenuZeroFillWindowRect(18, 10, 28, 13);
+}
+
+static void sub_8082D60(u8 taskId, u8 arg1)
+{
+ s16 *data = &gTasks[taskId].data[3];
+
+ if (arg1 != *data)
+ {
+ if (arg1 <= 1)
+ sub_8082D4C();
+ else
+ sub_8082D18(arg1);
+ *data = arg1;
+ }
+}
+
+static u32 sub_8082D9C(u8 minPlayers, u8 maxPlayers)
+{
+ int playerCount;
+
+ switch (GetLinkPlayerDataExchangeStatusTimed())
+ {
+ case EXCHANGE_COMPLETE:
+ playerCount = GetLinkPlayerCount_2();
+ if (minPlayers <= playerCount && playerCount <= maxPlayers)
+ return 1;
+ ConvertIntToDecimalStringN(gStringVar1, playerCount, STR_CONV_MODE_LEFT_ALIGN, 1);
+ return 4;
+ case EXCHANGE_TIMED_OUT:
+ return 0;
+ case EXCHANGE_IN_PROGRESS:
+ return 3;
+ default:
+ return 0;
+ }
+}
+
+static bool32 sub_8082DF4(u8 taskId)
+{
+ if (HasLinkErrorOccurred() == TRUE)
+ {
+ gTasks[taskId].func = sub_8083418;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static bool32 sub_8082E28(u8 taskId)
+{
+ if ((gMain.newKeys & B_BUTTON)
+ && IsLinkConnectionEstablished() == FALSE)
+ {
+ gTasks[taskId].func = sub_80833EC;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static bool32 sub_8082E6C(u8 taskId)
+{
+ if (IsLinkConnectionEstablished())
+ SetSuppressLinkErrorMessage(TRUE);
+
+ if (gMain.newKeys & B_BUTTON)
+ {
+ gTasks[taskId].func = sub_80833EC;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static bool32 sub_8082EB8(u8 taskId)
+{
+ if (GetSioMultiSI() == 1)
+ {
+ gTasks[taskId].func = sub_8083418;
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void unref_sub_8082EEC(u8 taskId)
+{
+ gTasks[taskId].data[0]++;
+ if (gTasks[taskId].data[0] == 10)
+ {
+ sub_8007E9C(2);
+ DestroyTask(taskId);
+ }
+}
+
+static void sub_8082F20(u8 taskId)
+{
+ s16 *data = gTasks[taskId].data;
+
+ if (data[0] == 0)
+ {
+ OpenLinkTimed();
+ sub_80082EC();
+ ResetLinkPlayers();
+ }
+ else if (data[0] > 9)
+ {
+ gTasks[taskId].func = sub_8082F68;
+ }
+ data[0]++;
+}
+
+static void sub_8082F68(u8 taskId)
+{
+ u32 playerCount = GetLinkPlayerCount_2();
+
+ if (sub_8082E28(taskId) == TRUE
+ || sub_8082E6C(taskId) == TRUE
+ || playerCount < 2)
+ return;
+
+ SetSuppressLinkErrorMessage(TRUE);
+ gTasks[taskId].data[3] = 0;
+ if (IsLinkMaster() == TRUE)
+ {
+ PlaySE(SE_PIN);
+ ShowFieldAutoScrollMessage(gUnknown_081A4932);
+ gTasks[taskId].func = sub_8082FEC;
+ }
+ else
+ {
+ PlaySE(SE_BOO);
+ ShowFieldAutoScrollMessage(gUnknown_081A49B6);
+ gTasks[taskId].func = sub_80831F8;
+ }
+}
+
+static void sub_8082FEC(u8 taskId)
+{
+ if (sub_8082E28(taskId) == TRUE
+ || sub_8082EB8(taskId) == TRUE
+ || sub_8082DF4(taskId) == TRUE)
+ return;
+
+ if (GetFieldMessageBoxMode() == FIELD_MESSAGE_BOX_HIDDEN)
+ {
+ gTasks[taskId].data[3] = 0;
+ gTasks[taskId].func = sub_808303C;
+ }
+}
+
+static void sub_808303C(u8 taskId)
+{
+ s16 *taskData = gTasks[taskId].data;
+ s32 linkPlayerCount = GetLinkPlayerCount_2();
+
+ if (sub_8082E28(taskId) == TRUE
+ || sub_8082EB8(taskId) == TRUE
+ || sub_8082DF4(taskId) == TRUE)
+ return;
+
+ sub_8082D60(taskId, linkPlayerCount);
+
+ if (!(gMain.newKeys & A_BUTTON))
+ return;
+
+#if ENGLISH
+ if (linkPlayerCount < taskData[1])
+ return;
+
+ sub_80081C8(linkPlayerCount);
+ sub_8082D4C();
+ ConvertIntToDecimalStringN(gStringVar1, linkPlayerCount, STR_CONV_MODE_LEFT_ALIGN, 1);
+ ShowFieldAutoScrollMessage((u8 *)gUnknown_081A4975);
+ gTasks[taskId].func = sub_80830E4;
+#elif GERMAN
+ if ((gLinkType == 0x2255 && (u32)linkPlayerCount > 1)
+ || (gLinkType != 0x2255 && taskData[1] <= linkPlayerCount))
+ {
+ sub_80081C8(linkPlayerCount);
+ sub_8082D4C();
+ ConvertIntToDecimalStringN(gStringVar1, linkPlayerCount, STR_CONV_MODE_LEFT_ALIGN, 1);
+ ShowFieldAutoScrollMessage((u8 *)gUnknown_081A4975);
+ gTasks[taskId].func = sub_80830E4;
+ }
+#endif
+}
+
+static void sub_80830E4(u8 taskId)
+{
+ if (sub_8082E28(taskId) == TRUE
+ || sub_8082EB8(taskId) == TRUE
+ || sub_8082DF4(taskId) == TRUE)
+ return;
+
+ if (GetFieldMessageBoxMode() == FIELD_MESSAGE_BOX_HIDDEN)
+ {
+ if (sub_800820C() != GetLinkPlayerCount_2())
+ {
+ ShowFieldAutoScrollMessage(gUnknown_081A4932);
+ gTasks[taskId].func = sub_8082FEC;
+ }
+ else if (gMain.heldKeys & B_BUTTON)
+ {
+ ShowFieldAutoScrollMessage(gUnknown_081A4932);
+ gTasks[taskId].func = sub_8082FEC;
+ }
+ else if (gMain.heldKeys & A_BUTTON)
+ {
+ PlaySE(SE_SELECT);
+ sub_8007F4C();
+ gTasks[taskId].func = sub_8083188;
+ }
+ }
+}
+
+static void sub_8083188(u8 taskId)
+{
+ u8 local1 = gTasks[taskId].data[1];
+ u8 local2 = gTasks[taskId].data[2];
+
+ if (sub_8082DF4(taskId) == TRUE
+ || sub_8083444(taskId) == TRUE)
+ return;
+
+ if (GetLinkPlayerCount_2() != sub_800820C())
+ {
+ gTasks[taskId].func = sub_8083418;
+ }
+ else
+ {
+ gScriptResult = sub_8082D9C(local1, local2);
+ if (gScriptResult != 0)
+ gTasks[taskId].func = sub_8083288;
+ }
+}
+
+void sub_80831F8(u8 taskId)
+{
+ u8 local1, local2;
+
+ local1 = gTasks[taskId].data[1];
+ local2 = gTasks[taskId].data[2];
+
+ if (sub_8082E28(taskId) == TRUE
+ || sub_8082DF4(taskId) == TRUE)
+ return;
+
+ gScriptResult = sub_8082D9C(local1, local2);
+ if (gScriptResult == 0)
+ return;
+ if (gScriptResult == 3)
+ {
+ sub_800832C();
+ HideFieldMessageBox();
+ gTasks[taskId].func = sub_80833C4;
+ }
+ else
+ {
+ gFieldLinkPlayerCount = GetLinkPlayerCount_2();
+ gUnknown_03004860 = GetMultiplayerId();
+ sub_80081C8(gFieldLinkPlayerCount);
+ sub_8093390((struct TrainerCard *)gBlockSendBuffer);
+ gTasks[taskId].func = sub_8083314;
+ }
+}
+
+static void sub_8083288(u8 taskId)
+{
+ if (sub_8082DF4(taskId) == TRUE)
+ return;
+
+ if (gScriptResult == 3)
+ {
+ sub_800832C();
+ HideFieldMessageBox();
+ gTasks[taskId].func = sub_80833C4;
+ }
+ else
+ {
+ gFieldLinkPlayerCount = GetLinkPlayerCount_2();
+ gUnknown_03004860 = GetMultiplayerId();
+ sub_80081C8(gFieldLinkPlayerCount);
+ sub_8093390((struct TrainerCard *)gBlockSendBuffer);
+ gTasks[taskId].func = sub_8083314;
+ sub_8007E9C(2);
+ }
+}
+
+static void sub_8083314(u8 taskId)
+{
+ u8 index;
+ struct TrainerCard *trainerCards;
+
+ if (sub_8082DF4(taskId) == TRUE)
+ return;
+
+ if (GetBlockReceivedStatus() != sub_8008198())
+ return;
+
+ index = 0;
+ trainerCards = gTrainerCards;
+ for (index = 0; index < GetLinkPlayerCount(); index++)
+ {
+ void *src;
+ src = gBlockRecvBuffer[index];
+ memcpy(&trainerCards[index], src, sizeof(struct TrainerCard));
+ }
+
+ SetSuppressLinkErrorMessage(FALSE);
+ ResetBlockReceivedFlags();
+ HideFieldMessageBox();
+
+ if (gScriptResult == 1)
+ {
+#if ENGLISH
+ u16 linkType;
+ linkType = gLinkType;
+ // FIXME: sub_8082D4C doesn't take any arguments
+ sub_8082D4C(0x4411, linkType);
+#elif GERMAN
+ if (gLinkType != 0x4411)
+ {
+ if (gLinkType == 0x6601)
+ deUnkValue2 = 1;
+ }
+ sub_8082D4C();
+#endif
+ EnableBothScriptContexts();
+ DestroyTask(taskId);
+ return;
+ }
+
+ sub_800832C();
+ gTasks[taskId].func = sub_80833C4;
+}
+
+static void sub_80833C4(u8 taskId)
+{
+ if (gReceivedRemoteLinkPlayers == FALSE)
+ {
+ sub_8082D4C();
+ EnableBothScriptContexts();
+ DestroyTask(taskId);
+ }
+}
+
+static void sub_80833EC(u8 taskId)
+{
+ gScriptResult = 5;
+ sub_8082D4C();
+ HideFieldMessageBox();
+ EnableBothScriptContexts();
+ DestroyTask(taskId);
+}
+
+static void sub_8083418(u8 taskId)
+{
+ gScriptResult = 6;
+ sub_8082D4C();
+ HideFieldMessageBox();
+ EnableBothScriptContexts();
+ DestroyTask(taskId);
+}
+
+static bool8 sub_8083444(u8 taskId)
+{
+ gTasks[taskId].data[4]++;
+ if (gTasks[taskId].data[4] > 600)
+ {
+ gTasks[taskId].func = sub_8083418;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void sub_808347C(u8 arg0)
+{
+ u32 r3 = 2;
+ u32 r2 = 2;
+
+ switch (gSpecialVar_0x8004)
+ {
+ case 1:
+ r3 = 2;
+ gLinkType = 0x2233;
+ break;
+ case 2:
+ r3 = 2;
+ gLinkType = 0x2244;
+ break;
+ case 5:
+ r3 = 4;
+ r2 = 4;
+ gLinkType = 0x2255;
+ break;
+ }
+
+ sub_8082CD4(r3, r2);
+}
+
+void sub_80834E4(void)
+{
+ gLinkType = 0x1133;
+ gBattleTypeFlags = 0;
+ sub_8082CD4(2, 2);
+}
+
+void sub_808350C(void)
+{
+ gScriptResult = 0;
+ gLinkType = 0x3311;
+ gBattleTypeFlags = 0;
+ sub_8082CD4(2, 4);
+}
+
+static void sub_808353C(u8 taskId)
+{
+ int playerCount;
+ int i;
+
+ switch (gTasks[taskId].data[0])
+ {
+ case 0:
+ if (gScriptResult == 1)
+ {
+ playerCount = GetLinkPlayerCount();
+ for (i = 0; i < playerCount; i++)
+ {
+ if (gLinkPlayers[i].language == LANGUAGE_JAPANESE)
+ {
+ gScriptResult = 7;
+ sub_8008480();
+ gTasks[taskId].data[0] = 1;
+ return;
+ }
+ }
+ }
+ EnableBothScriptContexts();
+ DestroyTask(taskId);
+ break;
+ case 1:
+ if (gReceivedRemoteLinkPlayers == FALSE)
+ {
+ EnableBothScriptContexts();
+ DestroyTask(taskId);
+ }
+ break;
+ }
+}
+
+void sub_80835D8(void)
+{
+ int taskId = FindTaskIdByFunc(sub_808353C);
+
+ if (taskId == 0xFF)
+ {
+ taskId = CreateTask(sub_808353C, 80);
+ gTasks[taskId].data[0] = 0;
+ }
+}
+
+void sub_8083614(void)
+{
+ gLinkType = 0x4411;
+ gBattleTypeFlags = 0;
+ sub_8082CD4(2, 4);
+}
+
+void sub_808363C(void)
+{
+ gLinkType = 0x6601;
+ gBattleTypeFlags = 0;
+ sub_8082CD4(4, 4);
+}
+
+u8 sub_8083664(void)
+{
+ if (FuncIsActiveTask(sub_8083710) != FALSE)
+ return 0xFF;
+
+ switch (gSpecialVar_0x8004)
+ {
+ case 1:
+ gLinkType = 0x2233;
+ break;
+ case 2:
+ gLinkType = 0x2244;
+ break;
+ case 5:
+ gLinkType = 0x2255;
+ break;
+ case 3:
+ gLinkType = 0x1111;
+ break;
+ case 4:
+ gLinkType = 0x3322;
+ break;
+ }
+
+ return CreateTask(sub_8083710, 80);
+}
+
+static void sub_8083710(u8 taskId)
+{
+ s16 *data = gTasks[taskId].data;
+
+ if (data[0] == 0)
+ {
+ OpenLink();
+ ResetLinkPlayers();
+ CreateTask(sub_8083C50, 80);
+ }
+ else if (data[0] >= 10)
+ {
+ gTasks[taskId].func = sub_8083760;
+ }
+ data[0]++;
+}
+
+static void sub_8083760(u8 taskId)
+{
+ if (GetLinkPlayerCount_2() >= 2)
+ {
+ if (IsLinkMaster() == TRUE)
+ gTasks[taskId].func = sub_80837B4;
+ else
+ gTasks[taskId].func = sub_80837EC;
+ }
+}
+
+static void sub_80837B4(u8 taskId)
+{
+ if (sub_800820C() == GetLinkPlayerCount_2())
+ {
+ sub_8007F4C();
+ gTasks[taskId].func = sub_80837EC;
+ }
+}
+
+static void sub_80837EC(u8 taskId)
+{
+ if (gReceivedRemoteLinkPlayers == TRUE
+ && IsLinkPlayerDataExchangeComplete() == TRUE)
+ {
+ sub_800826C();
+ sub_8007B14();
+ DestroyTask(taskId);
+ }
+}
+
+void sub_8083820(void)
+{
+ ScrSpecial_DoSaveDialog();
+}
+
+static void sub_808382C(u8 taskId)
+{
+ struct Task* task = &gTasks[taskId];
+
+ switch (task->data[0])
+ {
+ case 0:
+ fade_screen(1, 0);
+ gLinkType = 0x2211;
+ ClearLinkCallback_2();
+ task->data[0]++;
+ break;
+ case 1:
+ if (!gPaletteFade.active)
+ task->data[0]++;
+ break;
+ case 2:
+ task->data[1]++;
+ if (task->data[1] > 20)
+ task->data[0]++;
+ break;
+ case 3:
+ sub_800832C();
+ task->data[0]++;
+ break;
+ case 4:
+ if (!gReceivedRemoteLinkPlayers)
+ task->data[0]++;
+ break;
+ case 5:
+ if (gLinkPlayers[0].trainerId & 1)
+ current_map_music_set__default_for_battle(BGM_BATTLE32);
+ else
+ current_map_music_set__default_for_battle(BGM_BATTLE20);
+
+ switch (gSpecialVar_0x8004)
+ {
+ case 1:
+ gBattleTypeFlags = BATTLE_TYPE_TRAINER | BATTLE_TYPE_LINK;
+ break;
+ case 2:
+ gBattleTypeFlags = BATTLE_TYPE_TRAINER | BATTLE_TYPE_LINK | BATTLE_TYPE_DOUBLE;
+ break;
+ case 5:
+ ReducePlayerPartyToThree();
+ gBattleTypeFlags = BATTLE_TYPE_TRAINER | BATTLE_TYPE_LINK | BATTLE_TYPE_DOUBLE | BATTLE_TYPE_MULTI;
+ break;
+ }
+
+ SetMainCallback2(sub_800E7C4);
+ gMain.savedCallback = sub_8083958;
+ DestroyTask(taskId);
+ break;
+ }
+}
+
+static void sub_8083958(void)
+{
+ call_map_music_set_to_zero();
+ LoadPlayerParty();
+ SavePlayerBag();
+ sub_810FEFC();
+
+ if (gSpecialVar_0x8004 != 5)
+ UpdateLinkBattleRecords(gUnknown_03004860 ^ 1);
+
+ gMain.savedCallback = sub_805465C;
+ SetMainCallback2(sub_8071B28);
+}
+
+void sub_80839A4(void)
+{
+ if (gSpecialVar_0x8004 == 1 || gSpecialVar_0x8004 == 2 || gSpecialVar_0x8004 == 5)
+ {
+ LoadPlayerParty();
+ SavePlayerBag();
+ }
+ copy_saved_warp2_bank_and_enter_x_to_warp1(0x7F);
+}
+
+void sub_80839D0(void)
+{
+ sub_805559C();
+}
+
+static void sub_80839DC(u8 taskId)
+{
+ struct Task* task = &gTasks[taskId];
+
+ switch (task->data[0])
+ {
+ case 0:
+ ShowFieldMessage(gUnknown_081A490C);
+ task->data[0] = 1;
+ break;
+ case 1:
+ if (IsFieldMessageBoxHidden())
+ {
+ sub_8055574();
+ sub_8007270(gSpecialVar_0x8005);
+ task->data[0] = 2;
+ }
+ break;
+ case 2:
+ switch (sub_80554F8())
+ {
+ case 0:
+ break;
+ case 1:
+ HideFieldMessageBox();
+ task->data[0] = 0;
+ SwitchTaskToFollowupFunc(taskId);
+ break;
+ case 2:
+ task->data[0] = 3;
+ break;
+ }
+ break;
+ case 3:
+ sub_8055588();
+ HideFieldMessageBox();
+ MenuZeroFillScreen();
+ DestroyTask(taskId);
+ EnableBothScriptContexts();
+ break;
+ }
+}
+
+void sub_8083A84(TaskFunc followupFunc)
+{
+ u8 taskId = CreateTask(sub_80839DC, 80);
+ SetTaskFuncWithFollowupFunc(taskId, sub_80839DC, followupFunc);
+ ScriptContext1_Stop();
+}
+
+static void sub_8083AAC(u8 taskId)
+{
+ struct Task *task = &gTasks[taskId];
+
+ switch (task->data[0])
+ {
+ case 0:
+ ScriptContext2_Enable();
+ fade_screen(1, 0);
+ ClearLinkCallback_2();
+ task->data[0]++;
+ break;
+ case 1:
+ if (!gPaletteFade.active)
+ task->data[0]++;
+ break;
+ case 2:
+ gUnknown_020297D8.field0 = 0;
+ gUnknown_020297D8.field1 = 0;
+ m4aMPlayAllStop();
+ sub_800832C();
+ task->data[0]++;
+ break;
+ case 3:
+ if (!gReceivedRemoteLinkPlayers)
+ {
+ SetMainCallback2(sub_8047CD8);
+ DestroyTask(taskId);
+ }
+ break;
+ }
+}
+
+static void sub_8083B44(u8 taskId)
+{
+ sub_8083B6C();
+ DestroyTask(taskId);
+}
+
+void sub_8083B5C(void)
+{
+ sub_8083A84(sub_8083B44);
+}
+
+static void sub_8083B6C(void)
+{
+ CreateTask(sub_8083AAC, 80);
+}
+
+void sub_8083B80(void)
+{
+ sub_8083B6C();
+ ScriptContext1_Stop();
+}
+
+void sub_8083B90(void)
+{
+ gLinkType = 0x2211;
+ sub_8083A84(sub_808382C);
+}
+
+void unref_sub_8083BB0(void)
+{
+ u8 taskId = CreateTask(sub_80839DC, 80);
+ SetTaskFuncWithFollowupFunc(taskId, sub_80839DC, Task_RecordMixing_Main);
+ ScriptContext1_Stop();
+}
+
+void sub_8083BDC(void)
+{
+ sub_8093130(gSpecialVar_0x8006, c2_exit_to_overworld_1_continue_scripts_restart_music);
+}
+
+bool32 sub_8083BF4(u8 linkPlayerIndex)
+{
+ u32 trainerCardColorIndex;
+
+ gSpecialVar_0x8006 = linkPlayerIndex;
+ StringCopy(gStringVar1, gLinkPlayers[linkPlayerIndex].name);
+
+ trainerCardColorIndex = sub_80934C4(linkPlayerIndex);
+ if (trainerCardColorIndex == 0)
+ return FALSE;
+
+ StringCopy(gStringVar2, gTrainerCardColorNames[trainerCardColorIndex - 1]);
+ return TRUE;
+}
+
+void sub_8083C50(u8 taskId)
+{
+ struct Task *task = &gTasks[taskId];
+
+ task->data[0]++;
+ if (task->data[0] > 300)
+ {
+ CloseLink();
+ SetMainCallback2(CB2_LinkError);
+ DestroyTask(taskId);
+ }
+
+ if (gReceivedRemoteLinkPlayers)
+ DestroyTask(taskId);
+}
+
+static void sub_8083CA4(u8 taskId)
+{
+ if (!gReceivedRemoteLinkPlayers)
+ {
+ EnableBothScriptContexts();
+ DestroyTask(taskId);
+ }
+}
+
+void unref_sub_8083CC8(u8 taskId)
+{
+ sub_800832C();
+ gTasks[taskId].func = sub_8083CA4;
+} \ No newline at end of file
diff --git a/src/engine/clear_save_data_menu.c b/src/engine/clear_save_data_menu.c
new file mode 100644
index 000000000..498562fe0
--- /dev/null
+++ b/src/engine/clear_save_data_menu.c
@@ -0,0 +1,177 @@
+#include "global.h"
+#include "clear_save_data_menu.h"
+#include "main.h"
+#include "menu.h"
+#include "palette.h"
+#include "save.h"
+#include "songs.h"
+#include "sound.h"
+#include "sprite.h"
+#include "strings2.h"
+#include "task.h"
+
+static void VBlankCB_ClearSaveDataScreen(void);
+static void Task_InitMenu(u8);
+static void Task_ProcessMenuInput(u8);
+static void Task_ClearSaveData(u8);
+static void CB2_ClearSaveDataScreen(void);
+static void VBlankCB_InitClearSaveDataScreen(void);
+static u8 InitClearSaveDataScreen(void);
+static void CB2_SoftReset(void);
+
+void CB2_InitClearSaveDataScreen(void)
+{
+ if (InitClearSaveDataScreen())
+ {
+ CreateTask(Task_InitMenu, 0);
+ }
+}
+
+static void VBlankCB_ClearSaveDataScreen(void)
+{
+ LoadOam();
+ ProcessSpriteCopyRequests();
+ TransferPlttBuffer();
+}
+
+static void Task_InitMenu(u8 taskId)
+{
+ ResetSpriteData();
+
+ REG_DISPCNT = DISPCNT_MODE_0 | DISPCNT_OBJ_1D_MAP | DISPCNT_BG0_ON | DISPCNT_BG3_ON | DISPCNT_OBJ_ON;
+
+ SetVBlankCallback(VBlankCB_ClearSaveDataScreen);
+ MenuDrawTextWindow(2, 14, 27, 19);
+ MenuPrint(gSystemText_ClearAllSaveDataPrompt, 3, 15);
+
+ MenuDrawTextWindow(2, 1, 8, 6);
+ PrintMenuItems(3, 2, 2, gMenuYesNoItems);
+ InitMenu(0, 3, 2, 2, 1, 5);
+
+ gTasks[taskId].func = Task_ProcessMenuInput;
+}
+
+static void Task_ProcessMenuInput(u8 taskId)
+{
+ switch (ProcessMenuInputNoWrap_())
+ {
+ case 0:
+ PlaySE(SE_SELECT);
+ sub_8071F40(gSystemText_ClearingData);
+ gTasks[taskId].func = Task_ClearSaveData;
+ break;
+ case -1:
+ case 1:
+ PlaySE(SE_SELECT);
+ DestroyTask(taskId);
+ SetMainCallback2(CB2_SoftReset);
+ break;
+ }
+ AnimateSprites();
+ BuildOamBuffer();
+}
+
+static void Task_ClearSaveData(u8 taskId)
+{
+ ClearSaveData();
+ DestroyTask(taskId);
+ SetMainCallback2(CB2_SoftReset);
+}
+
+static void CB2_ClearSaveDataScreen(void)
+{
+ RunTasks();
+ UpdatePaletteFade();
+}
+
+static void VBlankCB_InitClearSaveDataScreen(void)
+{
+ TransferPlttBuffer();
+}
+
+static u8 InitClearSaveDataScreen(void)
+{
+ u16 i;
+ u16 ime;
+
+ switch (gMain.state)
+ {
+ case 0:
+ default:
+ SetVBlankCallback(NULL);
+
+ REG_DISPCNT = 0;
+ REG_BG0HOFS = 0;
+ REG_BG0VOFS = 0;
+ REG_BG3HOFS = 0;
+ REG_BG3VOFS = 0;
+ REG_WIN0H = 0;
+ REG_WIN0V = 0;
+ REG_WININ = 0;
+ REG_WINOUT = 0;
+ REG_BLDCNT = 0;
+ REG_BLDALPHA = 0;
+ REG_BLDY = 0;
+
+ DmaFill16(3, 0, (void *)VRAM, VRAM_SIZE);
+ DmaFill32(3, 0, (void *)OAM, OAM_SIZE);
+ DmaFill16(3, 0, (void *)(PLTT + 2), PLTT_SIZE - 2);
+
+ ResetPaletteFade();
+
+ gPlttBufferUnfaded[0] = 0x7fff;
+ gPlttBufferFaded[0] = 0x7fff;
+ gPlttBufferUnfaded[1] = 0x3945;
+ gPlttBufferFaded[1] = 0x3945;
+
+ for (i = 0; i < 0x10; i++)
+ ((u16 *)(VRAM + 0x20))[i] = 0x1111;
+
+ for (i = 0; i < 0x500; i++)
+ ((u16 *)(VRAM + 0x3800))[i] = 0x0001;
+
+ ResetTasks();
+ ResetSpriteData();
+
+ SetUpWindowConfig(&gWindowConfig_81E6C3C);
+ InitMenuWindow(&gWindowConfig_81E6CE4);
+ BeginNormalPaletteFade(-1, 0, 0x10, 0, 0xffff);
+
+ ime = REG_IME;
+ REG_IME = 0;
+ REG_IE |= INTR_FLAG_VBLANK;
+ REG_IME = ime;
+ REG_DISPSTAT |= DISPSTAT_VBLANK_INTR;
+
+ SetVBlankCallback(VBlankCB_InitClearSaveDataScreen);
+
+ REG_BG3CNT = BGCNT_PRIORITY(3) | BGCNT_CHARBASE(0) | BGCNT_SCREENBASE(7) | BGCNT_16COLOR | BGCNT_TXT256x256;
+ REG_DISPCNT = DISPCNT_MODE_0 | DISPCNT_BG0_ON | DISPCNT_BG3_ON;
+ gMain.state = 1;
+ return 0;
+ case 1:
+ UpdatePaletteFade();
+ if (gPaletteFade.active)
+ return 0;
+ SetMainCallback2(CB2_ClearSaveDataScreen);
+ return 1;
+ }
+}
+
+static void CB2_SoftReset(void)
+{
+ switch (gMain.state)
+ {
+ case 0:
+ default:
+ BeginNormalPaletteFade(-1, 0, 0, 0x10, 0xffff);
+ gMain.state = 1;
+ break;
+ case 1:
+ UpdatePaletteFade();
+ if (gPaletteFade.active)
+ return;
+ DoSoftReset();
+ break;
+ }
+}
diff --git a/src/engine/clock.c b/src/engine/clock.c
new file mode 100644
index 000000000..9635514d2
--- /dev/null
+++ b/src/engine/clock.c
@@ -0,0 +1,90 @@
+#include "global.h"
+#include "clock.h"
+#include "berry.h"
+#include "dewford_trend.h"
+#include "event_data.h"
+#include "field_specials.h"
+#include "field_weather.h"
+#include "lottery_corner.h"
+#include "main.h"
+#include "overworld.h"
+#include "rtc.h"
+#include "time_events.h"
+#include "tv.h"
+#include "wallclock.h"
+
+static void InitTimeBasedEvents(void);
+static void UpdatePerDay(struct Time *time);
+static void UpdatePerMinute(struct Time *time);
+static void ReturnFromStartWallClock(void);
+
+static void InitTimeBasedEvents(void)
+{
+ FlagSet(SYS_CLOCK_SET);
+ RtcCalcLocalTime();
+ gSaveBlock2.lastBerryTreeUpdate = gLocalTime;
+ VarSet(VAR_DAYS, gLocalTime.days);
+}
+
+void DoTimeBasedEvents(void)
+{
+ if (FlagGet(SYS_CLOCK_SET))
+ {
+ RtcCalcLocalTime();
+ UpdatePerDay(&gLocalTime);
+ UpdatePerMinute(&gLocalTime);
+ }
+}
+
+static void UpdatePerDay(struct Time *time)
+{
+ u16 *varPtr = GetVarPointer(VAR_DAYS);
+ int days = *varPtr;
+ u16 newDays;
+
+ if (days != time->days && days <= time->days)
+ {
+ newDays = time->days - days;
+ ClearUpperFlags();
+ UpdateDewfordTrendPerDay(newDays);
+ UpdateTVShowsPerDay(newDays);
+ UpdateWeatherPerDay(newDays);
+ UpdatePartyPokerusTime(newDays);
+ UpdateMirageRnd(newDays);
+ UpdateBirchState(newDays);
+ SetShoalItemFlag(newDays);
+ SetRandomLotteryNumber(newDays);
+ *varPtr = time->days;
+ }
+}
+
+static void UpdatePerMinute(struct Time *time)
+{
+ struct Time newTime;
+ s32 minutes;
+
+ CalcTimeDifference(&newTime, &gSaveBlock2.lastBerryTreeUpdate, time);
+ minutes = 1440 * newTime.days + 60 * newTime.hours + newTime.minutes;
+
+ // there's no way to get the correct assembly other than with this nested if check. so dumb.
+ if (minutes != 0)
+ {
+ if (minutes >= 0)
+ {
+ BerryTreeTimeUpdate(minutes);
+ gSaveBlock2.lastBerryTreeUpdate = *time;
+ }
+ }
+}
+
+static void ReturnFromStartWallClock(void)
+{
+ InitTimeBasedEvents();
+ SetMainCallback2(c2_exit_to_overworld_1_continue_scripts_restart_music);
+}
+
+void StartWallClock(void)
+{
+ SetMainCallback2(CB2_StartWallClock);
+ gMain.savedCallback = ReturnFromStartWallClock;
+}
diff --git a/src/engine/decompress.c b/src/engine/decompress.c
new file mode 100644
index 000000000..3e5993118
--- /dev/null
+++ b/src/engine/decompress.c
@@ -0,0 +1,112 @@
+#include "global.h"
+#include "decompress.h"
+#include "data2.h"
+#include "species.h"
+#include "text.h"
+
+#define WRAM 0x02000000
+
+void LZDecompressWram(const void *src, void *dest)
+{
+ LZ77UnCompWram(src, dest);
+}
+
+void LZDecompressVram(const void *src, void *dest)
+{
+ LZ77UnCompVram(src, dest);
+}
+
+void LoadCompressedObjectPic(const struct CompressedSpriteSheet *src)
+{
+ struct SpriteSheet dest;
+
+ LZ77UnCompWram(src->data, (void *)WRAM);
+ dest.data = (void *)WRAM;
+ dest.size = src->size;
+ dest.tag = src->tag;
+ LoadSpriteSheet(&dest);
+}
+
+void LoadCompressedObjectPicOverrideBuffer(const struct CompressedSpriteSheet *src, void *buffer)
+{
+ struct SpriteSheet dest;
+
+ LZ77UnCompWram(src->data, buffer);
+ dest.data = buffer;
+ dest.size = src->size;
+ dest.tag = src->tag;
+ LoadSpriteSheet(&dest);
+}
+
+void LoadCompressedObjectPalette(const struct CompressedSpritePalette *src)
+{
+ struct SpritePalette dest;
+
+ LZ77UnCompWram(src->data, (void *)WRAM);
+ dest.data = (void *)WRAM;
+ dest.tag = src->tag;
+ LoadSpritePalette(&dest);
+}
+
+void LoadCompressedObjectPaletteOverrideBuffer(const struct CompressedSpritePalette *a, void *buffer)
+{
+ struct SpritePalette dest;
+
+ LZ77UnCompWram(a->data, buffer);
+ dest.data = buffer;
+ dest.tag = a->tag;
+ LoadSpritePalette(&dest);
+}
+
+void DecompressPicFromTable_2(const struct CompressedSpriteSheet *src, u8 b, u8 c, void *d, void *buffer, s32 species)
+{
+ if (species > SPECIES_EGG)
+ LZ77UnCompWram(gMonFrontPicTable[0].data, buffer);
+ else
+ LZ77UnCompWram(src->data, buffer);
+}
+
+void HandleLoadSpecialPokePic(const struct CompressedSpriteSheet *src, u32 b, u32 c, u32 d, void *dest, s32 species, u32 g)
+{
+ u32 frontOrBack;
+
+ // gUnknown_081FAF4C appears to be a list of pointers to locations to store poke pics for back and front pic here. the first and third pointers are used for back while the others are used for front.
+ if (dest == gUnknown_081FAF4C[0] || dest == gUnknown_081FAF4C[2])
+ frontOrBack = 0; // backPic
+ else
+ frontOrBack = 1; // frontPic
+
+ LoadSpecialPokePic(src, b, c, d, dest, species, g, frontOrBack);
+}
+
+void LoadSpecialPokePic(const struct CompressedSpriteSheet *src, u32 b, u32 c, u32 d, void *dest, s32 species, u32 g, u32 frontOrBack)
+{
+ u8 frontOrBack8 = frontOrBack;
+
+ if (species == SPECIES_UNOWN)
+ {
+ u16 i = (((g & 0x3000000) >> 18) | ((g & 0x30000) >> 12) | ((g & 0x300) >> 6) | (g & 3)) % 0x1C;
+
+ // The other Unowns are separate from Unown A.
+ if (i == 0)
+ i = SPECIES_UNOWN;
+ else
+ i += SPECIES_UNOWN_B - 1;
+
+ if (frontOrBack8 == 0)
+ LZ77UnCompWram(gMonBackPicTable[i].data, dest);
+ else
+ LZ77UnCompWram(gMonFrontPicTable[i].data, dest);
+ }
+ else if (species > SPECIES_EGG) // is species unknown? draw the ? icon
+ LZ77UnCompWram(gMonFrontPicTable[0].data, dest);
+ else
+ LZ77UnCompWram(src->data, dest);
+
+ DrawSpindaSpots(species, g, dest, frontOrBack8);
+}
+
+void Unused_LZDecompressWramIndirect(const void **src, void *dest)
+{
+ LZ77UnCompWram(*src, dest);
+}
diff --git a/src/engine/link.c b/src/engine/link.c
new file mode 100644
index 000000000..850201ccb
--- /dev/null
+++ b/src/engine/link.c
@@ -0,0 +1,1821 @@
+#include "global.h"
+#include "link.h"
+#include "battle.h"
+#include "berry.h"
+#include "hall_of_fame.h"
+#include "item_use.h"
+#include "main.h"
+#include "menu.h"
+#include "palette.h"
+#include "rng.h"
+#include "save.h"
+#include "songs.h"
+#include "sound.h"
+#include "sprite.h"
+#include "strings2.h"
+#include "task.h"
+#include "text.h"
+
+#define SIO_MULTI_CNT ((struct SioMultiCnt *)REG_ADDR_SIOCNT)
+
+struct BlockTransfer
+{
+ u16 pos;
+ u16 size;
+ void *src;
+ bool8 active;
+ u8 multiplayerId;
+};
+
+struct LinkTestBGInfo
+{
+ u32 screenBaseBlock;
+ u32 paletteNum;
+ u32 dummy_8;
+ u32 dummy_C;
+};
+
+extern u8 unk_2000000[];
+extern u8 unk_2004000[];
+extern u16 gBattleTypeFlags;
+
+extern u16 word_3004858;
+
+extern void Blender_SetBankBerryData(u8 bank, u16 itemID);
+
+static void InitLinkTestBG(u8, u8, u8, u8);
+void InitLinkTestBG_Unused(u8, u8, u8, u8);
+void LinkTestScreen();
+static void InitLocalLinkPlayer(void);
+static void VBlankCB_LinkTest(void);
+static void InitLink(void);
+static void Task_TriggerHandshake(u8);
+static void TestBlockTransfer(u32, u32, u32);
+static void LinkTestProcessKeyInput(void);
+static void CB2_LinkTest(void);
+static void HandleReceiveRemoteLinkPlayer(u8);
+static void ProcessRecvCmds(u8);
+static void BuildSendCmd(u16);
+static void sub_8007B44(void);
+static void ResetBlockSend(void);
+static bool8 InitBlockSend(void *, u32);
+static void LinkCB_BlockSendBegin(void);
+static void LinkCB_BlockSend(void);
+static void LinkCB_BlockSendEnd(void);
+static void sub_8007E04(void);
+u32 sub_8007E40(void);
+static void SetBlockReceivedFlag(u8);
+static u16 LinkTestCalcBlockChecksum(void *, u16);
+static void PrintHexDigit(u8, u8, u8);
+static void PrintHex(u32, u8, u8, u8);
+static void LinkCB_RequestPlayerDataExchange(void);
+static void Task_PrintTestData(u8);
+bool8 sub_8008224(void);
+u8 GetDummy2(void);
+static void sub_8008350(void);
+static void sub_800837C(void);
+static void sub_80083E0(void);
+static void sub_8008454(void);
+static void sub_80084C8(void);
+static void sub_80084F4(void);
+
+static void CheckErrorStatus(void);
+static void CB2_PrintErrorMessage(void);
+static u8 IsSioMultiMaster(void);
+static void DisableSerial(void);
+static void EnableSerial(void);
+static void CheckMasterOrSlave(void);
+static void InitTimer(void);
+static void EnqueueSendCmd(u16 *);
+static void DequeueRecvCmds(u16[CMD_LENGTH][MAX_LINK_PLAYERS]);
+static void StartTransfer(void);
+static bool8 DoHandshake(void);
+static void DoRecv(void);
+static void DoSend(void);
+static void StopTimer(void);
+static void SendRecvDone(void);
+void ResetSendBuffer(void);
+void ResetRecvBuffer(void);
+
+static struct BlockTransfer sBlockSend;
+static struct BlockTransfer sBlockRecv[MAX_LINK_PLAYERS];
+static u32 sBlockSendDelayCounter;
+static u32 sDummy1;
+static u8 sDummy2;
+static u32 sPlayerDataExchangeStatus;
+static u32 sErrorLinkStatus;
+static u32 sErrorLastRecvQueueCount;
+static u32 sErrorLastSendQueueCount;
+static u32 sDummy3;
+static u8 sLinkTestLastBlockSendPos;
+static u8 sLinkTestLastBlockRecvPos[MAX_LINK_PLAYERS];
+static u8 sNumVBlanksWithoutSerialIntr;
+static bool8 sSendBufferEmpty;
+static u16 sSendNonzeroCheck;
+static u16 sRecvNonzeroCheck;
+static u8 sChecksumAvailable;
+static u8 sHandshakePlayerCount;
+
+u16 word_3002910[MAX_LINK_PLAYERS];
+u32 gLinkDebugValue1;
+struct LinkPlayerBlock localLinkPlayerBlock;
+bool8 gLinkErrorOccurred;
+u32 gLinkDebugValue2;
+bool8 gLinkPlayerPending[MAX_LINK_PLAYERS];
+struct LinkPlayer gLinkPlayers[MAX_LINK_PLAYERS];
+bool8 gBlockReceived[MAX_LINK_PLAYERS];
+u16 gLinkHeldKeys;
+u16 gLinkTimeOutCounter;
+struct LinkPlayer localLinkPlayer;
+u16 gRecvCmds[CMD_LENGTH][MAX_LINK_PLAYERS];
+u32 gLinkStatus;
+bool8 gLinkDummyBool;
+u8 byte_3002A68;
+u8 gBlockSendBuffer[BLOCK_BUFFER_SIZE];
+bool8 u8_array_3002B70[MAX_LINK_PLAYERS];
+u16 gLinkType;
+bool8 u8_array_3002B78[MAX_LINK_PLAYERS];
+u16 gBlockRecvBuffer[MAX_LINK_PLAYERS][BLOCK_BUFFER_SIZE / 2];
+bool8 gSuppressLinkErrorMessage;
+u8 gSavedLinkPlayerCount;
+u16 gSendCmd[CMD_LENGTH];
+u8 gSavedMultiplayerId;
+bool8 gReceivedRemoteLinkPlayers;
+struct LinkTestBGInfo gLinkTestBGInfo;
+void (*gLinkCallback)(void);
+struct LinkPlayer gSavedLinkPlayers[MAX_LINK_PLAYERS];
+u8 gShouldAdvanceLinkState;
+u16 gLinkTestBlockChecksums[MAX_LINK_PLAYERS];
+u8 gBlockRequestType;
+u8 gLastSendQueueCount;
+struct Link gLink;
+u8 gLastRecvQueueCount;
+u16 gLinkSavedIme;
+
+#ifdef GERMAN
+u8 deUnkValue1;
+u8 deUnkValue2;
+#endif
+
+EWRAM_DATA bool8 gLinkTestDebugValuesEnabled = {0};
+EWRAM_DATA bool8 gLinkTestDummyBool = {0};
+EWRAM_DATA u32 gFiller_20238B8 = {0};
+EWRAM_DATA u32 dword_20238BC = {0};
+EWRAM_DATA bool8 gLinkOpen = {0};
+
+static const u16 sLinkTestDigitPalette[] = INCBIN_U16("graphics/interface/link_test_digits.gbapal");
+static const u32 sLinkTestDigitTiles[] = INCBIN_U32("graphics/interface/link_test_digits.4bpp");
+
+static const u8 sDebugMessages[7][12] =
+{
+ _("せつぞく ちゅうです"),
+ _("せつぞく できません"),
+ _("かくにん ちゅうです"),
+ _("かくにん できました"),
+ _("かくにん できません"),
+ _("かくにん を かくにん"),
+ _("かくにん は しっぱい"),
+};
+
+static const u8 sColorCodes[] = _("{HIGHLIGHT TRANSPARENT}{COLOR WHITE2}");
+
+const struct BlockRequest sBlockRequestLookupTable[5] =
+{
+ {gBlockSendBuffer, 200},
+ {gBlockSendBuffer, 200},
+ {gBlockSendBuffer, 100},
+ {gBlockSendBuffer, 220},
+ {gBlockSendBuffer, 40},
+};
+
+static const u8 sTestString[] = _("テストな");
+
+ALIGNED(4) static const u8 sMagic[] = "GameFreak inc.";
+
+ALIGNED(4) static const u8 sEmptyString[] = _("");
+
+void Task_DestroySelf(u8 taskId)
+{
+ DestroyTask(taskId);
+}
+
+static void InitLinkTestBG(u8 paletteNum, u8 bgNum, u8 screenBaseBlock, u8 charBaseBlock)
+{
+ LoadPalette(sLinkTestDigitPalette, 16 * paletteNum, 32);
+ DmaCopy16(3, sLinkTestDigitTiles, BG_CHAR_ADDR(charBaseBlock), 0x220);
+
+ gLinkTestBGInfo.screenBaseBlock = screenBaseBlock;
+ gLinkTestBGInfo.paletteNum = paletteNum;
+
+ switch (bgNum)
+ {
+ case 1:
+ REG_BG1CNT = BGCNT_PRIORITY(1) | BGCNT_SCREENBASE(screenBaseBlock) | BGCNT_CHARBASE(charBaseBlock);
+ break;
+ case 2:
+ REG_BG2CNT = BGCNT_PRIORITY(1) | BGCNT_SCREENBASE(screenBaseBlock) | BGCNT_CHARBASE(charBaseBlock);
+ break;
+ case 3:
+ REG_BG3CNT = BGCNT_PRIORITY(1) | BGCNT_SCREENBASE(screenBaseBlock) | BGCNT_CHARBASE(charBaseBlock);
+ break;
+ }
+}
+
+void InitLinkTestBG_Unused(u8 paletteNum, u8 bgNum, u8 screenBaseBlock, u8 charBaseBlock)
+{
+ LoadPalette(sLinkTestDigitPalette, 16 * paletteNum, 32);
+ DmaCopy16(3, sLinkTestDigitTiles, BG_CHAR_ADDR(charBaseBlock), 0x220);
+
+ gLinkTestBGInfo.screenBaseBlock = screenBaseBlock;
+ gLinkTestBGInfo.paletteNum = paletteNum;
+
+ *gBGControlRegs[bgNum] = (screenBaseBlock << 8) | (charBaseBlock << 2);
+}
+
+void LinkTestScreen(void)
+{
+ s32 i;
+ ResetSpriteData();
+ FreeAllSpritePalettes();
+ ResetTasks();
+ SetVBlankCallback(VBlankCB_LinkTest);
+ SetUpWindowConfig(&gWindowConfig_81E6CE4);
+ InitMenuWindow((struct WindowConfig *)&gWindowConfig_81E6CE4);
+ ResetBlockSend();
+ gLinkType = 0x1111;
+ OpenLink();
+ SeedRng(gMain.vblankCounter1);
+
+ for (i = 0; i < 4; i++)
+ {
+ // Very weird code, but nothing else seems to match.
+ // The following would have the same effect:
+ // gSaveBlock2.playerTrainerId[i] = Random();
+ u8 *trainerId = gSaveBlock2.playerTrainerId;
+ s32 r;
+ s32 mask = 0xFF;
+ trainerId[i] = (r = Random()) & mask;
+ }
+
+ InitLinkTestBG(0, 2, 4, 0);
+ REG_DISPCNT = DISPCNT_MODE_0 | DISPCNT_OBJ_ON | DISPCNT_BG0_ON | DISPCNT_BG2_ON | DISPCNT_OBJ_1D_MAP;
+ CreateTask(Task_DestroySelf, 0);
+ RunTasks();
+ AnimateSprites();
+ BuildOamBuffer();
+ UpdatePaletteFade();
+ sDummy3 = 0;
+ InitLocalLinkPlayer();
+ CreateTask(Task_PrintTestData, 0);
+ SetMainCallback2(CB2_LinkTest);
+}
+
+void sub_8007270(u8 a1)
+{
+ localLinkPlayer.lp_field_18 = a1;
+}
+
+static void InitLocalLinkPlayer(void)
+{
+ s32 i;
+
+ localLinkPlayer.trainerId = gSaveBlock2.playerTrainerId[0]
+ | (gSaveBlock2.playerTrainerId[1] << 8)
+ | (gSaveBlock2.playerTrainerId[2] << 16)
+ | (gSaveBlock2.playerTrainerId[3] << 24);
+
+ for (i = 0; i < (s32)sizeof(localLinkPlayer.name); i++)
+ localLinkPlayer.name[i] = gSaveBlock2.playerName[i]; // UB: reads past the end of "playerName" array
+
+ localLinkPlayer.gender = gSaveBlock2.playerGender;
+ localLinkPlayer.linkType = gLinkType;
+ localLinkPlayer.language = gGameLanguage;
+ localLinkPlayer.version = gGameVersion + 0x4000;
+ localLinkPlayer.lp_field_2 = 0;
+}
+
+static void VBlankCB_LinkTest(void)
+{
+ LoadOam();
+ ProcessSpriteCopyRequests();
+ TransferPlttBuffer();
+}
+
+static void InitLink(void)
+{
+ s32 i;
+
+ for (i = 0; i < CMD_LENGTH; i++)
+ gSendCmd[i] = 0xEFFF;
+
+ gLinkOpen = TRUE;
+ EnableSerial();
+}
+
+static void Task_TriggerHandshake(u8 taskId)
+{
+ gTasks[taskId].data[0]++;
+
+ if (gTasks[taskId].data[0] == 5)
+ {
+ gShouldAdvanceLinkState = 1;
+ DestroyTask(taskId);
+ }
+}
+
+void OpenLink(void)
+{
+ s32 i;
+
+ ResetSerial();
+ InitLink();
+
+ gLinkCallback = LinkCB_RequestPlayerDataExchange;
+ gLinkVSyncDisabled = FALSE;
+ gLinkErrorOccurred = FALSE;
+ gSuppressLinkErrorMessage = FALSE;
+
+ ResetBlockReceivedFlags();
+
+ sDummy1 = 0;
+ byte_3002A68 = 0;
+ gLinkDummyBool = FALSE;
+ gReceivedRemoteLinkPlayers = FALSE;
+
+ for (i = 0; i < 4; i++)
+ {
+ gLinkPlayerPending[i] = TRUE;
+ u8_array_3002B78[i] = 0;
+ u8_array_3002B70[i] = 0;
+ }
+
+ CreateTask(Task_TriggerHandshake, 2);
+}
+
+void CloseLink(void)
+{
+ gReceivedRemoteLinkPlayers = FALSE;
+ gLinkOpen = FALSE;
+ DisableSerial();
+}
+
+static void TestBlockTransfer(u32 a1, u32 a2, u32 a3)
+{
+ u8 i;
+ u8 val;
+
+ if (sLinkTestLastBlockSendPos != sBlockSend.pos)
+ {
+ PrintHex(sBlockSend.pos, 2, 3, 2);
+ sLinkTestLastBlockSendPos = sBlockSend.pos;
+ }
+
+ for (i = 0; i < MAX_LINK_PLAYERS; i++)
+ {
+ if (sLinkTestLastBlockRecvPos[i] != sBlockRecv[i].pos)
+ {
+ PrintHex(sBlockRecv[i].pos, 2, i + 4, 2);
+ sLinkTestLastBlockRecvPos[i] = sBlockRecv[i].pos;
+ }
+ }
+
+ val = GetBlockReceivedStatus();
+
+ if (val == 0xF)
+ {
+ for (i = 0; i < MAX_LINK_PLAYERS; i++)
+ {
+ if ((val >> i) & 1)
+ {
+ gLinkTestBlockChecksums[i] = LinkTestCalcBlockChecksum(&gBlockRecvBuffer[i], sBlockRecv[i].size);
+ ResetBlockReceivedFlag(i);
+ if (gLinkTestBlockChecksums[i] != 834)
+ {
+ gLinkTestDebugValuesEnabled = FALSE;
+ gLinkTestDummyBool = FALSE;
+ }
+ }
+ }
+ }
+}
+
+static void LinkTestProcessKeyInput(void)
+{
+ if (gMain.newKeys & A_BUTTON)
+ gShouldAdvanceLinkState = 1;
+ if (gMain.heldKeys & B_BUTTON)
+ InitBlockSend(unk_2004000, 0x2004);
+ if (gMain.newKeys & L_BUTTON)
+ BeginNormalPaletteFade(-1, 0, 0x10, 0, 2);
+ if (gMain.newKeys & START_BUTTON)
+ SetSuppressLinkErrorMessage(TRUE);
+ if (gMain.newKeys & R_BUTTON)
+ TrySavingData(LINK_SAVE);
+ if (gMain.newKeys & SELECT_BUTTON)
+ sub_800832C();
+ if (gLinkTestDebugValuesEnabled)
+ {
+ u32 vblankCounter1 = gMain.vblankCounter1;
+ u8 val = gLinkVSyncDisabled;
+ if (!gLinkCallback)
+ val = gLinkVSyncDisabled | 0x10;
+ SetLinkDebugValues(vblankCounter1, val);
+ }
+}
+
+static void CB2_LinkTest(void)
+{
+ LinkTestProcessKeyInput();
+ TestBlockTransfer(1, 1, 0);
+ RunTasks();
+ AnimateSprites();
+ BuildOamBuffer();
+ UpdatePaletteFade();
+}
+
+u16 LinkMain2(u16 *heldKeys)
+{
+ u8 i;
+
+ if (!gLinkOpen)
+ return 0;
+
+ for (i = 0; i < CMD_LENGTH; i++)
+ gSendCmd[i] = 0;
+
+ gLinkHeldKeys = *heldKeys;
+
+ if (gLinkStatus & LINK_STAT_CONN_ESTABLISHED)
+ {
+ ProcessRecvCmds(SIO_MULTI_CNT->id);
+ if (gLinkCallback)
+ gLinkCallback();
+ CheckErrorStatus();
+ }
+
+ return gLinkStatus;
+}
+
+static void HandleReceiveRemoteLinkPlayer(u8 multiplayerId)
+{
+ u32 pendingLinkPlayerCount = 0;
+ s32 i;
+
+ gLinkPlayerPending[multiplayerId] = FALSE;
+
+ for (i = 0; i < GetLinkPlayerCount_2(); i++)
+ pendingLinkPlayerCount += gLinkPlayerPending[i];
+
+ if (pendingLinkPlayerCount == 0 && !gReceivedRemoteLinkPlayers)
+ gReceivedRemoteLinkPlayers = TRUE;
+}
+
+static void ProcessRecvCmds(u8 unusedParam)
+{
+ u16 i;
+ for (i = 0; i < MAX_LINK_PLAYERS; i++)
+ {
+ word_3002910[i] = 0;
+ if (!gRecvCmds[0][i])
+ continue;
+ switch (gRecvCmds[0][i])
+ {
+ case 0x2222:
+ InitLocalLinkPlayer();
+ localLinkPlayerBlock.linkPlayer = localLinkPlayer;
+ memcpy(localLinkPlayerBlock.magic1, sMagic, sizeof(localLinkPlayerBlock.magic1) - 1);
+ memcpy(localLinkPlayerBlock.magic2, sMagic, sizeof(localLinkPlayerBlock.magic2) - 1);
+ InitBlockSend(&localLinkPlayerBlock, sizeof(localLinkPlayerBlock));
+ break;
+ case 0x4444:
+ word_3002910[i] = gRecvCmds[1][i];
+ break;
+ case 0x5555:
+ byte_3002A68 = 1;
+ break;
+ case 0x5566:
+ byte_3002A68 = 1;
+ break;
+ case 0xBBBB:
+ {
+ struct BlockTransfer *blockRecv = &sBlockRecv[i];
+ blockRecv->pos = 0;
+ blockRecv->size = gRecvCmds[1][i];
+ blockRecv->multiplayerId = gRecvCmds[2][i];
+ break;
+ }
+ case 0x8888:
+ if (sBlockRecv[i].size > BLOCK_BUFFER_SIZE)
+ {
+ u16 *buffer = (u16 *)unk_2000000;
+ u16 j;
+ for (j = 0; j < CMD_LENGTH - 1; j++)
+ buffer[(sBlockRecv[i].pos / 2) + j] = gRecvCmds[j + 1][i];
+ }
+ else
+ {
+ u16 j;
+ for (j = 0; j < CMD_LENGTH - 1; j++)
+ gBlockRecvBuffer[i][(sBlockRecv[i].pos / 2) + j] = gRecvCmds[j + 1][i];
+ }
+
+ sBlockRecv[i].pos += (CMD_LENGTH - 1) * 2;
+
+ if (sBlockRecv[i].pos >= sBlockRecv[i].size)
+ {
+ if (gLinkPlayerPending[i] == TRUE)
+ {
+ struct LinkPlayerBlock *block = (struct LinkPlayerBlock *)&gBlockRecvBuffer[i];
+ struct LinkPlayer *linkPlayer = &gLinkPlayers[i];
+ *linkPlayer = block->linkPlayer;
+
+ if (strcmp(block->magic1, sMagic)
+ || strcmp(block->magic2, sMagic))
+ {
+ SetMainCallback2(CB2_LinkError);
+ }
+ else
+ {
+ HandleReceiveRemoteLinkPlayer(i);
+ }
+
+ ConvertInternationalString(gLinkPlayers[i].name, gLinkPlayers[i].language);
+ }
+ else
+ {
+ SetBlockReceivedFlag(i);
+ }
+ }
+ break;
+ case 0x5FFF:
+ u8_array_3002B78[i] = 1;
+ break;
+ case 0x2FFE:
+ u8_array_3002B70[i] = 1;
+ break;
+ case 0xAAAA:
+ sub_8007E24();
+ break;
+ case 0xAAAB:
+ Blender_SetBankBerryData(i, gRecvCmds[1][i]);
+ break;
+ case 0xCCCC:
+#if defined(ENGLISH)
+ SendBlock(0, sBlockRequestLookupTable[gRecvCmds[1][i]].address, sBlockRequestLookupTable[gRecvCmds[1][i]].size);
+#elif defined(GERMAN)
+ if (deUnkValue2 == 1)
+ {
+ deUnkValue2 = 2;
+ deUnkValue1 = gRecvCmds[1][i];
+ }
+ else if (deUnkValue2 == 2 || deUnkValue2 == 3)
+ {
+ SendBlock(0, sBlockRequestLookupTable[gRecvCmds[1][i]].address, sBlockRequestLookupTable[gRecvCmds[1][i]].size);
+
+ if (deUnkValue2 == 2)
+ deUnkValue2 = 1;
+ else
+ deUnkValue2 = 0;
+ }
+ else
+ {
+ SendBlock(0, sBlockRequestLookupTable[gRecvCmds[1][i]].address, sBlockRequestLookupTable[gRecvCmds[1][i]].size);
+ }
+#endif
+ break;
+ case 0xCAFE:
+ word_3002910[i] = gRecvCmds[1][i];
+ break;
+ }
+ }
+}
+
+static void BuildSendCmd(u16 code)
+{
+ switch (code)
+ {
+ case 0x2222:
+ gSendCmd[0] = 0x2222;
+ gSendCmd[1] = gLinkType;
+ break;
+ case 0x2FFE:
+ gSendCmd[0] = 0x2FFE;
+ break;
+ case 0x4444:
+ gSendCmd[0] = 0x4444;
+ gSendCmd[1] = gMain.heldKeys;
+ break;
+ case 0x5555:
+ gSendCmd[0] = 0x5555;
+ break;
+ case 0x5566:
+ gSendCmd[0] = 0x5566;
+ break;
+ case 0x6666:
+ gSendCmd[0] = 0x6666;
+ gSendCmd[1] = 0;
+ break;
+ case 0x7777:
+ {
+ u8 i;
+
+ gSendCmd[0] = 0x7777;
+
+ for (i = 0; i < 5; i++)
+ gSendCmd[i + 1] = 0xEE;
+
+ break;
+ }
+ case 0xBBBB:
+ gSendCmd[0] = 0xBBBB;
+ gSendCmd[1] = sBlockSend.size;
+ gSendCmd[2] = sBlockSend.multiplayerId + 128;
+ break;
+ case 0xAAAA:
+ gSendCmd[0] = 0xAAAA;
+ break;
+ case 0xAAAB:
+ gSendCmd[0] = 0xAAAB;
+ gSendCmd[1] = gScriptItemId;
+ break;
+ case 0xCCCC:
+ gSendCmd[0] = 0xCCCC;
+ gSendCmd[1] = gBlockRequestType;
+ break;
+ case 0x5FFF:
+ gSendCmd[0] = 0x5FFF;
+ break;
+ case 0xCAFE:
+ if (!word_3004858 || gLinkTransferringData)
+ break;
+ gSendCmd[0] = 0xCAFE;
+ gSendCmd[1] = word_3004858;
+ break;
+ }
+}
+
+void sub_8007B14(void)
+{
+ gLinkCallback = sub_8007B44;
+}
+
+bool32 sub_8007B24(void)
+{
+ if (gLinkCallback == sub_8007B44)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static void sub_8007B44(void)
+{
+ if (gReceivedRemoteLinkPlayers == TRUE)
+ BuildSendCmd(0xCAFE);
+}
+
+void ClearLinkCallback(void)
+{
+ gLinkCallback = NULL;
+}
+
+void ClearLinkCallback_2(void)
+{
+ gLinkCallback = NULL;
+}
+
+u8 GetLinkPlayerCount(void)
+{
+ return EXTRACT_PLAYER_COUNT(gLinkStatus);
+}
+
+void OpenLinkTimed(void)
+{
+ sPlayerDataExchangeStatus = EXCHANGE_NOT_STARTED;
+ gLinkTimeOutCounter = 0;
+#if defined(GERMAN)
+ ResetBlockSend();
+#endif
+ OpenLink();
+}
+
+u8 GetLinkPlayerDataExchangeStatusTimed(void)
+{
+ s32 i;
+ s32 count = 0;
+ u32 index;
+
+ if (gReceivedRemoteLinkPlayers == TRUE)
+ {
+ if (GetLinkPlayerCount() == 0)
+ {
+ gLinkErrorOccurred = TRUE;
+ CloseLink();
+ }
+
+ i = 0;
+ index = 0;
+
+ while (i < GetLinkPlayerCount())
+ {
+ if (gLinkPlayers[index].linkType == gLinkPlayers[0].linkType)
+ ++count;
+ ++index;
+ ++i;
+ }
+
+ if (count == GetLinkPlayerCount())
+ sPlayerDataExchangeStatus = EXCHANGE_COMPLETE;
+ else
+ sPlayerDataExchangeStatus = EXCHANGE_IN_PROGRESS;
+ }
+ else
+ {
+ gLinkTimeOutCounter++;
+ if (gLinkTimeOutCounter > 600)
+ sPlayerDataExchangeStatus = EXCHANGE_TIMED_OUT;
+ }
+
+ return sPlayerDataExchangeStatus;
+}
+
+bool8 IsLinkPlayerDataExchangeComplete(void)
+{
+ u8 i;
+ u8 count = 0;
+ u8 isComplete;
+
+ for (i = 0; i < GetLinkPlayerCount(); i++)
+ if (gLinkPlayers[i].linkType == gLinkPlayers[0].linkType)
+ count++;
+
+ if (count == GetLinkPlayerCount())
+ {
+ isComplete = TRUE;
+ sPlayerDataExchangeStatus = EXCHANGE_COMPLETE;
+ }
+ else
+ {
+ isComplete = FALSE;
+ sPlayerDataExchangeStatus = EXCHANGE_IN_PROGRESS;
+ }
+
+ return isComplete;
+}
+
+u32 GetLinkPlayerTrainerId(u8 multiplayerId)
+{
+ return gLinkPlayers[multiplayerId].trainerId;
+}
+
+void ResetLinkPlayers(void)
+{
+ s32 i;
+ for (i = 0; i < 4; i++)
+ memset(&gLinkPlayers[i], 0, sizeof(struct LinkPlayer));
+}
+
+static void ResetBlockSend(void)
+{
+ sBlockSend.active = FALSE;
+ sBlockSend.pos = 0;
+ sBlockSend.size = 0;
+ sBlockSend.src = NULL;
+}
+
+static bool8 InitBlockSend(void *data, u32 size)
+{
+ if (sBlockSend.active)
+ {
+ return FALSE;
+ }
+ else
+ {
+ sBlockSend.multiplayerId = GetMultiplayerId();
+ sBlockSend.active = TRUE;
+ sBlockSend.size = size;
+ sBlockSend.pos = 0;
+
+ if (size > BLOCK_BUFFER_SIZE)
+ {
+ sBlockSend.src = data;
+ }
+ else
+ {
+ if (data != gBlockSendBuffer)
+ memcpy(gBlockSendBuffer, data, size);
+ sBlockSend.src = gBlockSendBuffer;
+ }
+
+ BuildSendCmd(0xBBBB);
+ gLinkCallback = LinkCB_BlockSendBegin;
+ sBlockSendDelayCounter = 0;
+ return TRUE;
+ }
+}
+
+static void LinkCB_BlockSendBegin(void)
+{
+ sBlockSendDelayCounter++;
+ if (sBlockSendDelayCounter > 2)
+ gLinkCallback = LinkCB_BlockSend;
+}
+
+static void LinkCB_BlockSend(void)
+{
+ s32 i;
+ u8 *buffer = sBlockSend.src;
+
+ gSendCmd[0] = 0x8888;
+
+ for (i = 0; i < CMD_LENGTH - 1; i++)
+ {
+ s32 offset = sBlockSend.pos + 2 * i;
+ gSendCmd[i + 1] = (buffer[offset + 1] << 8) | buffer[offset];
+ }
+
+ sBlockSend.pos += (CMD_LENGTH - 1) * 2;
+
+ if (sBlockSend.size <= sBlockSend.pos)
+ {
+ sBlockSend.active = FALSE;
+ gLinkCallback = LinkCB_BlockSendEnd;
+ }
+}
+
+static void LinkCB_BlockSendEnd(void)
+{
+ gLinkCallback = NULL;
+}
+
+static void sub_8007E04(void)
+{
+ GetMultiplayerId(); // whats the point of calling this if you dont use the multiplayer ID?
+ BuildSendCmd(0x4444);
+ dword_20238BC++;
+}
+
+void sub_8007E24(void)
+{
+ dword_20238BC = 0;
+ gLinkCallback = sub_8007E04;
+}
+
+u32 sub_8007E40(void)
+{
+ return dword_20238BC;
+}
+
+void sub_8007E4C(void)
+{
+ BuildSendCmd(0xAAAA);
+}
+
+u8 GetMultiplayerId(void)
+{
+ return SIO_MULTI_CNT->id;
+}
+
+u8 bitmask_all_link_players_but_self(void)
+{
+ return ((1 << GetMultiplayerId()) ^ 0xF);
+}
+
+bool8 SendBlock(u8 a1, void *a2, u16 a3)
+{
+ return InitBlockSend(a2, a3);
+}
+
+bool8 sub_8007E9C(u8 a1)
+{
+ if (!gLinkCallback)
+ {
+ gBlockRequestType = a1;
+ BuildSendCmd(0xCCCC);
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+bool8 sub_8007ECC(void)
+{
+ u8 retVal = FALSE;
+
+ if (!gLinkCallback)
+ retVal = TRUE;
+
+ return retVal;
+}
+
+u8 GetBlockReceivedStatus(void)
+{
+ return (gBlockReceived[3] << 3)
+ | (gBlockReceived[2] << 2)
+ | (gBlockReceived[1] << 1)
+ | gBlockReceived[0];
+}
+
+static void SetBlockReceivedFlag(u8 multiplayerId)
+{
+ gBlockReceived[multiplayerId] = TRUE;
+}
+
+void ResetBlockReceivedFlags(void)
+{
+ s32 i;
+ for (i = 0; i < 4; i++)
+ gBlockReceived[i] = FALSE;
+}
+
+void ResetBlockReceivedFlag(u8 multiplayerId)
+{
+ if (gBlockReceived[multiplayerId])
+ gBlockReceived[multiplayerId] = FALSE;
+}
+
+void sub_8007F4C(void)
+{
+ if ((gLinkStatus & LINK_STAT_MASTER) && EXTRACT_PLAYER_COUNT(gLinkStatus) > 1)
+ gShouldAdvanceLinkState = 1;
+}
+
+static u16 LinkTestCalcBlockChecksum(void *data, u16 size)
+{
+ u16 sum = 0;
+ u16 i;
+
+ for (i = 0; i < size / 2; i++)
+ sum += ((u16 *)data)[i];
+
+ return sum;
+}
+
+static void PrintHexDigit(u8 tileNum, u8 x, u8 y)
+{
+ u16 *tilemap = (u16 *)BG_SCREEN_ADDR(gLinkTestBGInfo.screenBaseBlock);
+ tilemap[(32 * y) + x] = (gLinkTestBGInfo.paletteNum << 12) | (tileNum + 1);
+}
+
+static void PrintHex(u32 num, u8 x, u8 y, u8 maxDigits)
+{
+ u8 buffer[16];
+ s32 i;
+
+ for (i = 0; i < maxDigits; i++)
+ {
+ buffer[i] = num & 0xF;
+ num >>= 4;
+ }
+
+ for (i = maxDigits - 1; i >= 0; i--)
+ {
+ PrintHexDigit(buffer[i], x, y);
+ x++;
+ }
+}
+
+static void LinkCB_RequestPlayerDataExchange(void)
+{
+ // Only one request needs to be sent, so only the master sends it.
+ if (gLinkStatus & LINK_STAT_MASTER)
+ BuildSendCmd(0x2222);
+ gLinkCallback = NULL;
+}
+
+void Task_PrintTestData(u8 taskId)
+{
+ s32 i;
+
+ PrintHex(gShouldAdvanceLinkState, 2, 1, 2);
+ PrintHex(gLinkStatus, 15, 1, 8);
+ PrintHex(gLink.state, 2, 10, 2);
+ PrintHex(EXTRACT_PLAYER_COUNT(gLinkStatus), 15, 10, 2);
+ PrintHex(GetMultiplayerId(), 15, 12, 2);
+ PrintHex(gLastSendQueueCount, 25, 1, 2);
+ PrintHex(gLastRecvQueueCount, 25, 2, 2);
+ PrintHex(GetBlockReceivedStatus(), 15, 5, 2);
+ PrintHex(gLinkDebugValue1, 2, 12, 8);
+ PrintHex(gLinkDebugValue2, 2, 13, 8);
+ PrintHex(GetSioMultiSI(), 25, 5, 1);
+ PrintHex(IsSioMultiMaster(), 25, 6, 1);
+ PrintHex(IsLinkConnectionEstablished(), 25, 7, 1);
+ PrintHex(HasLinkErrorOccurred(), 25, 8, 1);
+
+ for (i = 0; i < MAX_LINK_PLAYERS; i++)
+ PrintHex(gLinkTestBlockChecksums[i], 10, 4 + i, 4);
+}
+
+void SetLinkDebugValues(u32 value1, u32 value2)
+{
+ gLinkDebugValue1 = value1;
+ gLinkDebugValue2 = value2;
+}
+
+u8 sub_8008198(void)
+{
+ u8 result = 0;
+ s32 i;
+
+ for (i = 0; i < gSavedLinkPlayerCount; i++)
+ result |= (1 << i);
+
+ return result;
+}
+
+void sub_80081C8(u8 playerCount)
+{
+ s32 i;
+
+ gSavedLinkPlayerCount = playerCount;
+ gSavedMultiplayerId = GetMultiplayerId();
+
+ for (i = 0; i < 4; i++)
+ gSavedLinkPlayers[i] = gLinkPlayers[i];
+}
+
+u8 sub_800820C(void)
+{
+ return gSavedLinkPlayerCount;
+}
+
+u8 sub_8008218(void)
+{
+ return gSavedMultiplayerId;
+}
+
+bool8 sub_8008224(void)
+{
+ s32 count = 0;
+ s32 i;
+
+ for (i = 0; i < gSavedLinkPlayerCount; i++)
+ if (gLinkPlayers[i].trainerId == gSavedLinkPlayers[i].trainerId)
+ count++;
+
+ if (count == gSavedLinkPlayerCount)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+void sub_800826C(void)
+{
+ u8 i;
+
+ for (i = 0; i < gSavedLinkPlayerCount; i++)
+ {
+ if (gSavedLinkPlayers[i].trainerId != gLinkPlayers[i].trainerId
+ || StringCompareWithoutExtCtrlCodes(gSavedLinkPlayers[i].name, gLinkPlayers[i].name))
+ {
+ gLinkErrorOccurred = TRUE;
+ CloseLink();
+ SetMainCallback2(CB2_LinkError);
+ }
+ }
+}
+
+void sub_80082EC(void)
+{
+ gSavedLinkPlayerCount = 0;
+ gSavedMultiplayerId = 0;
+}
+
+u8 GetLinkPlayerCount_2(void)
+{
+ return EXTRACT_PLAYER_COUNT(gLinkStatus);
+}
+
+bool8 IsLinkMaster(void)
+{
+ return EXTRACT_MASTER(gLinkStatus);
+}
+
+u8 GetDummy2(void)
+{
+ return sDummy2;
+}
+
+void sub_800832C(void)
+{
+ if (!gLinkCallback)
+ {
+ gLinkCallback = sub_8008350;
+ gLinkDummyBool = FALSE;
+ }
+}
+
+static void sub_8008350(void)
+{
+ if (gLastRecvQueueCount == 0)
+ {
+ BuildSendCmd(0x5FFF);
+ gLinkCallback = sub_800837C;
+ }
+}
+
+static void sub_800837C(void)
+{
+ s32 i;
+ s32 totalCount = GetLinkPlayerCount();
+ s32 count = 0;
+
+ for (i = 0; i < totalCount; i++)
+ if (u8_array_3002B78[i])
+ count++;
+
+ if (count == totalCount)
+ {
+ gBattleTypeFlags &= ~BATTLE_TYPE_20;
+ gLinkVSyncDisabled = TRUE;
+ CloseLink();
+ gLinkCallback = NULL;
+ gLinkDummyBool = TRUE;
+ }
+}
+
+static void sub_80083E0(void)
+{
+ s32 i;
+ s32 totalCount = GetLinkPlayerCount();
+ s32 count = 0;
+
+ for (i = 0; i < totalCount; i++)
+ {
+ if (gLinkPlayers[i].language == 1)
+ count++;
+ else if (u8_array_3002B78[i])
+ count++;
+ }
+
+ if (count == totalCount)
+ {
+ gBattleTypeFlags &= ~BATTLE_TYPE_20;
+ gLinkVSyncDisabled = TRUE;
+ CloseLink();
+ gLinkCallback = 0;
+ gLinkDummyBool = TRUE;
+ }
+}
+
+static void sub_8008454(void)
+{
+ if (gLastRecvQueueCount == 0)
+ {
+ BuildSendCmd(0x5FFF);
+ gLinkCallback = sub_80083E0;
+ }
+}
+
+void sub_8008480(void)
+{
+ if (!gLinkCallback)
+ {
+ gLinkCallback = sub_8008454;
+ gLinkDummyBool = FALSE;
+ }
+}
+
+void sub_80084A4(void)
+{
+ if (!gLinkCallback)
+ gLinkCallback = sub_80084C8;
+ gLinkDummyBool = FALSE;
+}
+
+static void sub_80084C8(void)
+{
+ if (gLastRecvQueueCount == 0)
+ {
+ BuildSendCmd(0x2FFE);
+ gLinkCallback = sub_80084F4;
+ }
+}
+
+static void sub_80084F4(void)
+{
+ u8 totalCount = GetLinkPlayerCount();
+ u8 count = 0;
+
+ while (count < totalCount && u8_array_3002B70[count])
+ count++;
+
+ if (count == totalCount)
+ {
+ u8 i;
+ for (i = 0; i < 4; i++)
+ u8_array_3002B70[i] = 0;
+ gLinkCallback = NULL;
+ }
+}
+
+static void CheckErrorStatus(void)
+{
+ if (gLinkOpen)
+ {
+ if (gLinkStatus & LINK_STAT_ERRORS)
+ {
+ if (!gSuppressLinkErrorMessage)
+ {
+ sErrorLinkStatus = gLinkStatus;
+ sErrorLastRecvQueueCount = gLastRecvQueueCount;
+ sErrorLastSendQueueCount = gLastSendQueueCount;
+ SetMainCallback2(CB2_LinkError);
+ }
+ gLinkErrorOccurred = TRUE;
+ CloseLink();
+ }
+ }
+}
+
+void CB2_LinkError(void)
+{
+ ResetSpriteData();
+ FreeAllSpritePalettes();
+ ResetPaletteFadeControl();
+ FillPalette(0, 0, 2);
+ ResetTasks();
+ SetVBlankCallback(VBlankCB_LinkTest);
+ SetUpWindowConfig(&gWindowConfig_81E7198);
+ InitMenuWindow((struct WindowConfig *)&gWindowConfig_81E7198);
+ MenuZeroFillScreen();
+ REG_BLDALPHA = 0;
+ REG_BG0VOFS = 0;
+ REG_BG0HOFS = 0;
+ REG_DISPCNT = DISPCNT_MODE_0 | DISPCNT_OBJ_1D_MAP | DISPCNT_BG0_ON;
+ gSoftResetDisabled = FALSE;
+ CreateTask(Task_DestroySelf, 0);
+ StopMapMusic();
+ RunTasks();
+ AnimateSprites();
+ BuildOamBuffer();
+ UpdatePaletteFade();
+ SetMainCallback2(CB2_PrintErrorMessage);
+}
+
+static void CB2_PrintErrorMessage(void)
+{
+ u8 array[64] __attribute__((unused)); // unused
+
+ switch (gMain.state)
+ {
+ case 0:
+ MenuPrint_PixelCoords(gMultiText_LinkError, 20, 56, 1);
+ break;
+ case 30:
+ case 60:
+ PlaySE(SE_BOO);
+ break;
+ case 90:
+ PlaySE(SE_BOO);
+ break;
+ }
+
+ if (gMain.state != 200)
+ gMain.state++;
+}
+
+u8 GetSioMultiSI(void)
+{
+ return (REG_SIOCNT >> SIO_MULTI_SI_SHIFT) & SIO_MULTI_SI_MASK;
+}
+
+static bool8 IsSioMultiMaster(void)
+{
+ bool8 isMaster = FALSE;
+
+ if ((REG_SIOCNT & SIO_MULTI_SD) && !(REG_SIOCNT & SIO_MULTI_SI))
+ isMaster = TRUE;
+
+ return isMaster;
+}
+
+bool8 IsLinkConnectionEstablished(void)
+{
+ return EXTRACT_CONN_ESTABLISHED(gLinkStatus);
+}
+
+void SetSuppressLinkErrorMessage(bool8 value)
+{
+ gSuppressLinkErrorMessage = value;
+}
+
+bool8 HasLinkErrorOccurred(void)
+{
+ return gLinkErrorOccurred;
+}
+
+static void DisableSerial(void)
+{
+ gLinkSavedIme = REG_IME;
+ REG_IME = 0;
+ REG_IE &= ~(INTR_FLAG_TIMER3 | INTR_FLAG_SERIAL);
+ REG_IME = gLinkSavedIme;
+
+ REG_SIOCNT = 0;
+ REG_TM3CNT_H = 0;
+ REG_IF = INTR_FLAG_TIMER3 | INTR_FLAG_SERIAL;
+
+ CpuFill32(0, &gLink, sizeof(gLink));
+}
+
+static void EnableSerial(void)
+{
+ gLinkSavedIme = REG_IME;
+ REG_IME = 0;
+ REG_IE &= ~(INTR_FLAG_TIMER3 | INTR_FLAG_SERIAL);
+ REG_IME = gLinkSavedIme;
+
+ REG_RCNT = 0;
+
+ REG_SIOCNT = SIO_MULTI_MODE;
+ REG_SIOCNT |= SIO_INTR_ENABLE | SIO_115200_BPS;
+
+ gLinkSavedIme = REG_IME;
+ REG_IME = 0;
+ REG_IE |= INTR_FLAG_SERIAL;
+ REG_IME = gLinkSavedIme;
+
+ REG_SIOMLT_SEND = 0;
+
+ CpuFill32(0, &gLink, sizeof(gLink));
+
+ sNumVBlanksWithoutSerialIntr = 0;
+ sSendNonzeroCheck = 0;
+ sRecvNonzeroCheck = 0;
+ sChecksumAvailable = FALSE;
+ sHandshakePlayerCount = 0;
+ gLastSendQueueCount = 0;
+ gLastRecvQueueCount = 0;
+}
+
+void ResetSerial(void)
+{
+ EnableSerial();
+ DisableSerial();
+}
+
+u32 LinkMain1(u8 *shouldAdvanceLinkState, u16 *sendCmd, u16 recvCmds[CMD_LENGTH][MAX_LINK_PLAYERS])
+{
+ u32 retVal;
+ u32 retVal2;
+
+ switch (gLink.state)
+ {
+ case LINK_STATE_START0:
+ DisableSerial();
+ gLink.state = LINK_STATE_START1;
+ break;
+ case LINK_STATE_START1:
+ if (*shouldAdvanceLinkState == 1)
+ {
+ EnableSerial();
+ gLink.state = LINK_STATE_HANDSHAKE;
+ }
+ break;
+ case LINK_STATE_HANDSHAKE:
+ switch (*shouldAdvanceLinkState)
+ {
+ case 1:
+ if (gLink.isMaster == 8 && gLink.playerCount > 1)
+ gLink.handshakeAsMaster = TRUE;
+ break;
+ case 2:
+ gLink.state = LINK_STATE_START0;
+ REG_SIOMLT_SEND = 0;
+ break;
+ default:
+ CheckMasterOrSlave();
+ break;
+ }
+ break;
+ case LINK_STATE_INIT_TIMER:
+ InitTimer();
+ gLink.state = LINK_STATE_CONN_ESTABLISHED;
+ case LINK_STATE_CONN_ESTABLISHED:
+ EnqueueSendCmd(sendCmd);
+ DequeueRecvCmds(recvCmds);
+ break;
+ }
+
+ *shouldAdvanceLinkState = 0;
+
+ retVal = gLink.localId;
+ retVal |= (gLink.playerCount << 2);
+
+ if (gLink.isMaster == 8)
+ retVal |= 0x20;
+
+ {
+ u32 receivedNothing = gLink.receivedNothing << 8;
+ u32 link_field_F = gLink.link_field_F << 9;
+ u32 hardwareError = gLink.hardwareError << 12;
+ u32 badChecksum = gLink.badChecksum << 13;
+ u32 queueFull = gLink.queueFull << 14;
+ u32 val;
+
+ if (gLink.state == LINK_STATE_CONN_ESTABLISHED)
+ {
+ val = 0x40;
+ val |= receivedNothing;
+ val |= retVal;
+ val |= link_field_F;
+ val |= hardwareError;
+ val |= badChecksum;
+ val |= queueFull;
+ }
+ else
+ {
+ val = retVal;
+ val |= receivedNothing;
+ val |= link_field_F;
+ val |= hardwareError;
+ val |= badChecksum;
+ val |= queueFull;
+ }
+
+ retVal = val;
+ }
+
+ if (gLink.lag == LAG_MASTER)
+ retVal |= 0x10000;
+
+ if (gLink.localId > 3)
+ retVal |= 0x20000;
+
+ retVal2 = retVal;
+ if (gLink.lag == LAG_SLAVE)
+ retVal2 |= 0x40000;
+
+ return retVal2;
+}
+
+static void CheckMasterOrSlave(void)
+{
+ u32 terminals = *(u32 *)REG_ADDR_SIOCNT & (SIO_MULTI_SD | SIO_MULTI_SI);
+
+ if (terminals == SIO_MULTI_SD && !gLink.localId)
+ gLink.isMaster = 8;
+ else
+ gLink.isMaster = 0;
+}
+
+static void InitTimer(void)
+{
+ if (gLink.isMaster)
+ {
+ REG_TM3CNT_L = 65339;
+ REG_TM3CNT_H = TIMER_INTR_ENABLE | TIMER_64CLK;
+
+ gLinkSavedIme = REG_IME;
+ REG_IME = 0;
+ REG_IE |= INTR_FLAG_TIMER3;
+ REG_IME = gLinkSavedIme;
+ }
+}
+
+static void EnqueueSendCmd(u16 *sendCmd)
+{
+ gLinkSavedIme = REG_IME;
+ REG_IME = 0;
+
+ if (gLink.sendQueue.count < QUEUE_CAPACITY)
+ {
+ u8 i;
+ u8 offset = gLink.sendQueue.pos + gLink.sendQueue.count;
+
+ if (offset >= QUEUE_CAPACITY)
+ offset -= QUEUE_CAPACITY;
+
+ for (i = 0; i < CMD_LENGTH; i++)
+ {
+ sSendNonzeroCheck |= *sendCmd;
+ gLink.sendQueue.data[i][offset] = *sendCmd;
+ *sendCmd = 0;
+ sendCmd++;
+ }
+ }
+ else
+ {
+ gLink.queueFull = QUEUE_FULL_SEND;
+ }
+
+ if (sSendNonzeroCheck)
+ {
+ gLink.sendQueue.count++;
+ sSendNonzeroCheck = 0;
+ }
+
+ REG_IME = gLinkSavedIme;
+ gLastSendQueueCount = gLink.sendQueue.count;
+}
+
+void DequeueRecvCmds(u16 recvCmds[CMD_LENGTH][MAX_LINK_PLAYERS])
+{
+ u8 i;
+ u8 j;
+
+ gLinkSavedIme = REG_IME;
+ REG_IME = 0;
+
+ if (gLink.recvQueue.count == 0)
+ {
+ for (i = 0; i < CMD_LENGTH; i++)
+ for (j = 0; j < gLink.playerCount; j++)
+ recvCmds[i][j] = 0;
+
+ gLink.receivedNothing = TRUE;
+ }
+ else
+ {
+ for (i = 0; i < CMD_LENGTH; i++)
+ for (j = 0; j < gLink.playerCount; j++)
+ recvCmds[i][j] = gLink.recvQueue.data[j][i][gLink.recvQueue.pos];
+
+ gLink.recvQueue.count--;
+ gLink.recvQueue.pos++;
+
+ if (gLink.recvQueue.pos >= QUEUE_CAPACITY)
+ gLink.recvQueue.pos = 0;
+
+ gLink.receivedNothing = FALSE;
+ }
+
+ REG_IME = gLinkSavedIme;
+}
+
+void LinkVSync(void)
+{
+ if (gLink.isMaster)
+ {
+ switch (gLink.state)
+ {
+ case LINK_STATE_HANDSHAKE:
+ StartTransfer();
+ break;
+ case LINK_STATE_CONN_ESTABLISHED:
+ if (gLink.serialIntrCounter > 8)
+ {
+ if (gLink.lag == LAG_MASTER)
+ return;
+ gLink.serialIntrCounter = 0;
+ }
+ else if (gLink.hardwareError != TRUE)
+ {
+ gLink.lag = LAG_MASTER;
+ return;
+ }
+ StartTransfer();
+ break;
+ }
+ }
+ else if (gLink.state == LINK_STATE_CONN_ESTABLISHED
+ || gLink.state == LINK_STATE_HANDSHAKE)
+ {
+ sNumVBlanksWithoutSerialIntr++;
+
+ if (sNumVBlanksWithoutSerialIntr > 10)
+ {
+ if (gLink.state == LINK_STATE_CONN_ESTABLISHED)
+ gLink.lag = LAG_SLAVE;
+
+ if (gLink.state == LINK_STATE_HANDSHAKE)
+ {
+ gLink.playerCount = 0;
+ gLink.link_field_F = 0;
+ }
+ }
+ }
+}
+
+void Timer3Intr(void)
+{
+ StopTimer();
+ StartTransfer();
+}
+
+void SerialCB(void)
+{
+ gLink.localId = SIO_MULTI_CNT->id;
+
+ switch (gLink.state)
+ {
+ case LINK_STATE_CONN_ESTABLISHED:
+ gLink.hardwareError = SIO_MULTI_CNT->error;
+ DoRecv();
+ DoSend();
+ SendRecvDone();
+ break;
+ case LINK_STATE_HANDSHAKE:
+ if (DoHandshake())
+ {
+ if (gLink.isMaster)
+ {
+ gLink.state = LINK_STATE_INIT_TIMER;
+ gLink.serialIntrCounter = 8;
+ }
+ else
+ {
+ gLink.state = LINK_STATE_CONN_ESTABLISHED;
+ }
+ }
+ break;
+ }
+
+ gLink.serialIntrCounter++;
+ sNumVBlanksWithoutSerialIntr = 0;
+
+ if (gLink.serialIntrCounter == 8)
+ gLastRecvQueueCount = gLink.recvQueue.count;
+}
+
+static void StartTransfer(void)
+{
+ REG_SIOCNT |= SIO_START;
+}
+
+static bool8 DoHandshake(void)
+{
+ u8 i;
+ u8 playerCount = 0;
+ u16 minRecv = 0xFFFF;
+
+ if (gLink.handshakeAsMaster == TRUE)
+ REG_SIOMLT_SEND = MASTER_HANDSHAKE;
+ else
+ REG_SIOMLT_SEND = SLAVE_HANDSHAKE;
+
+ *(u64 *)&gLink.tempRecvBuffer[0] = REG_SIOMLT_RECV;
+ REG_SIOMLT_RECV = 0;
+
+ gLink.handshakeAsMaster = FALSE;
+
+ for (i = 0; i < 4; i++)
+ {
+ if ((gLink.tempRecvBuffer[i] & ~0x3) == SLAVE_HANDSHAKE
+ || gLink.tempRecvBuffer[i] == MASTER_HANDSHAKE)
+ {
+ playerCount++;
+
+ if (minRecv > gLink.tempRecvBuffer[i] && gLink.tempRecvBuffer[i] != 0)
+ minRecv = gLink.tempRecvBuffer[i];
+ }
+ else
+ {
+ if (gLink.tempRecvBuffer[i] != 0xFFFF)
+ playerCount = 0;
+ break;
+ }
+ }
+
+ gLink.playerCount = playerCount;
+
+ // The handshake is successful when:
+ // 1. There are multiple players.
+ // 2. The number of players agrees with the last attempt.
+ // 3. Player no. 0 is identifying as the master.
+ if (gLink.playerCount > 1
+ && gLink.playerCount == sHandshakePlayerCount
+ && gLink.tempRecvBuffer[0] == MASTER_HANDSHAKE)
+ return TRUE;
+
+ if (gLink.playerCount > 1)
+ gLink.link_field_F = (minRecv & 3) + 1;
+ else
+ gLink.link_field_F = 0;
+
+ sHandshakePlayerCount = gLink.playerCount;
+
+ return FALSE;
+}
+
+static void DoRecv(void)
+{
+ u16 recvBuffer[4];
+ u8 i;
+
+ *(u64 *)&recvBuffer[0] = REG_SIOMLT_RECV;
+
+ if (gLink.sendCmdIndex == 0)
+ {
+ for (i = 0; i < gLink.playerCount; i++)
+ if (gLink.checksum != recvBuffer[i] && sChecksumAvailable)
+ gLink.badChecksum = TRUE;
+
+ gLink.checksum = 0;
+ sChecksumAvailable = TRUE;
+ }
+ else
+ {
+ u8 index = gLink.recvQueue.pos + gLink.recvQueue.count;
+
+ if (index >= QUEUE_CAPACITY)
+ index -= QUEUE_CAPACITY;
+
+ if (gLink.recvQueue.count < QUEUE_CAPACITY)
+ {
+ for (i = 0; i < gLink.playerCount; i++)
+ {
+ gLink.checksum += recvBuffer[i];
+ sRecvNonzeroCheck |= recvBuffer[i];
+ gLink.recvQueue.data[i][gLink.recvCmdIndex][index] = recvBuffer[i];
+ }
+ }
+ else
+ {
+ gLink.queueFull = QUEUE_FULL_RECV;
+ }
+
+ gLink.recvCmdIndex++;
+
+ if (gLink.recvCmdIndex == CMD_LENGTH && sRecvNonzeroCheck)
+ {
+ gLink.recvQueue.count++;
+ sRecvNonzeroCheck = 0;
+ }
+ }
+}
+
+static void DoSend(void)
+{
+ if (gLink.sendCmdIndex == CMD_LENGTH)
+ {
+ REG_SIOMLT_SEND = gLink.checksum;
+
+ if (!sSendBufferEmpty)
+ {
+ gLink.sendQueue.count--;
+ gLink.sendQueue.pos++;
+
+ if (gLink.sendQueue.pos >= QUEUE_CAPACITY)
+ gLink.sendQueue.pos = 0;
+ }
+ else
+ {
+ sSendBufferEmpty = FALSE;
+ }
+ }
+ else
+ {
+ if (!sSendBufferEmpty && gLink.sendQueue.count == 0)
+ sSendBufferEmpty = TRUE;
+
+ if (sSendBufferEmpty)
+ REG_SIOMLT_SEND = 0;
+ else
+ REG_SIOMLT_SEND = gLink.sendQueue.data[gLink.sendCmdIndex][gLink.sendQueue.pos];
+
+ gLink.sendCmdIndex++;
+ }
+}
+
+static void StopTimer(void)
+{
+ if (gLink.isMaster)
+ {
+ REG_TM3CNT_H &= ~TIMER_ENABLE;
+ REG_TM3CNT_L = 65339;
+ }
+}
+
+static void SendRecvDone(void)
+{
+ if (gLink.recvCmdIndex == CMD_LENGTH)
+ {
+ gLink.sendCmdIndex = 0;
+ gLink.recvCmdIndex = 0;
+ }
+ else if (gLink.isMaster)
+ {
+ REG_TM3CNT_H |= TIMER_ENABLE;
+ }
+}
+
+void ResetSendBuffer(void)
+{
+ u8 i;
+ u8 j;
+
+ gLink.sendQueue.count = 0;
+ gLink.sendQueue.pos = 0;
+
+ for (i = 0; i < CMD_LENGTH; i++)
+ for (j = 0; j < QUEUE_CAPACITY; j++)
+ gLink.sendQueue.data[i][j] = 0xEFFF;
+}
+
+void ResetRecvBuffer(void)
+{
+ u8 i;
+ u8 j;
+ u8 k;
+
+ gLink.recvQueue.count = 0;
+ gLink.recvQueue.pos = 0;
+
+ for (i = 0; i < 4; i++)
+ for (j = 0; j < CMD_LENGTH; j++)
+ for (k = 0; k < QUEUE_CAPACITY; k++)
+ gLink.recvQueue.data[i][j][k] = 0xEFFF;
+}
diff --git a/src/engine/load_save.c b/src/engine/load_save.c
new file mode 100644
index 000000000..8424b1121
--- /dev/null
+++ b/src/engine/load_save.c
@@ -0,0 +1,168 @@
+#include "global.h"
+#include "gba/flash_internal.h"
+#include "load_save.h"
+#include "main.h"
+#include "pokemon.h"
+#include "overworld.h"
+
+extern u8 gPlayerPartyCount;
+
+bool32 gFlashMemoryPresent;
+
+struct LoadedSaveData
+{
+ struct ItemSlot items[20];
+ struct ItemSlot keyItems[20];
+ struct ItemSlot pokeBalls[16];
+ struct ItemSlot TMsHMs[64];
+ struct ItemSlot berries[46];
+ struct MailStruct mail[16];
+};
+
+EWRAM_DATA struct SaveBlock2 gSaveBlock2 = {0};
+EWRAM_DATA struct SaveBlock1 gSaveBlock1 = {0};
+
+static EWRAM_DATA struct LoadedSaveData gLoadedSaveData = {0};
+
+void CheckForFlashMemory(void)
+{
+ if (!IdentifyFlash())
+ {
+ gFlashMemoryPresent = TRUE;
+ InitFlashTimer();
+ }
+ else
+ gFlashMemoryPresent = FALSE;
+}
+
+bool32 GetSecretBase2Field_9(void)
+{
+ return gSaveBlock2.specialSaveWarp;
+}
+
+void ClearSecretBase2Field_9(void)
+{
+ gSaveBlock2.specialSaveWarp = 0;
+}
+
+void SetSecretBase2Field_9(void)
+{
+ gSaveBlock2.specialSaveWarp = 1;
+}
+
+void SetSecretBase2Field_9_AndHideBG(void) // note: no other function sets specialSaveWarp to values other than 0 or 1, hence clear and set distinctions.
+{
+ gpu_sync_bg_hide(0); // the function doesn't use the parameter passed to it, but this is necessary to match.
+ gSaveBlock2.specialSaveWarp = 1;
+}
+
+void ClearSecretBase2Field_9_2(void) // duplicate function
+{
+ gSaveBlock2.specialSaveWarp = 0;
+}
+
+void SavePlayerParty(void)
+{
+ int i;
+
+ gSaveBlock1.playerPartyCount = gPlayerPartyCount;
+
+ for (i = 0; i < 6; i++)
+ gSaveBlock1.playerParty[i] = gPlayerParty[i];
+}
+
+void LoadPlayerParty(void)
+{
+ int i;
+
+ gPlayerPartyCount = gSaveBlock1.playerPartyCount;
+
+ for (i = 0; i < 6; i++)
+ gPlayerParty[i] = gSaveBlock1.playerParty[i];
+}
+
+static void SaveMapObjects(void)
+{
+ int i;
+
+ for (i = 0; i < 16; i++)
+ gSaveBlock1.mapObjects[i] = gMapObjects[i];
+}
+
+static void LoadMapObjects(void)
+{
+ int i;
+
+ for (i = 0; i < 16; i++)
+ gMapObjects[i] = gSaveBlock1.mapObjects[i];
+}
+
+void SaveSerializedGame(void)
+{
+ SavePlayerParty();
+ SaveMapObjects();
+}
+
+void LoadSerializedGame(void)
+{
+ LoadPlayerParty();
+ LoadMapObjects();
+}
+
+void LoadPlayerBag(void)
+{
+ int i;
+
+ // load player items.
+ for (i = 0; i < 20; i++)
+ gLoadedSaveData.items[i] = gSaveBlock1.bagPocket_Items[i];
+
+ // load player key items.
+ for (i = 0; i < 20; i++)
+ gLoadedSaveData.keyItems[i] = gSaveBlock1.bagPocket_KeyItems[i];
+
+ // load player pokeballs.
+ for (i = 0; i < 16; i++)
+ gLoadedSaveData.pokeBalls[i] = gSaveBlock1.bagPocket_PokeBalls[i];
+
+ // load player TMs and HMs.
+ for (i = 0; i < 64; i++)
+ gLoadedSaveData.TMsHMs[i] = gSaveBlock1.bagPocket_TMHM[i];
+
+ // load player berries.
+ for (i = 0; i < 46; i++)
+ gLoadedSaveData.berries[i] = gSaveBlock1.bagPocket_Berries[i];
+
+ // load mail.
+ for (i = 0; i < 16; i++)
+ gLoadedSaveData.mail[i] = gSaveBlock1.mail[i];
+}
+
+void SavePlayerBag(void)
+{
+ int i;
+
+ // save player items.
+ for (i = 0; i < 20; i++)
+ gSaveBlock1.bagPocket_Items[i] = gLoadedSaveData.items[i];
+
+ // save player key items.
+ for (i = 0; i < 20; i++)
+ gSaveBlock1.bagPocket_KeyItems[i] = gLoadedSaveData.keyItems[i];
+
+ // save player pokeballs.
+ for (i = 0; i < 16; i++)
+ gSaveBlock1.bagPocket_PokeBalls[i] = gLoadedSaveData.pokeBalls[i];
+
+ // save player TMs and HMs.
+ for (i = 0; i < 64; i++)
+ gSaveBlock1.bagPocket_TMHM[i] = gLoadedSaveData.TMsHMs[i];
+
+ // save player berries.
+ for (i = 0; i < 46; i++)
+ gSaveBlock1.bagPocket_Berries[i] = gLoadedSaveData.berries[i];
+
+ // save mail.
+ for (i = 0; i < 16; i++)
+ gSaveBlock1.mail[i] = gLoadedSaveData.mail[i];
+}
diff --git a/src/engine/main.c b/src/engine/main.c
new file mode 100644
index 000000000..d7c11b6c8
--- /dev/null
+++ b/src/engine/main.c
@@ -0,0 +1,363 @@
+#include "global.h"
+#include "gba/flash_internal.h"
+#include "gba/m4a_internal.h"
+#include "main.h"
+#include "intro.h"
+#include "link.h"
+#include "load_save.h"
+#include "m4a.h"
+#include "play_time.h"
+#include "rng.h"
+#include "rom3.h"
+#include "overworld.h"
+#include "rtc.h"
+#include "siirtc.h"
+#include "sound.h"
+#include "unknown_task.h"
+
+extern struct SoundInfo gSoundInfo;
+extern u32 IntrMain[];
+
+static void VBlankIntr(void);
+static void HBlankIntr(void);
+static void VCountIntr(void);
+static void SerialIntr(void);
+static void IntrDummy(void);
+
+#ifdef SAPPHIRE
+#define GAME_VERSION VERSION_SAPPHIRE
+#else
+#define GAME_VERSION VERSION_RUBY
+#endif
+
+const u8 gGameVersion = GAME_VERSION;
+
+const u8 gGameLanguage = GAME_LANGUAGE;
+
+#if defined(ENGLISH)
+const char BuildDateTime[] = "2002 10 15 20:34";
+#elif defined(GERMAN)
+const char BuildDateTime[] = "$Name: debug-Euro-2003-05-09-A $";
+#endif
+
+const IntrFunc gIntrTableTemplate[] =
+{
+ SerialIntr, // Serial interrupt
+ Timer3Intr, // Timer 3 interrupt
+ HBlankIntr, // H-blank interrupt
+ VBlankIntr, // V-blank interrupt
+ VCountIntr, // V-count interrupt
+ IntrDummy, // Timer 0 interrupt
+ IntrDummy, // Timer 1 interrupt
+ IntrDummy, // Timer 2 interrupt
+ IntrDummy, // DMA 0 interrupt
+ IntrDummy, // DMA 1 interrupt
+ IntrDummy, // DMA 2 interrupt
+ IntrDummy, // DMA 3 interrupt
+ IntrDummy, // Key interrupt
+ IntrDummy, // Game Pak interrupt
+};
+
+#define INTR_COUNT ((int)(sizeof(gIntrTableTemplate)/sizeof(IntrFunc)))
+
+u16 gKeyRepeatStartDelay;
+bool8 gLinkTransferringData;
+struct Main gMain;
+u16 gKeyRepeatContinueDelay;
+u8 gSoftResetDisabled;
+IntrFunc gIntrTable[INTR_COUNT];
+bool8 gLinkVSyncDisabled;
+u32 IntrMain_Buffer[0x200];
+u8 gPcmDmaCounter;
+
+EWRAM_DATA void (**gFlashTimerIntrFunc)(void) = NULL;
+
+static void UpdateLinkAndCallCallbacks(void);
+static void InitMainCallbacks(void);
+static void CallCallbacks(void);
+static void SeedRngWithRtc(void);
+static void ReadKeys(void);
+static void InitIntrHandlers(void);
+static void WaitForVBlank(void);
+
+#define B_START_SELECT (B_BUTTON | START_BUTTON | SELECT_BUTTON)
+
+void AgbMain()
+{
+ RegisterRamReset(RESET_ALL);
+ REG_WAITCNT = WAITCNT_PREFETCH_ENABLE | WAITCNT_WS0_S_1 | WAITCNT_WS0_N_3;
+ InitKeys();
+ InitIntrHandlers();
+ m4aSoundInit();
+ RtcInit();
+ CheckForFlashMemory();
+ InitMainCallbacks();
+ InitMapMusic();
+ SeedRngWithRtc();
+
+ gSoftResetDisabled = FALSE;
+
+ if (gFlashMemoryPresent != TRUE)
+ SetMainCallback2(NULL);
+
+ gLinkTransferringData = FALSE;
+
+ for (;;)
+ {
+ ReadKeys();
+
+ if (gSoftResetDisabled == FALSE
+ && (gMain.heldKeysRaw & A_BUTTON)
+ && (gMain.heldKeysRaw & B_START_SELECT) == B_START_SELECT)
+ DoSoftReset();
+
+ if (gLink.sendQueue.count > 1 && sub_8055910() == 1)
+ {
+ gLinkTransferringData = TRUE;
+ UpdateLinkAndCallCallbacks();
+ gLinkTransferringData = FALSE;
+ }
+ else
+ {
+ gLinkTransferringData = FALSE;
+ UpdateLinkAndCallCallbacks();
+
+ if (gLink.recvQueue.count > 1)
+ {
+ if (sub_80558AC() == 1)
+ {
+ gMain.newKeys = 0;
+ gLinkTransferringData = TRUE;
+ UpdateLinkAndCallCallbacks();
+ gLinkTransferringData = FALSE;
+ }
+ }
+ }
+
+ PlayTimeCounter_Update();
+ MapMusicMain();
+ WaitForVBlank();
+ }
+}
+
+static void UpdateLinkAndCallCallbacks(void)
+{
+ gLinkStatus = LinkMain1(&gShouldAdvanceLinkState, gSendCmd, gRecvCmds);
+ LinkMain2(&gMain.heldKeys);
+ if (!(gLinkStatus & LINK_STAT_RECEIVED_NOTHING) || sub_8055940() != 1)
+ CallCallbacks();
+}
+
+static void InitMainCallbacks(void)
+{
+ gMain.vblankCounter1 = 0;
+ gMain.vblankCounter2 = 0;
+ gMain.callback1 = NULL;
+ SetMainCallback2(CB2_InitCopyrightScreenAfterBootup);
+}
+
+static void CallCallbacks(void)
+{
+ if (gMain.callback1)
+ gMain.callback1();
+
+ if (gMain.callback2)
+ gMain.callback2();
+}
+
+void SetMainCallback2(MainCallback callback)
+{
+ gMain.callback2 = callback;
+ gMain.state = 0;
+}
+
+static void SeedRngWithRtc(void)
+{
+ u32 seed = RtcGetMinuteCount();
+ seed = (seed >> 16) ^ (seed & 0xFFFF);
+ SeedRng(seed);
+}
+
+void InitKeys(void)
+{
+ gKeyRepeatContinueDelay = 5;
+ gKeyRepeatStartDelay = 40;
+
+ gMain.heldKeys = 0;
+ gMain.newKeys = 0;
+ gMain.newAndRepeatedKeys = 0;
+ gMain.heldKeysRaw = 0;
+ gMain.newKeysRaw = 0;
+}
+
+static void ReadKeys(void)
+{
+ u16 keyInput = REG_KEYINPUT ^ KEYS_MASK;
+ gMain.newKeysRaw = keyInput & ~gMain.heldKeysRaw;
+ gMain.newKeys = gMain.newKeysRaw;
+ gMain.newAndRepeatedKeys = gMain.newKeysRaw;
+
+ // BUG: Key repeat won't work when pressing L using L=A button mode
+ // because it compares the raw key input with the remapped held keys.
+ // Note that newAndRepeatedKeys is never remapped either.
+
+ if (keyInput != 0 && gMain.heldKeys == keyInput)
+ {
+ gMain.keyRepeatCounter--;
+
+ if (gMain.keyRepeatCounter == 0)
+ {
+ gMain.newAndRepeatedKeys = keyInput;
+ gMain.keyRepeatCounter = gKeyRepeatContinueDelay;
+ }
+ }
+ else
+ {
+ // If there is no input or the input has changed, reset the counter.
+ gMain.keyRepeatCounter = gKeyRepeatStartDelay;
+ }
+
+ gMain.heldKeysRaw = keyInput;
+ gMain.heldKeys = gMain.heldKeysRaw;
+
+ // Remap L to A if the L=A option is enabled.
+ if (gSaveBlock2.optionsButtonMode == 2)
+ {
+ if (gMain.newKeys & L_BUTTON)
+ gMain.newKeys |= A_BUTTON;
+
+ if (gMain.heldKeys & L_BUTTON)
+ gMain.heldKeys |= A_BUTTON;
+ }
+
+ if (gMain.newKeys & gMain.watchedKeysMask)
+ gMain.watchedKeysPressed = TRUE;
+}
+
+static void InitIntrHandlers(void)
+{
+ int i;
+
+ for (i = 0; i < INTR_COUNT; i++)
+ gIntrTable[i] = gIntrTableTemplate[i];
+
+ DmaCopy32(3, IntrMain, IntrMain_Buffer, sizeof(IntrMain_Buffer));
+
+ INTR_VECTOR = IntrMain_Buffer;
+
+ SetVBlankCallback(NULL);
+ SetHBlankCallback(NULL);
+ SetSerialCallback(NULL);
+
+ REG_IME = 1;
+ REG_IE = INTR_FLAG_VBLANK;
+ REG_DISPSTAT = DISPSTAT_VBLANK_INTR;
+ REG_IE |= INTR_FLAG_VBLANK;
+}
+
+void SetVBlankCallback(IntrCallback callback)
+{
+ gMain.vblankCallback = callback;
+}
+
+void SetHBlankCallback(IntrCallback callback)
+{
+ gMain.hblankCallback = callback;
+}
+
+void SetVCountCallback(IntrCallback callback)
+{
+ gMain.vcountCallback = callback;
+}
+
+void SetSerialCallback(IntrCallback callback)
+{
+ gMain.serialCallback = callback;
+}
+
+static void VBlankIntr(void)
+{
+ u16 savedIme;
+
+ if (!gLinkVSyncDisabled)
+ LinkVSync();
+
+ savedIme = REG_IME;
+ REG_IME = 0;
+ m4aSoundVSync();
+ REG_IME = savedIme;
+
+ gMain.vblankCounter1++;
+
+ if (gMain.vblankCallback)
+ gMain.vblankCallback();
+
+ gMain.vblankCounter2++;
+
+ gPcmDmaCounter = gSoundInfo.pcmDmaCounter;
+
+ m4aSoundMain();
+ sub_800C35C();
+ Random();
+
+ INTR_CHECK |= INTR_FLAG_VBLANK;
+ gMain.intrCheck |= INTR_FLAG_VBLANK;
+}
+
+void InitFlashTimer(void)
+{
+ SetFlashTimerIntr(2, gFlashTimerIntrFunc);
+}
+
+static void HBlankIntr(void)
+{
+ if (gMain.hblankCallback)
+ gMain.hblankCallback();
+
+ INTR_CHECK |= INTR_FLAG_HBLANK;
+ gMain.intrCheck |= INTR_FLAG_HBLANK;
+}
+
+static void VCountIntr(void)
+{
+ if (gMain.vcountCallback)
+ gMain.vcountCallback();
+
+ INTR_CHECK |= INTR_FLAG_VCOUNT;
+ gMain.intrCheck |= INTR_FLAG_VCOUNT;
+}
+
+static void SerialIntr(void)
+{
+ if (gMain.serialCallback)
+ gMain.serialCallback();
+
+ INTR_CHECK |= INTR_FLAG_SERIAL;
+ gMain.intrCheck |= INTR_FLAG_SERIAL;
+}
+
+static void IntrDummy(void)
+{}
+
+static void WaitForVBlank(void)
+{
+ gMain.intrCheck &= ~INTR_FLAG_VBLANK;
+ VBlankIntrWait();
+}
+
+void DoSoftReset(void)
+{
+ REG_IME = 0;
+ m4aSoundVSyncOff();
+ remove_some_task();
+ DmaStop(1);
+ DmaStop(2);
+ DmaStop(3);
+ SiiRtcProtect();
+ SoftReset(RESET_ALL);
+}
+
+void ClearPokemonCrySongs(void)
+{
+ CpuFill16(0, gPokemonCrySongs, MAX_POKEMON_CRIES * sizeof(struct PokemonCrySong));
+}
diff --git a/src/engine/main_menu.c b/src/engine/main_menu.c
new file mode 100644
index 000000000..e0af86f3d
--- /dev/null
+++ b/src/engine/main_menu.c
@@ -0,0 +1,1667 @@
+#include "global.h"
+#include "main_menu.h"
+#include "data2.h"
+#include "decompress.h"
+#include "event_data.h"
+#include "field_effect.h"
+#include "menu.h"
+#include "mystery_event_menu.h"
+#include "naming_screen.h"
+#include "option_menu.h"
+#include "palette.h"
+#include "pokeball.h"
+#include "overworld.h"
+#include "rtc.h"
+#include "save_menu_util.h"
+#include "songs.h"
+#include "sound.h"
+#include "species.h"
+#include "string_util.h"
+#include "strings.h"
+#include "task.h"
+#include "text.h"
+#include "title_screen.h"
+#include "unknown_task.h"
+
+#define BirchSpeechUpdateWindowText() ((u8)MenuUpdateWindowText_OverrideLineLength(24))
+
+extern struct PaletteFadeControl gPaletteFade;
+
+extern u16 gSaveFileStatus;
+
+extern u16 gMainMenuPalette[];
+
+extern const u8 gBirchSpeech_Welcome[];
+extern const u8 gBirchSpeech_ThisIsPokemon[];
+extern const u8 gBirchSpeech_WorldInhabitedByPokemon[];
+extern const u8 gBirchSpeech_AndYouAre[];
+extern const u8 gBirchSpeech_AreYouBoyOrGirl[];
+extern const u8 gBirchSpeech_WhatsYourName[];
+extern u8 gBirchSpeech_SoItsPlayer[];
+extern u8 gBirchSpeech_AhOkayYouArePlayer[];
+extern u8 gBirchSpeech_AreYouReady[];
+
+extern struct SpriteTemplate gUnknown_02024E8C;
+extern u16 gUnknown_081E795C[];
+extern const struct MenuAction gUnknown_081E79B0[];
+extern const struct MenuAction gMalePresetNames[];
+extern const struct MenuAction gFemalePresetNames[];
+
+extern const u8 gUnknown_081E764C[];
+extern const u8 gBirchIntroShadowGfx[];
+extern const u8 gUnknown_081E7834[];
+extern const u8 gUnknown_081E796C[];
+
+extern const union AffineAnimCmd *const gSpriteAffineAnimTable_81E79AC[];
+
+extern u8 unk_2000000[];
+
+//Menu layouts
+enum
+{
+ HAS_NO_SAVED_GAME, //NEW GAME, OPTION
+ HAS_SAVED_GAME, //CONTINUE, NEW GAME, OPTION
+ HAS_MYSTERY_GIFT, //CONTINUE, NEW GAME, MYSTERY EVENTS, OPTION
+};
+
+static void CB2_MainMenu(void);
+static void VBlankCB_MainMenu(void);
+static void CB2_InitMainMenuFromOptions(void);
+static u32 InitMainMenu(bool8 a1);
+static void Task_MainMenuCheckSave(u8 taskId);
+static void Task_MainMenuWaitForSaveErrorAck(u8 taskId);
+static void Task_MainMenuCheckRtc(u8 taskId);
+static void Task_MainMenuWaitForRtcErrorAck(u8 taskId);
+static void Task_MainMenuDraw(u8 taskId);
+static void Task_MainMenuHighlight(u8 taskId);
+static bool8 MainMenuProcessKeyInput(u8 taskId);
+static void Task_MainMenuProcessKeyInput(u8 taskId);
+static void Task_MainMenuPressedA(u8 taskId);
+static void Task_MainMenuPressedB(u8 taskId);
+static void HighlightCurrentMenuItem(u8 layout, u8 menuItem);
+static void PrintMainMenuItem(const u8 *text, u8 left, u8 top);
+static void PrintSaveFileInfo(void);
+static void PrintPlayerName(void);
+static void PrintPlayTime(void);
+static void PrintPokedexCount(void);
+static void PrintBadgeCount(void);
+static void Task_NewGameSpeech1(u8 taskId);
+static void Task_NewGameSpeech2(u8 taskId);
+static void Task_NewGameSpeech3(u8 taskId);
+static void Task_NewGameSpeech4(u8 taskId);
+static void Task_NewGameSpeech5(u8 taskId);
+static void Task_NewGameSpeech6(u8 taskId);
+static void Task_NewGameSpeech7(u8 taskId);
+static void Task_NewGameSpeech8(u8 taskId);
+static void Task_NewGameSpeech9(u8 taskId);
+static void Task_NewGameSpeech10(u8 taskId);
+static void Task_NewGameSpeech11(u8 taskId);
+static void Task_NewGameSpeech12(u8 taskId);
+static void Task_NewGameSpeech13(u8 taskId);
+static void Task_NewGameSpeech14(u8 taskId);
+static void Task_NewGameSpeech15(u8 taskId);
+static void Task_NewGameSpeech16(u8 taskId);
+static void Task_NewGameSpeech17(u8 taskId);
+static void Task_NewGameSpeech18(u8 taskId);
+static void Task_NewGameSpeech19(u8 taskId);
+static void Task_NewGameSpeech20(u8 taskId);
+static void Task_NewGameSpeech21(u8 taskId);
+static void Task_NewGameSpeech22(u8 taskId);
+static void Task_NewGameSpeech23(u8 taskId);
+static void Task_NewGameSpeech24(u8 taskId);
+static void Task_NewGameSpeech25(u8 taskId);
+static void Task_NewGameSpeech26(u8 taskId);
+static void Task_NewGameSpeech27(u8 taskId);
+static void Task_NewGameSpeech28(u8 taskId);
+static void Task_NewGameSpeech29(u8 taskId);
+static void Task_NewGameSpeech30(u8 taskId);
+static void Task_NewGameSpeech31(u8 taskId);
+static void Task_NewGameSpeech32(u8 taskId);
+static void Task_NewGameSpeech33(u8 taskId);
+static void CB_ContinueNewGameSpeechPart2();
+static void nullsub_34(struct Sprite *sprite);
+static void ShrinkPlayerSprite(struct Sprite *sprite);
+static u8 CreateAzurillSprite(u8 x, u8 y);
+static void AddBirchSpeechObjects(u8 taskId);
+static void Task_SpriteFadeOut(u8 taskId);
+static void StartSpriteFadeOut(u8 taskId, u8 interval);
+static void Task_SpriteFadeIn(u8 taskId);
+static void StartSpriteFadeIn(u8 taskId, u8 interval);
+static void HandleFloorShadowFadeOut(u8 taskId);
+static void StartBackgroundFadeOut(u8 taskId, u8 interval);
+static void HandleFloorShadowFadeIn(u8 taskId);
+static void StartBackgroundFadeIn(u8 taskId, u8 interval);
+static void CreateGenderMenu(u8 left, u8 top);
+static s8 GenderMenuProcessInput(void);
+static void CreateNameMenu(u8 left, u8 top);
+static s8 NameMenuProcessInput(void);
+static void SetPresetPlayerName(u8 index);
+
+static void CB2_MainMenu(void)
+{
+ RunTasks();
+ AnimateSprites();
+ BuildOamBuffer();
+ UpdatePaletteFade();
+}
+
+void VBlankCB_MainMenu(void)
+{
+ LoadOam();
+ ProcessSpriteCopyRequests();
+ TransferPlttBuffer();
+}
+
+void CB2_InitMainMenu(void)
+{
+ InitMainMenu(FALSE);
+}
+
+static void CB2_InitMainMenuFromOptions(void)
+{
+ InitMainMenu(TRUE);
+}
+
+#define tMenuLayout data[0]
+#define tMenuSelection data[1]
+
+u32 InitMainMenu(u8 a1)
+{
+ u16 savedIme;
+ u8 taskId;
+
+ SetVBlankCallback(NULL);
+
+ REG_DISPCNT = 0;
+ REG_BG2CNT = 0;
+ REG_BG1CNT = 0;
+ REG_BG0CNT = 0;
+ REG_BG2HOFS = 0;
+ REG_BG2VOFS = 0;
+ REG_BG1HOFS = 0;
+ REG_BG1VOFS = 0;
+ REG_BG0HOFS = 0;
+ REG_BG0VOFS = 0;
+
+ DmaFill16(3, 0, (void *)VRAM, VRAM_SIZE);
+ DmaFill32(3, 0, (void *)OAM, OAM_SIZE);
+ DmaFill16(3, 0, (void *)(PLTT + 2), PLTT_SIZE - 2);
+
+ ResetPaletteFade();
+ LoadPalette(gMainMenuPalette, 0, 32);
+ remove_some_task();
+ ResetTasks();
+ ResetSpriteData();
+ FreeAllSpritePalettes();
+ SetUpWindowConfig(&gWindowConfig_81E6C3C);
+ InitMenuWindow((struct WindowConfig *)&gWindowConfig_81E6CE4);
+
+ if (a1)
+ BeginNormalPaletteFade(-1, 0, 0x10, 0, 0x0000); // fade to black
+ else
+ BeginNormalPaletteFade(-1, 0, 0x10, 0, 0xFFFF); // fade to white
+
+ REG_WIN0H = 0;
+ REG_WIN0V = 0;
+ REG_WININ = 0;
+ REG_WINOUT = 0;
+ REG_BLDCNT = 0;
+ REG_BLDALPHA = 0;
+ REG_BLDY = 0;
+
+ savedIme = REG_IME;
+ REG_IME = 0;
+ REG_IE |= INTR_FLAG_VBLANK;
+ REG_IME = savedIme;
+
+ SetVBlankCallback(VBlankCB_MainMenu);
+ SetMainCallback2(CB2_MainMenu);
+
+ REG_DISPCNT = DISPCNT_MODE_0
+ | DISPCNT_OBJ_1D_MAP
+ | DISPCNT_BG0_ON
+ | DISPCNT_OBJ_ON
+ | DISPCNT_WIN0_ON;
+
+ taskId = CreateTask(Task_MainMenuCheckSave, 0);
+ gTasks[taskId].tMenuSelection = 0;
+
+ return 0;
+}
+
+void Task_MainMenuCheckSave(u8 taskId)
+{
+ if (gPaletteFade.active)
+ return;
+
+ REG_WIN0H = 0;
+ REG_WIN0V = 0;
+ REG_WININ = 0x1111;
+ REG_WINOUT = 49;
+ REG_BLDCNT = 241;
+ REG_BLDALPHA = 0;
+ REG_BLDY = 7;
+
+ switch (gSaveFileStatus)
+ {
+ case 1:
+ if (IsMysteryGiftEnabled() == TRUE)
+ gTasks[taskId].tMenuLayout = HAS_MYSTERY_GIFT;
+ else
+ gTasks[taskId].tMenuLayout = HAS_SAVED_GAME;
+
+ gTasks[taskId].func = Task_MainMenuCheckRtc;
+ break;
+ case 2:
+ MenuDrawTextWindow(2, 14, 27, 19);
+ MenuPrintMessage(gSaveFileDeletedMessage, 3, 15);
+ REG_WIN0H = WIN_RANGE(17, 223);
+ REG_WIN0V = WIN_RANGE(113, 159);
+ gTasks[taskId].tMenuLayout = HAS_NO_SAVED_GAME;
+ gTasks[taskId].func = Task_MainMenuWaitForSaveErrorAck;
+ break;
+ case 255:
+ MenuDrawTextWindow(2, 14, 27, 19);
+ MenuPrintMessage(gSaveFileCorruptMessage, 3, 15);
+ REG_WIN0H = WIN_RANGE(17, 223);
+ REG_WIN0V = WIN_RANGE(113, 159);
+ gTasks[taskId].tMenuLayout = HAS_SAVED_GAME;
+ gTasks[taskId].func = Task_MainMenuWaitForSaveErrorAck;
+
+ if (IsMysteryGiftEnabled() == TRUE)
+ gTasks[taskId].tMenuLayout = HAS_MYSTERY_GIFT;
+ else
+ gTasks[taskId].tMenuLayout = HAS_SAVED_GAME;
+ break;
+ case 0:
+ default:
+ gTasks[taskId].tMenuLayout = HAS_NO_SAVED_GAME;
+ gTasks[taskId].func = Task_MainMenuCheckRtc;
+ break;
+ case 4:
+ MenuDrawTextWindow(2, 14, 27, 19);
+ MenuPrintMessage(gBoardNotInstalledMessage, 3, 15);
+ REG_WIN0H = WIN_RANGE(17, 223);
+ REG_WIN0V = WIN_RANGE(113, 159);
+ gTasks[taskId].tMenuLayout = HAS_NO_SAVED_GAME;
+ gTasks[taskId].func = Task_MainMenuWaitForSaveErrorAck;
+ return;
+ }
+}
+
+void Task_MainMenuWaitForSaveErrorAck(u8 taskId)
+{
+ if (MenuUpdateWindowText())
+ {
+ if (gMain.newKeys & A_BUTTON)
+ {
+ MenuZeroFillWindowRect(2, 14, 27, 19);
+ gTasks[taskId].func = Task_MainMenuCheckRtc;
+ }
+ }
+}
+
+void Task_MainMenuCheckRtc(u8 taskId)
+{
+ if (!gPaletteFade.active)
+ {
+ REG_WIN0H = 0;
+ REG_WIN0V = 0;
+ REG_WININ = 0x1111;
+ REG_WINOUT = 49;
+ REG_BLDCNT = 241;
+ REG_BLDALPHA = 0;
+ REG_BLDY = 7;
+
+ if (!(RtcGetErrorStatus() & RTC_ERR_FLAG_MASK))
+ {
+ gTasks[taskId].func = Task_MainMenuDraw;
+ }
+ else
+ {
+ MenuDrawTextWindow(2, 14, 27, 19);
+ MenuPrintMessage(gBatteryDryMessage, 3, 15);
+ REG_WIN0H = WIN_RANGE(17, 223);
+ REG_WIN0V = WIN_RANGE(113, 159);
+ gTasks[taskId].func = Task_MainMenuWaitForRtcErrorAck;
+ }
+ }
+}
+
+void Task_MainMenuWaitForRtcErrorAck(u8 taskId)
+{
+ if (MenuUpdateWindowText())
+ {
+ if ( gMain.newKeys & 1 )
+ {
+ MenuZeroFillWindowRect(2, 14, 27, 19);
+ gTasks[taskId].func = Task_MainMenuDraw;
+ }
+ }
+}
+
+void Task_MainMenuDraw(u8 taskId)
+{
+ u16 palette;
+
+ if (!gPaletteFade.active)
+ {
+ REG_WIN0H = 0;
+ REG_WIN0V = 0;
+ REG_WININ = 0x1111;
+ REG_WINOUT = 49;
+ REG_BLDCNT = 241;
+ REG_BLDALPHA = 0;
+ REG_BLDY = 7;
+
+ palette = RGB(0, 0, 0);
+ LoadPalette(&palette, 254, 2);
+
+ if (gSaveBlock2.playerGender == MALE)
+ {
+ palette = RGB(4, 16, 31);
+ LoadPalette(&palette, 241, 2);
+ }
+ else
+ {
+ palette = RGB(31, 3, 21);
+ LoadPalette(&palette, 241, 2);
+ }
+
+ switch (gTasks[taskId].tMenuLayout)
+ {
+ case HAS_NO_SAVED_GAME:
+ default:
+ MenuDrawTextWindow(1, 0, 28, 3);
+ PrintMainMenuItem(gMainMenuString_NewGame, 2, 1);
+ MenuDrawTextWindow(1, 4, 28, 7);
+ PrintMainMenuItem(gMainMenuString_Option, 2, 5);
+ break;
+ case HAS_SAVED_GAME:
+ MenuDrawTextWindow(1, 0, 28, 7);
+ PrintMainMenuItem(gMainMenuString_Continue, 2, 1);
+ MenuDrawTextWindow(1, 8, 28, 11);
+ PrintMainMenuItem(gMainMenuString_NewGame, 2, 9);
+ MenuDrawTextWindow(1, 12, 28, 15);
+ PrintMainMenuItem(gMainMenuString_Option, 2, 13);
+ PrintSaveFileInfo();
+ break;
+ case HAS_MYSTERY_GIFT:
+ MenuDrawTextWindow(1, 0, 28, 7);
+ PrintMainMenuItem(gMainMenuString_Continue, 2, 1);
+ MenuDrawTextWindow(1, 8, 28, 11);
+ PrintMainMenuItem(gMainMenuString_NewGame, 2, 9);
+ MenuDrawTextWindow(1, 12, 28, 15);
+ PrintMainMenuItem(gMainMenuString_MysteryEvents, 2, 13);
+ MenuDrawTextWindow(1, 16, 28, 19);
+ PrintMainMenuItem(gMainMenuString_Option, 2, 0x11);
+ PrintSaveFileInfo();
+ break;
+ }
+
+ gTasks[taskId].func = Task_MainMenuHighlight;
+ }
+}
+
+void Task_MainMenuHighlight(u8 taskId)
+{
+ HighlightCurrentMenuItem(gTasks[taskId].tMenuLayout, gTasks[taskId].tMenuSelection);
+ gTasks[taskId].func = Task_MainMenuProcessKeyInput;
+}
+
+bool8 MainMenuProcessKeyInput(u8 taskId)
+{
+ if (gMain.newKeys & A_BUTTON)
+ {
+ PlaySE(SE_SELECT);
+ BeginNormalPaletteFade(0xFFFFFFFF, 0, 0, 0x10, 0x0000);
+ gTasks[taskId].func = Task_MainMenuPressedA;
+ }
+ else if (gMain.newKeys & B_BUTTON)
+ {
+ PlaySE(SE_SELECT);
+ BeginNormalPaletteFade(0xFFFFFFFF, 0, 0, 0x10, 0xFFFF);
+ REG_WIN0H = WIN_RANGE(0, 240);
+ REG_WIN0V = WIN_RANGE(0, 160);
+ gTasks[taskId].func = Task_MainMenuPressedB;
+ }
+ else
+ {
+ s32 menuItemCount;
+
+ switch (gTasks[taskId].tMenuLayout)
+ {
+ case HAS_NO_SAVED_GAME:
+ default:
+ menuItemCount = 2;
+ break;
+ case HAS_SAVED_GAME:
+ menuItemCount = 3;
+ break;
+ case HAS_MYSTERY_GIFT:
+ menuItemCount = 4;
+ break;
+ }
+
+ if (gMain.newKeys & DPAD_UP)
+ {
+ if (gTasks[taskId].tMenuSelection > 0)
+ {
+ gTasks[taskId].tMenuSelection--;
+ return TRUE;
+ }
+ }
+ if (gMain.newKeys & DPAD_DOWN)
+ {
+ if (gTasks[taskId].tMenuSelection < menuItemCount - 1)
+ {
+ gTasks[taskId].tMenuSelection++;
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+void Task_MainMenuProcessKeyInput(u8 taskId)
+{
+ bool8 currentMenuItemChanged = MainMenuProcessKeyInput(taskId);
+ if (currentMenuItemChanged)
+ gTasks[taskId].func = Task_MainMenuHighlight;
+}
+
+void Task_MainMenuPressedA(u8 taskId)
+{
+ enum
+ {
+ NEW_GAME,
+ CONTINUE,
+ OPTION,
+ MYSTERY_EVENTS,
+ } action;
+
+ if (gPaletteFade.active)
+ return;
+
+ switch (gTasks[taskId].tMenuLayout)
+ {
+ case HAS_NO_SAVED_GAME:
+ default:
+ switch (gTasks[taskId].tMenuSelection)
+ {
+ case 0:
+ default:
+ action = NEW_GAME;
+ break;
+ case 1:
+ action = OPTION;
+ break;
+ }
+ break;
+ case HAS_SAVED_GAME:
+ switch (gTasks[taskId].tMenuSelection)
+ {
+ case 0:
+ default:
+ action = CONTINUE;
+ break;
+ case 1:
+ action = NEW_GAME;
+ break;
+ case 2:
+ action = OPTION;
+ break;
+ }
+ break;
+ case HAS_MYSTERY_GIFT:
+ switch (gTasks[taskId].tMenuSelection)
+ {
+ case 0:
+ default:
+ action = CONTINUE;
+ break;
+ case 1:
+ action = NEW_GAME;
+ break;
+ case 2:
+ action = MYSTERY_EVENTS;
+ break;
+ case 3:
+ action = OPTION;
+ break;
+ }
+ break;
+ }
+
+ switch ((int)action)
+ {
+ case NEW_GAME:
+ default:
+ gPlttBufferUnfaded[0] = 0;
+ gPlttBufferFaded[0] = 0;
+ gTasks[taskId].func = Task_NewGameSpeech1;
+ break;
+ case CONTINUE:
+ gPlttBufferUnfaded[0] = 0;
+ gPlttBufferFaded[0] = 0;
+ SetMainCallback2(CB2_ContinueSavedGame);
+ DestroyTask(taskId);
+ break;
+ case OPTION:
+ gMain.savedCallback = CB2_InitMainMenuFromOptions;
+ SetMainCallback2(CB2_InitOptionMenu);
+ DestroyTask(taskId);
+ break;
+ case MYSTERY_EVENTS:
+ SetMainCallback2(CB2_InitMysteryEventMenu);
+ DestroyTask(taskId);
+ break;
+ }
+}
+
+void Task_MainMenuPressedB(u8 taskId)
+{
+ if (!gPaletteFade.active)
+ {
+ SetMainCallback2(CB2_InitTitleScreen);
+ DestroyTask(taskId);
+ }
+}
+
+#undef tMenuLayout
+#undef tMenuSelection
+
+void HighlightCurrentMenuItem(u8 layout, u8 menuItem)
+{
+ REG_WIN0H = WIN_RANGE(9, 231);
+
+ switch (layout)
+ {
+ case HAS_NO_SAVED_GAME:
+ default:
+ switch (menuItem)
+ {
+ case 0:
+ default:
+ REG_WIN0V = WIN_RANGE(1, 31);
+ break;
+ case 1:
+ REG_WIN0V = WIN_RANGE(33, 63);
+ break;
+ }
+ break;
+ case HAS_SAVED_GAME:
+ switch (menuItem)
+ {
+ case 0:
+ default:
+ REG_WIN0V = WIN_RANGE(1, 63);
+ break;
+ case 1:
+ REG_WIN0V = WIN_RANGE(65, 95);
+ break;
+ case 2:
+ REG_WIN0V = WIN_RANGE(97, 127);
+ break;
+ }
+ break;
+ case HAS_MYSTERY_GIFT:
+ switch (menuItem)
+ {
+ case 0:
+ default:
+ REG_WIN0V = WIN_RANGE(1, 63);
+ break;
+ case 1:
+ REG_WIN0V = WIN_RANGE(65, 95);
+ break;
+ case 2:
+ REG_WIN0V = WIN_RANGE(97, 127);
+ break;
+ case 3:
+ REG_WIN0V = WIN_RANGE(129, 159);
+ break;
+ }
+ break;
+ }
+}
+
+void PrintMainMenuItem(const u8 *text, u8 left, u8 top)
+{
+ u8 i;
+ u8 buffer[32];
+
+ buffer[0] = 0xFC;
+ buffer[1] = 1;
+ buffer[2] = 14;
+
+ for (i = 0; i < 26; i++)
+ buffer[3 + i] = text[i];
+
+ buffer[29] = EOS;
+
+ MenuPrint(buffer, left, top);
+}
+
+void PrintSaveFileInfo(void)
+{
+ PrintPlayerName();
+ PrintPokedexCount();
+ PrintPlayTime();
+ PrintBadgeCount();
+}
+
+void PrintPlayerName(void)
+{
+ MenuPrint(gMainMenuString_Player, 2, 3);
+ MenuPrint(gSaveBlock2.playerName, 9, 3);
+}
+
+void PrintPlayTime(void)
+{
+ u8 playTime[16];
+ u8 alignedPlayTime[32];
+
+#if defined(ENGLISH)
+ MenuPrint(gMainMenuString_Time, 16, 3);
+ FormatPlayTime(playTime, gSaveBlock2.playTimeHours, gSaveBlock2.playTimeMinutes, 1);
+ sub_8072C74(alignedPlayTime, playTime, 48, 1);
+ MenuPrint(alignedPlayTime, 22, 3);
+#elif defined(GERMAN)
+ MenuPrint_PixelCoords(gMainMenuString_Time, 124, 24, TRUE);
+ FormatPlayTime(playTime, gSaveBlock2.playTimeHours, gSaveBlock2.playTimeMinutes, 1);
+ sub_8072C74(alignedPlayTime, playTime, 40, 1);
+ MenuPrint(alignedPlayTime, 23, 3);
+#endif
+}
+
+void PrintPokedexCount(void)
+{
+ u8 buffer[16];
+
+ MenuPrint(gMainMenuString_Pokedex, 2, 5);
+ sub_8072C14(buffer, GetPokedexSeenCount(), 18, 0);
+ MenuPrint(buffer, 9, 5);
+}
+
+void PrintBadgeCount(void)
+{
+ u8 buffer[16];
+
+#if defined(ENGLISH)
+ MenuPrint(gMainMenuString_Badges, 16, 5);
+#elif defined(GERMAN)
+ MenuPrint_PixelCoords(gMainMenuString_Badges, 124, 40, TRUE);
+#endif
+ ConvertIntToDecimalString(buffer, GetBadgeCount());
+ MenuPrint_PixelCoords(buffer, 205, 40, 1);
+}
+
+#define tTrainerSpriteId data[2]
+#define tBGhofs data[4]
+#define tSubtaskIsDone data[5]
+#define tGenderSelection data[6]
+#define tFrameCounter data[7]
+#define tBirchSpriteId data[8]
+#define tAzurillSpriteId data[9]
+#define tBrendanSpriteId data[10]
+#define tMaySpriteId data[11]
+
+static void Task_NewGameSpeech1(u8 taskId)
+{
+ SetUpWindowConfig(&gWindowConfig_81E6C3C);
+ InitMenuWindow((struct WindowConfig *)&gWindowConfig_81E6CE4);
+ REG_WIN0H = 0;
+ REG_WIN0V = 0;
+ REG_WININ = 0;
+ REG_WINOUT = 0;
+ REG_BLDCNT = 0;
+ REG_BLDALPHA = 0;
+ REG_BLDY = 0;
+ LZ77UnCompVram(gBirchIntroShadowGfx, (void *)BG_VRAM);
+ LZ77UnCompVram(gUnknown_081E7834, (void *)(BG_VRAM + 0x3800));
+ LoadPalette(gUnknown_081E764C, 0, 0x40);
+ LoadPalette(gUnknown_081E796C, 1, 0x10);
+ remove_some_task();
+ ResetSpriteData();
+ FreeAllSpritePalettes();
+ AddBirchSpeechObjects(taskId);
+ BeginNormalPaletteFade(-1, 0, 0x10, 0, 0);
+ REG_BG1CNT = BGCNT_PRIORITY(3) | BGCNT_CHARBASE(0) | BGCNT_SCREENBASE(7) | BGCNT_16COLOR | BGCNT_TXT256x256;
+ REG_DISPCNT = DISPCNT_MODE_0 | DISPCNT_BG0_ON | DISPCNT_BG1_ON | DISPCNT_OBJ_ON | DISPCNT_OBJ_1D_MAP;
+ gTasks[taskId].tBGhofs = 0;
+ gTasks[taskId].func = Task_NewGameSpeech2;
+ gTasks[taskId].tTrainerSpriteId = 0xFF;
+ gTasks[taskId].data[3] = 0xFF;
+ gTasks[taskId].tFrameCounter = 216; //Wait 3.6 seconds (216 frames) before starting speech
+
+ PlayBGM(BGM_DOORO_X4);
+}
+
+static void Task_NewGameSpeech2(u8 taskId)
+{
+ if (gTasks[taskId].tFrameCounter != 0)
+ {
+ gTasks[taskId].tFrameCounter--;
+ }
+ else
+ {
+ //Initialize Birch sprite
+ u8 spriteId = gTasks[taskId].tBirchSpriteId;
+
+ gSprites[spriteId].pos1.x = 136;
+ gSprites[spriteId].pos1.y = 60;
+ gSprites[spriteId].invisible = 0;
+ gSprites[spriteId].oam.objMode = ST_OAM_OBJ_BLEND;
+ StartSpriteFadeIn(taskId, 10);
+ StartBackgroundFadeIn(taskId, 20);
+ gTasks[taskId].tFrameCounter = 80;
+ gTasks[taskId].func = Task_NewGameSpeech3;
+ }
+}
+
+static void Task_NewGameSpeech3(u8 taskId)
+{
+ if (gTasks[taskId].tSubtaskIsDone)
+ {
+ gSprites[gTasks[taskId].tBirchSpriteId].oam.objMode = ST_OAM_OBJ_NORMAL;
+ if (gTasks[taskId].tFrameCounter)
+ {
+ gTasks[taskId].tFrameCounter--;
+ }
+ else
+ {
+ MenuDrawTextWindow(2, 13, 27, 18);
+ //"Hi! Sorry to keep you waiting...
+ //...But everyone calls me the POKEMON PROFESSOR."
+ MenuPrintMessage(gBirchSpeech_Welcome, 3, 14);
+ gTasks[taskId].func = Task_NewGameSpeech4;
+ }
+ }
+}
+
+static void Task_NewGameSpeech4(u8 taskId)
+{
+ if (!gPaletteFade.active && BirchSpeechUpdateWindowText())
+ {
+ gTasks[taskId].func = Task_NewGameSpeech5;
+ //"This is what we call a POKEMON."
+ MenuPrintMessage(gBirchSpeech_ThisIsPokemon, 3, 14);
+ }
+}
+
+static void Task_NewGameSpeech5(u8 taskId)
+{
+ if (BirchSpeechUpdateWindowText())
+ gTasks[taskId].func = Task_NewGameSpeech6;
+}
+
+static void Task_NewGameSpeech6(u8 taskId)
+{
+ u8 spriteId = gTasks[taskId].tAzurillSpriteId;
+
+ gSprites[spriteId].pos1.x = 104;
+ gSprites[spriteId].pos1.y = 72;
+ gSprites[spriteId].invisible = 0;
+ gSprites[spriteId].data0 = 0;
+ CreatePokeballSprite(spriteId, gSprites[spriteId].oam.paletteNum, 0x70, 0x3A, 0, 0, 0x20, 0x0000FFFF);
+ gTasks[taskId].func = Task_NewGameSpeech7;
+ gTasks[taskId].tFrameCounter = 0;
+}
+
+static void Task_NewGameSpeech7(u8 taskId)
+{
+ if (IsCryFinished())
+ {
+ //Go on to next sentence after frame 95
+ if (gTasks[taskId].tFrameCounter > 95)
+ {
+ MenuSetText(gSystemText_NewPara);
+ gTasks[taskId].func = Task_NewGameSpeech8;
+ }
+ }
+
+ if (gTasks[taskId].tFrameCounter < 16384)
+ {
+ gTasks[taskId].tFrameCounter++;
+ //Play Azurill cry at frame 32
+ if (gTasks[taskId].tFrameCounter == 32)
+ PlayCry1(SPECIES_AZURILL, 0);
+ }
+}
+
+static void Task_NewGameSpeech8(u8 taskId)
+{
+ if (BirchSpeechUpdateWindowText())
+ {
+ //"This world is widely inhabited by POKEMON...
+ //...That's what I do."
+ MenuPrintMessage(gBirchSpeech_WorldInhabitedByPokemon, 3, 14);
+ gTasks[taskId].func = Task_NewGameSpeech9;
+ }
+}
+
+static void Task_NewGameSpeech9(u8 taskId)
+{
+ if (BirchSpeechUpdateWindowText())
+ {
+ MenuDrawTextWindow(2, 13, 27, 18);
+ //"And you are?"
+ MenuPrintMessage(gBirchSpeech_AndYouAre, 3, 14);
+ gTasks[taskId].func = Task_NewGameSpeech10;
+ }
+}
+
+static void Task_NewGameSpeech10(u8 taskId)
+{
+ if (BirchSpeechUpdateWindowText())
+ {
+ gSprites[gTasks[taskId].tBirchSpriteId].oam.objMode = ST_OAM_OBJ_BLEND;
+ gSprites[gTasks[taskId].tAzurillSpriteId].oam.objMode = ST_OAM_OBJ_BLEND;
+ StartSpriteFadeOut(taskId, 2);
+ StartBackgroundFadeOut(taskId, 1);
+ gTasks[taskId].tFrameCounter = 64;
+ gTasks[taskId].func = Task_NewGameSpeech11;
+ }
+}
+
+//Slide platform away to the right
+static void Task_NewGameSpeech11(u8 taskId)
+{
+ if (gTasks[taskId].tBGhofs != -60)
+ {
+ gTasks[taskId].tBGhofs -= 2;
+ REG_BG1HOFS = gTasks[taskId].tBGhofs;
+ }
+ else
+ {
+ gTasks[taskId].tBGhofs = -60;
+ gTasks[taskId].func = Task_NewGameSpeech12;
+ }
+}
+
+static void Task_NewGameSpeech12(u8 taskId)
+{
+ if (gTasks[taskId].tSubtaskIsDone)
+ {
+ //Hide Birch and Azurill
+ gSprites[gTasks[taskId].tBirchSpriteId].invisible = TRUE;
+ gSprites[gTasks[taskId].tAzurillSpriteId].invisible = TRUE;
+
+ if (gTasks[taskId].tFrameCounter)
+ {
+ gTasks[taskId].tFrameCounter--;
+ }
+ else
+ {
+ //Initialize Brendan sprite
+ u8 spriteId = gTasks[taskId].tBrendanSpriteId;
+
+ gSprites[spriteId].pos1.x = 180;
+ gSprites[spriteId].pos1.y = 60;
+ gSprites[spriteId].invisible = FALSE;
+ gSprites[spriteId].oam.objMode = ST_OAM_OBJ_BLEND;
+ gTasks[taskId].tTrainerSpriteId = spriteId;
+ gTasks[taskId].tGenderSelection = 0;
+ StartSpriteFadeIn(taskId, 2);
+ StartBackgroundFadeIn(taskId, 1);
+ gTasks[taskId].func = Task_NewGameSpeech13;
+ }
+ }
+}
+
+static void Task_NewGameSpeech13(u8 taskId)
+{
+ if (gTasks[taskId].tSubtaskIsDone)
+ {
+ gSprites[gTasks[taskId].tTrainerSpriteId].oam.objMode = ST_OAM_OBJ_NORMAL;
+ gTasks[taskId].func = Task_NewGameSpeech14;
+ }
+}
+
+static void Task_NewGameSpeech14(u8 taskId)
+{
+ MenuDrawTextWindow(2, 13, 27, 18);
+ //"Are you a boy? Or are you a girl?"
+ MenuPrintMessage(gBirchSpeech_AreYouBoyOrGirl, 3, 14);
+ gTasks[taskId].func = Task_NewGameSpeech15;
+}
+
+static void Task_NewGameSpeech15(u8 taskId)
+{
+ if (BirchSpeechUpdateWindowText())
+ {
+ CreateGenderMenu(2, 4);
+ gTasks[taskId].func = Task_NewGameSpeech16;
+ }
+}
+
+//Process gender menu
+static void Task_NewGameSpeech16(u8 taskId)
+{
+ u8 cursorPos;
+
+ switch (GenderMenuProcessInput())
+ {
+ case MALE:
+ HandleDestroyMenuCursors();
+ PlaySE(SE_SELECT);
+ gSaveBlock2.playerGender = MALE;
+ MenuZeroFillWindowRect(2, 4, 8, 9);
+ gTasks[taskId].func = Task_NewGameSpeech19;
+ break;
+ case FEMALE:
+ HandleDestroyMenuCursors();
+ PlaySE(SE_SELECT);
+ gSaveBlock2.playerGender = FEMALE;
+ MenuZeroFillWindowRect(2, 4, 8, 9);
+ gTasks[taskId].func = Task_NewGameSpeech19;
+ break;
+ }
+
+ cursorPos = GetMenuCursorPos();
+
+ if (cursorPos != gTasks[taskId].tGenderSelection)
+ {
+ //Menu selection changed. Slide Brendan or May out and slide the other in
+ gTasks[taskId].tGenderSelection = cursorPos;
+ gSprites[gTasks[taskId].tTrainerSpriteId].oam.objMode = ST_OAM_OBJ_BLEND;
+ StartSpriteFadeOut(taskId, 0);
+ gTasks[taskId].func = Task_NewGameSpeech17;
+ }
+}
+
+//Slide old trainer sprite off right of screen
+static void Task_NewGameSpeech17(u8 taskId)
+{
+ u8 spriteId = gTasks[taskId].tTrainerSpriteId;
+
+ if (gTasks[taskId].tSubtaskIsDone == FALSE)
+ {
+ gSprites[spriteId].pos1.x += 4; //Move sprite right
+ }
+ else
+ {
+ gSprites[spriteId].invisible = TRUE;
+
+ //Set up new trainer sprite
+ if (gTasks[taskId].tGenderSelection)
+ spriteId = gTasks[taskId].tMaySpriteId;
+ else
+ spriteId = gTasks[taskId].tBrendanSpriteId;
+ gSprites[spriteId].pos1.x = 240;
+ gSprites[spriteId].pos1.y = 60;
+ gSprites[spriteId].invisible = FALSE;
+ gTasks[taskId].tTrainerSpriteId = spriteId;
+ gSprites[spriteId].oam.objMode = ST_OAM_OBJ_BLEND;
+ StartSpriteFadeIn(taskId, 0);
+ gTasks[taskId].func = Task_NewGameSpeech18;
+ }
+}
+
+//Slide new trainer sprite from right of screen
+static void Task_NewGameSpeech18(u8 taskId)
+{
+ u8 spriteId = gTasks[taskId].tTrainerSpriteId;
+
+ if (gSprites[spriteId].pos1.x > 180)
+ {
+ gSprites[spriteId].pos1.x -= 4; //Move sprite left
+ }
+ else
+ {
+ gSprites[spriteId].pos1.x = 180;
+ if (gTasks[taskId].tSubtaskIsDone)
+ {
+ gSprites[spriteId].oam.objMode = ST_OAM_OBJ_NORMAL;
+ gTasks[taskId].func = Task_NewGameSpeech16; //Go back to gender menu
+ }
+ }
+}
+
+static void Task_NewGameSpeech19(u8 taskId)
+{
+ MenuDrawTextWindow(2, 13, 27, 18);
+ //"All right. What's your name?"
+ MenuPrintMessage(gBirchSpeech_WhatsYourName, 3, 14);
+ gTasks[taskId].func = Task_NewGameSpeech20;
+}
+
+static void Task_NewGameSpeech20(u8 taskId)
+{
+ if (BirchSpeechUpdateWindowText())
+ {
+ CreateNameMenu(2, 1);
+ gTasks[taskId].func = Task_NewGameSpeech21;
+ }
+}
+
+//Handle name menu selection
+static void Task_NewGameSpeech21(u8 taskId)
+{
+ s8 selection = NameMenuProcessInput();
+
+ switch (selection)
+ {
+ case 1: //preset names
+ case 2:
+ case 3:
+ case 4:
+ HandleDestroyMenuCursors();
+ PlaySE(SE_SELECT);
+ MenuZeroFillWindowRect(2, 1, 22, 12);
+ SetPresetPlayerName(selection);
+ gTasks[taskId].func = Task_NewGameSpeech23;
+ break;
+ case 0: //NEW NAME
+ PlaySE(SE_SELECT);
+ BeginNormalPaletteFade(-1, 0, 0, 16, 0);
+ gTasks[taskId].func = Task_NewGameSpeech22;
+ break;
+ case -1: //B button
+ HandleDestroyMenuCursors();
+ PlaySE(SE_SELECT);
+ MenuZeroFillWindowRect(2, 1, 22, 12);
+ gTasks[taskId].func = Task_NewGameSpeech14; //Go back to gender menu
+ break;
+ }
+}
+
+//Open naming screen
+static void Task_NewGameSpeech22(u8 taskId)
+{
+ if (!gPaletteFade.active)
+ {
+ SetPresetPlayerName(1);
+ DoNamingScreen(0, gSaveBlock2.playerName, gSaveBlock2.playerGender, 0, 0, CB_ContinueNewGameSpeechPart2);
+ }
+}
+
+static void Task_NewGameSpeech23(u8 taskId)
+{
+ MenuDrawTextWindow(2, 13, 27, 18);
+ //"So it's (PLAYER)?"
+ StringExpandPlaceholders(gStringVar4, gBirchSpeech_SoItsPlayer);
+ MenuPrintMessage(gStringVar4, 3, 14);
+ gTasks[taskId].func = Task_NewGameSpeech24;
+}
+
+static void Task_NewGameSpeech24(u8 taskId)
+{
+ if (BirchSpeechUpdateWindowText())
+ {
+ DisplayYesNoMenu(2, 1, 1);
+ gTasks[taskId].func = Task_NewGameSpeech25;
+ }
+}
+
+//Handle yes/no menu selection
+static void Task_NewGameSpeech25(u8 taskId)
+{
+ switch (ProcessMenuInputNoWrap_())
+ {
+ case 0: //YES
+ PlaySE(SE_SELECT);
+ MenuZeroFillWindowRect(2, 1, 8, 7);
+ gSprites[gTasks[taskId].tTrainerSpriteId].oam.objMode = ST_OAM_OBJ_BLEND;
+ StartSpriteFadeOut(taskId, 2);
+ StartBackgroundFadeOut(taskId, 1);
+ gTasks[taskId].func = Task_NewGameSpeech26; //Continue
+ break;
+ case -1: //B button
+ case 1: //NO
+ PlaySE(SE_SELECT);
+ MenuZeroFillWindowRect(2, 1, 8, 7);
+ gTasks[taskId].func = Task_NewGameSpeech14; //Go back to gender menu
+ break;
+ }
+}
+
+static void Task_NewGameSpeech26(u8 taskId)
+{
+ if (gTasks[taskId].tBGhofs)
+ {
+ gTasks[taskId].tBGhofs += 2;
+ REG_BG1HOFS = gTasks[taskId].tBGhofs;
+ }
+ else
+ {
+ gTasks[taskId].func = Task_NewGameSpeech27;
+ }
+}
+
+static void Task_NewGameSpeech27(u8 taskId)
+{
+ if (gTasks[taskId].tSubtaskIsDone)
+ {
+ s16 spriteId;
+
+ //Hide Brendan and May sprites
+ spriteId = gTasks[taskId].tBrendanSpriteId;
+ gSprites[spriteId].invisible = TRUE;
+ spriteId = gTasks[taskId].tMaySpriteId;
+ gSprites[spriteId].invisible = TRUE;
+
+ //Fade in Birch and Azurill
+ spriteId = (u8)gTasks[taskId].tBirchSpriteId;
+ gSprites[spriteId].pos1.x = 136;
+ gSprites[spriteId].pos1.y = 64;
+ gSprites[spriteId].invisible = FALSE;
+ gSprites[spriteId].oam.objMode = ST_OAM_OBJ_BLEND;
+ spriteId = (u8)gTasks[taskId].tAzurillSpriteId;
+ gSprites[spriteId].pos1.x = 104;
+ gSprites[spriteId].pos1.y = 72;
+ gSprites[spriteId].invisible = FALSE;
+ gSprites[spriteId].oam.objMode = ST_OAM_OBJ_BLEND;
+
+ StartSpriteFadeIn(taskId, 2);
+ StartBackgroundFadeIn(taskId, 1);
+ MenuDrawTextWindow(2, 13, 27, 18);
+ StringExpandPlaceholders(gStringVar4, gBirchSpeech_AhOkayYouArePlayer);
+ //"Ah, okay! You're (PLAYER) who's moving...
+ //...I get it now!"
+ MenuPrintMessage(gStringVar4, 3, 14);
+ gTasks[taskId].func = Task_NewGameSpeech28;
+ }
+}
+
+static void Task_NewGameSpeech28(u8 taskId)
+{
+ if (gTasks[taskId].tSubtaskIsDone)
+ {
+ s16 spriteId;
+
+ spriteId = gTasks[taskId].tBirchSpriteId;
+ gSprites[spriteId].oam.objMode = ST_OAM_OBJ_NORMAL;
+
+ spriteId = gTasks[taskId].tAzurillSpriteId;
+ gSprites[spriteId].oam.objMode = ST_OAM_OBJ_NORMAL;
+
+ if (BirchSpeechUpdateWindowText())
+ {
+ //Fade out Birch and Azurill
+ spriteId = gTasks[taskId].tBirchSpriteId;
+ gSprites[spriteId].oam.objMode = ST_OAM_OBJ_BLEND;
+ spriteId = gTasks[taskId].tAzurillSpriteId;
+ gSprites[spriteId].oam.objMode = ST_OAM_OBJ_BLEND;
+
+ StartSpriteFadeOut(taskId, 2);
+ StartBackgroundFadeOut(taskId, 1);
+ gTasks[taskId].tFrameCounter = 64;
+ gTasks[taskId].func = Task_NewGameSpeech29;
+ }
+ }
+}
+
+static void Task_NewGameSpeech29(u8 taskId)
+{
+ if (gTasks[taskId].tSubtaskIsDone)
+ {
+ s16 spriteId;
+
+ //Hide Birch and Azurill
+ spriteId = gTasks[taskId].tBirchSpriteId;
+ gSprites[spriteId].invisible = TRUE;
+ spriteId = gTasks[taskId].tAzurillSpriteId;
+ gSprites[spriteId].invisible = TRUE;
+
+ if (gTasks[taskId].tFrameCounter)
+ {
+ gTasks[taskId].tFrameCounter--;
+ }
+ else
+ {
+ u8 spriteId;
+
+ //Fade in trainer and background
+ if (gSaveBlock2.playerGender)
+ spriteId = (u8)gTasks[taskId].tMaySpriteId;
+ else
+ spriteId = (u8)gTasks[taskId].tBrendanSpriteId;
+ gSprites[spriteId].pos1.x = 120;
+ gSprites[spriteId].pos1.y = 60;
+ gSprites[spriteId].invisible = FALSE;
+ gSprites[spriteId].oam.objMode = ST_OAM_OBJ_BLEND;
+ gTasks[taskId].tTrainerSpriteId = spriteId;
+
+ StartSpriteFadeIn(taskId, 2);
+ StartBackgroundFadeIn(taskId, 1);
+ MenuDrawTextWindow(2, 13, 27, 18);
+ MenuPrintMessage(gBirchSpeech_AreYouReady, 3, 14);
+ gTasks[taskId].func = Task_NewGameSpeech30;
+ }
+ }
+}
+
+static void Task_NewGameSpeech30(u8 taskId)
+{
+ if (gTasks[taskId].tSubtaskIsDone)
+ {
+ s16 spriteId;
+
+ spriteId = gTasks[taskId].tTrainerSpriteId;
+ gSprites[spriteId].oam.objMode = ST_OAM_OBJ_NORMAL;
+
+ if (BirchSpeechUpdateWindowText())
+ {
+ u8 spriteId;
+
+ spriteId = gTasks[taskId].tTrainerSpriteId;
+ gSprites[spriteId].oam.affineMode = 1;
+ gSprites[spriteId].affineAnims = gSpriteAffineAnimTable_81E79AC;
+ InitSpriteAffineAnim(&gSprites[spriteId]);
+ StartSpriteAffineAnim(&gSprites[spriteId], 0);
+ gSprites[spriteId].callback = ShrinkPlayerSprite;
+ BeginNormalPaletteFade(0x0000FFFF, 0, 0, 0x10, 0);
+ FadeOutBGM(4);
+ gTasks[taskId].func = Task_NewGameSpeech31;
+ }
+ }
+}
+
+static void Task_NewGameSpeech31(u8 taskId)
+{
+ u8 spriteId = gTasks[taskId].tTrainerSpriteId;
+
+ if (gSprites[spriteId].affineAnimEnded)
+ gTasks[taskId].func = Task_NewGameSpeech32;
+}
+
+static void Task_NewGameSpeech32(u8 taskId)
+{
+ if (!gPaletteFade.active)
+ {
+ u8 spriteId = gTasks[taskId].tTrainerSpriteId;
+ gSprites[spriteId].callback = nullsub_34;
+ REG_DISPCNT = DISPCNT_MODE_0 | DISPCNT_OBJ_1D_MAP | DISPCNT_OBJ_ON;
+ BeginNormalPaletteFade(0xFFFF0000, 0, 0, 0x10, 0xFFFF);
+ gTasks[taskId].func = Task_NewGameSpeech33;
+ }
+}
+
+static void Task_NewGameSpeech33(u8 taskId)
+{
+ if (!gPaletteFade.active)
+ {
+ //We're finished setting up. Start the new game!
+ SetMainCallback2(CB2_NewGame);
+ DestroyTask(taskId);
+ }
+}
+
+// Re-initializes graphics state after running the naming screen
+// and continues the new game speech
+void CB_ContinueNewGameSpeechPart2()
+{
+ u8 taskId;
+ u8 spriteId;
+ u16 savedIme;
+
+ SetVBlankCallback(NULL);
+
+ REG_DISPCNT = 0;
+ REG_BG2CNT = 0;
+ REG_BG1CNT = 0;
+ REG_BG0CNT = 0;
+ REG_BG2HOFS = 0;
+ REG_BG2VOFS = 0;
+ REG_BG1HOFS = 0;
+ REG_BG1VOFS = 0;
+ REG_BG0HOFS = 0;
+ REG_BG0VOFS = 0;
+
+ DmaFill16(3, 0, (void *)VRAM, VRAM_SIZE);
+ DmaFill32(3, 0, (void *)OAM, OAM_SIZE);
+ DmaFill16(3, 0, (void *)PLTT, PLTT_SIZE);
+
+ ResetPaletteFade();
+
+ LZ77UnCompVram(gBirchIntroShadowGfx, (void *)BG_VRAM);
+ LZ77UnCompVram(gUnknown_081E7834, (void *)(BG_VRAM + 0x3800));
+
+ LoadPalette(gUnknown_081E764C, 0, 0x40);
+
+ ResetTasks();
+
+ taskId = CreateTask(Task_NewGameSpeech23, 0);
+
+ gTasks[taskId].tBGhofs = -60;
+
+ remove_some_task();
+ ResetSpriteData();
+ FreeAllSpritePalettes();
+ AddBirchSpeechObjects(taskId);
+
+ SetUpWindowConfig(&gWindowConfig_81E6C3C);
+ InitMenuWindow((struct WindowConfig *)&gWindowConfig_81E6CE4);
+
+ if (gSaveBlock2.playerGender != MALE)
+ {
+ gTasks[taskId].tGenderSelection = FEMALE;
+ spriteId = gTasks[taskId].tMaySpriteId;
+ }
+ else
+ {
+ gTasks[taskId].tGenderSelection = MALE;
+ spriteId = gTasks[taskId].tBrendanSpriteId;
+ }
+
+ gSprites[spriteId].pos1.x = 180;
+ gSprites[spriteId].pos1.y = 60;
+ gSprites[spriteId].invisible = FALSE;
+
+ gTasks[taskId].tTrainerSpriteId = spriteId;
+
+ REG_BG1HOFS = -60;
+
+ BeginNormalPaletteFade(0xFFFFFFFFu, 0, 0x10, 0, 0);
+
+ REG_WIN0H = 0;
+ REG_WIN0V = 0;
+ REG_WININ = 0;
+ REG_WINOUT = 0;
+ REG_BLDCNT = 0;
+ REG_BLDALPHA = 0;
+ REG_BLDY = 0;
+
+ savedIme = REG_IME;
+ REG_IME = 0;
+ REG_IE |= INTR_FLAG_VBLANK;
+ REG_IME = savedIme;
+
+ SetVBlankCallback(VBlankCB_MainMenu);
+ SetMainCallback2(CB2_MainMenu);
+ REG_BG1CNT = BGCNT_PRIORITY(3) | BGCNT_CHARBASE(0) | BGCNT_SCREENBASE(7) | BGCNT_16COLOR | BGCNT_TXT256x256;
+ REG_DISPCNT = DISPCNT_MODE_0 | DISPCNT_OBJ_1D_MAP |
+ DISPCNT_BG0_ON | DISPCNT_BG1_ON | DISPCNT_OBJ_ON;
+}
+
+void nullsub_34(struct Sprite *sprite)
+{
+}
+
+void ShrinkPlayerSprite(struct Sprite *sprite)
+{
+ u32 y = (sprite->pos1.y << 16) + sprite->data0 + 0xC000;
+ sprite->pos1.y = y >> 16;
+ sprite->data0 = y;
+}
+
+u8 CreateAzurillSprite(u8 x, u8 y)
+{
+ DecompressPicFromTable_2(
+ &gMonFrontPicTable[SPECIES_AZURILL],
+ gMonFrontPicCoords[SPECIES_AZURILL].coords,
+ gMonFrontPicCoords[SPECIES_AZURILL].y_offset,
+ gUnknown_081FAF4C[0],
+ gUnknown_081FAF4C[1],
+ SPECIES_AZURILL);
+ LoadCompressedObjectPalette(&gMonPaletteTable[SPECIES_AZURILL]);
+ GetMonSpriteTemplate_803C56C(SPECIES_AZURILL, 1);
+ return CreateSprite(&gUnknown_02024E8C, x, y, 0);
+}
+
+void AddBirchSpeechObjects(u8 taskId)
+{
+ u8 spriteId;
+
+ spriteId = CreateBirchSprite(136, 60, 1);
+ gSprites[spriteId].callback = nullsub_34;
+ gSprites[spriteId].oam.priority = 0;
+ gSprites[spriteId].invisible = 1;
+ gTasks[taskId].tBirchSpriteId = spriteId;
+
+ spriteId = CreateAzurillSprite(0x68, 0x48);
+ gSprites[spriteId].callback = nullsub_34;
+ gSprites[spriteId].oam.priority = 0;
+ gSprites[spriteId].invisible = 1;
+ gTasks[taskId].tAzurillSpriteId = spriteId;
+
+ //Create Brendan sprite
+ spriteId = CreateTrainerSprite(0, 120, 60, 0, unk_2000000);
+ gSprites[spriteId].callback = nullsub_34;
+ gSprites[spriteId].invisible = 1;
+ gSprites[spriteId].oam.priority = 0;
+ gTasks[taskId].tBrendanSpriteId = spriteId;
+
+ //Create May sprite
+ spriteId = CreateTrainerSprite(1, 120, 60, 0, unk_2000000 + 0x800);
+ gSprites[spriteId].callback = nullsub_34;
+ gSprites[spriteId].invisible = 1;
+ gSprites[spriteId].oam.priority = 0;
+ gTasks[taskId].tMaySpriteId = spriteId;
+}
+
+#undef tTrainerSpriteId
+#undef tBGhofs
+//#undef tSubtaskIsDone
+#undef tGenderSelection
+#undef tFrameCounter
+#undef tBirchSpriteId
+#undef tAzurillSpriteId
+#undef tBrendanSpriteId
+#undef tMaySpriteId
+
+
+// Sprite Fade task
+
+#define tMainTaskId data[0]
+#define tBlendEVA data[1]
+#define tBlendEVB data[2]
+#define tUpdateInterval data[3]
+#define tFrameCounter data[4]
+
+static void Task_SpriteFadeOut(u8 taskId)
+{
+ if (gTasks[taskId].tBlendEVA == 0)
+ {
+ gTasks[gTasks[taskId].tMainTaskId].tSubtaskIsDone = TRUE;
+ DestroyTask(taskId);
+ }
+ else
+ {
+ if (gTasks[taskId].tFrameCounter)
+ {
+ gTasks[taskId].tFrameCounter--;
+ }
+ else
+ {
+ gTasks[taskId].tFrameCounter = gTasks[taskId].tUpdateInterval;
+ gTasks[taskId].tBlendEVA--;
+ gTasks[taskId].tBlendEVB++;
+ REG_BLDALPHA = gTasks[taskId].tBlendEVA + (gTasks[taskId].tBlendEVB * 256);
+ }
+ }
+}
+
+//Launches a helper task to fade out sprites
+static void StartSpriteFadeOut(u8 taskId, u8 interval)
+{
+ u8 newTaskId;
+
+ REG_BLDCNT = 592;
+ REG_BLDALPHA = 16;
+ REG_BLDY = 0;
+ gTasks[taskId].tSubtaskIsDone = FALSE;
+ newTaskId = CreateTask(Task_SpriteFadeOut, 0);
+
+ gTasks[newTaskId].tMainTaskId = taskId;
+ gTasks[newTaskId].tBlendEVA = 16;
+ gTasks[newTaskId].tBlendEVB = 0;
+ gTasks[newTaskId].tUpdateInterval = interval;
+ gTasks[newTaskId].tFrameCounter = interval;
+}
+
+static void Task_SpriteFadeIn(u8 taskId)
+{
+ if (gTasks[taskId].tBlendEVA == 16)
+ {
+ gTasks[gTasks[taskId].tMainTaskId].tSubtaskIsDone = TRUE;
+ DestroyTask(taskId);
+ }
+ else if (gTasks[taskId].tFrameCounter)
+ {
+ gTasks[taskId].tFrameCounter--;
+ }
+ else
+ {
+ gTasks[taskId].tFrameCounter = gTasks[taskId].tUpdateInterval;
+ gTasks[taskId].tBlendEVA++;
+ gTasks[taskId].tBlendEVB--;
+ REG_BLDALPHA = gTasks[taskId].tBlendEVA + (gTasks[taskId].tBlendEVB * 256);
+ }
+}
+
+//Launches a helper task to fade in sprites
+static void StartSpriteFadeIn(u8 taskId, u8 interval)
+{
+ u8 newTaskId;
+
+ REG_BLDCNT = 592;
+ REG_BLDALPHA = 4096;
+ REG_BLDY = 0;
+ gTasks[taskId].tSubtaskIsDone = FALSE;
+ newTaskId = CreateTask(Task_SpriteFadeIn, 0);
+
+ gTasks[newTaskId].tMainTaskId = taskId;
+ gTasks[newTaskId].tBlendEVA = 0;
+ gTasks[newTaskId].tBlendEVB = 16;
+ gTasks[newTaskId].tUpdateInterval = interval;
+ gTasks[newTaskId].tFrameCounter = interval;
+}
+
+#undef tMainTaskId
+#undef tBlendEVA
+#undef tBlendEVB
+#undef tUpdateInterval
+#undef tFrameCounter
+
+
+// Background fade task
+
+#define tMainTaskId data[0]
+#define tFadeLevel data[1]
+#define tDelay data[2]
+#define tUpdateInterval data[3]
+#define tFrameCounter data[4]
+
+static void HandleFloorShadowFadeOut(u8 taskId)
+{
+ if (gTasks[taskId].tDelay)
+ gTasks[taskId].tDelay--;
+ else
+ {
+ if (gTasks[taskId].tFadeLevel == 8)
+ DestroyTask(taskId);
+ else
+ {
+ if (gTasks[taskId].tFrameCounter)
+ gTasks[taskId].tFrameCounter--;
+ else
+ {
+ gTasks[taskId].tFrameCounter = gTasks[taskId].tUpdateInterval;
+ gTasks[taskId].tFadeLevel++;
+ LoadPalette(&gUnknown_081E795C[gTasks[taskId].tFadeLevel], 1, 0x10);
+ }
+ }
+ }
+}
+
+//Launches a helper task to fade out the background
+static void StartBackgroundFadeOut(u8 taskId, u8 interval)
+{
+ u8 newTaskId = CreateTask(HandleFloorShadowFadeOut, 0);
+ gTasks[newTaskId].tMainTaskId = taskId;
+ gTasks[newTaskId].tFadeLevel = 0;
+ gTasks[newTaskId].tDelay = 8;
+ gTasks[newTaskId].tUpdateInterval = interval;
+ gTasks[newTaskId].tFrameCounter = interval;
+}
+
+static void HandleFloorShadowFadeIn(u8 taskId)
+{
+ if (gTasks[taskId].tDelay)
+ gTasks[taskId].tDelay--;
+ else
+ {
+ if (gTasks[taskId].tFadeLevel == 0)
+ DestroyTask(taskId);
+ else
+ {
+ if (gTasks[taskId].tFrameCounter)
+ gTasks[taskId].tFrameCounter--;
+ else
+ {
+ gTasks[taskId].tFrameCounter = gTasks[taskId].tUpdateInterval;
+ gTasks[taskId].tFadeLevel--;
+ LoadPalette(&gUnknown_081E795C[gTasks[taskId].tFadeLevel], 1, 0x10);
+ }
+ }
+ }
+}
+
+//Launches a helper task to fade in the background
+static void StartBackgroundFadeIn(u8 taskId, u8 interval)
+{
+ u8 newTaskId = CreateTask(HandleFloorShadowFadeIn, 0);
+ gTasks[newTaskId].tMainTaskId = taskId;
+ gTasks[newTaskId].tFadeLevel = 8;
+ gTasks[newTaskId].tDelay = 8;
+ gTasks[newTaskId].tUpdateInterval = interval;
+ gTasks[newTaskId].tFrameCounter = interval;
+}
+
+#undef tMainTaskId
+#undef tFadeLevel
+#undef tDelay
+#undef tUpdateInterval
+#undef tFrameCounter
+
+static void CreateGenderMenu(u8 left, u8 top)
+{
+ u8 menuLeft, menuTop;
+ MenuDrawTextWindow(left, top, left + 6, top + 5);
+ menuLeft = left + 1;
+ menuTop = top + 1;
+ PrintMenuItems(menuLeft, menuTop, 2, gUnknown_081E79B0);
+ InitMenu(0, menuLeft, menuTop, 2, 0, 5);
+}
+
+static s8 GenderMenuProcessInput(void)
+{
+ return ProcessMenuInputNoWrap();
+}
+
+static void CreateNameMenu(u8 left, u8 top)
+{
+ MenuDrawTextWindow(left, top, left + 10, top + 11);
+
+ if (gSaveBlock2.playerGender == MALE)
+ PrintMenuItems(left + 1, top + 1, 5, gMalePresetNames);
+ else
+ PrintMenuItems(left + 1, top + 1, 5, gFemalePresetNames);
+
+ InitMenu(0, left + 1, top + 1, 5, 0, 9);
+}
+
+static s8 NameMenuProcessInput(void)
+{
+ return ProcessMenuInput();
+}
+
+static void SetPresetPlayerName(u8 index)
+{
+ u8 i;
+ u8 *name;
+
+ if (gSaveBlock2.playerGender == MALE)
+ name = (u8 *) gMalePresetNames[index].text;
+ else
+ name = (u8 *) gFemalePresetNames[index].text;
+
+ for (i = 0; i < 7; i++)
+ gSaveBlock2.playerName[i] = name[i];
+
+ gSaveBlock2.playerName[i] = EOS;
+}
diff --git a/src/engine/menu.c b/src/engine/menu.c
new file mode 100644
index 000000000..a9c4aaa43
--- /dev/null
+++ b/src/engine/menu.c
@@ -0,0 +1,871 @@
+#include "global.h"
+#include "menu.h"
+#include "main.h"
+#include "map_obj_lock.h"
+#include "menu_cursor.h"
+#include "script.h"
+#include "songs.h"
+#include "sound.h"
+#include "strings.h"
+#include "text.h"
+#include "text_window.h"
+#include "string_util.h"
+
+struct Menu
+{
+ u8 left;
+ u8 top;
+ s8 cursorPos;
+ s8 minCursorPos;
+ s8 maxCursorPos;
+ u8 width;
+ u8 height;
+ u8 menu_field_7;
+ u8 columnXCoords[8];
+};
+
+static void MultistepInitMenuWindowInternal(const struct WindowConfig *, u16);
+static void InitMenuWindowInternal(const struct WindowConfig *, u16);
+static bool8 sub_80723D4(void);
+static u8 sub_8072484(u8, u8, u8, u8, u8, u8, u32);
+static u8 sub_80724F4(u8, u8, u8, const struct MenuAction[], u8);
+static void sub_8072620(u8, u8, u8, const struct MenuAction[], u8);
+static void sub_8072D18(u8, u8);
+
+static struct Menu gMenu;
+
+EWRAM_DATA struct Window gMenuWindow = {0};
+EWRAM_DATA u8 gFiller_202E908[0x90] = {0};
+EWRAM_DATA struct Window *gMenuWindowPtr = NULL;
+EWRAM_DATA u8 gMenuMultistepInitState = 0;
+EWRAM_DATA u16 gMenuTextTileOffset = 0;
+EWRAM_DATA u16 gMenuTextWindowTileOffset = 0;
+EWRAM_DATA u16 gMenuTextWindowContentTileOffset = 0;
+EWRAM_DATA u16 gMenuMessageBoxContentTileOffset = 0;
+
+const struct MenuAction gMenuYesNoItems[] =
+{
+ { OtherText_Yes, NULL },
+ { OtherText_No, NULL },
+};
+
+void CloseMenu(void)
+{
+ PlaySE(SE_SELECT);
+ MenuZeroFillScreen();
+ sub_8064E2C();
+ ScriptContext2_Disable();
+ HandleDestroyMenuCursors();
+}
+
+void AppendToList(u8 *list, u8 *pindex, u32 value)
+{
+ list[*pindex] = value;
+ (*pindex)++;
+}
+
+void InitMenuWindow(const struct WindowConfig *winConfig)
+{
+ InitMenuWindowInternal(winConfig, 1);
+}
+
+void MultistepInitMenuWindowBegin(const struct WindowConfig *winConfig)
+{
+ MultistepInitMenuWindowInternal(winConfig, 1);
+}
+
+static void MultistepInitMenuWindowInternal(const struct WindowConfig *winConfig, u16 tileOffset)
+{
+ gMenuMultistepInitState = 0;
+ gMenuTextTileOffset = tileOffset;
+ gMenuWindowPtr = &gMenuWindow;
+ InitWindowFromConfig(&gMenuWindow, winConfig);
+}
+
+bool32 MultistepInitMenuWindowContinue(void)
+{
+ switch (gMenuMultistepInitState)
+ {
+ case 0:
+ gMenuMultistepInitState++;
+ return 0;
+ case 1:
+ gMenuTextWindowTileOffset = MultistepInitWindowTileData(gMenuWindowPtr, gMenuTextTileOffset);
+ goto next;
+ case 2:
+ if (!MultistepLoadFont())
+ goto fail;
+ goto next;
+ case 3:
+ gMenuTextWindowContentTileOffset = SetTextWindowBaseTileNum(gMenuTextWindowTileOffset);
+ next:
+ gMenuMultistepInitState++;
+ return 0;
+ case 4:
+ LoadTextWindowGraphics(gMenuWindowPtr);
+ gMenuMessageBoxContentTileOffset = SetMessageBoxBaseTileNum(gMenuTextWindowContentTileOffset);
+ return 1;
+ default:
+ fail:
+ return 0;
+ }
+}
+
+static void InitMenuWindowInternal(const struct WindowConfig *winConfig, u16 tileOffset)
+{
+ gMenuWindowPtr = &gMenuWindow;
+ InitWindowFromConfig(&gMenuWindow, winConfig);
+ gMenuTextTileOffset = tileOffset;
+ gMenuTextWindowTileOffset = InitWindowTileData(gMenuWindowPtr, gMenuTextTileOffset);
+ gMenuTextWindowContentTileOffset = SetTextWindowBaseTileNum(gMenuTextWindowTileOffset);
+ LoadTextWindowGraphics(gMenuWindowPtr);
+ gMenuMessageBoxContentTileOffset = SetMessageBoxBaseTileNum(gMenuTextWindowContentTileOffset);
+}
+
+void unref_sub_8071DA4(struct WindowConfig *winConfig, u16 tileOffset)
+{
+ gMenuWindowPtr = &gMenuWindow;
+ InitWindowFromConfig(&gMenuWindow, winConfig);
+ gMenuTextWindowTileOffset = tileOffset;
+ gMenuTextWindowContentTileOffset = SetTextWindowBaseTileNum(gMenuTextWindowTileOffset);
+ LoadTextWindowGraphics(gMenuWindowPtr);
+ gMenuTextTileOffset = SetMessageBoxBaseTileNum(gMenuTextWindowContentTileOffset);
+ gMenuMessageBoxContentTileOffset = InitWindowTileData(gMenuWindowPtr, gMenuTextTileOffset);
+}
+
+void MenuLoadTextWindowGraphics_OverrideFrameType(u8 frameType)
+{
+ LoadTextWindowGraphics_OverrideFrameType(gMenuWindowPtr, frameType);
+}
+
+void MenuLoadTextWindowGraphics(void)
+{
+ LoadTextWindowGraphics(gMenuWindowPtr);
+}
+
+void BasicInitMenuWindow(const struct WindowConfig *winConfig)
+{
+ InitWindowFromConfig(gMenuWindowPtr, winConfig);
+ gMenuWindowPtr->tileDataStartOffset = gMenuTextTileOffset;
+}
+
+void MenuPrint(const u8 *str, u8 left, u8 top)
+{
+ sub_8003460(gMenuWindowPtr, str, gMenuTextTileOffset, left, top);
+}
+
+void MenuZeroFillWindowRect(u8 left, u8 top, u8 right, u8 bottom)
+{
+ ZeroFillWindowRect(gMenuWindowPtr, left, top, right, bottom);
+}
+
+void MenuFillWindowRectWithBlankTile(u8 left, u8 top, u8 right, u8 bottom)
+{
+ FillWindowRectWithBlankTile(gMenuWindowPtr, left, top, right, bottom);
+}
+
+void MenuZeroFillScreen(void)
+{
+ MenuZeroFillWindowRect(0, 0, 29, 19);
+}
+
+void MenuDrawTextWindow(u8 left, u8 top, u8 right, u8 bottom)
+{
+ DrawTextWindow(gMenuWindowPtr, left, top, right, bottom);
+}
+
+void sub_8071F40(const u8 *str)
+{
+ MenuDrawTextWindow(2, 14, 28, 19);
+ MenuPrint(str, 3, 15);
+}
+
+void sub_8071F60(u8 a1, u8 a2, u8 a3)
+{
+ sub_8003490(gMenuWindowPtr, a1, gMenuTextTileOffset, a2, a3);
+}
+
+u16 unref_sub_8071F98(u8 x, u8 y)
+{
+ return GetWindowTilemapEntry(gMenuWindowPtr, x, y);
+}
+
+void unref_sub_8071FBC(u16 a1, u8 a2, u8 a3, u8 a4, u8 a5)
+{
+ DrawWindowRect(gMenuWindowPtr, a1, a2, a3, a4, a5);
+}
+
+void MenuDisplayMessageBox(void)
+{
+ DisplayMessageBox(gMenuWindowPtr);
+}
+
+void MenuPrintMessage(const u8 *str, u8 left, u8 top)
+{
+ sub_8002EB0(gMenuWindowPtr, str, gMenuTextTileOffset, left, top);
+}
+
+void MenuPrintMessageDefaultCoords(const u8 *str)
+{
+ sub_8002EB0(gMenuWindowPtr, str, gMenuTextTileOffset, 2, 15);
+}
+
+void MenuSetText(const u8 *str)
+{
+ sub_8002E90(gMenuWindowPtr, str);
+}
+
+u8 MenuUpdateWindowText(void)
+{
+ return sub_80035AC(gMenuWindowPtr);
+}
+
+u8 unref_sub_8072098(void)
+{
+ return sub_8003418(gMenuWindowPtr);
+}
+
+void sub_80720B0(void)
+{
+ ClearWindowTextLines(gMenuWindowPtr);
+}
+
+u8 MoveMenuCursor(s8 delta)
+{
+ s32 newPos = gMenu.cursorPos + delta;
+
+ if (newPos < gMenu.minCursorPos)
+ gMenu.cursorPos = gMenu.maxCursorPos;
+ else if (newPos > gMenu.maxCursorPos)
+ gMenu.cursorPos = gMenu.minCursorPos;
+ else
+ gMenu.cursorPos += delta;
+
+ RedrawMenuCursor(gMenu.left, 2 * gMenu.cursorPos + gMenu.top);
+ return gMenu.cursorPos;
+}
+
+u8 MoveMenuCursorNoWrap(s8 delta)
+{
+ s32 newPos = gMenu.cursorPos + delta;
+
+ if (newPos < gMenu.minCursorPos)
+ gMenu.cursorPos = gMenu.minCursorPos;
+ else if (newPos > gMenu.maxCursorPos)
+ gMenu.cursorPos = gMenu.maxCursorPos;
+ else
+ gMenu.cursorPos += delta;
+
+ RedrawMenuCursor(gMenu.left, 2 * gMenu.cursorPos + gMenu.top);
+ return gMenu.cursorPos;
+}
+
+u8 GetMenuCursorPos(void)
+{
+ return gMenu.cursorPos;
+}
+
+s8 ProcessMenuInput(void)
+{
+ if (gMain.newKeys & A_BUTTON)
+ {
+ PlaySE(SE_SELECT);
+ if (gMenu.menu_field_7)
+ HandleDestroyMenuCursors();
+ return gMenu.cursorPos;
+ }
+
+ if (gMain.newKeys & B_BUTTON)
+ {
+ if (gMenu.menu_field_7)
+ HandleDestroyMenuCursors();
+ return -1;
+ }
+
+ if (gMain.newKeys & DPAD_UP)
+ {
+ PlaySE(SE_SELECT);
+ MoveMenuCursor(-1);
+ return -2;
+ }
+ else if (gMain.newKeys & DPAD_DOWN)
+ {
+ PlaySE(SE_SELECT);
+ MoveMenuCursor(1);
+ return -2;
+ }
+
+ return -2;
+}
+
+s8 ProcessMenuInputNoWrap(void)
+{
+ u8 cursorPos = gMenu.cursorPos;
+
+ if (gMain.newKeys & A_BUTTON)
+ {
+ PlaySE(SE_SELECT);
+ if (gMenu.menu_field_7)
+ HandleDestroyMenuCursors();
+ return gMenu.cursorPos;
+ }
+
+ if (gMain.newKeys & B_BUTTON)
+ {
+ if (gMenu.menu_field_7)
+ HandleDestroyMenuCursors();
+ return -1;
+ }
+
+ if (gMain.newKeys & DPAD_UP)
+ {
+ if (cursorPos != MoveMenuCursorNoWrap(-1))
+ PlaySE(SE_SELECT);
+ return -2;
+ }
+ else if (gMain.newKeys & DPAD_DOWN)
+ {
+ if (cursorPos != MoveMenuCursorNoWrap(1))
+ PlaySE(SE_SELECT);
+ return -2;
+ }
+
+ MoveMenuCursorNoWrap(0);
+ return -2;
+}
+
+u8 MoveMenuCursor3(s8 delta)
+{
+ u8 menuHeight = (gMenu.maxCursorPos + 1) >> 1;
+ s32 newPos = gMenu.cursorPos + delta;
+
+ if (newPos < gMenu.minCursorPos)
+ gMenu.cursorPos = gMenu.maxCursorPos;
+ else if (newPos > gMenu.maxCursorPos)
+ gMenu.cursorPos = gMenu.minCursorPos;
+ else
+ gMenu.cursorPos += delta;
+
+ RedrawMenuCursor(
+ 6 * (gMenu.cursorPos / menuHeight) + gMenu.left,
+ 2 * (gMenu.cursorPos % menuHeight) + gMenu.top);
+
+ return gMenu.cursorPos;
+}
+
+u8 MoveMenuCursor4(s8 delta)
+{
+ if (gMenu.cursorPos + delta <= gMenu.maxCursorPos)
+ {
+ if (sub_80723D4() == TRUE)
+ return gMenu.cursorPos;
+ }
+ else
+ {
+ return gMenu.cursorPos;
+ }
+
+ gMenu.cursorPos += delta;
+
+ if ((gMenu.maxCursorPos + 1) / gMenu.width == 0)
+ RedrawMenuCursor(
+ gMenu.left + gMenu.columnXCoords[gMenu.cursorPos % gMenu.width],
+ 2 * ((gMenu.cursorPos / gMenu.width) % gMenu.height) + gMenu.top);
+ else
+ RedrawMenuCursor(
+ gMenu.left + gMenu.columnXCoords[gMenu.cursorPos % gMenu.width],
+ 2 * (gMenu.cursorPos / gMenu.width) + gMenu.top);
+
+ return gMenu.cursorPos;
+}
+
+static bool8 sub_80723D4(void)
+{
+ if ((gMain.newKeys & DPAD_UP) && gMenu.cursorPos < gMenu.width)
+ return TRUE;
+
+ if ((gMain.newKeys & DPAD_DOWN) && gMenu.cursorPos >= (gMenu.maxCursorPos + 1) - gMenu.width)
+ return TRUE;
+
+ if ((gMain.newKeys & DPAD_LEFT)
+ && ((gMenu.cursorPos - (gMenu.cursorPos % gMenu.width)) % gMenu.width == 1 // always false
+ || gMenu.cursorPos == 0
+ || gMenu.cursorPos % gMenu.width == 0))
+ return TRUE;
+
+ if ((gMain.newKeys & DPAD_RIGHT) && gMenu.cursorPos % gMenu.width == gMenu.width - 1)
+ return TRUE;
+
+ return FALSE;
+}
+
+static u8 sub_8072484(u8 a1, u8 a2, u8 menuItemCount, u8 a4, u8 width, u8 a6, u32 a7)
+{
+ u8 v7;
+
+ gMenu.width = width;
+ gMenu.height = menuItemCount / width;
+ InitMenu(0, a1, a2, menuItemCount, a4, a6);
+ v7 = 0;
+ if (a7)
+ v7 = -1;
+ gMenu.menu_field_7 = v7;
+ return a4;
+}
+
+static u8 sub_80724F4(u8 left, u8 top, u8 menuItemCount, const struct MenuAction menuItems[], u8 columnCount)
+{
+ u8 i;
+ u8 maxWidth;
+ s32 height;
+
+ for (i = 0; i < 7; i++)
+ gMenu.columnXCoords[i] = 0;
+
+ maxWidth = 0;
+ for (i = 0; i < menuItemCount; i++)
+ {
+ u8 width = (sub_8072CA4(menuItems[i].text) + 7) / 8;
+
+ if (width > maxWidth)
+ maxWidth = width;
+ }
+
+ for (i = 1; i <= columnCount; i++)
+ gMenu.columnXCoords[i] = maxWidth;
+
+ for (i = 1; i <= columnCount; i++)
+ gMenu.columnXCoords[i] += 1 + gMenu.columnXCoords[i - 1];
+
+ gMenu.columnXCoords[columnCount]--;
+
+ if (!((menuItemCount / 2) < columnCount || (menuItemCount % 2 != 0))
+ || columnCount == 1
+ || columnCount == menuItemCount)
+ {
+ height = 2 * (menuItemCount / columnCount) + 1;
+ }
+ else
+ {
+ height = 2 * ((menuItemCount / columnCount) + 1) + 1;
+ }
+
+ {
+ // TODO: Make this code less hideous but still match the original asm.
+ u8 right;
+ u8 bottom;
+ u32 totalWidth;
+ register s32 val asm("r1");
+
+ val = (s8)top + height;
+ val = val << 24;
+ asm("" ::: "r3");
+ bottom = val >> 24;
+
+ totalWidth = (gMenu.columnXCoords[columnCount] + 1);
+ right = left + totalWidth;
+
+ MenuDrawTextWindow(left, top, right, bottom);
+ }
+
+ return maxWidth;
+}
+
+static void sub_8072620(u8 left, u8 top, u8 menuItemCount, const struct MenuAction menuItems[], u8 columnCount)
+{
+ u8 i;
+ u8 maxWidth;
+
+ for (i = 0; i < 7; i++)
+ gMenu.columnXCoords[i] = 0;
+
+ maxWidth = 0;
+ for (i = 0; i < menuItemCount; i++)
+ {
+ u8 width = (sub_8072CA4(menuItems[i].text) + 7) / 8;
+
+ if (width > maxWidth)
+ maxWidth = width;
+ }
+
+ for (i = 1; i <= columnCount; i++)
+ gMenu.columnXCoords[i] = maxWidth;
+
+ for (i = 1; i <= columnCount; i++)
+ gMenu.columnXCoords[i] += 1 + gMenu.columnXCoords[i - 1];
+
+ gMenu.columnXCoords[columnCount]--;
+
+ for (i = 0; i < columnCount; i++)
+ {
+ u8 row = 0;
+ u8 j;
+ for (j = 0; i + j < menuItemCount; j += columnCount, row++)
+ MenuPrint(menuItems[i + j].text, left + gMenu.columnXCoords[i % columnCount], top + 2 * row);
+ }
+}
+
+void sub_807274C(u8 left, u8 top, u8 menuItemCount, u8 a4, const struct MenuAction menuItems[], u8 columnCount, u32 a7)
+{
+ u8 maxWidth = sub_80724F4(left, top, menuItemCount, menuItems, columnCount);
+
+ sub_8072484(left + 1, top + 1, menuItemCount, a4, columnCount, maxWidth, a7);
+ sub_8072620(left + 1, top + 1, menuItemCount, menuItems, columnCount);
+}
+
+s8 sub_80727CC(void)
+{
+ if (gMain.newKeys & A_BUTTON)
+ {
+ if (gMenu.menu_field_7)
+ HandleDestroyMenuCursors();
+ PlaySE(SE_SELECT);
+ return GetMenuCursorPos();
+ }
+
+ if (gMain.newKeys & B_BUTTON)
+ {
+ if (gMenu.menu_field_7)
+ HandleDestroyMenuCursors();
+ return -1;
+ }
+
+ if (gMain.newKeys & DPAD_UP)
+ {
+ PlaySE(SE_SELECT);
+ MoveMenuCursor4(-gMenu.width);
+ return -2;
+ }
+ else if (gMain.newKeys & DPAD_DOWN)
+ {
+ PlaySE(SE_SELECT);
+ MoveMenuCursor4(gMenu.width);
+ return -2;
+ }
+ else if (gMain.newKeys & DPAD_LEFT)
+ {
+ PlaySE(SE_SELECT);
+ MoveMenuCursor4(-1);
+ return -2;
+ }
+ else if (gMain.newKeys & DPAD_RIGHT)
+ {
+ PlaySE(SE_SELECT);
+ MoveMenuCursor4(1);
+ return -2;
+ }
+
+ return -2;
+}
+
+u8 sub_807288C(u8 column)
+{
+ return gMenu.columnXCoords[column];
+}
+
+void PrintMenuItems(u8 left, u8 top, u8 menuItemCount, const struct MenuAction menuItems[])
+{
+ u8 i;
+
+ for (i = 0; i < menuItemCount; i++)
+ MenuPrint(menuItems[i].text, left, top + 2 * i);
+}
+
+void PrintMenuItemsReordered(u8 left, u8 top, u8 menuItemCount, const struct MenuAction2 menuItems[], const u8 *order)
+{
+ u8 i;
+
+ for (i = 0; i < menuItemCount; i++)
+ MenuPrint(menuItems[order[i]].text, left, top + 2 * i);
+}
+
+void InitYesNoMenu(u8 left, u8 top, u8 a3)
+{
+ PrintMenuItems(left + 1, top + 1, 2, gMenuYesNoItems);
+ InitMenu(0, left + 1, top + 1, 2, 0, a3);
+}
+
+void DisplayYesNoMenu(u8 left, u8 top, u32 a3)
+{
+ MenuDrawTextWindow(left, top, left + 6, top + 5);
+ InitYesNoMenu(left, top, 5);
+ gMenu.menu_field_7 = a3 ? -1 : 0;
+}
+
+s8 ProcessMenuInputNoWrap_(void)
+{
+ return ProcessMenuInputNoWrap();
+}
+
+u8 MenuPrint_PixelCoords(const u8 *text, u8 left, u16 top, u8 a4)
+{
+ return sub_8004D04(gMenuWindowPtr, text, gMenuTextTileOffset, left, top, a4);
+}
+
+u8 sub_8072A18(const u8 *text, u8 left, u16 top, u8 width, u32 a5)
+{
+ return sub_8004FD0(gMenuWindowPtr, 0, text, gMenuTextTileOffset, left, top, width, a5);
+}
+
+u8 unref_sub_8072A5C(u8 *dest, u8 *src, u8 left, u16 top, u8 width, u32 a6)
+{
+ return sub_8004FD0(gMenuWindowPtr, dest, src, gMenuTextTileOffset, left, top, width, a6);
+}
+
+#if ENGLISH
+int sub_8072AB0(const u8 *str, u8 left, u16 top, u8 width, u8 height, u32 a6)
+{
+ u8 newlineCount = sub_8004FD0(gMenuWindowPtr, NULL, str, gMenuTextTileOffset, left, top, width, a6);
+
+ left /= 8;
+ top /= 8;
+ width = (width + 7) / 8;
+ height = (height + 7) / 8;
+
+ if (newlineCount < height)
+ MenuFillWindowRectWithBlankTile(left, top + 2 * newlineCount, left + width - 1, height + top - 1);
+}
+#elif GERMAN
+__attribute__((naked))
+int sub_8072AB0(const u8 *str, u8 left, u16 top, u8 width, u8 height, u32 a6)
+{
+ asm(".syntax unified\n\
+ push {r4-r7,lr}\n\
+ sub sp, 0x10\n\
+ mov r12, r0\n\
+ ldr r0, [sp, 0x24]\n\
+ ldr r4, [sp, 0x28]\n\
+ str r4, [sp, 0xC]\n\
+ lsls r1, 24\n\
+ lsrs r5, r1, 24\n\
+ lsls r2, 16\n\
+ lsrs r4, r2, 16\n\
+ lsls r3, 24\n\
+ lsrs r6, r3, 24\n\
+ lsls r0, 24\n\
+ lsrs r7, r0, 24\n\
+ ldr r0, _08072AF8 @ =gMenuWindowPtr\n\
+ ldr r0, [r0]\n\
+ ldr r1, _08072AFC @ =gMenuTextTileOffset\n\
+ ldrh r3, [r1]\n\
+ str r5, [sp]\n\
+ str r4, [sp, 0x4]\n\
+ str r6, [sp, 0x8]\n\
+ movs r1, 0\n\
+ mov r2, r12\n\
+ bl sub_8004FD0\n\
+ adds r1, r0, 0\n\
+ lsls r1, 24\n\
+ lsrs r2, r1, 24\n\
+ movs r3, 0x7\n\
+ ands r3, r5\n\
+ cmp r3, 0\n\
+ bne _08072B00\n\
+ adds r1, r6, 0x7\n\
+ asrs r1, 3\n\
+ subs r1, 0x1\n\
+ b _08072B0C\n\
+ .align 2, 0\n\
+_08072AF8: .4byte gMenuWindowPtr\n\
+_08072AFC: .4byte gMenuTextTileOffset\n\
+_08072B00:\n\
+ adds r3, r6, r3\n\
+ subs r1, r3, 0x1\n\
+ cmp r1, 0\n\
+ bge _08072B0A\n\
+ adds r1, r3, 0x6\n\
+_08072B0A:\n\
+ asrs r1, 3\n\
+_08072B0C:\n\
+ lsls r1, 24\n\
+ lsrs r1, 24\n\
+ adds r6, r1, 0\n\
+ lsrs r5, 3\n\
+ adds r1, r7, 0x7\n\
+ asrs r1, 3\n\
+ lsls r1, 24\n\
+ lsrs r7, r1, 24\n\
+ lsrs r4, 3\n\
+ cmp r2, r7\n\
+ bcs _08072B3E\n\
+ lsls r1, r2, 1\n\
+ adds r1, r4, r1\n\
+ lsls r1, 24\n\
+ lsrs r1, 24\n\
+ adds r2, r5, r6\n\
+ lsls r2, 24\n\
+ lsrs r2, 24\n\
+ adds r3, r7, r4\n\
+ subs r3, 0x1\n\
+ lsls r3, 24\n\
+ lsrs r3, 24\n\
+ adds r0, r5, 0\n\
+ bl MenuFillWindowRectWithBlankTile\n\
+_08072B3E:\n\
+ add sp, 0x10\n\
+ pop {r4-r7}\n\
+ pop {r1}\n\
+ bx r1\n\
+ .syntax divided\n");
+}
+#endif
+
+void MenuPrint_RightAligned(const u8 *str, u8 left, u8 top)
+{
+ sub_8004D38(gMenuWindowPtr, str, gMenuTextTileOffset, left, top);
+}
+
+void sub_8072B80(const u8 *a1, u8 a2, u8 a3, const u8 *a4)
+{
+ u8 buffer[64];
+ u8 width = GetStringWidth(gMenuWindowPtr, a4);
+ AlignString(gMenuWindowPtr, buffer, a1, width, 1);
+ sub_8003460(gMenuWindowPtr, buffer, gMenuTextTileOffset, a2, a3);
+}
+
+void sub_8072BD8(const u8 *a1, u8 a2, u8 a3, u16 a4)
+{
+ sub_8004DB0(gMenuWindowPtr, a1, gMenuTextTileOffset, a2, a3, a4);
+}
+
+u8 *sub_8072C14(u8 *a1, s32 a2, u8 a3, u8 a4)
+{
+ return AlignInt1(gMenuWindowPtr, a1, a2, a3, a4);
+}
+
+u8 *sub_8072C44(u8 *a1, s32 a2, u8 a3, u8 a4)
+{
+ return AlignInt2(gMenuWindowPtr, a1, a2, a3, a4);
+}
+
+u8 *sub_8072C74(u8 *a1, const u8 *a2, u8 a3, u8 a4)
+{
+ return AlignString(gMenuWindowPtr, a1, a2, a3, a4);
+}
+
+u8 sub_8072CA4(const u8 *str)
+{
+ return GetStringWidth(gMenuWindowPtr, str);
+}
+
+u8 sub_8072CBC()
+{
+ return sub_8004E24(gMenuWindowPtr);
+}
+
+void sub_8072CD4(u8 *a1, u8 *a2, u8 *a3)
+{
+ sub_8004E28(gMenuWindowPtr, a1, a2, a3);
+}
+
+u32 MenuUpdateWindowText_OverrideLineLength(u8 lineLength)
+{
+ return sub_80037C8(gMenuWindowPtr, lineLength);
+}
+
+struct Window *unref_sub_8072D0C(void)
+{
+ return gMenuWindowPtr;
+}
+
+static void sub_8072D18(u8 a1, u8 a2)
+{
+ sub_814A5C0(a1, 0xFFFF, 12, 11679, 8 * a2);
+}
+
+u8 InitMenu(u8 cursorSubpriority, u8 left, u8 top, u8 numChoices, u8 cursorPos, u8 cursorWidth)
+{
+ s32 pos;
+
+ if (cursorWidth)
+ sub_8072D18(cursorSubpriority, cursorWidth);
+
+ gMenu.left = left - 1;
+ gMenu.top = top;
+ gMenu.minCursorPos = 0;
+ gMenu.maxCursorPos = numChoices - 1;
+ gMenu.menu_field_7 = 0;
+
+ pos = cursorPos;
+
+ if (pos < 0 || pos > gMenu.maxCursorPos)
+ pos = 0;
+
+ gMenu.cursorPos = pos;
+ MoveMenuCursor(0);
+
+ return pos;
+}
+
+void RedrawMenuCursor(u8 a1, u8 a2)
+{
+ sub_814A880((a1 + 1) * 8, 8 * a2);
+}
+
+void unref_sub_8072DC0()
+{
+ sub_814A904();
+}
+
+void sub_8072DCC(u8 a1)
+{
+ sub_814A958(a1);
+}
+
+void sub_8072DDC(u8 a1)
+{
+ sub_8072DCC(8 * a1);
+}
+
+void HandleDestroyMenuCursors(void)
+{
+ DestroyMenuCursor();
+}
+
+#if GERMAN
+void de_sub_8073110(u8 * buffer, u8 * name) {
+ u8 * ptr, *ptr2, *ptr3;
+
+ ptr2 = buffer;
+ ptr = &gStringVar1[1 + StringLengthN(gStringVar1, 256)];
+ ptr3 = ptr;
+
+ for (;;)
+ {
+ if (*ptr2 == EOS)
+ break;
+
+ if (*ptr2 == 0xFD)
+ {
+
+ *ptr3 = EOS;
+ ptr2 += 2;
+
+ StringAppend(ptr, name);
+ StringAppend(ptr, ptr2);
+
+ buffer[0] = EOS;
+ StringAppend(buffer, ptr);
+ break;
+ }
+
+ *ptr3 = *ptr2;
+ ptr2 += 1;
+ ptr3 += 1;
+ }
+}
+
+u8 *de_sub_8073174(u8 *name, const u8 *format) {
+ u32 offset;
+ u8 *ptr;
+
+ offset = StringLengthN(gStringVar2, 0x100);
+ ptr = &gStringVar2[1 + offset];
+
+ StringCopy(ptr, format);
+
+ de_sub_8073110(ptr, name);
+
+ return StringCopy(name, ptr);
+}
+#endif
diff --git a/src/engine/menu_cursor.c b/src/engine/menu_cursor.c
new file mode 100644
index 000000000..d43be2a2f
--- /dev/null
+++ b/src/engine/menu_cursor.c
@@ -0,0 +1,793 @@
+#include "global.h"
+#include "menu_cursor.h"
+#include "palette.h"
+#include "sprite.h"
+
+extern const struct SpriteSheet gUnknown_0842F140[];
+extern const struct SpriteSheet gUnknown_0842F1C0[];
+extern const struct SpritePalette gUnknown_0842F240;
+extern const struct SpritePalette gUnknown_0842F248;
+extern const struct SpriteTemplate gSpriteTemplate_842F250[];
+extern const struct SpriteTemplate gSpriteTemplate_842F298[];
+
+extern struct Subsprite *const gUnknown_0842F5BC[];
+
+extern const struct SubspriteTable gSubspriteTables_842F5C0[];
+extern const struct SubspriteTable gSubspriteTables_842F6C0[];
+extern const struct SubspriteTable gUnknown_0842F758[];
+
+extern const struct Subsprite gUnknown_0842F780;
+extern const struct Subsprite gUnknown_0842F788;
+extern const struct Subsprite gUnknown_0842F790;
+
+extern u16 gUnknown_0203A360[];
+
+EWRAM_DATA struct Subsprite gMenuCursorSubsprites[10] = {0};
+EWRAM_DATA u8 gUnknown_0203A3D0 = 0;
+EWRAM_DATA u8 gUnknown_0203A3D1 = 0;
+EWRAM_DATA u8 gUnknown_0203A3D2 = 0;
+EWRAM_DATA u8 gUnknown_0203A3D3 = 0;
+EWRAM_DATA u8 gUnknown_0203A3D4 = 0;
+
+void sub_814A590(void)
+{
+ gUnknown_0203A3D0 = 0x40;
+ gUnknown_0203A3D1 = 0x40;
+ gUnknown_0203A3D2 = 0x40;
+ gUnknown_0203A3D3 = 0;
+ gUnknown_0203A3D4 = 0;
+}
+
+u8 sub_814A5C0(u8 a1, u16 a2, u8 a3, u16 a4, u8 a5)
+{
+ int v9;
+ struct Sprite *v10;
+
+ if (gUnknown_0203A3D0 != 0x40 || gUnknown_0203A3D1 != 0x40)
+ DestroyMenuCursor();
+
+ v9 = 1;
+ if (a2 == 0xFFFF)
+ {
+ gUnknown_0203A360[a3 & 0xF] = a4;
+ if (LoadSpritePalette(&gUnknown_0842F240) != 0xFF)
+ {
+ a2 = 0xFFF0;
+ v9 = 0;
+ }
+ }
+
+ LoadSpriteSheetDeferred(&gUnknown_0842F140[a3 & 0xF]);
+ gUnknown_0203A3D0 = CreateSprite(&gSpriteTemplate_842F250[v9], 0, 0xA0, a1);
+ gUnknown_0203A3D1 = CreateSprite(&gSpriteTemplate_842F250[2], 0, 0xA0, a1);
+ if (gUnknown_0203A3D0 != 0x40)
+ {
+ v10 = &gSprites[gUnknown_0203A3D0];
+ if (a2 == 0xFFFF)
+ v10->oam.paletteNum = 0;
+ else
+ v10->oam.paletteNum = IndexOfSpritePaletteTag(a2);
+ }
+ if (gUnknown_0203A3D1 != 0x40)
+ {
+ v10 = &gSprites[gUnknown_0203A3D1];
+ if (a2 == 0xFFFF)
+ v10->oam.paletteNum = 0;
+ else
+ v10->oam.paletteNum = IndexOfSpritePaletteTag(a2);
+
+ if (!(REG_DISPCNT & (DISPCNT_WIN0_ON | DISPCNT_WIN1_ON)))
+ *(u8 *)(REG_ADDR_WINOUT) |= 0x1F;
+ gUnknown_0203A3D3 = REG_DISPCNT >> 0xF;
+ gUnknown_0203A3D4 = *(u8 *)(REG_BASE + REG_OFFSET_WINOUT + 1);
+ REG_DISPCNT |= DISPCNT_OBJWIN_ON;
+ *(u8 *)(REG_ADDR_WINOUT + 1) = 0x10;
+ }
+ sub_814A958(a5);
+ return gUnknown_0203A3D0;
+}
+
+u8 sub_814A758(u8 a1, u8 a2, u8 a3, u8 a4)
+{
+ u8 result;
+ struct Sprite *spr;
+
+ result = sub_814A5C0(a1, 0, a3, 0, a4);
+ if (result != 0x40)
+ {
+ spr = &gSprites[gUnknown_0203A3D0];
+ spr->oam.paletteNum = a2;
+ }
+ return result;
+}
+
+u8 unref_sub_814A7AC(u8 a1, u16 a2, u8 a3)
+{
+ u16 i;
+ u8 val1 = 0;
+ u16 val2 = 0xF;
+
+ for (i = 0; i <= 0xFF; i++)
+ {
+ if (gPlttBufferUnfaded[i] == a2)
+ {
+ val1 = (u8)(i >> 4);
+ val2 = i & 0xF;
+ }
+ }
+
+ return sub_814A758(a1, val1, val2, a3);
+}
+
+void DestroyMenuCursor(void)
+{
+ if (gUnknown_0203A3D0 != 0x40)
+ {
+ LoadTilesForSpriteSheet(&gUnknown_0842F140[0]);
+ DestroySpriteAndFreeResources(&gSprites[gUnknown_0203A3D0]);
+ gUnknown_0203A3D0 = 0x40;
+ }
+
+ if (gUnknown_0203A3D1 != 0x40)
+ {
+ DestroySpriteAndFreeResources(&gSprites[gUnknown_0203A3D1]);
+ gUnknown_0203A3D1 = 0x40;
+ if (!gUnknown_0203A3D3)
+ REG_DISPCNT &= ~DISPCNT_OBJWIN_ON;
+ *(u8 *)(REG_BASE + REG_OFFSET_WINOUT + 1) = gUnknown_0203A3D4;
+ }
+
+ return;
+}
+
+void sub_814A880(u8 a1, u8 a2)
+{
+ struct Sprite *spr;
+
+ if (gUnknown_0203A3D0 != 0x40)
+ {
+ spr = &gSprites[gUnknown_0203A3D0];
+ spr->invisible = 0;
+ spr->centerToCornerVecX = 0;
+ spr->centerToCornerVecY = 0;
+ spr->pos1.x = a1;
+ spr->pos1.y = a2;
+ }
+
+ if (gUnknown_0203A3D1 != 0x40)
+ {
+ spr = &gSprites[gUnknown_0203A3D1];
+ spr->invisible = 0;
+ spr->centerToCornerVecX = 0;
+ spr->centerToCornerVecY = 0;
+ spr->pos1.x = a1;
+ spr->pos1.y = a2;
+ }
+
+ return;
+}
+
+void sub_814A904(void)
+{
+ struct Sprite *spr;
+
+ if (gUnknown_0203A3D0 != 0x40)
+ {
+ spr = &gSprites[gUnknown_0203A3D0];
+ spr->invisible = 1;
+ }
+
+ if (gUnknown_0203A3D1 != 0x40)
+ {
+ spr = &gSprites[gUnknown_0203A3D1];
+ spr->invisible = 1;
+ }
+
+ return;
+}
+
+#if ENGLISH
+#ifdef NONMATCHING
+// Fix pls
+void sub_814A958(u8 a)
+{
+ u8 r7;
+ struct Subsprite *r4 = &gMenuCursorSubsprites[0];
+ s16 r2 = -1;
+ s32 _a = a;
+ s16 r5;
+ s16 i;
+
+ *r4 = (struct Subsprite){.x = 0, .y = 0, .shape = 2, .size = 0, .tileOffset = 0, .priority = 0};
+ r4->x = r2;
+ r4++;
+ r7 = 1;
+ r2 = 1;
+ r5 = a;
+ i = r5;
+ while ((i -= r2) >= 8)
+ {
+ if (i > 0x1F)
+ {
+ *r4 = gUnknown_0842F780;
+ r4->x = r2;
+ r2 += 32;
+ r5 = a;
+ }
+ //_0814A9D4
+ else
+ {
+ r5 = a;
+ if (_a > 0x27 && i > 8)
+ {
+ *r4 = gUnknown_0842F780;
+ r4->x = (r2 - 32) + (i & ~7);
+ r2 += i & 0x18;
+ }
+ //_0814AA0A
+ else
+ {
+ *r4 = gUnknown_0842F788;
+ r4->x = r2;
+ r2 += 8;
+ }
+ }
+ //_0814AA20
+ r4++;
+ r7++;
+ i = r5;
+ }
+ //_0814AA3A
+ *r4 = gUnknown_0842F790;
+ r4->x = r2 - 7 + i;
+ r7++;
+ if (gUnknown_0203A3D0 != 64)
+ SetSubspriteTables(&gSprites[gUnknown_0203A3D0], gSubspriteTables_842F5C0 + r7);
+ if (gUnknown_0203A3D1 != 64)
+ SetSubspriteTables(&gSprites[gUnknown_0203A3D1], gSubspriteTables_842F5C0 + r7);
+}
+#else
+__attribute__((naked))
+void sub_814A958(u8 a1)
+{
+ asm(".syntax unified\n\
+ push {r4-r7,lr}\n\
+ mov r7, r10\n\
+ mov r6, r9\n\
+ mov r5, r8\n\
+ push {r5-r7}\n\
+ sub sp, 0x4\n\
+ lsls r0, 24\n\
+ ldr r4, _0814A9C4\n\
+ ldr r2, _0814A9C8\n\
+ lsrs r0, 24\n\
+ str r0, [sp]\n\
+ movs r0, 0\n\
+ movs r1, 0\n\
+ movs r1, 0x2\n\
+ str r0, [r4]\n\
+ str r1, [r4, 0x4]\n\
+ strh r2, [r4]\n\
+ adds r4, 0x8\n\
+ movs r7, 0x1\n\
+ movs r2, 0x1\n\
+ ldr r1, [sp]\n\
+ subs r0, r1, 0x1\n\
+ lsls r0, 16\n\
+ lsrs r3, r0, 16\n\
+ asrs r0, 16\n\
+ cmp r0, 0x7\n\
+ ble _0814AA3A\n\
+ ldr r0, _0814A9CC\n\
+ mov r12, r0\n\
+ mov r8, r1\n\
+ movs r1, 0x8\n\
+ negs r1, r1\n\
+ mov r10, r1\n\
+ ldr r5, _0814A9D0\n\
+ mov r9, r5\n\
+_0814A99E:\n\
+ lsls r0, r3, 16\n\
+ asrs r3, r0, 16\n\
+ cmp r3, 0x1F\n\
+ ble _0814A9D4\n\
+ mov r6, r12\n\
+ ldr r0, [r6]\n\
+ ldr r1, [r6, 0x4]\n\
+ str r0, [r4]\n\
+ str r1, [r4, 0x4]\n\
+ strh r2, [r4]\n\
+ lsls r0, r2, 16\n\
+ movs r1, 0x80\n\
+ lsls r1, 14\n\
+ adds r0, r1\n\
+ lsrs r2, r0, 16\n\
+ ldr r3, [sp]\n\
+ lsls r5, r3, 16\n\
+ b _0814AA20\n\
+ .align 2, 0\n\
+_0814A9C4: .4byte gMenuCursorSubsprites\n\
+_0814A9C8: .4byte 0x0000ffff\n\
+_0814A9CC: .4byte gUnknown_0842F780\n\
+_0814A9D0: .4byte gUnknown_0842F788\n\
+_0814A9D4:\n\
+ ldr r6, [sp]\n\
+ lsls r5, r6, 16\n\
+ mov r0, r8\n\
+ cmp r0, 0x27\n\
+ ble _0814AA0A\n\
+ cmp r3, 0x8\n\
+ ble _0814AA0A\n\
+ mov r6, r12\n\
+ ldr r0, [r6]\n\
+ ldr r1, [r6, 0x4]\n\
+ str r0, [r4]\n\
+ str r1, [r4, 0x4]\n\
+ lsls r1, r2, 16\n\
+ asrs r1, 16\n\
+ adds r2, r1, 0\n\
+ subs r2, 0x20\n\
+ adds r0, r3, 0\n\
+ mov r6, r10\n\
+ ands r0, r6\n\
+ adds r2, r0\n\
+ strh r2, [r4]\n\
+ movs r0, 0x18\n\
+ ands r0, r3\n\
+ adds r1, r0\n\
+ lsls r1, 16\n\
+ lsrs r2, r1, 16\n\
+ b _0814AA20\n\
+_0814AA0A:\n\
+ mov r3, r9\n\
+ ldr r0, [r3]\n\
+ ldr r1, [r3, 0x4]\n\
+ str r0, [r4]\n\
+ str r1, [r4, 0x4]\n\
+ strh r2, [r4]\n\
+ lsls r0, r2, 16\n\
+ movs r6, 0x80\n\
+ lsls r6, 12\n\
+ adds r0, r6\n\
+ lsrs r2, r0, 16\n\
+_0814AA20:\n\
+ adds r4, 0x8\n\
+ adds r0, r7, 0x1\n\
+ lsls r0, 24\n\
+ lsrs r7, r0, 24\n\
+ asrs r1, r5, 16\n\
+ lsls r0, r2, 16\n\
+ asrs r0, 16\n\
+ subs r1, r0\n\
+ lsls r1, 16\n\
+ lsrs r3, r1, 16\n\
+ asrs r1, 16\n\
+ cmp r1, 0x7\n\
+ bgt _0814A99E\n\
+_0814AA3A:\n\
+ ldr r5, _0814AAA8\n\
+ ldr r0, [r5]\n\
+ ldr r1, [r5, 0x4]\n\
+ str r0, [r4]\n\
+ str r1, [r4, 0x4]\n\
+ lsls r1, r2, 16\n\
+ asrs r1, 16\n\
+ subs r1, 0x7\n\
+ lsls r0, r3, 16\n\
+ asrs r0, 16\n\
+ adds r0, r1\n\
+ strh r0, [r4]\n\
+ adds r0, r7, 0x1\n\
+ lsls r0, 24\n\
+ lsrs r7, r0, 24\n\
+ ldr r6, _0814AAAC\n\
+ ldrb r0, [r6]\n\
+ cmp r0, 0x40\n\
+ beq _0814AA78\n\
+ adds r1, r0, 0\n\
+ lsls r0, r1, 4\n\
+ adds r0, r1\n\
+ lsls r0, 2\n\
+ ldr r1, _0814AAB0\n\
+ adds r2, r0, r1\n\
+ lsls r1, r7, 3\n\
+ ldr r0, _0814AAB4\n\
+ adds r1, r0\n\
+ adds r0, r2, 0\n\
+ bl SetSubspriteTables\n\
+_0814AA78:\n\
+ ldr r1, _0814AAB8\n\
+ ldrb r0, [r1]\n\
+ cmp r0, 0x40\n\
+ beq _0814AA98\n\
+ adds r1, r0, 0\n\
+ lsls r0, r1, 4\n\
+ adds r0, r1\n\
+ lsls r0, 2\n\
+ ldr r1, _0814AAB0\n\
+ adds r2, r0, r1\n\
+ lsls r1, r7, 3\n\
+ ldr r0, _0814AAB4\n\
+ adds r1, r0\n\
+ adds r0, r2, 0\n\
+ bl SetSubspriteTables\n\
+_0814AA98:\n\
+ add sp, 0x4\n\
+ pop {r3-r5}\n\
+ mov r8, r3\n\
+ mov r9, r4\n\
+ mov r10, r5\n\
+ pop {r4-r7}\n\
+ pop {r0}\n\
+ bx r0\n\
+ .align 2, 0\n\
+_0814AAA8: .4byte gUnknown_0842F790\n\
+_0814AAAC: .4byte gUnknown_0203A3D0\n\
+_0814AAB0: .4byte gSprites\n\
+_0814AAB4: .4byte gSubspriteTables_842F5C0\n\
+_0814AAB8: .4byte gUnknown_0203A3D1\n\
+ .syntax divided\n");
+}
+#endif
+#elif GERMAN
+__attribute__((naked))
+void sub_814A958(u8 a1)
+{
+ asm(".syntax unified\n\
+ push {r4-r7,lr}\n\
+ mov r7, r10\n\
+ mov r6, r9\n\
+ mov r5, r8\n\
+ push {r5-r7}\n\
+ sub sp, 0x4\n\
+ lsls r0, 24\n\
+ ldr r4, _0814A9C4 @ =gMenuCursorSubsprites\n\
+ ldr r2, _0814A9C8 @ =0x0000ffff\n\
+ lsrs r0, 24\n\
+ str r0, [sp]\n\
+ ldr r0, _0814A9CC @ =gUnknown_0842F780\n\
+ ldr r1, [r0, 0x4]\n\
+ ldr r0, [r0]\n\
+ str r0, [r4]\n\
+ str r1, [r4, 0x4]\n\
+ strh r2, [r4]\n\
+ adds r4, 0x8\n\
+ movs r7, 0x1\n\
+ movs r2, 0x1\n\
+ ldr r1, [sp]\n\
+ subs r0, r1, 0x1\n\
+ lsls r0, 16\n\
+ lsrs r3, r0, 16\n\
+ asrs r0, 16\n\
+ cmp r0, 0x7\n\
+ ble _0814AA3E\n\
+ ldr r0, _0814A9D0 @ =gUnknown_0842F788\n\
+ mov r12, r0\n\
+ mov r8, r1\n\
+ movs r1, 0x8\n\
+ negs r1, r1\n\
+ mov r10, r1\n\
+ ldr r5, _0814A9D4 @ =gUnknown_0842F790\n\
+ mov r9, r5\n\
+_0814A99E:\n\
+ lsls r0, r3, 16\n\
+ asrs r3, r0, 16\n\
+ cmp r3, 0x1F\n\
+ ble _0814A9D8\n\
+ mov r6, r12\n\
+ ldr r0, [r6]\n\
+ ldr r1, [r6, 0x4]\n\
+ str r0, [r4]\n\
+ str r1, [r4, 0x4]\n\
+ strh r2, [r4]\n\
+ lsls r0, r2, 16\n\
+ movs r1, 0x80\n\
+ lsls r1, 14\n\
+ adds r0, r1\n\
+ lsrs r2, r0, 16\n\
+ ldr r3, [sp]\n\
+ lsls r5, r3, 16\n\
+ b _0814AA24\n\
+ .align 2, 0\n\
+_0814A9C4: .4byte gMenuCursorSubsprites\n\
+_0814A9C8: .4byte 0x0000ffff\n\
+_0814A9CC: .4byte gUnknown_0842F780\n\
+_0814A9D0: .4byte gUnknown_0842F788\n\
+_0814A9D4: .4byte gUnknown_0842F790\n\
+_0814A9D8:\n\
+ ldr r6, [sp]\n\
+ lsls r5, r6, 16\n\
+ mov r0, r8\n\
+ cmp r0, 0x27\n\
+ ble _0814AA0E\n\
+ cmp r3, 0x8\n\
+ ble _0814AA0E\n\
+ mov r6, r12\n\
+ ldr r0, [r6]\n\
+ ldr r1, [r6, 0x4]\n\
+ str r0, [r4]\n\
+ str r1, [r4, 0x4]\n\
+ lsls r1, r2, 16\n\
+ asrs r1, 16\n\
+ adds r2, r1, 0\n\
+ subs r2, 0x20\n\
+ adds r0, r3, 0\n\
+ mov r6, r10\n\
+ ands r0, r6\n\
+ adds r2, r0\n\
+ strh r2, [r4]\n\
+ movs r0, 0x18\n\
+ ands r0, r3\n\
+ adds r1, r0\n\
+ lsls r1, 16\n\
+ lsrs r2, r1, 16\n\
+ b _0814AA24\n\
+_0814AA0E:\n\
+ mov r3, r9\n\
+ ldr r0, [r3]\n\
+ ldr r1, [r3, 0x4]\n\
+ str r0, [r4]\n\
+ str r1, [r4, 0x4]\n\
+ strh r2, [r4]\n\
+ lsls r0, r2, 16\n\
+ movs r6, 0x80\n\
+ lsls r6, 12\n\
+ adds r0, r6\n\
+ lsrs r2, r0, 16\n\
+_0814AA24:\n\
+ adds r4, 0x8\n\
+ adds r0, r7, 0x1\n\
+ lsls r0, 24\n\
+ lsrs r7, r0, 24\n\
+ asrs r1, r5, 16\n\
+ lsls r0, r2, 16\n\
+ asrs r0, 16\n\
+ subs r1, r0\n\
+ lsls r1, 16\n\
+ lsrs r3, r1, 16\n\
+ asrs r1, 16\n\
+ cmp r1, 0x7\n\
+ bgt _0814A99E\n\
+_0814AA3E:\n\
+ ldr r5, _0814AAAC @ =gUnknown_0842F798\n\
+ ldr r0, [r5]\n\
+ ldr r1, [r5, 0x4]\n\
+ str r0, [r4]\n\
+ str r1, [r4, 0x4]\n\
+ lsls r1, r2, 16\n\
+ asrs r1, 16\n\
+ subs r1, 0x7\n\
+ lsls r0, r3, 16\n\
+ asrs r0, 16\n\
+ adds r0, r1\n\
+ strh r0, [r4]\n\
+ adds r0, r7, 0x1\n\
+ lsls r0, 24\n\
+ lsrs r7, r0, 24\n\
+ ldr r6, _0814AAB0 @ =gUnknown_0203A3D0\n\
+ ldrb r0, [r6]\n\
+ cmp r0, 0x40\n\
+ beq _0814AA7C\n\
+ adds r1, r0, 0\n\
+ lsls r0, r1, 4\n\
+ adds r0, r1\n\
+ lsls r0, 2\n\
+ ldr r1, _0814AAB4 @ =gSprites\n\
+ adds r2, r0, r1\n\
+ lsls r1, r7, 3\n\
+ ldr r0, _0814AAB8 @ =gSubspriteTables_842F5C0\n\
+ adds r1, r0\n\
+ adds r0, r2, 0\n\
+ bl SetSubspriteTables\n\
+_0814AA7C:\n\
+ ldr r1, _0814AABC @ =gUnknown_0203A3D1\n\
+ ldrb r0, [r1]\n\
+ cmp r0, 0x40\n\
+ beq _0814AA9C\n\
+ adds r1, r0, 0\n\
+ lsls r0, r1, 4\n\
+ adds r0, r1\n\
+ lsls r0, 2\n\
+ ldr r1, _0814AAB4 @ =gSprites\n\
+ adds r2, r0, r1\n\
+ lsls r1, r7, 3\n\
+ ldr r0, _0814AAB8 @ =gSubspriteTables_842F5C0\n\
+ adds r1, r0\n\
+ adds r0, r2, 0\n\
+ bl SetSubspriteTables\n\
+_0814AA9C:\n\
+ add sp, 0x4\n\
+ pop {r3-r5}\n\
+ mov r8, r3\n\
+ mov r9, r4\n\
+ mov r10, r5\n\
+ pop {r4-r7}\n\
+ pop {r0}\n\
+ bx r0\n\
+ .align 2, 0\n\
+_0814AAAC: .4byte gUnknown_0842F798\n\
+_0814AAB0: .4byte gUnknown_0203A3D0\n\
+_0814AAB4: .4byte gSprites\n\
+_0814AAB8: .4byte gSubspriteTables_842F5C0\n\
+_0814AABC: .4byte gUnknown_0203A3D1\n\
+ .syntax divided\n");
+}
+#endif
+
+void sub_814AABC(void (*callback)(struct Sprite *))
+{
+ struct Sprite *spr;
+
+ if (gUnknown_0203A3D0 != 0x40)
+ {
+ spr = &gSprites[gUnknown_0203A3D0];
+ spr->callback = callback;
+ }
+
+ if (gUnknown_0203A3D1 != 0x40)
+ {
+ spr = &gSprites[gUnknown_0203A3D1];
+ spr->callback = callback;
+ }
+
+ return;
+}
+
+void sub_814AAF8(u16 a1)
+{
+ struct Sprite *spr;
+ u8 v2;
+ u8 v3;
+ u16 v4;
+
+ if (gUnknown_0203A3D0 != 0x40)
+ {
+ spr = &gSprites[gUnknown_0203A3D0];
+ if (spr->template->paletteTag == 0xFFFF)
+ {
+ for (v2 = 0, v3 = 0xF, v4 = 0; v4 <= 0xFF; v4++)
+ {
+ if (gPlttBufferUnfaded[v4] == a1)
+ {
+ v2 = v4 >> 4;
+ v3 = v4 & 0xF;
+ }
+ }
+ spr->oam.paletteNum = v2;
+ RequestSpriteSheetCopy(&gUnknown_0842F140[v3 & 0xF]);
+ }
+ }
+ return;
+}
+
+void sub_814AB84(void)
+{
+ struct Sprite *spr;
+
+ if (gUnknown_0203A3D1 != 0x40)
+ {
+ spr = &gSprites[gUnknown_0203A3D1];
+ FreeSpriteOamMatrix(spr);
+ DestroySprite(spr);
+ gUnknown_0203A3D1 = 0x40;
+
+ if (!gUnknown_0203A3D3)
+ REG_DISPCNT &= ~DISPCNT_OBJWIN_ON;
+ *(u8 *)(REG_ADDR_WINOUT + 1) = gUnknown_0203A3D4;
+ }
+ return;
+}
+
+void unref_sub_814ABE4(int a1)
+{
+ struct Sprite *spr;
+
+ CpuCopy16(gUnknown_0842F5BC[a1], &gMenuCursorSubsprites, 80);
+
+ if (gUnknown_0203A3D0 != 0x40)
+ {
+ spr = &gSprites[gUnknown_0203A3D0];
+ SetSubspriteTables(spr, &gUnknown_0842F758[a1]);
+ }
+ if (gUnknown_0203A3D1 != 0x40)
+ {
+ spr = &gSprites[gUnknown_0203A3D1];
+ SetSubspriteTables(spr, &gUnknown_0842F758[a1]);
+ }
+ return;
+}
+
+u8 CreateBlendedOutlineCursor(u8 a1, u16 a2, u8 a3, u16 a4, u8 a5)
+{
+ int v8;
+ struct Sprite *spr;
+ u8 var1 = gUnknown_0203A3D2;
+
+ if (var1 != 0x40)
+ sub_814AD44();
+
+ v8 = 1;
+
+ if (a2 == 0xFFFF)
+ {
+ gUnknown_0203A360[a3 & 0xF] = a4;
+ if (LoadSpritePalette(&gUnknown_0842F248) != 0xFF )
+ {
+ a2 = 0xFFF1;
+ v8 = 0;
+ }
+ }
+
+ LoadSpriteSheetDeferred(&gUnknown_0842F1C0[a3 & 0xF]);
+#if ENGLISH
+ gUnknown_0203A3D2 = CreateSprite(&gSpriteTemplate_842F298[v8], 0, 160, a1);
+#elif GERMAN
+ gUnknown_0203A3D2 = CreateSprite(&gSpriteTemplate_842F298[v8], 0, 161, a1);
+#endif
+
+ if (gUnknown_0203A3D2 != 0x40)
+ {
+ spr = &gSprites[gUnknown_0203A3D2];
+
+ if (a2 == 0xFFFF)
+ spr->oam.paletteNum = 0;
+ else
+ spr->oam.paletteNum = IndexOfSpritePaletteTag(a2);
+ }
+ sub_814ADF4(a5);
+
+ return gUnknown_0203A3D2;
+}
+
+void sub_814AD44(void)
+{
+ if (gUnknown_0203A3D2 != 0x40)
+ {
+ LoadTilesForSpriteSheet(&gUnknown_0842F1C0[0]);
+ DestroySpriteAndFreeResources(&gSprites[gUnknown_0203A3D2]);
+ gUnknown_0203A3D2 = 0x40;
+ }
+ return;
+}
+
+void sub_814AD7C(u8 a1, u8 a2)
+{
+ struct Sprite *spr;
+ if (gUnknown_0203A3D2 != 0x40)
+ {
+ spr = &gSprites[gUnknown_0203A3D2];
+ spr->invisible = 0;
+ spr->centerToCornerVecX = 0;
+ spr->centerToCornerVecY = 0;
+ spr->pos1.x = a1;
+ spr->pos1.y = a2;
+ }
+ return;
+}
+
+void sub_814ADC8()
+{
+ struct Sprite *spr;
+ if (gUnknown_0203A3D2 != 0x40)
+ {
+ spr = &gSprites[gUnknown_0203A3D2];
+ spr->invisible = 1;
+ }
+ return;
+}
+
+void sub_814ADF4(u8 a1)
+{
+ if (a1 > 0x12)
+ a1 = 0;
+
+ if (gUnknown_0203A3D2 != 0x40)
+ SetSubspriteTables(&gSprites[gUnknown_0203A3D2], &gSubspriteTables_842F6C0[a1]);
+ return;
+}
+
+#if GERMAN
+void nullsub_814B200(void)
+{
+}
+#endif
diff --git a/src/engine/mystery_event_menu.c b/src/engine/mystery_event_menu.c
new file mode 100644
index 000000000..0e48dc177
--- /dev/null
+++ b/src/engine/mystery_event_menu.c
@@ -0,0 +1,341 @@
+#include "global.h"
+#include "mystery_event_menu.h"
+#include "link.h"
+#include "main.h"
+#include "menu.h"
+#include "mystery_event_script.h"
+#include "palette.h"
+#include "save.h"
+#include "songs.h"
+#include "sound.h"
+#include "sprite.h"
+#include "string_util.h"
+#include "strings2.h"
+#include "task.h"
+#include "text.h"
+
+extern u8 unk_2000000[];
+
+static EWRAM_DATA u8 gUnknown_02039338 = 0;
+
+static void VBlankCB(void);
+static bool8 CheckLanguageMatch(void);
+static bool8 GetEventLoadMessage(u8 *dest, u32 status);
+static void CB2_MysteryEventMenu(void);
+
+static void VBlankCB(void)
+{
+ LoadOam();
+ ProcessSpriteCopyRequests();
+ TransferPlttBuffer();
+}
+
+static bool8 CheckLanguageMatch(void)
+{
+ bool8 val = FALSE;
+
+ if (gLinkPlayers[0].language == gLinkPlayers[1].language)
+ val = TRUE;
+
+ return val;
+}
+
+void CB2_InitMysteryEventMenu(void)
+{
+ ResetSpriteData();
+ FreeAllSpritePalettes();
+ ResetTasks();
+ SetVBlankCallback(VBlankCB);
+ SetUpWindowConfig(&gWindowConfig_81E6CE4);
+ InitMenuWindow(&gWindowConfig_81E6CE4);
+ MenuZeroFillScreen();
+ REG_DISPCNT = DISPCNT_MODE_0 | DISPCNT_OBJ_1D_MAP | DISPCNT_BG0_ON;
+ REG_BLDCNT = 0;
+ CreateTask(Task_DestroySelf, 0);
+ StopMapMusic();
+ RunTasks();
+ AnimateSprites();
+ BuildOamBuffer();
+ UpdatePaletteFade();
+ FillPalette(0, 0, 2);
+ SetMainCallback2(CB2_MysteryEventMenu);
+}
+
+static bool8 GetEventLoadMessage(u8 *dest, u32 status)
+{
+ bool8 retVal = 1;
+
+ if (status == 0)
+ {
+ StringCopy(dest, gSystemText_EventLoadSuccess);
+ retVal = 0;
+ }
+
+ if (status == 2)
+ retVal = 0;
+
+ if (status == 1)
+ StringCopy(dest, gSystemText_LoadingError);
+
+ return retVal;
+}
+
+static void CB2_MysteryEventMenu(void)
+{
+ u16 unkVal;
+
+ switch (gMain.state)
+ {
+ case 0:
+ MenuDrawTextWindow(0, 14, 29, 19);
+ BeginNormalPaletteFade(0xFFFFFFFF, 0, 0x10, 0, 0);
+ gMain.state++;
+ break;
+ case 1:
+ if (gPaletteFade.active)
+ break;
+ MenuPrintMessageDefaultCoords(gSystemText_LinkStandby);
+ gMain.state++;
+ break;
+ case 2:
+ if (MenuUpdateWindowText())
+ {
+ gMain.state++;
+ gLinkType = 21761;
+ OpenLink();
+ }
+ break;
+ case 3:
+ if ((gLinkStatus & 0x20) && (gLinkStatus & 0x1C) > 4)
+ {
+ PlaySE(SE_PIN);
+ MenuPrintMessageDefaultCoords(gSystemText_LoadEventPressA);
+ gMain.state++;
+ }
+ if (gMain.newKeys & B_BUTTON)
+ {
+ PlaySE(SE_SELECT);
+ CloseLink();
+ gMain.state = 15;
+ }
+ break;
+ case 4:
+ if (MenuUpdateWindowText())
+ gMain.state++;
+ break;
+#ifdef NONMATCHING
+ case 5:
+ if (GetLinkPlayerCount_2() != 2)
+ {
+ GetEventLoadMessage(gStringVar4, 1);
+ MenuPrintMessageDefaultCoords(gStringVar4);
+ gMain.state = 13;
+ break;
+ }
+ if (gMain.newKeys & A_BUTTON)
+ {
+ PlaySE(SE_SELECT);
+ sub_8007F4C();
+ MenuDrawTextWindow(6, 5, 23, 8);
+ MenuPrint(gSystemText_LoadingEvent, 7, 6);
+ gMain.state++;
+ }
+ else if (gMain.newKeys & B_BUTTON)
+ {
+ PlaySE(SE_SELECT);
+ CloseLink();
+ gMain.state = 15;
+ }
+ break;
+ case 6:
+ if (IsLinkConnectionEstablished())
+ {
+ if (!gReceivedRemoteLinkPlayers)
+ break;
+
+ if (GetLinkPlayerDataExchangeStatusTimed() == 3)
+ {
+ sub_800832C();
+ MenuZeroFillWindowRect(6, 5, 23, 8);
+ GetEventLoadMessage(gStringVar4, 1);
+ MenuPrintMessageDefaultCoords(gStringVar4);
+ gMain.state = 13;
+ break;
+ }
+ else if (CheckLanguageMatch())
+ {
+ MenuPrintMessageDefaultCoords(gSystemText_DontCutLink);
+ gMain.state++;
+ break;
+ }
+ else
+ {
+ CloseLink();
+ MenuZeroFillWindowRect(6, 5, 23, 8);
+ GetEventLoadMessage(gStringVar4, 1);
+ MenuPrintMessageDefaultCoords(gStringVar4);
+ gMain.state = 13;
+ break;
+ }
+ }
+ if (gMain.newKeys & B_BUTTON)
+ {
+ PlaySE(SE_SELECT);
+ CloseLink();
+ gMain.state = 15;
+ break;
+ }
+ break;
+#else
+ case 5:
+ if (GetLinkPlayerCount_2() != 2)
+ {
+ goto label;
+ }
+ if (gMain.newKeys & A_BUTTON)
+ {
+ PlaySE(SE_SELECT);
+ sub_8007F4C();
+ MenuDrawTextWindow(6, 5, 23, 8);
+ MenuPrint(gSystemText_LoadingEvent, 7, 6);
+ gMain.state++;
+ }
+ else if (gMain.newKeys & B_BUTTON)
+ {
+ PlaySE(SE_SELECT);
+ CloseLink();
+ gMain.state = 15;
+ }
+ break;
+ case 6:
+ if (IsLinkConnectionEstablished())
+ {
+ register u8 *ptr asm("r0");
+ register u32 offset1 asm("r2");
+ register u32 offset2 asm("r1");
+
+ if (!gReceivedRemoteLinkPlayers)
+ break;
+
+ if (GetLinkPlayerDataExchangeStatusTimed() == 3)
+ {
+ sub_800832C();
+ MenuZeroFillWindowRect(6, 5, 23, 8);
+ GetEventLoadMessage(gStringVar4, 1);
+ MenuPrintMessageDefaultCoords(gStringVar4);
+ ptr = (u8 *)&gMain;
+ offset1 = offsetof(struct Main, state);
+ asm("" ::: "r1");
+ ptr += offset1;
+ *ptr = 13;
+ }
+ else if (CheckLanguageMatch())
+ {
+ register u8 *ptr2 asm("r1");
+ register int offset3 asm("r0");
+ register int dummy asm("r2");
+ MenuPrintMessageDefaultCoords(gSystemText_DontCutLink);
+ ptr2 = (u8 *)&gMain;
+ offset3 = offsetof(struct Main, state);
+ if (dummy)
+ dummy++;
+ ptr2 += offset3;
+ (*ptr2)++;
+ break;
+ }
+ else
+ {
+ CloseLink();
+ MenuZeroFillWindowRect(6, 5, 23, 8);
+ label:
+ GetEventLoadMessage(gStringVar4, 1);
+ MenuPrintMessageDefaultCoords(gStringVar4);
+ ptr = (u8 *)&gMain;
+ offset2 = offsetof(struct Main, state);
+ ptr += offset2;
+ *ptr = 13;
+ }
+ break;
+ }
+ if (gMain.newKeys & B_BUTTON)
+ {
+ PlaySE(SE_SELECT);
+ CloseLink();
+ gMain.state = 15;
+ break;
+ }
+ break;
+#endif
+ case 7:
+ if (MenuUpdateWindowText())
+ gMain.state++;
+ break;
+ case 8:
+ if (GetBlockReceivedStatus())
+ {
+ ResetBlockReceivedFlags();
+ gMain.state++;
+ }
+ break;
+ case 9:
+ gMain.state++;
+ break;
+ case 10:
+ sub_800832C();
+ gMain.state++;
+ break;
+ case 11:
+ if (gReceivedRemoteLinkPlayers)
+ break;
+ unkVal = RunMysteryEventScript(unk_2000000);
+ CpuFill32(0, unk_2000000, 0x7D4);
+ if (!GetEventLoadMessage(gStringVar4, unkVal))
+ TrySavingData(NORMAL_SAVE);
+ gMain.state++;
+ break;
+ case 12:
+ MenuPrintMessageDefaultCoords(gStringVar4);
+ gMain.state++;
+ break;
+ case 13:
+ MenuZeroFillWindowRect(6, 5, 23, 8);
+ if (MenuUpdateWindowText())
+ {
+ gMain.state++;
+ gUnknown_02039338 = 0;
+ }
+ break;
+ case 14:
+ if (gMain.newKeys & A_BUTTON)
+ {
+ PlaySE(SE_SELECT);
+ gMain.state++;
+ }
+ break;
+ case 15:
+ BeginNormalPaletteFade(0xFFFFFFFF, 0, 0, 0x10, 0);
+ gMain.state++;
+ break;
+ case 16:
+ if (!gPaletteFade.active)
+ DoSoftReset();
+ break;
+ }
+
+ if (gLinkStatus & 0x40)
+ {
+ if (!IsLinkMaster())
+ {
+ CloseLink();
+ MenuZeroFillWindowRect(6, 5, 23, 8);
+ GetEventLoadMessage(gStringVar4, 1);
+ MenuPrintMessageDefaultCoords(gStringVar4);
+ gMain.state = 13;
+ }
+ }
+
+ RunTasks();
+ AnimateSprites();
+ BuildOamBuffer();
+ UpdatePaletteFade();
+}
diff --git a/src/engine/mystery_event_script.c b/src/engine/mystery_event_script.c
new file mode 100644
index 000000000..a6568f5de
--- /dev/null
+++ b/src/engine/mystery_event_script.c
@@ -0,0 +1,462 @@
+#include "global.h"
+#include "berry.h"
+#include "easy_chat.h"
+#include "event_data.h"
+#include "mail_data.h"
+#include "mystery_event_script.h"
+#include "pokedex.h"
+#include "pokemon.h"
+#include "pokemon_size_record.h"
+#include "script.h"
+#include "species.h"
+#include "strings.h"
+#include "string_util.h"
+#include "text.h"
+#include "util.h"
+
+#if ENGLISH
+#define LANGUAGE_MASK 0x2
+#elif GERMAN
+#define LANGUAGE_MASK 0x4
+#endif
+
+#ifdef SAPPHIRE
+#define VERSION_MASK 0x100
+#else
+#define VERSION_MASK 0x80
+#endif
+
+extern void party_compaction(void);
+extern void sub_813601C(void);
+
+extern ScrCmdFunc gMysteryEventScriptCmdTable[];
+extern ScrCmdFunc gMysteryEventScriptCmdTableEnd[];
+
+extern const u8 gOtherText_BerryObtainedDadHasIt[];
+extern const u8 gOtherText_BerryTransformed[];
+extern const u8 gOtherText_BerryAlreadyObtained[];
+extern const u8 gOtherText_SpecialRibbonReceived[];
+extern const u8 gOtherText_DexUpgraded[];
+extern const u8 gOtherText_RareWordAdded[];
+extern const u8 gOtherText_PokeWasSentOver[];
+extern const u8 gOtherText_PartyIsFull[];
+extern const u8 gOtherText_NewTrainerInHoenn[];
+extern const u8 gOtherText_DataCannotUseVersion[];
+
+static EWRAM_DATA struct ScriptContext sMysteryEventScriptContext = {0};
+
+static bool32 CheckCompatibility(u16 a1, u32 a2, u16 a3, u32 a4)
+{
+ if (!(a1 & LANGUAGE_MASK))
+ return FALSE;
+
+ if (!(a2 & LANGUAGE_MASK))
+ return FALSE;
+
+ if (!(a3 & 0x4))
+ return FALSE;
+
+ if (!(a4 & VERSION_MASK))
+ return FALSE;
+
+ return TRUE;
+}
+
+static void SetIncompatible(void)
+{
+ StringExpandPlaceholders(gStringVar4, gOtherText_DataCannotUseVersion);
+ SetMysteryEventScriptStatus(3);
+}
+
+static void InitMysteryEventScript(struct ScriptContext *ctx, u8 *script)
+{
+ InitScriptContext(ctx, gMysteryEventScriptCmdTable, gMysteryEventScriptCmdTableEnd);
+ SetupBytecodeScript(ctx, script);
+ ctx->data[0] = (u32)script;
+ ctx->data[1] = 0;
+ ctx->data[2] = 0;
+ ctx->data[3] = 0;
+}
+
+static bool32 RunMysteryEventScriptCommand(struct ScriptContext *ctx)
+{
+ if (RunScriptCommand(ctx) && ctx->data[3])
+ return TRUE;
+ else
+ return FALSE;
+}
+
+u32 RunMysteryEventScript(u8 *script)
+{
+ struct ScriptContext *ctx = &sMysteryEventScriptContext;
+ InitMysteryEventScript(ctx, script);
+ while (RunMysteryEventScriptCommand(ctx))
+ ;
+ return ctx->data[2];
+}
+
+void SetMysteryEventScriptStatus(u32 val)
+{
+ sMysteryEventScriptContext.data[2] = val;
+}
+
+static int CalcChecksum(u8 *data, int size)
+{
+ unsigned int i;
+ int sum = 0;
+
+ for (i = 0; i < size; i++)
+ sum += data[i];
+
+ return sum;
+}
+
+static u32 GetWord(u8 *ptr)
+{
+ return ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | (ptr[3] << 24);
+}
+
+static void SetWord(u8 *ptr, u32 val)
+{
+ ptr[0] = val;
+ ptr[1] = val >> 8;
+ ptr[2] = val >> 16;
+ ptr[3] = val >> 24;
+}
+
+bool8 unref_sub_81261B4(u8 *a1, int a2)
+{
+ if (a1[0x0] == 1 && a1[0x11] == 15 && !GetWord(a1 + 0x12))
+ {
+ int v4 = GetWord(a1 + 0x16) - a2 + (int)a1;
+ int v5 = GetWord(a1 + 0x1A);
+ int v6 = CalcChecksum((u8*)v4, v5 - a2 + (int)a1 - v4);
+ SetWord(a1 + 0x12, v6);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+bool8 unref_sub_812620C(u8 *a1, int a2)
+{
+ if (a1[0x0] == 1 && a1[0x11] == 16 && !GetWord(a1 + 0x12))
+ {
+ int v4 = GetWord(a1 + 0x16) - a2 + (int)a1;
+ int v5 = GetWord(a1 + 0x1A);
+ int v6 = CalcCRC16((u8*)v4, v5 - a2 + (int)a1 - v4);
+ SetWord(a1 + 0x12, v6);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static u32 CalcRecordMixingGiftChecksum(void)
+{
+ u32 sum = 0;
+ int i;
+ char *data = (char *)&gSaveBlock1.recordMixingGift.data;
+
+ for (i = 0; i < sizeof(gSaveBlock1.recordMixingGift.data); i++)
+ {
+ sum += data[i];
+ }
+
+ return sum;
+}
+
+static bool32 IsRecordMixingGiftValid(void)
+{
+ struct RecordMixingGiftData *data = &gSaveBlock1.recordMixingGift.data;
+
+ u32 checksum = CalcRecordMixingGiftChecksum();
+
+ if (!data->unk0)
+ return FALSE;
+
+ if (!data->quantity)
+ return FALSE;
+
+ if (!data->itemId)
+ return FALSE;
+
+ if (checksum == 0)
+ return FALSE;
+
+ if (checksum == gSaveBlock1.recordMixingGift.checksum)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static void ClearRecordMixingGift(void)
+{
+ CpuFill16(0, &gSaveBlock1.recordMixingGift, sizeof(gSaveBlock1.recordMixingGift));
+}
+
+static void SetRecordMixingGift(u8 unk, u8 quantity, u16 itemId)
+{
+ if (!unk || !quantity || !itemId)
+ {
+ ClearRecordMixingGift();
+ }
+ else
+ {
+ gSaveBlock1.recordMixingGift.data.unk0 = unk;
+ gSaveBlock1.recordMixingGift.data.quantity = quantity;
+ gSaveBlock1.recordMixingGift.data.itemId = itemId;
+ gSaveBlock1.recordMixingGift.checksum = CalcRecordMixingGiftChecksum();
+ }
+}
+
+u16 GetRecordMixingGift(void)
+{
+ struct RecordMixingGiftData *data = &gSaveBlock1.recordMixingGift.data;
+
+ if (!IsRecordMixingGiftValid())
+ {
+ ClearRecordMixingGift();
+ return 0;
+ }
+ else
+ {
+ u16 itemId = data->itemId;
+ data->quantity--;
+ if (data->quantity == 0)
+ ClearRecordMixingGift();
+ else
+ gSaveBlock1.recordMixingGift.checksum = CalcRecordMixingGiftChecksum();
+ return itemId;
+ }
+}
+
+bool8 MEScrCmd_end(struct ScriptContext *ctx)
+{
+ StopScript(ctx);
+ return TRUE;
+}
+
+bool8 MEScrCmd_checkcompat(struct ScriptContext *ctx)
+{
+ u16 v1;
+ u32 v2;
+ u16 v3;
+ u32 v4;
+
+ ctx->data[1] = ScriptReadWord(ctx);
+ v1 = ScriptReadHalfword(ctx);
+ v2 = ScriptReadWord(ctx);
+ v3 = ScriptReadHalfword(ctx);
+ v4 = ScriptReadWord(ctx);
+
+ if (CheckCompatibility(v1, v2, v3, v4) == TRUE)
+ ctx->data[3] = 1;
+ else
+ SetIncompatible();
+
+ return TRUE;
+}
+
+bool8 MEScrCmd_nop(struct ScriptContext *ctx)
+{
+ return FALSE;
+}
+
+bool8 MEScrCmd_setstatus(struct ScriptContext *ctx)
+{
+ u8 value = ScriptReadByte(ctx);
+ ctx->data[2] = value;
+ return FALSE;
+}
+
+bool8 MEScrCmd_setmsg(struct ScriptContext *ctx)
+{
+ u8 value = ScriptReadByte(ctx);
+ u8 *str = (u8 *)(ScriptReadWord(ctx) - ctx->data[1] + ctx->data[0]);
+ if (value == 255 || value == ctx->data[2])
+ StringExpandPlaceholders(gStringVar4, str);
+ return FALSE;
+}
+
+bool8 MEScrCmd_runscript(struct ScriptContext *ctx)
+{
+ u8 *script = (u8 *)(ScriptReadWord(ctx) - ctx->data[1] + ctx->data[0]);
+ ScriptContext2_RunNewScript(script);
+ return FALSE;
+}
+
+bool8 MEScrCmd_setenigmaberry(struct ScriptContext *ctx)
+{
+ u8 *str;
+ const u8 *message;
+ bool32 haveBerry = IsEnigmaBerryValid();
+ u8 *berry = (u8 *)(ScriptReadWord(ctx) - ctx->data[1] + ctx->data[0]);
+ StringCopyN(gStringVar1, gSaveBlock1.enigmaBerry.berry.name, 7);
+ SetEnigmaBerry(berry);
+ StringCopyN(gStringVar2, gSaveBlock1.enigmaBerry.berry.name, 7);
+
+ if (!haveBerry)
+ {
+ str = gStringVar4;
+ message = gOtherText_BerryObtainedDadHasIt;
+ }
+ else if (StringCompare(gStringVar1, gStringVar2))
+ {
+ str = gStringVar4;
+ message = gOtherText_BerryTransformed;
+ }
+ else
+ {
+ str = gStringVar4;
+ message = gOtherText_BerryAlreadyObtained;
+ }
+
+ StringExpandPlaceholders(str, message);
+
+ ctx->data[2] = 2;
+
+ if (IsEnigmaBerryValid() == TRUE)
+ VarSet(0x402D, 1);
+ else
+ ctx->data[2] = 1;
+
+ return FALSE;
+}
+
+bool8 MEScrCmd_giveribbon(struct ScriptContext *ctx)
+{
+ u8 index = ScriptReadByte(ctx);
+ u8 ribbonId = ScriptReadByte(ctx);
+ GiveGiftRibbonToParty(index, ribbonId);
+ StringExpandPlaceholders(gStringVar4, gOtherText_SpecialRibbonReceived);
+ ctx->data[2] = 2;
+ return FALSE;
+}
+
+bool8 MEScrCmd_initramscript(struct ScriptContext *ctx)
+{
+ u8 mapGroup = ScriptReadByte(ctx);
+ u8 mapNum = ScriptReadByte(ctx);
+ u8 objectId = ScriptReadByte(ctx);
+ u8 *script = (u8 *)(ScriptReadWord(ctx) - ctx->data[1] + ctx->data[0]);
+ u8 *scriptEnd = (u8 *)(ScriptReadWord(ctx) - ctx->data[1] + ctx->data[0]);
+ InitRamScript(script, scriptEnd - script, mapGroup, mapNum, objectId);
+ return FALSE;
+}
+
+bool8 MEScrCmd_givenationaldex(struct ScriptContext *ctx)
+{
+ EnableNationalPokedex();
+ StringExpandPlaceholders(gStringVar4, gOtherText_DexUpgraded);
+ ctx->data[2] = 2;
+ return FALSE;
+}
+
+bool8 MEScrCmd_addrareword(struct ScriptContext *ctx)
+{
+ sub_80EB890(ScriptReadByte(ctx));
+ StringExpandPlaceholders(gStringVar4, gOtherText_RareWordAdded);
+ ctx->data[2] = 2;
+ return FALSE;
+}
+
+bool8 MEScrCmd_setrecordmixinggift(struct ScriptContext *ctx)
+{
+ u8 unk = ScriptReadByte(ctx);
+ u8 quantity = ScriptReadByte(ctx);
+ u16 itemId = ScriptReadHalfword(ctx);
+ SetRecordMixingGift(unk, quantity, itemId);
+ return FALSE;
+}
+
+bool8 MEScrCmd_givepokemon(struct ScriptContext *ctx)
+{
+ struct MailStruct mail;
+ struct Pokemon pokemon;
+ u16 species;
+ u16 heldItem;
+ u32 data = ScriptReadWord(ctx) - ctx->data[1] + ctx->data[0];
+ void *pokemonPtr = (void *)data;
+ void *mailPtr = (void *)(data + sizeof(struct Pokemon));
+
+ pokemon = *(struct Pokemon *)pokemonPtr;
+ species = GetMonData(&pokemon, MON_DATA_SPECIES2);
+
+ if (species == SPECIES_EGG)
+ StringCopyN(gStringVar1, gSystemText_Egg, 11);
+ else
+ StringCopyN(gStringVar1, gSystemText_Pokemon2, 11);
+
+ if (gPlayerPartyCount == 6)
+ {
+ StringExpandPlaceholders(gStringVar4, gOtherText_PartyIsFull);
+ ctx->data[2] = 3;
+ }
+ else
+ {
+ memcpy(&gPlayerParty[5], pokemonPtr, sizeof(struct Pokemon));
+ memcpy(&mail, mailPtr, sizeof(struct MailStruct));
+
+ if (species != SPECIES_EGG)
+ {
+ u16 pokedexNum = SpeciesToNationalPokedexNum(species);
+ GetSetPokedexFlag(pokedexNum, 2);
+ GetSetPokedexFlag(pokedexNum, 3);
+ }
+
+ heldItem = GetMonData(&gPlayerParty[5], MON_DATA_HELD_ITEM);
+ if (ItemIsMail(heldItem))
+ GiveMailToMon2(&gPlayerParty[5], &mail);
+ party_compaction();
+ CalculatePlayerPartyCount();
+ StringExpandPlaceholders(gStringVar4, gOtherText_PokeWasSentOver);
+ ctx->data[2] = 2;
+ }
+
+ return FALSE;
+}
+
+bool8 MEScrCmd_addtrainer(struct ScriptContext *ctx)
+{
+ u32 data = ScriptReadWord(ctx) - ctx->data[1] + ctx->data[0];
+ memcpy(gSaveBlock2.filler_A8.ereaderTrainer, (void *)data, sizeof(gSaveBlock2.filler_A8.ereaderTrainer));
+ sub_813601C();
+ StringExpandPlaceholders(gStringVar4, gOtherText_NewTrainerInHoenn);
+ ctx->data[2] = 2;
+ return FALSE;
+}
+
+bool8 MEScrCmd_enableresetrtc(struct ScriptContext *ctx)
+{
+ EnableResetRTC();
+ StringExpandPlaceholders(gStringVar4, gSystemText_ClockAdjustmentUsable);
+ ctx->data[2] = 2;
+ return FALSE;
+}
+
+bool8 MEScrCmd_checksum(struct ScriptContext *ctx)
+{
+ int checksum = ScriptReadWord(ctx);
+ u8 *data = (u8 *)(ScriptReadWord(ctx) - ctx->data[1] + ctx->data[0]);
+ u8 *dataEnd = (u8 *)(ScriptReadWord(ctx) - ctx->data[1] + ctx->data[0]);
+ if (checksum != CalcChecksum(data, dataEnd - data))
+ {
+ ctx->data[3] = 0;
+ ctx->data[2] = 1;
+ }
+ return TRUE;
+}
+
+bool8 MEScrCmd_crc(struct ScriptContext *ctx)
+{
+ int crc = ScriptReadWord(ctx);
+ u8 *data = (u8 *)(ScriptReadWord(ctx) - ctx->data[1] + ctx->data[0]);
+ u8 *dataEnd = (u8 *)(ScriptReadWord(ctx) - ctx->data[1] + ctx->data[0]);
+ if (crc != CalcCRC16(data, dataEnd - data))
+ {
+ ctx->data[3] = 0;
+ ctx->data[2] = 1;
+ }
+ return TRUE;
+}
diff --git a/src/engine/name_string_util.c b/src/engine/name_string_util.c
new file mode 100644
index 000000000..f1a935453
--- /dev/null
+++ b/src/engine/name_string_util.c
@@ -0,0 +1,40 @@
+#include "global.h"
+#include "name_string_util.h"
+#include "string_util.h"
+#include "text.h"
+
+void PadNameString(u8 *a1, u8 a2)
+{
+ u8 i;
+
+ StripExtCtrlCodes(a1);
+ i = StringLength(a1);
+
+ if (a2 == 0xFC)
+ {
+ while (i < 6)
+ {
+ a1[i] = 0xFC;
+ a1[i + 1] = 7;
+ i += 2;
+ }
+ }
+ else
+ {
+ while (i < 6)
+ {
+ a1[i] = a2;
+ i++;
+ }
+ }
+
+ a1[i] = EOS;
+}
+
+void SanitizeNameString(u8 *a1)
+{
+ if (StringLength(a1) < 6)
+ ConvertInternationalString(a1, 1);
+ else
+ StripExtCtrlCodes(a1);
+}
diff --git a/src/engine/naming_screen.c b/src/engine/naming_screen.c
new file mode 100644
index 000000000..3f8417e6d
--- /dev/null
+++ b/src/engine/naming_screen.c
@@ -0,0 +1,2038 @@
+#include "global.h"
+#include "naming_screen.h"
+#include "data2.h"
+#include "field_effect.h"
+#include "field_map_obj.h"
+#include "field_player_avatar.h"
+#include "main.h"
+#include "menu.h"
+#include "palette.h"
+#include "pokemon_icon.h"
+#include "songs.h"
+#include "sound.h"
+#include "sprite.h"
+#include "string_util.h"
+#include "strings2.h"
+#include "task.h"
+#include "text.h"
+#include "trig.h"
+#include "util.h"
+
+#ifdef ENGLISH
+#define COLUMN_COUNT 9
+#elif GERMAN
+#define COLUMN_COUNT 10
+#endif
+
+extern u16 gKeyRepeatStartDelay;
+
+extern u8 unk_2000000[];
+
+#define namingScreenData (*(struct NamingScreenData *)(unk_2000000))
+
+const u32 gSpriteImage_83CE094[] = INCBIN_U32("graphics/naming_screen/pc_icon/0.4bpp");
+const u32 gSpriteImage_83CE154[] = INCBIN_U32("graphics/naming_screen/pc_icon/1.4bpp");
+
+//Some unused pointer, perhaps.
+asm(".section .rodata\n\
+@ XXX: what is this?\n\
+ .align 2\n\
+ .4byte 0x2000000\n");
+
+extern u16 *const gUnknown_083CE28C[];
+extern const struct SubspriteTable gSubspriteTables_83CE558[];
+extern const struct SubspriteTable gSubspriteTables_83CE560[];
+extern const struct SubspriteTable gSubspriteTables_83CE578[];
+extern const struct SubspriteTable gSubspriteTables_83CE580[];
+extern const struct SpriteTemplate gSpriteTemplate_83CE5C8;
+extern const struct SpriteTemplate gSpriteTemplate_83CE5E0;
+extern const struct SpriteTemplate gSpriteTemplate_83CE5F8;
+extern const struct SpriteTemplate gSpriteTemplate_83CE610;
+extern const struct SpriteTemplate gSpriteTemplate_83CE628;
+extern const struct SpriteTemplate gSpriteTemplate_83CE640;
+extern const struct SpriteTemplate gSpriteTemplate_83CE658;
+extern const struct SpriteTemplate gSpriteTemplate_83CE670;
+extern const struct SpriteTemplate gSpriteTemplate_83CE688;
+extern const struct SpriteSheet gUnknown_083CE6A0[];
+extern const struct SpritePalette gUnknown_083CE708[];
+extern const u8 gNamingScreenMenu_Gfx[];
+extern const u16 gNamingScreenPalettes[];
+extern const u16 gUnknown_083CE748[];
+extern const u16 gUnknown_083CEBF8[];
+extern const u16 gUnknown_083CF0A8[];
+extern const u16 gUnknown_08E86258[];
+
+static void C2_NamingScreen(void);
+static void sub_80B5AA0(void);
+static void StoreNamingScreenParameters(u8, u8 *, u16, u16, u32, MainCallback);
+static void NamingScreen_TurnOffScreen(void);
+static void NamingScreen_Init(void);
+static void NamingScreen_ClearVram(void);
+static void NamingScreen_ClearOam(void);
+static void NamingScreen_SetUpVideoRegs(void);
+static void NamingScreen_SetUpWindow(void);
+static void NamingScreen_ResetObjects(void);
+static void sub_80B5DFC(void);
+static void sub_80B5E20(void);
+static void sub_80B5E3C(void);
+static void NamingScreen_InitDisplayMode(void);
+static void Task_DoNothing(u8);
+static void sub_80B7558(void);
+static void sub_80B753C(void);
+static void sub_80B7680(void);
+static void sub_80B75C4(void);
+static void sub_80B7794(void);
+static void sub_80B78A8(void);
+static void sub_80B7960(void);
+static void CursorInit(void);
+static void sub_80B6A80(void);
+static void sub_80B6CA8(void);
+static void sub_80B6D04(void);
+static void sub_80B6E44(void);
+static void InputInit(void);
+static void sub_80B6438(void);
+static void sub_80B5E50(void);
+static void Task_NamingScreenMain(u8);
+static void SetInputState(u8);
+static void sub_80B68D8(u8);
+static bool8 HandleKeyboardEvent(void);
+static bool8 IsCursorAnimFinished(void);
+static void MoveCursorToOKButton(void);
+static void sub_80B6B14(void);
+static void StartPageSwapAnim(void);
+static void sub_80B6888(u8);
+static void sub_80B6460(u8, u8, u8);
+static bool8 IsPageSwapAnimNotInProgress(void);
+static void sub_80B7614(void);
+static void GetCursorPos(s16 *, s16 *);
+static void SetCursorPos(s16, s16);
+static void sub_80B77F8(void);
+static void sub_80B74B0(void);
+static void DisplaySentToPCMessage(void);
+static u8 GetKeyRoleAtCursorPos(void);
+static u8 sub_80B61C8(void);
+static void DeleteTextCharacter(void);
+static void sub_80B7090(void);
+static u8 GetInputEvent(void);
+static bool8 sub_80B7004(void);
+static void sub_80B6914(void);
+static void Task_HandlePageSwapAnim(u8);
+static void sub_80B6C48(u8, struct Sprite *, struct Sprite *);
+static u8 GetTextCaretPosition(void);
+static u8 GetCharAtKeyboardPos(s16, s16);
+static bool8 sub_80B7104(void);
+static bool8 sub_80B713C(void);
+static void AddTextCharacter(u8);
+static bool8 sub_80B7198(u8);
+static bool8 sub_80B7264(u8);
+static void sub_80B7370(u8, u8);
+static void sub_80B73CC(u8, u8);
+static bool8 sub_80B71E4(u8);
+static void sub_80B7474(u8, u8);
+static void sub_80B72A4(u8, u8);
+static bool8 sub_80B720C(u8);
+static void sub_80B7568(void);
+static void sub_80B75B0(void);
+static void sub_80B7698(u16 *, const u16 *);
+static void sub_80B76E0();
+static void nullsub_20(u8, u8);
+static void PrintKeyboardCharacters(u8);
+
+void DoNamingScreen(u8 templateNum, u8 *destBuffer, u16 c, u16 d, u32 e, MainCallback returnCallback)
+{
+ StoreNamingScreenParameters(templateNum, destBuffer, c, d, e, returnCallback);
+ SetMainCallback2(C2_NamingScreen);
+}
+
+static void C2_NamingScreen(void)
+{
+ switch (gMain.state)
+ {
+ case 0:
+ NamingScreen_TurnOffScreen();
+ NamingScreen_Init();
+ gMain.state++;
+ break;
+ case 1:
+ NamingScreen_ClearVram();
+ gMain.state++;
+ break;
+ case 2:
+ NamingScreen_ClearOam();
+ gMain.state++;
+ break;
+ case 3:
+ NamingScreen_SetUpVideoRegs();
+ gMain.state++;
+ break;
+ case 4:
+ NamingScreen_SetUpWindow();
+ gMain.state++;
+ break;
+ case 5:
+ NamingScreen_ResetObjects();
+ gMain.state++;
+ break;
+ case 6:
+ sub_80B5DFC();
+ gMain.state++;
+ break;
+ case 7:
+ sub_80B5E20();
+ sub_80B5E3C();
+ NamingScreen_InitDisplayMode();
+ SetMainCallback2(sub_80B5AA0);
+ break;
+ }
+}
+
+static void sub_80B5AA0(void)
+{
+ RunTasks();
+ AnimateSprites();
+ BuildOamBuffer();
+ UpdatePaletteFade();
+}
+
+static void VBlankCB_NamingScreen(void)
+{
+ LoadOam();
+ ProcessSpriteCopyRequests();
+ TransferPlttBuffer();
+ REG_BG1VOFS = namingScreenData.bg1vOffset;
+ REG_BG2VOFS = namingScreenData.bg2vOffset;
+ REG_BG1CNT &= 0xFFFC;
+ REG_BG1CNT |= namingScreenData.unk8;
+ REG_BG2CNT &= 0xFFFC;
+ REG_BG2CNT |= namingScreenData.unkA;
+}
+
+static void StoreNamingScreenParameters(u8 templateNum, u8 *destBuffer, u16 c, u16 d, u32 e, MainCallback returnCallback)
+{
+ struct Task *task;
+
+ //Create a task that does nothing, and use it as a temporary space to store parameters
+ task = &gTasks[CreateTask(Task_DoNothing, 0xFF)];
+ task->data[0] = templateNum;
+ task->data[1] = c;
+ task->data[2] = d;
+ task->data[3] = e >> 16;
+ task->data[4] = e;
+ StoreWordInTwoHalfwords(&task->data[5], (u32)destBuffer);
+ StoreWordInTwoHalfwords(&task->data[7], (u32)returnCallback);
+}
+
+static void GetNamingScreenParameters(void)
+{
+ u8 taskId;
+ struct Task *task;
+
+ taskId = FindTaskIdByFunc(Task_DoNothing);
+ task = &gTasks[taskId];
+ namingScreenData.templateNum = task->data[0];
+ namingScreenData.unk3E = task->data[1];
+ namingScreenData.unk40 = task->data[2];
+ namingScreenData.unk42 = (task->data[3] << 16) | (u16)task->data[4];
+ LoadWordFromTwoHalfwords(&task->data[5], (u32 *)&namingScreenData.destBuffer);
+ LoadWordFromTwoHalfwords(&task->data[7], (u32 *)&namingScreenData.returnCallback);
+ DestroyTask(taskId);
+}
+
+static void Task_DoNothing(u8 taskId)
+{
+}
+
+static void NamingScreen_TurnOffScreen(void)
+{
+ SetVBlankCallback(NULL);
+ SetHBlankCallback(NULL);
+ REG_DISPCNT = 0;
+}
+
+static void NamingScreen_InitDisplayMode(void)
+{
+ u16 savedIme;
+
+ SetVBlankCallback(VBlankCB_NamingScreen);
+ savedIme = REG_IME;
+ REG_IME = 0;
+ REG_IE |= INTR_FLAG_VBLANK;
+ REG_IME = savedIme;
+ REG_DISPSTAT |= DISPSTAT_VBLANK_INTR;
+ REG_DISPCNT = DISPCNT_MODE_0 | DISPCNT_OBJ_1D_MAP | DISPCNT_BG_ALL_ON | DISPCNT_OBJ_ON;
+}
+
+static void NamingScreen_ClearVram(void)
+{
+ u8 *addr = (void *)VRAM;
+ u32 size = 0x10000;
+
+ while (1)
+ {
+ DmaFill16(3, 0, addr, 0x1000);
+ addr += 0x1000;
+ size -= 0x1000;
+ if (size <= 0x1000)
+ {
+ DmaFill16(3, 0, addr, size);
+ break;
+ }
+ }
+}
+
+static void NamingScreen_ClearOam(void)
+{
+ DmaClear16(3, (void *)OAM, 0x400);
+}
+
+static void NamingScreen_SetUpVideoRegs(void)
+{
+ REG_BG0CNT = 0;
+ REG_BG1CNT = 0;
+ REG_BG2CNT = 0;
+ REG_BG3CNT = 0;
+ REG_BG0HOFS = 0;
+ REG_BG0VOFS = 0;
+ REG_BG1HOFS = 0;
+ REG_BG1VOFS = 0;
+ REG_BG2HOFS = 0;
+ REG_BG2VOFS = 0;
+ REG_BG3HOFS = 0;
+ REG_BG3VOFS = 0;
+ REG_BG0CNT = BGCNT_PRIORITY(0) | BGCNT_CHARBASE(0) | BGCNT_SCREENBASE(31) | BGCNT_16COLOR | BGCNT_TXT256x256;
+ REG_BG1CNT = BGCNT_PRIORITY(1) | BGCNT_CHARBASE(0) | BGCNT_SCREENBASE(28) | BGCNT_16COLOR | BGCNT_TXT256x256;
+ REG_BG2CNT = BGCNT_PRIORITY(2) | BGCNT_CHARBASE(2) | BGCNT_SCREENBASE(29) | BGCNT_16COLOR | BGCNT_TXT256x256;
+ REG_BG3CNT = BGCNT_PRIORITY(3) | BGCNT_CHARBASE(0) | BGCNT_SCREENBASE(30) | BGCNT_16COLOR | BGCNT_TXT256x256;
+ REG_BLDCNT = 0x640;
+ REG_BLDALPHA = 0x80C;
+}
+
+static const struct NamingScreenTemplate *const sNamingScreenTemplates[]; //forward declaration
+
+static void NamingScreen_Init(void)
+{
+ GetNamingScreenParameters();
+ namingScreenData.state = 0;
+ namingScreenData.bg1vOffset = 0;
+ namingScreenData.bg2vOffset = 0;
+ namingScreenData.unk8 = 1;
+ namingScreenData.unkA = 2;
+ namingScreenData.unkC = 0;
+ namingScreenData.unkD = 1;
+ namingScreenData.template = sNamingScreenTemplates[namingScreenData.templateNum];
+ namingScreenData.currentPage = namingScreenData.template->unk4;
+ namingScreenData.unk2 = 14 - namingScreenData.template->maxChars / 2;
+ namingScreenData.unk3C = gKeyRepeatStartDelay;
+ memset(namingScreenData.textBuffer, 0xFF, sizeof(namingScreenData.textBuffer));
+ if (namingScreenData.template->unk0 != 0)
+ StringCopy(namingScreenData.textBuffer, namingScreenData.destBuffer);
+ gKeyRepeatStartDelay = 16;
+}
+
+static void NamingScreen_SetUpWindow(void)
+{
+ SetUpWindowConfig(&gWindowConfig_81E6E88);
+ InitMenuWindow(&gWindowConfig_81E6E88);
+}
+
+static void NamingScreen_ResetObjects(void)
+{
+ ResetPaletteFade();
+ ResetSpriteData();
+ FreeAllSpritePalettes();
+ ResetTasks();
+}
+
+static void sub_80B5DFC(void)
+{
+ sub_80B7558();
+ sub_80B753C();
+ sub_80B7680();
+ sub_80B75C4();
+ sub_80B7794();
+ sub_80B78A8();
+ sub_80B7960();
+}
+
+static void sub_80B5E20(void)
+{
+ CursorInit();
+ sub_80B6A80();
+ sub_80B6CA8();
+ sub_80B6D04();
+ sub_80B6E44();
+}
+
+static void sub_80B5E3C(void)
+{
+ InputInit();
+ sub_80B6438();
+ sub_80B5E50();
+}
+
+//--------------------------------------------------
+// Naming screen main
+//--------------------------------------------------
+
+static bool8 MainState_BeginFadeIn(struct Task *);
+static bool8 MainState_WaitFadeIn(struct Task *);
+static bool8 MainState_HandleInput(struct Task *);
+static bool8 MainState_MoveToOKButton(struct Task *);
+static bool8 MainState_StartPageSwap(struct Task *);
+static bool8 MainState_WaitPageSwap(struct Task *);
+static bool8 MainState_6(struct Task *);
+static bool8 MainState_UpdateSentToPCMessage(struct Task *);
+static bool8 MainState_BeginFadeInOut(struct Task *);
+static bool8 MainState_WaitFadeOutAndExit(struct Task *);
+
+static bool8 (*const sMainStateFuncs[])(struct Task *) =
+{
+ MainState_BeginFadeIn,
+ MainState_WaitFadeIn,
+ MainState_HandleInput,
+ MainState_MoveToOKButton,
+ MainState_StartPageSwap,
+ MainState_WaitPageSwap,
+ MainState_6,
+ MainState_UpdateSentToPCMessage,
+ MainState_BeginFadeInOut,
+ MainState_WaitFadeOutAndExit,
+};
+
+static void sub_80B5E50(void)
+{
+ u8 taskId;
+
+ taskId = CreateTask(Task_NamingScreenMain, 2);
+ Task_NamingScreenMain(taskId);
+}
+
+static void Task_NamingScreenMain(u8 taskId)
+{
+ while (sMainStateFuncs[namingScreenData.state](&gTasks[taskId]) != 0)
+ ;
+}
+
+static bool8 MainState_BeginFadeIn(struct Task *task)
+{
+ BeginNormalPaletteFade(0xFFFFFFFF, 0, 16, 0, 0);
+ namingScreenData.state++;
+ return 0;
+}
+
+static bool8 MainState_WaitFadeIn(struct Task *task)
+{
+ if (!gPaletteFade.active)
+ {
+ SetInputState(INPUT_STATE_ENABLED);
+ sub_80B68D8(1);
+ namingScreenData.state++;
+ }
+ return FALSE;
+}
+
+static bool8 MainState_HandleInput(struct Task *task)
+{
+ return HandleKeyboardEvent();
+}
+
+static bool8 MainState_MoveToOKButton(struct Task *task)
+{
+ if (IsCursorAnimFinished())
+ {
+ SetInputState(INPUT_STATE_ENABLED);
+ MoveCursorToOKButton();
+ namingScreenData.state = MAIN_STATE_HANDLE_INPUT;
+ }
+ return FALSE;
+}
+
+static bool8 MainState_StartPageSwap(struct Task *task)
+{
+ SetInputState(INPUT_STATE_DISABLED);
+ sub_80B6B14();
+ StartPageSwapAnim();
+ sub_80B6888(1);
+ sub_80B6460(0, 0, 1);
+ PlaySE(SE_WIN_OPEN);
+ namingScreenData.state = MAIN_STATE_WAIT_PAGE_SWAP;
+ return FALSE;
+}
+
+static bool8 MainState_WaitPageSwap(struct Task *task)
+{
+ s16 cursorX;
+ s16 cursorY;
+
+ if (IsPageSwapAnimNotInProgress())
+ {
+ namingScreenData.state = MAIN_STATE_HANDLE_INPUT;
+ namingScreenData.currentPage++;
+ namingScreenData.currentPage %= 3;
+ sub_80B7614();
+ sub_80B77F8();
+ SetInputState(INPUT_STATE_ENABLED);
+ GetCursorPos(&cursorX, &cursorY);
+#if ENGLISH
+ if (namingScreenData.currentPage == PAGE_OTHERS && (cursorX == 6 || cursorX == 7))
+ cursorX = 5;
+#elif GERMAN
+ if (namingScreenData.currentPage == PAGE_OTHERS && (cursorX == 7 || cursorX == 8))
+ cursorX = 6;
+#endif
+ SetCursorPos(cursorX, cursorY);
+ sub_80B6888(0);
+ }
+ return FALSE;
+}
+
+static bool8 MainState_6(struct Task *task)
+{
+ sub_80B74B0();
+ SetInputState(INPUT_STATE_DISABLED);
+ sub_80B68D8(0);
+ sub_80B6460(3, 0, 1);
+ gKeyRepeatStartDelay = namingScreenData.unk3C;
+ if (namingScreenData.templateNum == NAMING_SCREEN_TEMPLATE_MON_NAME
+ && CalculatePlayerPartyCount() >= 6)
+ {
+ DisplaySentToPCMessage();
+ namingScreenData.state = MAIN_STATE_UPDATE_SENT_TO_PC_MESSAGE;
+ return FALSE;
+ }
+ else
+ {
+ namingScreenData.state = MAIN_STATE_BEGIN_FADE_OUT;
+ return TRUE; //Exit the naming screen
+ }
+}
+
+static bool8 MainState_UpdateSentToPCMessage(struct Task *task)
+{
+ if (MenuUpdateWindowText())
+ namingScreenData.state++;
+ return FALSE;
+}
+
+static bool8 MainState_BeginFadeInOut(struct Task *task)
+{
+ BeginNormalPaletteFade(0xFFFFFFFF, 0, 0, 16, 0);
+ namingScreenData.state++;
+ return FALSE;
+}
+
+static bool8 MainState_WaitFadeOutAndExit(struct Task *task)
+{
+ if (!gPaletteFade.active)
+ SetMainCallback2(namingScreenData.returnCallback);
+ return FALSE;
+}
+
+//--------------------------------------------------
+// Keyboard handling
+//--------------------------------------------------
+
+enum
+{
+ KEY_ROLE_CHAR,
+ KEY_ROLE_PAGE,
+ KEY_ROLE_BACKSPACE,
+ KEY_ROLE_OK,
+};
+
+
+static bool8 KeyboardKeyHandler_Character(u8);
+static bool8 KeyboardKeyHandler_Page(u8);
+static bool8 KeyboardKeyHandler_Backspace(u8);
+static bool8 KeyboardKeyHandler_OK(u8);
+
+static bool8 (*const sKeyboardKeyHandlers[])(u8) =
+{
+ KeyboardKeyHandler_Character,
+ KeyboardKeyHandler_Page,
+ KeyboardKeyHandler_Backspace,
+ KeyboardKeyHandler_OK,
+};
+
+static bool8 HandleKeyboardEvent(void)
+{
+ u8 event = GetInputEvent();
+ u8 keyRole = GetKeyRoleAtCursorPos();
+
+ if (event == KBEVENT_PRESSED_SELECT)
+ return sub_80B61C8();
+ else if (event == KBEVENT_PRESSED_B)
+ {
+ DeleteTextCharacter();
+ return FALSE;
+ }
+ else if (event == 7)
+ {
+ sub_80B7090();
+ return FALSE;
+ }
+ return sKeyboardKeyHandlers[keyRole](event);
+}
+
+static bool8 KeyboardKeyHandler_Character(u8 event)
+{
+ sub_80B6460(3, 0, 0);
+ if (event == KBEVENT_PRESSED_A)
+ {
+ u8 var = sub_80B7004();
+
+ sub_80B6914();
+ if (var)
+ {
+ SetInputState(INPUT_STATE_DISABLED);
+ namingScreenData.state = MAIN_STATE_MOVE_TO_OK_BUTTON;
+ }
+ }
+ return FALSE;
+}
+
+static bool8 KeyboardKeyHandler_Page(u8 event)
+{
+ sub_80B6460(0, 1, 0);
+ if (event == KBEVENT_PRESSED_A)
+ return sub_80B61C8();
+ else
+ return FALSE;
+}
+
+static bool8 KeyboardKeyHandler_Backspace(u8 event)
+{
+ sub_80B6460(1, 1, 0);
+ if (event == KBEVENT_PRESSED_A)
+ DeleteTextCharacter();
+ return FALSE;
+}
+
+static bool8 KeyboardKeyHandler_OK(u8 event)
+{
+ sub_80B6460(2, 1, 0);
+ if (event == KBEVENT_PRESSED_A)
+ {
+ PlaySE(SE_SELECT);
+ namingScreenData.state = MAIN_STATE_6;
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+static bool8 sub_80B61C8(void)
+{
+ namingScreenData.state = MAIN_STATE_START_PAGE_SWAP;
+ return TRUE;
+}
+
+//--------------------------------------------------
+// Input handling
+//--------------------------------------------------
+
+enum
+{
+ FNKEY_CASE,
+ FNKEY_BACK,
+ FNKEY_OK,
+};
+
+#define tState data[0]
+#define tKeyboardEvent data[1]
+#define tKbFunctionKey data[2]
+
+static void InputState_Disabled(struct Task *);
+static void InputState_Enabled(struct Task *);
+
+static void (*const sInputStateFuncs[])(struct Task *) =
+{
+ InputState_Disabled,
+ InputState_Enabled,
+};
+
+static void Task_HandleInput(u8);
+static void HandleDpadMovement(struct Task *);
+
+static void InputInit(void)
+{
+ CreateTask(Task_HandleInput, 1);
+}
+
+static u8 GetInputEvent(void)
+{
+ u8 taskId = FindTaskIdByFunc(Task_HandleInput);
+
+ return gTasks[taskId].tKeyboardEvent;
+}
+
+static void SetInputState(u8 state)
+{
+ u8 taskId = FindTaskIdByFunc(Task_HandleInput);
+
+ gTasks[taskId].tState = state;
+}
+
+static void Task_HandleInput(u8 taskId)
+{
+ sInputStateFuncs[gTasks[taskId].tState](&gTasks[taskId]);
+}
+
+static void InputState_Disabled(struct Task *task)
+{
+ task->tKeyboardEvent = 0;
+}
+
+static void InputState_Enabled(struct Task *task)
+{
+ task->tKeyboardEvent = 0;
+ if (gMain.newKeys & A_BUTTON)
+ {
+ task->tKeyboardEvent = KBEVENT_PRESSED_A;
+ return;
+ }
+ if (gMain.newKeys & B_BUTTON)
+ {
+ task->tKeyboardEvent = KBEVENT_PRESSED_B;
+ return;
+ }
+ if (gMain.newKeys & SELECT_BUTTON)
+ {
+ task->tKeyboardEvent = KBEVENT_PRESSED_SELECT;
+ return;
+ }
+ if (gMain.newKeys & START_BUTTON)
+ {
+ task->tKeyboardEvent = KBEVENT_PRESSED_START;
+ MoveCursorToOKButton();
+ return;
+ }
+ HandleDpadMovement(task);
+}
+
+static const s16 sDpadDeltaX[] =
+{
+ 0, //none
+ 0, //up
+ 0, //down
+ -1, //left
+ 1 //right
+};
+
+static const s16 sDpadDeltaY[] =
+{
+ 0, //none
+ -1, //up
+ 1, //down
+ 0, //left
+ 0 //right
+};
+
+static const s16 s4RowTo3RowTableY[] = {0, 1, 1, 2};
+static const s16 gUnknown_083CE274[] = {0, 0, 3, 0};
+
+static void HandleDpadMovement(struct Task *task)
+{
+ s16 cursorX;
+ s16 cursorY;
+ u16 dpadDir;
+ s16 prevCursorX;
+
+ GetCursorPos(&cursorX, &cursorY);
+ dpadDir = 0;
+ if (gMain.newAndRepeatedKeys & DPAD_UP)
+ dpadDir = 1;
+ if (gMain.newAndRepeatedKeys & DPAD_DOWN)
+ dpadDir = 2;
+ if (gMain.newAndRepeatedKeys & DPAD_LEFT)
+ dpadDir = 3;
+ if (gMain.newAndRepeatedKeys & DPAD_RIGHT)
+ dpadDir = 4;
+
+ //Get new cursor position
+ prevCursorX = cursorX;
+ cursorX += sDpadDeltaX[dpadDir];
+ cursorY += sDpadDeltaY[dpadDir];
+
+ //Wrap cursor position in the X direction
+ if (cursorX < 0)
+ cursorX = COLUMN_COUNT - 1;
+ if (cursorX > COLUMN_COUNT - 1)
+ cursorX = 0;
+
+ //Handle cursor movement in X direction
+ if (sDpadDeltaX[dpadDir] != 0)
+ {
+ //The "others" page only has 5 columns
+#if ENGLISH
+ if (namingScreenData.currentPage == PAGE_OTHERS && (cursorX == 6 || cursorX == 7))
+#elif GERMAN
+ if (namingScreenData.currentPage == PAGE_OTHERS && (cursorX == 6 || cursorX == 7 || cursorX == 8))
+#endif
+ {
+ if (sDpadDeltaX[dpadDir] > 0)
+ cursorX = COLUMN_COUNT - 1;
+ else
+ cursorX = 5;
+ }
+
+ if (cursorX == COLUMN_COUNT - 1)
+ {
+ //We are now on the last column
+ task->tKbFunctionKey = cursorY;
+ cursorY = s4RowTo3RowTableY[cursorY];
+ }
+ else if (prevCursorX == COLUMN_COUNT - 1)
+ {
+ if (cursorY == 1)
+ cursorY = task->tKbFunctionKey;
+ else
+ cursorY = gUnknown_083CE274[cursorY];
+ }
+ }
+
+ if (cursorX == COLUMN_COUNT - 1)
+ {
+ //There are only 3 keys on the last column, unlike the others,
+ //so wrap Y accordingly
+ if (cursorY < 0)
+ cursorY = 2;
+ if (cursorY > 2)
+ cursorY = 0;
+ if (cursorY == 0)
+ task->tKbFunctionKey = FNKEY_BACK;
+ else if (cursorY == 2)
+ task->tKbFunctionKey = FNKEY_OK;
+ }
+ else
+ {
+ if (cursorY < 0)
+ cursorY = 3;
+ if (cursorY > 3)
+ cursorY = 0;
+ }
+ SetCursorPos(cursorX, cursorY);
+}
+
+#undef tState
+#undef tKeyboardEvent
+#undef tKbFunctionKey
+
+//--------------------------------------------------
+//
+//--------------------------------------------------
+
+static void Task_80B64D4(u8);
+static u16 sub_80B654C(u8);
+static void sub_80B65AC(u8);
+static void sub_80B65D4(struct Task *, u8, u8);
+
+static void sub_80B6438(void)
+{
+ u8 taskId;
+
+ taskId = CreateTask(Task_80B64D4, 3);
+ gTasks[taskId].data[0] = 3;
+}
+
+static void sub_80B6460(u8 a, u8 b, u8 c)
+{
+ struct Task *task = &gTasks[FindTaskIdByFunc(Task_80B64D4)];
+
+ if (a == task->data[0] && c == 0)
+ {
+ task->data[1] = b;
+ task->data[2] = 1;
+ return;
+ }
+ if (a == 3 && task->data[1] == 0 && c == 0)
+ return;
+ if (task->data[0] != 3)
+ sub_80B65AC(task->data[0]);
+ sub_80B65D4(task, a, b);
+}
+
+static void Task_80B64D4(u8 taskId)
+{
+ struct Task *task = &gTasks[taskId];
+
+ if (task->data[0] == 3 || task->data[2] == 0)
+ return;
+ MultiplyInvertedPaletteRGBComponents(sub_80B654C(task->data[0]), task->data[3], task->data[3], task->data[3]);
+ if (task->data[5] != 0)
+ {
+ task->data[5]--;
+ if (task->data[5] != 0)
+ return;
+ }
+ task->data[5] = 2;
+ task->data[3] += task->data[4];
+ if (task->data[3] == 16)
+ task->data[4] = -task->data[4];
+ else if (task->data[3] == 0)
+ {
+ task->data[2] = task->data[1];
+ task->data[4] = -task->data[4];
+ }
+}
+
+static u16 sub_80B654C(u8 a)
+{
+ const u16 arr[] =
+ {
+ IndexOfSpritePaletteTag(4) * 16 + 0x10E,
+ IndexOfSpritePaletteTag(6) * 16 + 0x10C,
+ IndexOfSpritePaletteTag(6) * 16 + 0x10E,
+ };
+
+ return arr[a];
+}
+
+static void sub_80B65AC(u8 a)
+{
+ u16 index = sub_80B654C(a);
+
+ gPlttBufferFaded[index] = gPlttBufferUnfaded[index];
+}
+
+static void sub_80B65D4(struct Task *task, u8 b, u8 c)
+{
+ task->data[0] = b;
+ task->data[1] = c;
+ task->data[2] = 1;
+ task->data[3] = 15;
+ task->data[4] = 1;
+ task->data[5] = 0;
+}
+
+//--------------------------------------------------
+// Page Swap
+//--------------------------------------------------
+
+#define tState data[0]
+#define tFrameCount data[1]
+
+static bool8 PageSwapAnimState_Init(struct Task *);
+static bool8 PageSwapAnimState_1(struct Task *);
+static bool8 PageSwapAnimState_2(struct Task *);
+static bool8 PageSwapAnimState_Done(struct Task *);
+
+static bool8 (*const sPageSwapAnimStateFuncs[])(struct Task *) =
+{
+ PageSwapAnimState_Init,
+ PageSwapAnimState_1,
+ PageSwapAnimState_2,
+ PageSwapAnimState_Done,
+};
+
+static void StartPageSwapAnim(void)
+{
+ u8 taskId;
+
+ taskId = CreateTask(Task_HandlePageSwapAnim, 0);
+ Task_HandlePageSwapAnim(taskId);
+}
+
+static bool8 IsPageSwapAnimNotInProgress(void)
+{
+ if (FindTaskIdByFunc(Task_HandlePageSwapAnim) == 0xFF)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static void Task_HandlePageSwapAnim(u8 taskId)
+{
+ while (sPageSwapAnimStateFuncs[gTasks[taskId].tState](&gTasks[taskId]) != 0)
+ ;
+}
+
+static bool8 PageSwapAnimState_Init(struct Task *task)
+{
+ namingScreenData.bg1vOffset = 0;
+ namingScreenData.bg2vOffset = 0;
+ task->tState++;
+ return 0;
+}
+
+static bool8 PageSwapAnimState_1(struct Task *task)
+{
+ u16 *const arr[] =
+ {
+ &namingScreenData.bg2vOffset,
+ &namingScreenData.bg1vOffset
+ };
+
+ task->tFrameCount += 4;
+ *arr[namingScreenData.unkC] = Sin(task->tFrameCount, 40);
+ *arr[namingScreenData.unkD] = Sin((task->tFrameCount + 128) & 0xFF, 40);
+ if (task->tFrameCount >= 64)
+ {
+ u8 temp = namingScreenData.unk8; //Why u8 and not u16?
+
+ namingScreenData.unk8 = namingScreenData.unkA;
+ namingScreenData.unkA = temp;
+ task->tState++;
+ }
+ return 0;
+}
+
+static bool8 PageSwapAnimState_2(struct Task *task)
+{
+ u16 *const arr[] = {&namingScreenData.bg2vOffset, &namingScreenData.bg1vOffset};
+
+ task->tFrameCount += 4;
+ *arr[namingScreenData.unkC] = Sin(task->tFrameCount, 40);
+ *arr[namingScreenData.unkD] = Sin((task->tFrameCount + 128) & 0xFF, 40);
+ if (task->tFrameCount >= 128)
+ {
+ u8 temp = namingScreenData.unkC;
+
+ namingScreenData.unkC = namingScreenData.unkD;
+ namingScreenData.unkD = temp;
+ task->tState++;
+ }
+ return 0;
+}
+
+static bool8 PageSwapAnimState_Done(struct Task *task)
+{
+ DestroyTask(FindTaskIdByFunc(Task_HandlePageSwapAnim));
+ return 0;
+}
+
+#undef tState
+#undef tFrameCount
+
+//--------------------------------------------------
+// Cursor
+//--------------------------------------------------
+
+static void CursorInit(void)
+{
+ namingScreenData.cursorSpriteId = CreateSprite(&gSpriteTemplate_83CE640, 0, 0, 0);
+ gSprites[namingScreenData.cursorSpriteId].oam.priority = 1;
+ gSprites[namingScreenData.cursorSpriteId].oam.objMode = 1;
+ gSprites[namingScreenData.cursorSpriteId].data6 = 1;
+ gSprites[namingScreenData.cursorSpriteId].data6 = 2;
+ SetCursorPos(0, 0);
+}
+
+static const u8 sKeyboardSymbolPositions[][COLUMN_COUNT] = {
+#if ENGLISH
+ {1, 3, 5, 8, 10, 12, 14, 17, 19}, //Upper page
+ {1, 3, 5, 8, 10, 12, 14, 17, 19}, //Lower page
+ {1, 4, 7, 10, 13, 16, 16, 16, 19}, //Others page
+#elif GERMAN
+ {2, 3, 4, 5, 9, 10, 11, 12, 16, 19}, //Upper page
+ {2, 3, 4, 5, 9, 10, 11, 12, 16, 19}, //Lower page
+ {1, 4, 7, 10, 13, 16, 16, 16, 16, 19}, //Others page
+#endif
+};
+
+static u8 CursorColToKeyboardCol(s16 x)
+{
+ return sKeyboardSymbolPositions[namingScreenData.currentPage][x];
+}
+
+static void SetCursorPos(s16 x, s16 y)
+{
+ struct Sprite *cursorSprite = &gSprites[namingScreenData.cursorSpriteId];
+
+ cursorSprite->pos1.x = CursorColToKeyboardCol(x) * 8 + 27;
+ cursorSprite->pos1.y = y * 16 + 80;
+ cursorSprite->data2 = cursorSprite->data0;
+ cursorSprite->data3 = cursorSprite->data1;
+ cursorSprite->data0 = x;
+ cursorSprite->data1 = y;
+}
+
+static void GetCursorPos(s16 *x, s16 *y)
+{
+ struct Sprite *cursorSprite = &gSprites[namingScreenData.cursorSpriteId];
+
+ *x = cursorSprite->data0;
+ *y = cursorSprite->data1;
+}
+
+static void MoveCursorToOKButton(void)
+{
+ SetCursorPos(COLUMN_COUNT - 1, 2);
+}
+
+static void sub_80B6888(u8 a)
+{
+ gSprites[namingScreenData.cursorSpriteId].data4 &= -256;
+ gSprites[namingScreenData.cursorSpriteId].data4 |= a;
+ StartSpriteAnim(&gSprites[namingScreenData.cursorSpriteId], 0);
+}
+
+static void sub_80B68D8(u8 a)
+{
+ gSprites[namingScreenData.cursorSpriteId].data4 &= 0xFF;
+ gSprites[namingScreenData.cursorSpriteId].data4 |= a << 8;
+}
+
+static void sub_80B6914(void)
+{
+ StartSpriteAnim(&gSprites[namingScreenData.cursorSpriteId], 1);
+}
+
+static bool8 IsCursorAnimFinished(void)
+{
+ return gSprites[namingScreenData.cursorSpriteId].animEnded;
+}
+
+static u8 GetKeyRoleAtCursorPos(void)
+{
+ const u8 keyRoles[] = {KEY_ROLE_PAGE, KEY_ROLE_BACKSPACE, KEY_ROLE_OK};
+ s16 cursorX;
+ s16 cursorY;
+
+ GetCursorPos(&cursorX, &cursorY);
+ if (cursorX < COLUMN_COUNT - 1)
+ return KEY_ROLE_CHAR;
+ else
+ return keyRoles[cursorY];
+}
+
+void sub_80B6998(struct Sprite *sprite)
+{
+ if (sprite->animEnded)
+ StartSpriteAnim(sprite, 0);
+ sprite->invisible = (sprite->data4 & 0xFF);
+ if (sprite->data0 == COLUMN_COUNT - 1)
+ sprite->invisible = TRUE;
+ if (sprite->invisible || (sprite->data4 & 0xFF00) == 0
+ || sprite->data0 != sprite->data2 || sprite->data1 != sprite->data3)
+ {
+ sprite->data5 = 0;
+ sprite->data6 = 1;
+ sprite->data7 = 2;
+ }
+ sprite->data7--;
+ if (sprite->data7 == 0)
+ {
+ sprite->data5 += sprite->data6;
+ if (sprite->data5 == 16 || sprite->data5 == 0)
+ sprite->data6 = -sprite->data6;
+ sprite->data7 = 2;
+ }
+ if ((sprite->data4 & 0xFF00) != 0)
+ {
+ s8 gb = sprite->data5;
+ s8 r = sprite->data5 >> 1;
+ u16 index = IndexOfSpritePaletteTag(5) * 16 + 0x0101;
+
+ MultiplyInvertedPaletteRGBComponents(index, r, gb, gb);
+ }
+}
+
+static void sub_80B6A80(void)
+{
+ u8 spriteId1;
+ u8 spriteId2;
+ u8 spriteId3;
+
+ spriteId1 = CreateSprite(&gSpriteTemplate_83CE5C8, 0xCC, 0x50, 0);
+ namingScreenData.unk10 = spriteId1;
+ SetSubspriteTables(&gSprites[spriteId1], gSubspriteTables_83CE558);
+
+ spriteId2 = CreateSprite(&gSpriteTemplate_83CE5F8, 0xCC, 0x4C, 1);
+ gSprites[spriteId1].data6 = spriteId2;
+ SetSubspriteTables(&gSprites[spriteId2], gSubspriteTables_83CE560);
+
+ spriteId3 = CreateSprite(&gSpriteTemplate_83CE5E0, 0xCC, 0x4B, 2);
+ gSprites[spriteId3].oam.priority = 1;
+ gSprites[spriteId1].data7 = spriteId3;
+}
+
+static void sub_80B6B14(void)
+{
+ struct Sprite *sprite = &gSprites[namingScreenData.unk10];
+
+ sprite->data0 = 2;
+ sprite->data1 = namingScreenData.currentPage;
+}
+
+static u8 sub_80B6B5C(struct Sprite *);
+static u8 sub_80B6B98(struct Sprite *);
+static u8 sub_80B6B9C(struct Sprite *);
+static u8 sub_80B6C08(struct Sprite *);
+
+static u8 (*const gUnknown_083CE2B4[])(struct Sprite *) =
+{
+ sub_80B6B5C,
+ sub_80B6B98,
+ sub_80B6B9C,
+ sub_80B6C08,
+};
+
+void sub_80B6B34(struct Sprite *sprite)
+{
+ while (gUnknown_083CE2B4[sprite->data0](sprite) != 0)
+ ;
+}
+
+static u8 sub_80B6B5C(struct Sprite *sprite)
+{
+ struct Sprite *sprite1 = &gSprites[sprite->data6];
+ struct Sprite *sprite2 = &gSprites[sprite->data7];
+
+ sub_80B6C48(namingScreenData.currentPage, sprite1, sprite2);
+ sprite->data0++;
+ return 0;
+}
+
+static u8 sub_80B6B98(struct Sprite *sprite)
+{
+ return 0;
+}
+
+static u8 sub_80B6B9C(struct Sprite *sprite)
+{
+ struct Sprite *r4 = &gSprites[sprite->data6];
+ struct Sprite *r5 = &gSprites[sprite->data7];
+
+ r4->pos2.y++;
+ if (r4->pos2.y > 7)
+ {
+ sprite->data0++;
+ r4->pos2.y = -4;
+ r4->invisible = TRUE;
+ sub_80B6C48(((u8)sprite->data1 + 1) % 3, r4, r5);
+ }
+ return 0;
+}
+
+static u8 sub_80B6C08(struct Sprite *sprite)
+{
+ struct Sprite *r2 = &gSprites[sprite->data6];
+
+ r2->invisible = FALSE;
+ r2->pos2.y++;
+ if (r2->pos2.y >= 0)
+ {
+ r2->pos2.y = 0;
+ sprite->data0 = 1;
+ }
+ return 0;
+}
+
+static const u16 gUnknown_083CE2C4[] = {1, 3, 2};
+static const u16 gUnknown_083CE2CA[] = {4, 6, 5};
+
+static void sub_80B6C48(u8 a, struct Sprite *b, struct Sprite *c)
+{
+ c->oam.paletteNum = IndexOfSpritePaletteTag(gUnknown_083CE2C4[a]);
+ b->sheetTileStart = GetSpriteTileStartByTag(gUnknown_083CE2CA[a]);
+ b->subspriteTableNum = a;
+}
+
+//
+
+static void sub_80B6CA8(void)
+{
+ u8 spriteId;
+
+ spriteId = CreateSprite(&gSpriteTemplate_83CE610, 0xCC, 0x6C, 0);
+ SetSubspriteTables(&gSprites[spriteId], gSubspriteTables_83CE578);
+
+ spriteId = CreateSprite(&gSpriteTemplate_83CE628, 0xCC, 0x84, 0);
+ SetSubspriteTables(&gSprites[spriteId], gSubspriteTables_83CE578);
+}
+
+static void sub_80B6D04(void)
+{
+ u8 spriteId;
+ s16 r1;
+ u8 i;
+
+ r1 = (namingScreenData.unk2 - 1) * 8 + 4;
+ spriteId = CreateSprite(&gSpriteTemplate_83CE658, r1, 0x28, 0);
+ gSprites[spriteId].oam.priority = 3;
+ r1 = namingScreenData.unk2 * 8 + 4;
+ for (i = 0; i < namingScreenData.template->maxChars; i++, r1 += 8)
+ {
+ spriteId = CreateSprite(&gSpriteTemplate_83CE670, r1, 0x2C, 0);
+ gSprites[spriteId].oam.priority = 3;
+ gSprites[spriteId].data0 = i;
+ }
+}
+
+void sub_80B6D9C(struct Sprite *sprite)
+{
+ const s16 arr[] = {0, -4, -2, -1};
+
+ if (sprite->data0 == 0 || --sprite->data0 == 0)
+ {
+ sprite->data0 = 8;
+ sprite->data1 = (sprite->data1 + 1) & 3;
+ }
+ sprite->pos2.x = arr[sprite->data1];
+}
+
+void sub_80B6DE8(struct Sprite *sprite)
+{
+ const s16 arr[] = {2, 3, 2, 1};
+ u8 var;
+
+ var = GetTextCaretPosition();
+ if (var != (u8)sprite->data0)
+ {
+ sprite->pos2.y = 0;
+ sprite->data1 = 0;
+ sprite->data2 = 0;
+ }
+ else
+ {
+ sprite->pos2.y = arr[sprite->data1];
+ sprite->data2++;
+ if (sprite->data2 > 8)
+ {
+ sprite->data1 = (sprite->data1 + 1) & 3;
+ sprite->data2 = 0;
+ }
+ }
+}
+
+//
+
+static void nullsub_40(void);
+static void sub_80B6E68(void);
+static void sub_80B6EBC(void);
+static void sub_80B6EFC(void);
+
+static void (*const gUnknown_083CE2E0[])(void) =
+{
+ nullsub_40,
+ sub_80B6E68,
+ sub_80B6EBC,
+ sub_80B6EFC,
+};
+
+static void sub_80B6E44(void)
+{
+ gUnknown_083CE2E0[namingScreenData.template->unk2]();
+}
+
+static void nullsub_40(void)
+{
+}
+
+static void sub_80B6E68(void)
+{
+ u8 rivalGfxId;
+ u8 spriteId;
+
+ rivalGfxId = GetRivalAvatarGraphicsIdByStateIdAndGender(0, namingScreenData.unk3E);
+ spriteId = AddPseudoFieldObject(rivalGfxId, SpriteCallbackDummy, 0x38, 0x18, 0);
+ gSprites[spriteId].oam.priority = 3;
+ StartSpriteAnim(&gSprites[spriteId], 4);
+}
+
+static void sub_80B6EBC(void)
+{
+ u8 spriteId;
+
+ spriteId = CreateSprite(&gSpriteTemplate_83CE688, 0x34, 0x18, 0);
+ SetSubspriteTables(&gSprites[spriteId], gSubspriteTables_83CE580);
+ gSprites[spriteId].oam.priority = 3;
+}
+
+static void sub_80B6EFC(void)
+{
+ u8 spriteId;
+
+ sub_809D51C();
+ spriteId = CreateMonIcon(namingScreenData.unk3E, SpriteCallbackDummy, 0x34, 0x18, 0, namingScreenData.unk42);
+ gSprites[spriteId].oam.priority = 3;
+}
+
+static u8 GetTextCaretPosition(void)
+{
+ u8 i;
+
+ for (i = 0; i < namingScreenData.template->maxChars; i++)
+ {
+ if (namingScreenData.textBuffer[i] == EOS)
+ return i;
+ }
+ return namingScreenData.template->maxChars - 1;
+}
+
+static u8 GetPreviousTextCaretPosition(void)
+{
+ s8 i;
+
+ for (i = namingScreenData.template->maxChars - 1; i > 0; i--)
+ {
+ if (namingScreenData.textBuffer[i] != EOS)
+ return i;
+ }
+ return 0;
+}
+
+static void DeleteTextCharacter(void)
+{
+ u8 index;
+ u8 var2;
+
+ index = GetPreviousTextCaretPosition();
+ namingScreenData.textBuffer[index] = 0;
+ sub_80B7960();
+ namingScreenData.textBuffer[index] = EOS;
+ var2 = GetKeyRoleAtCursorPos();
+ if (var2 == 0 || var2 == 2)
+ sub_80B6460(1, 0, 1);
+ PlaySE(SE_BOWA);
+}
+
+static bool8 sub_80B7004(void)
+{
+ s16 x;
+ s16 y;
+ u8 ch;
+ bool8 r4;
+
+ GetCursorPos(&x, &y);
+ x = CursorColToKeyboardCol(x);
+ ch = GetCharAtKeyboardPos(x, y);
+ r4 = 1;
+ if (ch == 0xFF)
+ r4 = sub_80B7104();
+ else if (ch == 0xFE)
+ r4 = sub_80B713C();
+ else
+ AddTextCharacter(ch);
+ sub_80B7960();
+ PlaySE(SE_SELECT);
+ if (r4)
+ {
+ if (GetPreviousTextCaretPosition() == namingScreenData.template->maxChars - 1)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void sub_80B7090(void) // DoInput?
+{
+ u8 r5;
+ u8 r4;
+
+ r5 = GetPreviousTextCaretPosition();
+ r4 = namingScreenData.textBuffer[r5];
+ if (sub_80B7198(r4))
+ {
+ if (sub_80B7264(r4))
+ sub_80B7370(r4, r5);
+ else
+ sub_80B73CC(r4, r5);
+ }
+ else
+ {
+ if (sub_80B71E4(r4))
+ sub_80B7474(r4, r5);
+ else
+ sub_80B72A4(r4, r5);
+ }
+ sub_80B7960();
+ PlaySE(SE_SELECT);
+}
+
+static bool8 sub_80B7104(void)
+{
+ u8 r5;
+ u8 r4;
+
+ r5 = GetPreviousTextCaretPosition();
+ r4 = namingScreenData.textBuffer[r5];
+ if (sub_80B720C(r4))
+ {
+ sub_80B72A4(r4, r5);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static bool8 sub_80B713C(void)
+{
+ u8 r5;
+ u8 r4;
+
+ r5 = GetPreviousTextCaretPosition();
+ r4 = namingScreenData.textBuffer[r5];
+ if (sub_80B7264(r4))
+ {
+ sub_80B7370(r4, r5);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void AddTextCharacter(u8 ch)
+{
+ u8 index = GetTextCaretPosition();
+
+ namingScreenData.textBuffer[index] = ch;
+}
+
+static bool8 sub_80B7198(u8 a)
+{
+ if ((a >= 55 && a <= 74)
+ || (a >= 135 && a <= 139)
+ || (a >= 140 && a <= 144)
+ || (a >= 145 && a <= 149)
+ || (a >= 150 && a <= 154))
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static bool8 sub_80B71E4(u8 a)
+{
+ if ((a >= 75 && a <= 79)
+ || (a >= 155 && a <= 159))
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static bool8 sub_80B720C(u8 a)
+{
+ if ((a >= 6 && a <= 20)
+ || (a >= 26 && a <= 30)
+ || (a >= 75 && a <= 79)
+ || (a >= 86 && a <= 100)
+ || (a >= 106 && a <= 110)
+ || (a >= 155 && a <= 159))
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static bool8 sub_80B7264(u8 a)
+{
+ if ((a >= 26 && a <= 30)
+ || (a >= 70 && a <= 74)
+ || (a >= 106 && a <= 110)
+ || (a >= 150 && a <= 154))
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static void sub_80B72A4(u8 a, u8 b)
+{
+ u8 chr = a;
+
+ if (a >= 6 && a <= 10)
+ chr = a + 0x31;
+ else if (a >= 11 && a <= 15)
+ chr = a + 0x31;
+ else if (a >= 16 && a <= 20)
+ chr = a + 0x31;
+ else if (a >= 26 && a <= 30)
+ chr = a + 0x2C;
+ else if (a >= 75 && a <= 79)
+ chr = a + 0xFB;
+ else if (a >= 86 && a <= 90)
+ chr = a + 0x31;
+ else if (a >= 91 && a <= 95)
+ chr = a + 0x31;
+ else if (a >= 96 && a <= 100)
+ chr = a + 0x31;
+ else if (a >= 106 && a <= 110)
+ chr = a + 0x2C;
+ else if (a >= 155 && a <= 159)
+ chr = a + 0xFB;
+ namingScreenData.textBuffer[b] = chr;
+}
+
+static void sub_80B7370(u8 a, u8 b)
+{
+ u8 chr = a;
+
+ if (a >= 26 && a <= 30)
+ chr = a + 0x31;
+ else if (a >= 70 && a <= 74)
+ chr = a + 5;
+ else if (a >= 106 && a <= 110)
+ chr = a + 0x31;
+ else if (a >= 150 && a <= 154)
+ chr = a + 5;
+ namingScreenData.textBuffer[b] = chr;
+}
+
+static void sub_80B73CC(u8 a, u8 b)
+{
+ u8 chr = a;
+
+ if (a >= 55 && a <= 59)
+ chr = a + 0xCF;
+ else if (a >= 60 && a <= 64)
+ chr = a + 0xCF;
+ else if (a >= 65 && a <= 69)
+ chr = a + 0xCF;
+ else if (a >= 70 && a <= 74)
+ chr = a + 0xD4;
+ else if (a >= 135 && a <= 139)
+ chr = a + 0xCF;
+ else if (a >= 140 && a <= 144)
+ chr = a + 0xCF;
+ else if (a >= 145 && a <= 149)
+ chr = a + 0xCF;
+ else if (a >= 150 && a <= 154)
+ chr = a + 0xD4;
+ namingScreenData.textBuffer[b] = chr;
+}
+
+static void sub_80B7474(u8 a, u8 b)
+{
+ u8 chr = a;
+
+ if (a >= 75 && a <= 79)
+ chr = a + 0xCF;
+ else if (a >= 155 && a <= 159)
+ chr = a + 0xCF;
+ namingScreenData.textBuffer[b] = chr;
+}
+
+static void sub_80B74B0(void)
+{
+ u8 i;
+
+ for (i = 0; i < namingScreenData.template->maxChars; i++)
+ {
+ if (namingScreenData.textBuffer[i] != 0 && namingScreenData.textBuffer[i] != 0xFF)
+ {
+ StringCopyN(namingScreenData.destBuffer, namingScreenData.textBuffer, namingScreenData.template->maxChars + 1);
+ break;
+ }
+ }
+}
+
+static void DisplaySentToPCMessage(void)
+{
+ StringCopy(gStringVar1, namingScreenData.destBuffer);
+ StringExpandPlaceholders(gStringVar4, gOtherText_SentToPC);
+ BasicInitMenuWindow(&gWindowConfig_81E6E88);
+ MenuDisplayMessageBox();
+ MenuPrintMessageDefaultCoords(gStringVar4);
+}
+
+static void sub_80B753C(void)
+{
+ LoadSpriteSheets(gUnknown_083CE6A0);
+ LoadSpritePalettes(gUnknown_083CE708);
+}
+
+static void sub_80B7558(void)
+{
+ sub_80B7568();
+ sub_80B75B0();
+}
+
+static void sub_80B7568(void)
+{
+ const void *src;
+ void *dst;
+
+ src = gNamingScreenMenu_Gfx;
+ dst = (void *)(VRAM + gMenuMessageBoxContentTileOffset * 32);
+ DmaCopy16(3, src, dst, 0x800);
+
+ src = gNamingScreenMenu_Gfx;
+ dst = (void *)(VRAM + 0x8000 + gMenuMessageBoxContentTileOffset * 32);
+ DmaCopy16(3, src, dst, 0x800);
+}
+
+static void sub_80B75B0(void)
+{
+ LoadPalette(gNamingScreenPalettes, 0, 0x80);
+}
+
+static void sub_80B7650(u16 *);
+static void sub_80B7660(u16 *);
+static void sub_80B7670(u16 *);
+
+static void (*const gUnknown_083CE2F0[][2])(u16 *) =
+{
+ {sub_80B7660, sub_80B7650},
+ {sub_80B7650, sub_80B7670},
+ {sub_80B7670, sub_80B7660},
+};
+
+static void sub_80B75C4(void)
+{
+ u16 *const arr[] =
+ {
+ (u16 *)(VRAM + 0xE000),
+ (u16 *)(VRAM + 0xE800),
+ };
+
+ gUnknown_083CE2F0[namingScreenData.currentPage][0](arr[namingScreenData.unkC]);
+ gUnknown_083CE2F0[namingScreenData.currentPage][1](arr[namingScreenData.unkD]);
+}
+
+static void sub_80B7614(void)
+{
+ u16 *const arr[] =
+ {
+ (u16 *)(VRAM + 0xE000),
+ (u16 *)(VRAM + 0xE800),
+ };
+
+ gUnknown_083CE2F0[namingScreenData.currentPage][1](arr[namingScreenData.unkD]);
+}
+
+static void sub_80B7650(u16 *vramBuffer)
+{
+ sub_80B7698(vramBuffer, gUnknown_083CE748);
+}
+
+static void sub_80B7660(u16 *vramBuffer)
+{
+ sub_80B7698(vramBuffer, gUnknown_083CEBF8);
+}
+
+static void sub_80B7670(u16 *vramBuffer)
+{
+ sub_80B7698(vramBuffer, gUnknown_083CF0A8);
+}
+
+static void sub_80B7680(void)
+{
+ sub_80B76E0(VRAM + 0xF000, gUnknown_08E86258);
+}
+
+static void sub_80B7698(u16 *vramBuffer, const u16 *src)
+{
+ s16 i;
+ s16 j;
+
+ for (i = 0; i < 20; i++)
+ {
+ for (j = 0; j < 30; j++, src++)
+ {
+ vramBuffer[i * 32 + j] = *src + gMenuMessageBoxContentTileOffset;
+ }
+ }
+}
+
+static void sub_80B76E0(u16 *vramBuffer, const u16 *src)
+{
+ s16 i;
+ s16 j;
+
+ for (i = 0; i < 20; i++)
+ {
+ for (j = 0; j < 30; j++, src++)
+ {
+ vramBuffer[i * 32 + j] = *src + gMenuMessageBoxContentTileOffset;
+ }
+ src += 2;
+ }
+}
+
+static void sub_80B772C(void)
+{
+ nullsub_20(namingScreenData.currentPage, namingScreenData.unkC);
+}
+
+static void sub_80B7740(void)
+{
+ nullsub_20((namingScreenData.currentPage + 1) % 3, namingScreenData.unkD);
+}
+
+static void nullsub_20(u8 a, u8 b)
+{
+}
+
+static void sub_80B7838(void);
+static void sub_80B7844(void);
+static void sub_80B7850(void);
+
+static void (*const gUnknown_083CE310[][2])(void) =
+{
+ sub_80B7844,
+ sub_80B7838,
+ sub_80B7838,
+ sub_80B7850,
+ sub_80B7850,
+ sub_80B7844,
+};
+
+static const struct WindowConfig *const gUnknown_083CE328[][2][2] =
+{
+ {
+ {&gWindowConfig_81E6EDC, &gWindowConfig_81E6EF8},
+ {&gWindowConfig_81E6EA4, &gWindowConfig_81E6EC0},
+ },
+ {
+ {&gWindowConfig_81E6EA4, &gWindowConfig_81E6EC0},
+ {&gWindowConfig_81E6F14, &gWindowConfig_81E6F30},
+ },
+ {
+ {&gWindowConfig_81E6F14, &gWindowConfig_81E6F30},
+ {&gWindowConfig_81E6EDC, &gWindowConfig_81E6EF8},
+ },
+};
+
+static void nullsub_61(void);
+static void sub_80B78F8(void);
+
+static void (*const gUnknown_083CE358[])(void) =
+{
+ nullsub_61,
+ nullsub_61,
+ sub_80B78F8,
+ sub_80B78F8,
+};
+
+static void nullsub_62(void);
+static void sub_80B7924(void);
+
+static void (*const gUnknown_083CE368[])(void) =
+{
+ nullsub_62,
+ sub_80B7924,
+};
+
+static const u8 sKeyboardCharacters[][4][20]; //forward declaration
+
+static u8 GetCharAtKeyboardPos(s16 a, s16 b)
+{
+ return sKeyboardCharacters[namingScreenData.currentPage][b][a];
+}
+
+static void sub_80B7794(void)
+{
+ BasicInitMenuWindow(gUnknown_083CE328[namingScreenData.currentPage][0][namingScreenData.unkC]);
+ gUnknown_083CE310[namingScreenData.currentPage][0]();
+ BasicInitMenuWindow(gUnknown_083CE328[namingScreenData.currentPage][1][namingScreenData.unkD]);
+ gUnknown_083CE310[namingScreenData.currentPage][1]();
+ sub_80B772C();
+ sub_80B7740();
+}
+
+static void sub_80B77F8(void)
+{
+ BasicInitMenuWindow(gUnknown_083CE328[namingScreenData.currentPage][1][namingScreenData.unkD]);
+ gUnknown_083CE310[namingScreenData.currentPage][1]();
+ sub_80B7740();
+}
+
+static void sub_80B7838(void)
+{
+ PrintKeyboardCharacters(1);
+}
+
+static void sub_80B7844(void)
+{
+ PrintKeyboardCharacters(0);
+}
+
+static void sub_80B7850(void)
+{
+ PrintKeyboardCharacters(2);
+}
+
+static void PrintKeyboardCharacters(u8 page) //print letters on page
+{
+ s16 i;
+ s16 r5;
+
+ for (i = 0, r5 = 9; i < 4; i++, r5 += 2)
+ MenuPrint(sKeyboardCharacters[page][i], 3, r5);
+}
+
+static void sub_80B78A8(void)
+{
+ BasicInitMenuWindow(&gWindowConfig_81E6F4C);
+ gUnknown_083CE358[namingScreenData.templateNum]();
+ gUnknown_083CE368[namingScreenData.template->unk3]();
+ MenuPrint(namingScreenData.template->title, 9, 2);
+}
+
+static void nullsub_61(void)
+{
+}
+
+static void sub_80B78F8(void)
+{
+ StringCopy(gStringVar1, gSpeciesNames[(s16)namingScreenData.unk3E]);
+}
+
+static void nullsub_62(void)
+{
+}
+
+static void sub_80B7924(void)
+{
+ u8 genderSymbol[2] = _("♂");
+
+ if ((s16)namingScreenData.unk40 != MON_GENDERLESS)
+ {
+ if ((s16)namingScreenData.unk40 == MON_FEMALE)
+ genderSymbol[0] = 0xB6; //female symbol
+ MenuPrint(genderSymbol, 0x14, 4);
+ }
+}
+
+static void sub_80B7960(void)
+{
+ u8 *string = gStringVar1;
+
+ string[0] = 0xFC;
+ string[1] = 0x14;
+ string[2] = 8;
+ string[3] = 0xFC;
+ string[4] = 0x11;
+ string[5] = 1;
+ string += 6;
+ StringCopy(string, namingScreenData.textBuffer);
+ BasicInitMenuWindow(&gWindowConfig_81E6F4C);
+ MenuPrint(gStringVar1, namingScreenData.unk2, 4);
+}
+
+//--------------------------------------------------
+// Forward-declared variables
+//--------------------------------------------------
+
+static const struct NamingScreenTemplate playerNamingScreenTemplate =
+{
+ .unk0 = 0,
+ .maxChars = 7,
+ .unk2 = 1,
+ .unk3 = 0,
+ .unk4 = 0,
+ .unk5 = 0,
+ .unk6 = 0,
+ .unk7 = 0,
+ .title = OtherText_YourName,
+};
+
+static const struct NamingScreenTemplate pcBoxNamingTemplate =
+{
+ .unk0 = 0,
+ .maxChars = 8,
+ .unk2 = 2,
+ .unk3 = 0,
+ .unk4 = 0,
+ .unk5 = 0,
+ .unk6 = 0,
+ .unk7 = 0,
+ .title = OtherText_BoxName,
+};
+
+static const struct NamingScreenTemplate monNamingScreenTemplate =
+{
+ .unk0 = 0,
+ .maxChars = 10,
+ .unk2 = 3,
+ .unk3 = 1,
+ .unk4 = 0,
+ .unk5 = 0,
+ .unk6 = 0,
+ .unk7 = 0,
+ .title = OtherText_PokeName,
+};
+
+static const struct NamingScreenTemplate *const sNamingScreenTemplates[] =
+{
+ &playerNamingScreenTemplate,
+ &pcBoxNamingTemplate,
+ &monNamingScreenTemplate,
+ &monNamingScreenTemplate,
+};
+
+static const u8 sKeyboardCharacters[][4][20] =
+{
+#if ENGLISH
+ {
+ _(" A B C D E F . "),
+ _(" G H I J K L , "),
+ _(" M N O P Q R S "),
+ _(" T U V W X Y Z "),
+ },
+ {
+ _(" a b c d e f . "),
+ _(" g h i j k l , "),
+ _(" m n o p q r s "),
+ _(" t u v w x y z "),
+ },
+#elif GERMAN
+ {
+ _(" ABCD EFGH . "),
+ _(" IJKL MNOP , "),
+ _(" QRST UVWX "),
+ _(" YZ ÄÖÜ "),
+ },
+ {
+ _(" abcd efgh . "),
+ _(" ijkl mnop , "),
+ _(" qrst uvwx "),
+ _(" yz äöü "),
+ },
+#endif
+ {
+ _(" 0 1 2 3 4 "),
+ _(" 5 6 7 8 9 "),
+ _(" ! ? ♂ ♀ / - "),
+ _(" … “ ” ‘ ’ "),
+ },
+};
+
+const struct OamData gOamData_83CE498 =
+{
+ .y = 0,
+ .affineMode = 0,
+ .objMode = 0,
+ .mosaic = 0,
+ .bpp = 0,
+ .shape = 0,
+ .x = 0,
+ .matrixNum = 0,
+ .size = 0,
+ .tileNum = 0,
+ .priority = 0,
+ .paletteNum = 0,
+ .affineParam = 0,
+};
+
+const struct OamData gOamData_83CE4A0 =
+{
+ .y = 0,
+ .affineMode = 0,
+ .objMode = 0,
+ .mosaic = 0,
+ .bpp = 0,
+ .shape = 0,
+ .x = 0,
+ .matrixNum = 0,
+ .size = 1,
+ .tileNum = 0,
+ .priority = 0,
+ .paletteNum = 0,
+ .affineParam = 0,
+};
+
+const struct OamData gOamData_83CE4A8 =
+{
+ .y = 0,
+ .affineMode = 0,
+ .objMode = 0,
+ .mosaic = 0,
+ .bpp = 0,
+ .shape = 1,
+ .x = 0,
+ .matrixNum = 0,
+ .size = 2,
+ .tileNum = 0,
+ .priority = 0,
+ .paletteNum = 0,
+ .affineParam = 0,
+};
+
+//TODO: dump sprite data
diff --git a/src/engine/option_menu.c b/src/engine/option_menu.c
new file mode 100644
index 000000000..dfc49b035
--- /dev/null
+++ b/src/engine/option_menu.c
@@ -0,0 +1,579 @@
+#include "global.h"
+#include "option_menu.h"
+#include "main.h"
+#include "menu.h"
+#include "palette.h"
+#include "sprite.h"
+#include "strings2.h"
+#include "task.h"
+
+extern void SetPokemonCryStereo(u32 val);
+extern void remove_some_task(void);
+
+//Task data
+enum {
+ TD_MENUSELECTION,
+ TD_TEXTSPEED,
+ TD_BATTLESCENE,
+ TD_BATTLESTYLE,
+ TD_SOUND,
+ TD_BUTTONMODE,
+ TD_FRAMETYPE,
+};
+
+//Menu items
+enum {
+ MENUITEM_TEXTSPEED,
+ MENUITEM_BATTLESCENE,
+ MENUITEM_BATTLESTYLE,
+ MENUITEM_SOUND,
+ MENUITEM_BUTTONMODE,
+ MENUITEM_FRAMETYPE,
+ MENUITEM_CANCEL,
+};
+
+const u16 gUnknown_0839F5FC[] = INCBIN_U16("graphics/misc/option_menu_text.gbapal");
+// note: this is only used in the Japanese release
+const u8 gUnknown_0839F63C[] = INCBIN_U8("graphics/misc/option_menu_equals_sign.4bpp");
+
+static void Task_OptionMenuFadeIn(u8 taskId);
+static void Task_OptionMenuProcessInput(u8 taskId);
+static void Task_OptionMenuSave(u8 taskId);
+static void Task_OptionMenuFadeOut(u8 taskId);
+static void HighlightOptionMenuItem(u8 selection);
+static u8 TextSpeed_ProcessInput(u8 selection);
+static void TextSpeed_DrawChoices(u8 selection);
+static u8 BattleScene_ProcessInput(u8 selection);
+static void BattleScene_DrawChoices(u8 selection);
+static u8 BattleStyle_ProcessInput(u8 selection);
+static void BattleStyle_DrawChoices(u8 selection);
+static u8 Sound_ProcessInput(u8 selection);
+static void Sound_DrawChoices(u8 selection);
+static u8 FrameType_ProcessInput(u8 selection);
+static void FrameType_DrawChoices(u8 selection);
+static u8 ButtonMode_ProcessInput(u8 selection);
+static void ButtonMode_DrawChoices(u8 selection);
+
+static void MainCB(void)
+{
+ RunTasks();
+ AnimateSprites();
+ BuildOamBuffer();
+ UpdatePaletteFade();
+}
+
+static void VBlankCB(void)
+{
+ LoadOam();
+ ProcessSpriteCopyRequests();
+ TransferPlttBuffer();
+}
+
+void CB2_InitOptionMenu(void)
+{
+ switch (gMain.state)
+ {
+ default:
+ case 0:
+ {
+ u8 *addr;
+ u32 size;
+
+ SetVBlankCallback(NULL);
+ REG_DISPCNT = 0;
+ REG_BG2CNT = 0;
+ REG_BG1CNT = 0;
+ REG_BG0CNT = 0;
+ REG_BG2HOFS = 0;
+ REG_BG2VOFS = 0;
+ REG_BG1HOFS = 0;
+ REG_BG1VOFS = 0;
+ REG_BG0HOFS = 0;
+ REG_BG0VOFS = 0;
+ addr = (u8 *)VRAM;
+ size = 0x18000;
+ while (1)
+ {
+ DmaFill16(3, 0, addr, 0x1000);
+ addr += 0x1000;
+ size -= 0x1000;
+ if (size <= 0x1000)
+ {
+ DmaFill16(3, 0, addr, size);
+ break;
+ }
+ }
+ DmaClear32(3, OAM, OAM_SIZE);
+ DmaClear16(3, PLTT, PLTT_SIZE);
+ gMain.state++;
+ break;
+ }
+ case 1:
+ ResetPaletteFade();
+ remove_some_task();
+ ResetTasks();
+ ResetSpriteData();
+ gMain.state++;
+ break;
+ case 2:
+ SetUpWindowConfig(&gWindowConfig_81E71B4);
+ gMain.state++;
+ break;
+ case 3:
+ MultistepInitMenuWindowBegin(&gWindowConfig_81E71B4);
+ gMain.state++;
+ break;
+ case 4:
+ if (!MultistepInitMenuWindowContinue())
+ return;
+ gMain.state++;
+ break;
+ case 5:
+ LoadPalette(gUnknown_0839F5FC, 0x80, 0x40);
+ CpuCopy16(gUnknown_0839F63C, (void *)0x0600BEE0, 0x40);
+ gMain.state++;
+ break;
+ case 6:
+ BeginNormalPaletteFade(-1, 0, 0x10, 0, 0);
+ gMain.state++;
+ break;
+ case 7:
+ {
+ u16 savedIme;
+
+ REG_WIN0H = 0;
+ REG_WIN0V = 0;
+ REG_WIN1H = 0;
+ REG_WIN1V = 0;
+ REG_WININ = 0x1111;
+ REG_WINOUT = 0x31;
+ REG_BLDCNT = 0xE1;
+ REG_BLDALPHA = 0;
+ REG_BLDY = 7;
+ savedIme = REG_IME;
+ REG_IME = 0;
+ REG_IE |= INTR_FLAG_VBLANK;
+ REG_IME = savedIme;
+ REG_DISPSTAT |= DISPSTAT_VBLANK_INTR;
+ SetVBlankCallback(VBlankCB);
+ REG_DISPCNT = DISPCNT_MODE_0 | DISPCNT_OBJ_1D_MAP | DISPCNT_BG0_ON | DISPCNT_OBJ_ON |
+ DISPCNT_WIN0_ON | DISPCNT_WIN1_ON;
+ gMain.state++;
+ break;
+ }
+ case 8:
+ {
+ u8 taskId = CreateTask(Task_OptionMenuFadeIn, 0);
+
+ gTasks[taskId].data[TD_MENUSELECTION] = 0;
+ gTasks[taskId].data[TD_TEXTSPEED] = gSaveBlock2.optionsTextSpeed;
+ gTasks[taskId].data[TD_BATTLESCENE] = gSaveBlock2.optionsBattleSceneOff;
+ gTasks[taskId].data[TD_BATTLESTYLE] = gSaveBlock2.optionsBattleStyle;
+ gTasks[taskId].data[TD_SOUND] = gSaveBlock2.optionsSound;
+ gTasks[taskId].data[TD_BUTTONMODE] = gSaveBlock2.optionsButtonMode;
+ gTasks[taskId].data[TD_FRAMETYPE] = gSaveBlock2.optionsWindowFrameType;
+
+ MenuDrawTextWindow(2, 0, 27, 3);
+ MenuDrawTextWindow(2, 4, 27, 19);
+
+ MenuPrint(gSystemText_OptionMenu, 4, 1);
+ MenuPrint(gSystemText_TextSpeed, 4, 5);
+ MenuPrint(gSystemText_BattleScene, 4, 7);
+ MenuPrint(gSystemText_BattleStyle, 4, 9);
+ MenuPrint(gSystemText_Sound, 4, 11);
+ MenuPrint(gSystemText_ButtonMode, 4, 13);
+ MenuPrint(gSystemText_Frame, 4, 15);
+ MenuPrint(gSystemText_Cancel, 4, 17);
+
+ TextSpeed_DrawChoices(gTasks[taskId].data[TD_TEXTSPEED]);
+ BattleScene_DrawChoices(gTasks[taskId].data[TD_BATTLESCENE]);
+ BattleStyle_DrawChoices(gTasks[taskId].data[TD_BATTLESTYLE]);
+ Sound_DrawChoices(gTasks[taskId].data[TD_SOUND]);
+ ButtonMode_DrawChoices(gTasks[taskId].data[TD_BUTTONMODE]);
+ FrameType_DrawChoices(gTasks[taskId].data[TD_FRAMETYPE]);
+
+ REG_WIN0H = WIN_RANGE(17, 223);
+ REG_WIN0V = WIN_RANGE(1, 31);
+
+ HighlightOptionMenuItem(gTasks[taskId].data[TD_MENUSELECTION]);
+ gMain.state++;
+ break;
+ }
+ case 9:
+ SetMainCallback2(MainCB);
+ return;
+ }
+}
+
+static void Task_OptionMenuFadeIn(u8 taskId)
+{
+ if (!gPaletteFade.active)
+ {
+ gTasks[taskId].func = Task_OptionMenuProcessInput;
+ }
+}
+
+static void Task_OptionMenuProcessInput(u8 taskId)
+{
+ if (gMain.newKeys & A_BUTTON)
+ {
+ if (gTasks[taskId].data[TD_MENUSELECTION] == MENUITEM_CANCEL)
+ gTasks[taskId].func = Task_OptionMenuSave;
+ }
+ else if (gMain.newKeys & B_BUTTON)
+ {
+ gTasks[taskId].func = Task_OptionMenuSave;
+ }
+ else if (gMain.newKeys & DPAD_UP)
+ {
+ if (gTasks[taskId].data[TD_MENUSELECTION] > 0)
+ gTasks[taskId].data[TD_MENUSELECTION]--;
+ else
+ gTasks[taskId].data[TD_MENUSELECTION] = 6;
+ HighlightOptionMenuItem(gTasks[taskId].data[TD_MENUSELECTION]);
+ }
+ else if (gMain.newKeys & DPAD_DOWN)
+ {
+ if (gTasks[taskId].data[TD_MENUSELECTION] <= 5)
+ gTasks[taskId].data[TD_MENUSELECTION]++;
+ else
+ gTasks[taskId].data[TD_MENUSELECTION] = 0;
+ HighlightOptionMenuItem(gTasks[taskId].data[TD_MENUSELECTION]);
+ }
+ else
+ {
+ switch (gTasks[taskId].data[TD_MENUSELECTION])
+ {
+ case MENUITEM_TEXTSPEED:
+ gTasks[taskId].data[TD_TEXTSPEED] = TextSpeed_ProcessInput(gTasks[taskId].data[TD_TEXTSPEED]);
+ TextSpeed_DrawChoices(gTasks[taskId].data[TD_TEXTSPEED]);
+ break;
+ case MENUITEM_BATTLESCENE:
+ gTasks[taskId].data[TD_BATTLESCENE] = BattleScene_ProcessInput(gTasks[taskId].data[TD_BATTLESCENE]);
+ BattleScene_DrawChoices(gTasks[taskId].data[TD_BATTLESCENE]);
+ break;
+ case MENUITEM_BATTLESTYLE:
+ gTasks[taskId].data[TD_BATTLESTYLE] = BattleStyle_ProcessInput(gTasks[taskId].data[TD_BATTLESTYLE]);
+ BattleStyle_DrawChoices(gTasks[taskId].data[TD_BATTLESTYLE]);
+ break;
+ case MENUITEM_SOUND:
+ gTasks[taskId].data[TD_SOUND] = Sound_ProcessInput(gTasks[taskId].data[TD_SOUND]);
+ Sound_DrawChoices(gTasks[taskId].data[TD_SOUND]);
+ break;
+ case MENUITEM_BUTTONMODE:
+ gTasks[taskId].data[TD_BUTTONMODE] = ButtonMode_ProcessInput(gTasks[taskId].data[TD_BUTTONMODE]);
+ ButtonMode_DrawChoices(gTasks[taskId].data[TD_BUTTONMODE]);
+ break;
+ case MENUITEM_FRAMETYPE:
+ gTasks[taskId].data[TD_FRAMETYPE] = FrameType_ProcessInput(gTasks[taskId].data[TD_FRAMETYPE]);
+ FrameType_DrawChoices(gTasks[taskId].data[TD_FRAMETYPE]);
+ break;
+ }
+ }
+}
+
+static void Task_OptionMenuSave(u8 taskId)
+{
+ gSaveBlock2.optionsTextSpeed = gTasks[taskId].data[TD_TEXTSPEED];
+ gSaveBlock2.optionsBattleSceneOff = gTasks[taskId].data[TD_BATTLESCENE];
+ gSaveBlock2.optionsBattleStyle = gTasks[taskId].data[TD_BATTLESTYLE];
+ gSaveBlock2.optionsSound = gTasks[taskId].data[TD_SOUND];
+ gSaveBlock2.optionsButtonMode = gTasks[taskId].data[TD_BUTTONMODE];
+ gSaveBlock2.optionsWindowFrameType = gTasks[taskId].data[TD_FRAMETYPE];
+
+ BeginNormalPaletteFade(-1, 0, 0, 0x10, 0);
+ gTasks[taskId].func = Task_OptionMenuFadeOut;
+}
+
+static void Task_OptionMenuFadeOut(u8 taskId)
+{
+ if (!gPaletteFade.active)
+ {
+ DestroyTask(taskId);
+ SetMainCallback2(gMain.savedCallback);
+ }
+}
+
+//This version uses addition '+' instead of OR '|'.
+#define WIN_RANGE_(a, b) (((a) << 8) + (b))
+
+static void HighlightOptionMenuItem(u8 index)
+{
+ REG_WIN1H = WIN_RANGE(24, 215);
+ REG_WIN1V = WIN_RANGE_(index * 16 + 40, index * 16 + 56);
+}
+
+static void DrawOptionMenuChoice(const u8 *text, u8 x, u8 y, u8 style)
+{
+ u8 dst[16];
+ u16 i;
+
+ for (i = 0; *text != EOS && i <= 14; i++)
+ dst[i] = *(text++);
+
+ dst[2] = style;
+ dst[i] = EOS;
+ MenuPrint_PixelCoords(dst, x, y, 1);
+}
+
+static u8 TextSpeed_ProcessInput(u8 selection)
+{
+ if (gMain.newKeys & DPAD_RIGHT)
+ {
+ if (selection <= 1)
+ selection++;
+ else
+ selection = 0;
+ }
+ if (gMain.newKeys & DPAD_LEFT)
+ {
+ if (selection != 0)
+ selection--;
+ else
+ selection = 2;
+ }
+ return selection;
+}
+
+#if ENGLISH
+#define TEXTSPEED_SLOW_LEFT (120)
+#define TEXTSPEED_MIX_LEFT (155)
+#define TEXTSPEED_FAST_LEFT (184)
+#endif
+#if GERMAN
+#define TEXTSPEED_SLOW_LEFT (120)
+#define TEXTSPEED_MIX_LEFT (161)
+#define TEXTSPEED_FAST_LEFT (202)
+#endif
+
+static void TextSpeed_DrawChoices(u8 selection)
+{
+ u8 styles[3];
+
+ styles[0] = 0xF;
+ styles[1] = 0xF;
+ styles[2] = 0xF;
+ styles[selection] = 0x8;
+
+ DrawOptionMenuChoice(gSystemText_Slow, TEXTSPEED_SLOW_LEFT, 40, styles[0]);
+ DrawOptionMenuChoice(gSystemText_Mid, TEXTSPEED_MIX_LEFT, 40, styles[1]);
+ DrawOptionMenuChoice(gSystemText_Fast, TEXTSPEED_FAST_LEFT, 40, styles[2]);
+}
+
+static u8 BattleScene_ProcessInput(u8 selection)
+{
+ if (gMain.newKeys & (DPAD_LEFT | DPAD_RIGHT))
+ selection ^= 1;
+ return selection;
+}
+
+static void BattleScene_DrawChoices(u8 selection)
+{
+ u8 styles[2];
+
+ styles[0] = 0xF;
+ styles[1] = 0xF;
+ styles[selection] = 0x8;
+
+ DrawOptionMenuChoice(gSystemText_On, 120, 56, styles[0]);
+ DrawOptionMenuChoice(gSystemText_Off, 190, 56, styles[1]);
+}
+
+static u8 BattleStyle_ProcessInput(u8 selection)
+{
+ if (gMain.newKeys & (DPAD_LEFT | DPAD_RIGHT))
+ selection ^= 1;
+ return selection;
+}
+
+#if ENGLISH
+#define BATTLESTYLE_SHIFT (120)
+#define BATTLESTYLE_SET (190)
+#elif GERMAN
+#define BATTLESTYLE_SHIFT (120)
+#define BATTLESTYLE_SET (178)
+#endif
+
+static void BattleStyle_DrawChoices(u8 selection)
+{
+ u8 styles[2];
+
+ styles[0] = 0xF;
+ styles[1] = 0xF;
+ styles[selection] = 0x8;
+
+ DrawOptionMenuChoice(gSystemText_Shift, BATTLESTYLE_SHIFT, 72, styles[0]);
+ DrawOptionMenuChoice(gSystemText_Set, BATTLESTYLE_SET, 72, styles[1]);
+}
+
+static u8 Sound_ProcessInput(u8 selection)
+{
+ if (gMain.newKeys & (DPAD_LEFT | DPAD_RIGHT))
+ {
+ selection ^= 1;
+ SetPokemonCryStereo(selection);
+ }
+ return selection;
+}
+
+static void Sound_DrawChoices(u8 selection)
+{
+ u8 styles[3];
+
+ styles[0] = 0xF;
+ styles[1] = 0xF;
+ styles[selection] = 0x8;
+
+ DrawOptionMenuChoice(gSystemText_Mono, 120, 88, styles[0]);
+ DrawOptionMenuChoice(gSystemText_Stereo, 172, 88, styles[1]);
+}
+
+static u8 FrameType_ProcessInput(u8 selection)
+{
+ if (gMain.newKeys & DPAD_RIGHT)
+ {
+ if (selection <= 18)
+ selection++;
+ else
+ selection = 0;
+ MenuLoadTextWindowGraphics_OverrideFrameType(selection);
+ }
+ if (gMain.newKeys & DPAD_LEFT)
+ {
+ if (selection != 0)
+ selection--;
+ else
+ selection = 19;
+ MenuLoadTextWindowGraphics_OverrideFrameType(selection);
+ }
+ return selection;
+}
+
+#define CHAR_0 0xA1 //Character code of '0' character
+
+#if ENGLISH
+static void FrameType_DrawChoices(u8 selection)
+{
+ u8 text[8];
+ u8 n = selection + 1;
+ u16 i;
+
+ for (i = 0; gSystemText_Terminator[i] != EOS && i <= 5; i++)
+ text[i] = gSystemText_Terminator[i];
+
+ //Convert number to decimal string
+ if (n / 10 != 0)
+ {
+ text[i] = n / 10 + CHAR_0;
+ i++;
+ text[i] = n % 10 + CHAR_0;
+ i++;
+ }
+ else
+ {
+ text[i] = n % 10 + CHAR_0;
+ i++;
+ text[i] = CHAR_SPACE;
+ i++;
+ }
+
+ text[i] = EOS;
+ MenuPrint(gSystemText_Type, 15, 15);
+ MenuPrint(text, 18, 15);
+}
+#elif GERMAN
+__attribute__((naked))
+static void FrameType_DrawChoices(u8 selection)
+{
+ asm(".syntax unified\n\
+ push {r4-r6,lr}\n\
+ sub sp, 0x10\n\
+ lsls r0, 24\n\
+ movs r1, 0x80\n\
+ lsls r1, 17\n\
+ adds r0, r1\n\
+ lsrs r5, r0, 24\n\
+ ldr r1, _0808C368 @ =gSystemText_Type\n\
+ mov r0, sp\n\
+ bl StringCopy\n\
+ ldr r1, _0808C36C @ =gSystemText_Terminator\n\
+ mov r0, sp\n\
+ bl StringAppend\n\
+ adds r4, r0, 0\n\
+ adds r0, r5, 0\n\
+ movs r1, 0xA\n\
+ bl __udivsi3\n\
+ adds r1, r0, 0\n\
+ lsls r0, r1, 24\n\
+ lsrs r6, r0, 24\n\
+ cmp r6, 0\n\
+ beq _0808C370\n\
+ adds r0, r1, 0\n\
+ adds r0, 0xA1\n\
+ strb r0, [r4]\n\
+ adds r4, 0x1\n\
+ adds r0, r5, 0\n\
+ movs r1, 0xA\n\
+ bl __umodsi3\n\
+ adds r0, 0xA1\n\
+ strb r0, [r4]\n\
+ b _0808C380\n\
+ .align 2, 0\n\
+_0808C368: .4byte gSystemText_Type\n\
+_0808C36C: .4byte gSystemText_Terminator\n\
+_0808C370:\n\
+ adds r0, r5, 0\n\
+ movs r1, 0xA\n\
+ bl __umodsi3\n\
+ adds r0, 0xA1\n\
+ strb r0, [r4]\n\
+ adds r4, 0x1\n\
+ strb r6, [r4]\n\
+_0808C380:\n\
+ adds r4, 0x1\n\
+ movs r0, 0xFF\n\
+ strb r0, [r4]\n\
+ mov r0, sp\n\
+ movs r1, 0xF\n\
+ movs r2, 0xF\n\
+ bl MenuPrint\n\
+ add sp, 0x10\n\
+ pop {r4-r6}\n\
+ pop {r0}\n\
+ bx r0\n\
+ .syntax divided\n");
+}
+#endif
+
+static u8 ButtonMode_ProcessInput(u8 selection)
+{
+ if (gMain.newKeys & DPAD_RIGHT)
+ {
+ if (selection <= 1)
+ selection++;
+ else
+ selection = 0;
+ }
+ if (gMain.newKeys & DPAD_LEFT)
+ {
+ if (selection != 0)
+ selection--;
+ else
+ selection = 2;
+ }
+ return selection;
+}
+
+static void ButtonMode_DrawChoices(u8 selection)
+{
+ u8 styles[3];
+
+ styles[0] = 0xF;
+ styles[1] = 0xF;
+ styles[2] = 0xF;
+ styles[selection] = 0x8;
+
+ DrawOptionMenuChoice(gSystemText_Normal, 120, 104, styles[0]);
+ DrawOptionMenuChoice(gSystemText_LR, 166, 104, styles[1]);
+ DrawOptionMenuChoice(gSystemText_LA, 188, 104, styles[2]);
+}
diff --git a/src/engine/palette.c b/src/engine/palette.c
new file mode 100644
index 000000000..17e9ca178
--- /dev/null
+++ b/src/engine/palette.c
@@ -0,0 +1,834 @@
+#include "global.h"
+#include "palette.h"
+#include "blend_palette.h"
+#include "decompress.h"
+
+enum
+{
+ NORMAL_FADE,
+ FAST_FADE,
+ HARDWARE_FADE,
+};
+
+// These are structs for some unused palette system.
+// The full functionality of this system is unknown.
+
+struct PaletteStructTemplate
+{
+ u16 uid;
+ u16 *src;
+ u16 pst_field_8_0:1;
+ u16 pst_field_8_1:9;
+ u16 size:5;
+ u16 pst_field_9_7:1;
+ u8 pst_field_A;
+ u8 srcCount:5;
+ u8 pst_field_B_5:3;
+ u8 pst_field_C;
+};
+
+struct PaletteStruct
+{
+ const struct PaletteStructTemplate *base;
+ u32 ps_field_4_0:1;
+ u16 ps_field_4_1:1;
+ u32 baseDestOffset:9;
+ u16 destOffset:10;
+ u16 srcIndex:7;
+ u8 ps_field_8;
+ u8 ps_field_9;
+};
+
+EWRAM_DATA u16 gPlttBufferUnfaded[0x200] = {0};
+EWRAM_DATA u16 gPlttBufferFaded[0x200] = {0};
+EWRAM_DATA static struct PaletteStruct sPaletteStructs[0x10] = {0};
+EWRAM_DATA struct PaletteFadeControl gPaletteFade = {0};
+EWRAM_DATA u32 gFiller_202F394 = 0;
+EWRAM_DATA static u32 sPlttBufferTransferPending = 0;
+EWRAM_DATA static u8 sPaletteDecompressionBuffer[0x400] = {0};
+
+static const struct PaletteStructTemplate sDummyPaletteStructTemplate =
+{
+ 0xFFFF,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 0
+};
+
+static void unused_sub_8073DFC(struct PaletteStruct *, u32 *);
+static void unused_sub_8073F60(struct PaletteStruct *, u32 *);
+static void unused_sub_8074020(struct PaletteStruct *);
+static u8 GetPaletteNumByUid(u16);
+static u8 UpdateNormalPaletteFade(void);
+static void BeginFastPaletteFadeInternal(u8);
+static u8 UpdateFastPaletteFade(void);
+static u8 UpdateHardwarePaletteFade(void);
+static void UpdateBlendRegisters(void);
+static bool8 IsSoftwarePaletteFadeFinishing(void);
+
+void LoadCompressedPalette(const void *src, u16 offset, u16 size)
+{
+ LZDecompressWram(src, sPaletteDecompressionBuffer);
+ CpuCopy16(sPaletteDecompressionBuffer, gPlttBufferUnfaded + offset, size);
+ CpuCopy16(sPaletteDecompressionBuffer, gPlttBufferFaded + offset, size);
+}
+
+void LoadPalette(const void *src, u16 offset, u16 size)
+{
+ CpuCopy16(src, gPlttBufferUnfaded + offset, size);
+ CpuCopy16(src, gPlttBufferFaded + offset, size);
+}
+
+void FillPalette(u16 value, u16 offset, u16 size)
+{
+ CpuFill16(value, gPlttBufferUnfaded + offset, size);
+ CpuFill16(value, gPlttBufferFaded + offset, size);
+}
+
+void TransferPlttBuffer(void)
+{
+ if (!gPaletteFade.bufferTransferDisabled)
+ {
+ void *src = gPlttBufferFaded;
+ void *dest = (void *)PLTT;
+ DmaCopy16(3, src, dest, PLTT_SIZE);
+ sPlttBufferTransferPending = 0;
+ if (gPaletteFade.mode == HARDWARE_FADE && gPaletteFade.active)
+ UpdateBlendRegisters();
+ }
+}
+
+u8 UpdatePaletteFade(void)
+{
+ u8 result;
+ u8 dummy = 0;
+
+ if (sPlttBufferTransferPending)
+ return -1;
+
+ if (gPaletteFade.mode == NORMAL_FADE)
+ result = UpdateNormalPaletteFade();
+ else if (gPaletteFade.mode == FAST_FADE)
+ result = UpdateFastPaletteFade();
+ else
+ result = UpdateHardwarePaletteFade();
+
+ sPlttBufferTransferPending = gPaletteFade.multipurpose1 | dummy;
+
+ return result;
+}
+
+void ResetPaletteFade(void)
+{
+ u8 i;
+
+ for (i = 0; i < 16; i++)
+ ResetPaletteStruct(i);
+
+ ResetPaletteFadeControl();
+}
+
+void ReadPlttIntoBuffers(void)
+{
+ u16 i;
+ u16 *pltt = (u16 *)PLTT;
+
+ for (i = 0; i < PLTT_SIZE / 2; i++)
+ {
+ gPlttBufferUnfaded[i] = pltt[i];
+ gPlttBufferFaded[i] = pltt[i];
+ }
+}
+
+bool8 BeginNormalPaletteFade(u32 selectedPalettes, s8 delay, u8 startY, u8 targetY, u16 blendColor)
+{
+ u8 temp;
+ register u32 _blendColor asm("r8") = blendColor;
+
+ if (gPaletteFade.active)
+ {
+ return FALSE;
+ }
+ else
+ {
+ gPaletteFade.deltaY = 2;
+
+ if (delay < 0)
+ {
+ gPaletteFade.deltaY += (delay * -1);
+ delay = 0;
+ }
+
+ gPaletteFade_selectedPalettes = selectedPalettes;
+ gPaletteFade.delayCounter = delay;
+ gPaletteFade_delay = delay;
+ gPaletteFade.y = startY;
+ gPaletteFade.targetY = targetY;
+ gPaletteFade.blendColor = _blendColor;
+ gPaletteFade.active = 1;
+ gPaletteFade.mode = NORMAL_FADE;
+
+ if (startY < targetY)
+ gPaletteFade.yDec = 0;
+ else
+ gPaletteFade.yDec = 1;
+
+ UpdatePaletteFade();
+
+ temp = gPaletteFade.bufferTransferDisabled;
+ gPaletteFade.bufferTransferDisabled = 0;
+ CpuCopy32(gPlttBufferFaded, (void *)PLTT, PLTT_SIZE);
+ sPlttBufferTransferPending = 0;
+ if (gPaletteFade.mode == HARDWARE_FADE && gPaletteFade.active)
+ UpdateBlendRegisters();
+ gPaletteFade.bufferTransferDisabled = temp;
+ return TRUE;
+ }
+}
+
+bool8 unref_sub_8073D3C(u32 a1, u8 a2, u8 a3, u8 a4, u16 a5)
+{
+ ReadPlttIntoBuffers();
+ return BeginNormalPaletteFade(a1, a2, a3, a4, a5);
+}
+
+void unref_sub_8073D84(u8 a1, u32 *a2)
+{
+ u8 i;
+
+ for (i = 0; i < 16; i++)
+ {
+ struct PaletteStruct *palstruct = &sPaletteStructs[i];
+ if (palstruct->ps_field_4_0)
+ {
+ if (palstruct->base->pst_field_8_0 == a1)
+ {
+ u8 val1 = palstruct->srcIndex;
+ u8 val2 = palstruct->base->srcCount;
+ if (val1 == val2)
+ {
+ unused_sub_8074020(palstruct);
+ if (!palstruct->ps_field_4_0)
+ continue;
+ }
+ if (palstruct->ps_field_8 == 0)
+ unused_sub_8073DFC(palstruct, a2);
+ else
+ palstruct->ps_field_8--;
+
+ unused_sub_8073F60(palstruct, a2);
+ }
+ }
+ }
+}
+
+static void unused_sub_8073DFC(struct PaletteStruct *a1, u32 *a2)
+{
+ s32 srcIndex;
+ s32 srcCount;
+ u8 i = 0;
+ u16 srcOffset = a1->srcIndex * a1->base->size;
+
+ if (!a1->base->pst_field_8_0)
+ {
+ while (i < a1->base->size)
+ {
+ gPlttBufferUnfaded[a1->destOffset] = a1->base->src[srcOffset];
+ gPlttBufferFaded[a1->destOffset] = a1->base->src[srcOffset];
+ i++;
+ a1->destOffset++;
+ srcOffset++;
+ }
+ }
+ else
+ {
+ while (i < a1->base->size)
+ {
+ gPlttBufferFaded[a1->destOffset] = a1->base->src[srcOffset];
+ i++;
+ a1->destOffset++;
+ srcOffset++;
+ }
+ }
+
+ a1->destOffset = a1->baseDestOffset;
+ a1->ps_field_8 = a1->base->pst_field_A;
+ a1->srcIndex++;
+
+ srcIndex = a1->srcIndex;
+ srcCount = a1->base->srcCount;
+
+ if (srcIndex >= srcCount)
+ {
+ if (a1->ps_field_9)
+ a1->ps_field_9--;
+ a1->srcIndex = 0;
+ }
+
+ *a2 |= 1 << (a1->baseDestOffset >> 4);
+}
+
+static void unused_sub_8073F60(struct PaletteStruct *a1, u32 *a2)
+{
+ if (gPaletteFade.active && ((1 << (a1->baseDestOffset >> 4)) & gPaletteFade_selectedPalettes))
+ {
+ if (!a1->base->pst_field_8_0)
+ {
+ if (gPaletteFade.delayCounter != gPaletteFade_delay)
+ {
+ BlendPalette(
+ a1->baseDestOffset,
+ a1->base->size,
+ gPaletteFade.y,
+ gPaletteFade.blendColor);
+ }
+ }
+ else
+ {
+ if (!gPaletteFade.delayCounter)
+ {
+ if (a1->ps_field_8 != a1->base->pst_field_A)
+ {
+ u32 srcOffset = a1->srcIndex * a1->base->size;
+ u8 i;
+
+ for (i = 0; i < a1->base->size; i++)
+ gPlttBufferFaded[a1->baseDestOffset + i] = a1->base->src[srcOffset + i];
+ }
+ }
+ }
+ }
+}
+
+static void unused_sub_8074020(struct PaletteStruct *a1)
+{
+ if (!a1->ps_field_9)
+ {
+ s32 val = a1->base->pst_field_B_5;
+
+ if (!val)
+ {
+ a1->srcIndex = 0;
+ a1->ps_field_8 = a1->base->pst_field_A;
+ a1->ps_field_9 = a1->base->pst_field_C;
+ a1->destOffset = a1->baseDestOffset;
+ }
+ else
+ {
+ if (val < 0)
+ return;
+ if (val > 2)
+ return;
+ ResetPaletteStructByUid(a1->base->uid);
+ }
+ }
+ else
+ {
+ a1->ps_field_9--;
+ }
+}
+
+void ResetPaletteStructByUid(u16 a1)
+{
+ u8 paletteNum = GetPaletteNumByUid(a1);
+ if (paletteNum != 16)
+ ResetPaletteStruct(paletteNum);
+}
+
+void ResetPaletteStruct(u8 paletteNum)
+{
+ sPaletteStructs[paletteNum].base = &sDummyPaletteStructTemplate;
+ sPaletteStructs[paletteNum].ps_field_4_0 = 0;
+ sPaletteStructs[paletteNum].baseDestOffset = 0;
+ sPaletteStructs[paletteNum].destOffset = 0;
+ sPaletteStructs[paletteNum].srcIndex = 0;
+ sPaletteStructs[paletteNum].ps_field_4_1 = 0;
+ sPaletteStructs[paletteNum].ps_field_8 = 0;
+ sPaletteStructs[paletteNum].ps_field_9 = 0;
+}
+
+void ResetPaletteFadeControl()
+{
+ gPaletteFade.multipurpose1 = 0;
+ gPaletteFade.multipurpose2 = 0;
+ gPaletteFade.delayCounter = 0;
+ gPaletteFade.y = 0;
+ gPaletteFade.targetY = 0;
+ gPaletteFade.blendColor = 0;
+ gPaletteFade.active = 0;
+ gPaletteFade.multipurpose2 = 0; // assign same value twice
+ gPaletteFade.yDec = 0;
+ gPaletteFade.bufferTransferDisabled = 0;
+ gPaletteFade.shouldResetBlendRegisters = 0;
+ gPaletteFade.hardwareFadeFinishing = 0;
+ gPaletteFade.softwareFadeFinishing = 0;
+ gPaletteFade.softwareFadeFinishingCounter = 0;
+ gPaletteFade.objPaletteToggle = 0;
+ gPaletteFade.deltaY = 2;
+}
+
+void unref_sub_8074168(u16 uid)
+{
+ u8 paletteNum = GetPaletteNumByUid(uid);
+ if (paletteNum != 16)
+ sPaletteStructs[paletteNum].ps_field_4_1 = 1;
+}
+
+void unref_sub_8074194(u16 uid)
+{
+ u8 paletteNum = GetPaletteNumByUid(uid);
+ if (paletteNum != 16)
+ sPaletteStructs[paletteNum].ps_field_4_1 = 0;
+}
+
+static u8 GetPaletteNumByUid(u16 uid)
+{
+ u8 i;
+
+ for (i = 0; i < 16; i++)
+ if (sPaletteStructs[i].base->uid == uid)
+ return i;
+
+ return 16;
+}
+
+static u8 UpdateNormalPaletteFade()
+{
+ u16 paletteOffset;
+ u16 selectedPalettes;
+
+ if (!gPaletteFade.active)
+ return 0;
+
+ if (IsSoftwarePaletteFadeFinishing())
+ {
+ return gPaletteFade.active;
+ }
+ else
+ {
+ if (!gPaletteFade.objPaletteToggle)
+ {
+ if (gPaletteFade.delayCounter < gPaletteFade_delay)
+ {
+ gPaletteFade.delayCounter++;
+ return 2;
+ }
+ gPaletteFade.delayCounter = 0;
+ }
+
+ paletteOffset = 0;
+
+ if (!gPaletteFade.objPaletteToggle)
+ {
+ selectedPalettes = gPaletteFade_selectedPalettes;
+ }
+ else
+ {
+ selectedPalettes = gPaletteFade_selectedPalettes >> 16;
+ paletteOffset = 256;
+ }
+
+ while (selectedPalettes)
+ {
+ if (selectedPalettes & 1)
+ BlendPalette(
+ paletteOffset,
+ 16,
+ gPaletteFade.y,
+ gPaletteFade.blendColor);
+ selectedPalettes >>= 1;
+ paletteOffset += 16;
+ }
+
+ gPaletteFade.objPaletteToggle ^= 1;
+
+ if (!gPaletteFade.objPaletteToggle)
+ {
+ if (gPaletteFade.y == gPaletteFade.targetY)
+ {
+ gPaletteFade_selectedPalettes = 0;
+ gPaletteFade.softwareFadeFinishing = 1;
+ }
+ else
+ {
+ s8 val;
+
+ if (!gPaletteFade.yDec)
+ {
+ val = gPaletteFade.y;
+ val += gPaletteFade.deltaY;
+ if (val > gPaletteFade.targetY)
+ val = gPaletteFade.targetY;
+ gPaletteFade.y = val;
+ }
+ else
+ {
+ val = gPaletteFade.y;
+ val -= gPaletteFade.deltaY;
+ if (val < gPaletteFade.targetY)
+ val = gPaletteFade.targetY;
+ gPaletteFade.y = val;
+ }
+ }
+ }
+
+ return gPaletteFade.active;
+ }
+}
+
+void InvertPlttBuffer(u32 selectedPalettes)
+{
+ u16 paletteOffset = 0;
+
+ while (selectedPalettes)
+ {
+ if (selectedPalettes & 1)
+ {
+ u8 i;
+ for (i = 0; i < 16; i++)
+ gPlttBufferFaded[paletteOffset + i] = ~gPlttBufferFaded[paletteOffset + i];
+ }
+ selectedPalettes >>= 1;
+ paletteOffset += 16;
+ }
+}
+
+void TintPlttBuffer(u32 selectedPalettes, s8 r, s8 g, s8 b)
+{
+ u16 paletteOffset = 0;
+
+ while (selectedPalettes)
+ {
+ if (selectedPalettes & 1)
+ {
+ u8 i;
+ for (i = 0; i < 16; i++)
+ {
+ struct PlttData *data = (struct PlttData *)&gPlttBufferFaded[paletteOffset + i];
+ data->r += r;
+ data->g += g;
+ data->b += b;
+ }
+ }
+ selectedPalettes >>= 1;
+ paletteOffset += 16;
+ }
+}
+
+void UnfadePlttBuffer(u32 selectedPalettes)
+{
+ u16 paletteOffset = 0;
+
+ while (selectedPalettes)
+ {
+ if (selectedPalettes & 1)
+ {
+ u8 i;
+ for (i = 0; i < 16; i++)
+ gPlttBufferFaded[paletteOffset + i] = gPlttBufferUnfaded[paletteOffset + i];
+ }
+ selectedPalettes >>= 1;
+ paletteOffset += 16;
+ }
+}
+
+void BeginFastPaletteFade(u8 submode)
+{
+ gPaletteFade.deltaY = 2;
+ BeginFastPaletteFadeInternal(submode);
+}
+
+static void BeginFastPaletteFadeInternal(u8 submode)
+{
+ gPaletteFade.y = 31;
+ gPaletteFade_submode = submode & 0x3F;
+ gPaletteFade.active = 1;
+ gPaletteFade.mode = FAST_FADE;
+
+ if (submode == FAST_FADE_IN_FROM_BLACK)
+ CpuFill16(RGB_BLACK, gPlttBufferFaded, PLTT_SIZE);
+
+ if (submode == FAST_FADE_IN_FROM_WHITE)
+ CpuFill16(RGB_WHITE, gPlttBufferFaded, PLTT_SIZE);
+
+ UpdatePaletteFade();
+}
+
+static u8 UpdateFastPaletteFade(void)
+{
+ u16 i;
+ u16 paletteOffsetStart;
+ u16 paletteOffsetEnd;
+ s8 r0;
+ s8 g0;
+ s8 b0;
+ s8 r;
+ s8 g;
+ s8 b;
+
+ if (!gPaletteFade.active)
+ return 0;
+
+ if (IsSoftwarePaletteFadeFinishing())
+ return gPaletteFade.active;
+
+ if (gPaletteFade.objPaletteToggle)
+ {
+ paletteOffsetStart = 256;
+ paletteOffsetEnd = 512;
+ }
+ else
+ {
+ paletteOffsetStart = 0;
+ paletteOffsetEnd = 256;
+ }
+
+ switch (gPaletteFade_submode)
+ {
+ case FAST_FADE_IN_FROM_WHITE:
+ for (i = paletteOffsetStart; i < paletteOffsetEnd; i++)
+ {
+ struct PlttData *unfaded;
+ struct PlttData *faded;
+
+ unfaded = (struct PlttData *)&gPlttBufferUnfaded[i];
+ r0 = unfaded->r;
+ g0 = unfaded->g;
+ b0 = unfaded->b;
+
+ faded = (struct PlttData *)&gPlttBufferFaded[i];
+ r = faded->r - 2;
+ g = faded->g - 2;
+ b = faded->b - 2;
+
+ if (r < r0)
+ r = r0;
+ if (g < g0)
+ g = g0;
+ if (b < b0)
+ b = b0;
+
+ gPlttBufferFaded[i] = RGB(r, g, b);
+ }
+ break;
+ case FAST_FADE_OUT_TO_WHTIE:
+ for (i = paletteOffsetStart; i < paletteOffsetEnd; i++)
+ {
+ struct PlttData *data = (struct PlttData *)&gPlttBufferFaded[i];
+ r = data->r + 2;
+ g = data->g + 2;
+ b = data->b + 2;
+
+ if (r > 31)
+ r = 31;
+ if (g > 31)
+ g = 31;
+ if (b > 31)
+ b = 31;
+
+ gPlttBufferFaded[i] = RGB(r, g, b);
+ }
+ break;
+ case FAST_FADE_IN_FROM_BLACK:
+ for (i = paletteOffsetStart; i < paletteOffsetEnd; i++)
+ {
+ struct PlttData *unfaded;
+ struct PlttData *faded;
+
+ unfaded = (struct PlttData *)&gPlttBufferUnfaded[i];
+ r0 = unfaded->r;
+ g0 = unfaded->g;
+ b0 = unfaded->b;
+
+ faded = (struct PlttData *)&gPlttBufferFaded[i];
+ r = faded->r + 2;
+ g = faded->g + 2;
+ b = faded->b + 2;
+
+ if (r > r0)
+ r = r0;
+ if (g > g0)
+ g = g0;
+ if (b > b0)
+ b = b0;
+
+ gPlttBufferFaded[i] = RGB(r, g, b);
+ }
+ break;
+ case FAST_FADE_OUT_TO_BLACK:
+ for (i = paletteOffsetStart; i < paletteOffsetEnd; i++)
+ {
+ struct PlttData *data = (struct PlttData *)&gPlttBufferFaded[i];
+ r = data->r - 2;
+ g = data->g - 2;
+ b = data->b - 2;
+
+ if (r < 0)
+ r = 0;
+ if (g < 0)
+ g = 0;
+ if (b < 0)
+ b = 0;
+
+ gPlttBufferFaded[i] = RGB(r, g, b);
+ }
+ }
+
+ gPaletteFade.objPaletteToggle ^= 1;
+
+ if (gPaletteFade.objPaletteToggle)
+ return gPaletteFade.active;
+
+ if (gPaletteFade.y - gPaletteFade.deltaY < 0)
+ gPaletteFade.y = 0;
+ else
+ gPaletteFade.y -= gPaletteFade.deltaY;
+
+ if (gPaletteFade.y == 0)
+ {
+ switch (gPaletteFade_submode)
+ {
+ case FAST_FADE_IN_FROM_WHITE:
+ case FAST_FADE_IN_FROM_BLACK:
+ CpuCopy32(gPlttBufferUnfaded, gPlttBufferFaded, PLTT_SIZE);
+ break;
+ case FAST_FADE_OUT_TO_WHTIE:
+ CpuFill32(0xFFFFFFFF, gPlttBufferFaded, PLTT_SIZE);
+ break;
+ case FAST_FADE_OUT_TO_BLACK:
+ CpuFill32(0x00000000, gPlttBufferFaded, PLTT_SIZE);
+ break;
+ }
+
+ gPaletteFade.mode = NORMAL_FADE;
+ gPaletteFade.softwareFadeFinishing = 1;
+ }
+
+ return gPaletteFade.active;
+}
+
+void BeginHardwarePaletteFade(u8 blendCnt, u8 delay, u8 y, u8 targetY, u8 shouldResetBlendRegisters)
+{
+ gPaletteFade_blendCnt = blendCnt;
+ gPaletteFade.delayCounter = delay;
+ gPaletteFade_delay = delay;
+ gPaletteFade.y = y;
+ gPaletteFade.targetY = targetY;
+ gPaletteFade.active = 1;
+ gPaletteFade.mode = HARDWARE_FADE;
+ gPaletteFade.shouldResetBlendRegisters = shouldResetBlendRegisters & 1;
+ gPaletteFade.hardwareFadeFinishing = 0;
+
+ if (y < targetY)
+ gPaletteFade.yDec = 0;
+ else
+ gPaletteFade.yDec = 1;
+}
+
+static u8 UpdateHardwarePaletteFade(void)
+{
+ if (!gPaletteFade.active)
+ return 0;
+
+ if (gPaletteFade.delayCounter < gPaletteFade_delay)
+ {
+ gPaletteFade.delayCounter++;
+ return 2;
+ }
+
+ gPaletteFade.delayCounter = 0;
+
+ if (!gPaletteFade.yDec)
+ {
+ gPaletteFade.y++;
+ if (gPaletteFade.y > gPaletteFade.targetY)
+ {
+ gPaletteFade.hardwareFadeFinishing++;
+ gPaletteFade.y--;
+ }
+ }
+ else
+ {
+ s32 y = gPaletteFade.y--;
+ if (y - 1 < gPaletteFade.targetY)
+ {
+ gPaletteFade.hardwareFadeFinishing++;
+ gPaletteFade.y++;
+ }
+ }
+
+ if (gPaletteFade.hardwareFadeFinishing)
+ {
+ if (gPaletteFade.shouldResetBlendRegisters)
+ {
+ gPaletteFade_blendCnt = 0;
+ gPaletteFade.y = 0;
+ }
+ gPaletteFade.shouldResetBlendRegisters = 0;
+ }
+
+ return gPaletteFade.active;
+}
+
+static void UpdateBlendRegisters(void)
+{
+ REG_BLDCNT = gPaletteFade_blendCnt;
+ REG_BLDY = gPaletteFade.y;
+ if (gPaletteFade.hardwareFadeFinishing)
+ {
+ gPaletteFade.hardwareFadeFinishing = 0;
+ gPaletteFade.mode = 0;
+ gPaletteFade_blendCnt = 0;
+ gPaletteFade.y = 0;
+ gPaletteFade.active = 0;
+ }
+}
+
+static bool8 IsSoftwarePaletteFadeFinishing(void)
+{
+ if (gPaletteFade.softwareFadeFinishing)
+ {
+ if (gPaletteFade.softwareFadeFinishingCounter == 4)
+ {
+ gPaletteFade.active = 0;
+ gPaletteFade.softwareFadeFinishing = 0;
+ gPaletteFade.softwareFadeFinishingCounter = 0;
+ }
+ else
+ {
+ gPaletteFade.softwareFadeFinishingCounter++;
+ }
+
+ return TRUE;
+ }
+ else
+ {
+ return FALSE;
+ }
+}
+
+void BlendPalettes(u32 selectedPalettes, u8 coeff, u16 color)
+{
+ u16 paletteOffset;
+
+ for (paletteOffset = 0; selectedPalettes; paletteOffset += 16)
+ {
+ if (selectedPalettes & 1)
+ BlendPalette(paletteOffset, 16, coeff, color);
+ selectedPalettes >>= 1;
+ }
+}
+
+void BlendPalettesUnfaded(u32 selectedPalettes, u8 coeff, u16 color)
+{
+ void *src = gPlttBufferUnfaded;
+ void *dest = gPlttBufferFaded;
+ DmaCopy32(3, src, dest, PLTT_SIZE);
+ BlendPalettes(selectedPalettes, coeff, color);
+}
diff --git a/src/engine/play_time.c b/src/engine/play_time.c
new file mode 100644
index 000000000..9882c9c4b
--- /dev/null
+++ b/src/engine/play_time.c
@@ -0,0 +1,73 @@
+#include "global.h"
+#include "play_time.h"
+
+enum
+{
+ STOPPED,
+ RUNNING,
+ MAXED_OUT
+};
+
+static u8 sPlayTimeCounterState;
+
+void PlayTimeCounter_Reset()
+{
+ sPlayTimeCounterState = STOPPED;
+
+ gSaveBlock2.playTimeHours = 0;
+ gSaveBlock2.playTimeMinutes = 0;
+ gSaveBlock2.playTimeSeconds = 0;
+ gSaveBlock2.playTimeVBlanks = 0;
+}
+
+void PlayTimeCounter_Start()
+{
+ sPlayTimeCounterState = RUNNING;
+
+ if (gSaveBlock2.playTimeHours > 999)
+ PlayTimeCounter_SetToMax();
+}
+
+void PlayTimeCounter_Stop()
+{
+ sPlayTimeCounterState = STOPPED;
+}
+
+void PlayTimeCounter_Update()
+{
+ if (sPlayTimeCounterState == RUNNING)
+ {
+ gSaveBlock2.playTimeVBlanks++;
+
+ if (gSaveBlock2.playTimeVBlanks > 59)
+ {
+ gSaveBlock2.playTimeVBlanks = 0;
+ gSaveBlock2.playTimeSeconds++;
+
+ if (gSaveBlock2.playTimeSeconds > 59)
+ {
+ gSaveBlock2.playTimeSeconds = 0;
+ gSaveBlock2.playTimeMinutes++;
+
+ if (gSaveBlock2.playTimeMinutes > 59)
+ {
+ gSaveBlock2.playTimeMinutes = 0;
+ gSaveBlock2.playTimeHours++;
+
+ if (gSaveBlock2.playTimeHours > 999)
+ PlayTimeCounter_SetToMax();
+ }
+ }
+ }
+ }
+}
+
+void PlayTimeCounter_SetToMax()
+{
+ sPlayTimeCounterState = MAXED_OUT;
+
+ gSaveBlock2.playTimeHours = 999;
+ gSaveBlock2.playTimeMinutes = 59;
+ gSaveBlock2.playTimeSeconds = 59;
+ gSaveBlock2.playTimeVBlanks = 59;
+}
diff --git a/src/engine/record_mixing.c b/src/engine/record_mixing.c
new file mode 100644
index 000000000..8dff432c1
--- /dev/null
+++ b/src/engine/record_mixing.c
@@ -0,0 +1,1085 @@
+#include "global.h"
+#include "record_mixing.h"
+#include "battle_tower.h"
+#include "cable_club.h"
+#include "daycare.h"
+#include "dewford_trend.h"
+#include "event_data.h"
+#include "fldeff_80C5CD4.h"
+#include "item.h"
+#include "items.h"
+#include "load_save.h"
+#include "link.h"
+#include "mauville_man.h"
+#include "menu.h"
+#include "mystery_event_script.h"
+#include "rng.h"
+#include "overworld.h"
+#include "save.h"
+#include "script.h"
+#include "secret_base.h"
+#include "songs.h"
+#include "sound.h"
+#include "string_util.h"
+#include "strings2.h"
+#include "task.h"
+#include "tv.h"
+
+extern u8 ewram[];
+#define unk_2018000 (*(struct PlayerRecords *)(ewram + 0x18000))
+#define unk_2008000 (*(struct PlayerRecords *)(ewram + 0x08000))
+
+extern struct RecordMixingDayCareMail gUnknown_02038738;
+extern u16 gSpecialVar_0x8005;
+
+u32 gUnknown_03005D2C;
+
+static u8 gUnknown_03000718;
+static u8 gUnknown_0300071C[4];
+
+void *recordMixingSecretBases = &gSaveBlock1.secretBases;
+void *recordMixingTvShows = &gSaveBlock1.tvShows;
+void *gUnknown_083D0274 = &gSaveBlock1.unknown_2ABC;
+void *gUnknown_083D0278 = &gSaveBlock1.mauvilleMan;
+void *recordMixingEasyChatPairs = &gSaveBlock1.easyChatPairs;
+struct RecordMixingDayCareMail *gUnknown_083D0280 = &gUnknown_02038738;
+void *gUnknown_083D0284 = &gSaveBlock2.filler_A8;
+
+#define BUFFER_CHUNK_SIZE 200
+
+void sub_80B929C(void)
+{
+ sub_8083A84(Task_RecordMixing_Main);
+}
+
+struct PlayerRecords
+{
+ struct SecretBaseRecord secretBases[20];
+ TVShow tvShows[25];
+ u8 filler1004[0x40];
+ u8 filler1044[0x40];
+ struct EasyChatPair easyChatPairs[5];
+ struct RecordMixingDayCareMail filler10AC;
+ u8 filler1124[0xA4];
+ u16 filler11C8[0x34];
+};
+
+void RecordMixing_PrepareExchangePacket(void)
+{
+ sub_80BC300();
+ sub_80C045C();
+
+ memcpy(unk_2018000.secretBases, recordMixingSecretBases, sizeof(unk_2018000.secretBases));
+ memcpy(unk_2018000.tvShows, recordMixingTvShows, sizeof(unk_2018000.tvShows));
+ memcpy(unk_2018000.filler1004, gUnknown_083D0274, sizeof(unk_2008000.filler1004));
+ memcpy(unk_2018000.filler1044, gUnknown_083D0278, sizeof(unk_2008000.filler1044));
+ memcpy(unk_2018000.easyChatPairs, recordMixingEasyChatPairs, sizeof(unk_2018000.easyChatPairs));
+ gUnknown_02038738.mail[0] = gSaveBlock1.daycareData.misc.mail[0];
+ gUnknown_02038738.mail[1] = gSaveBlock1.daycareData.misc.mail[1];
+ sub_8041324(gSaveBlock1.daycareData.mons, &gUnknown_02038738);
+ memcpy(&unk_2018000.filler10AC, gUnknown_083D0280, sizeof(struct RecordMixingDayCareMail));
+ memcpy(unk_2018000.filler1124, gUnknown_083D0284, sizeof(unk_2018000.filler1124));
+
+ if (GetMultiplayerId() == 0)
+ unk_2018000.filler11C8[0] = GetRecordMixingGift();
+}
+
+void RecordMixing_ReceiveExchangePacket(u32 a)
+{
+ sub_80BD674(unk_2008000.secretBases, sizeof(struct PlayerRecords), a);
+ sub_80BFD44((u8 *)unk_2008000.tvShows, sizeof(struct PlayerRecords), a);
+ sub_80C0514(unk_2008000.filler1004, sizeof(struct PlayerRecords), a);
+ sub_80B9B1C(unk_2008000.filler1044, sizeof(struct PlayerRecords), a);
+ sub_80FA4E4(unk_2008000.easyChatPairs, sizeof(struct PlayerRecords), a);
+ sub_80B9C6C((u8 *)&unk_2008000.filler10AC, sizeof(struct PlayerRecords), a, unk_2008000.tvShows);
+ sub_80B9B70(unk_2008000.filler1124, sizeof(struct PlayerRecords), a);
+ sub_80B9F3C(unk_2008000.filler11C8, a);
+}
+
+#define tCounter data[0]
+
+void Task_RecordMixing_SoundEffect(u8 taskId)
+{
+ gTasks[taskId].tCounter++;
+ if (gTasks[taskId].tCounter == 50)
+ {
+ PlaySE(SE_W213);
+ gTasks[taskId].tCounter = 0;
+ }
+}
+
+#undef tCounter
+
+
+#define tState data[0]
+#define tSndEffTaskId data[15]
+
+void Task_RecordMixing_Main(u8 taskId)
+{
+ s16 *data = gTasks[taskId].data;
+
+ switch (tState)
+ {
+ case 0: // init
+ sub_8007270(gSpecialVar_0x8005);
+ VarSet(VAR_0x4000, 1);
+ gUnknown_03000718 = 0;
+ RecordMixing_PrepareExchangePacket();
+ CreateRecordMixingSprite();
+ tState = 1;
+ data[10] = CreateTask(sub_80B95F0, 0x50);
+ tSndEffTaskId = CreateTask(Task_RecordMixing_SoundEffect, 0x51);
+ break;
+ case 1: // wait for sub_80B95F0
+ if (!gTasks[data[10]].isActive)
+ {
+ tState = 2;
+ FlagSet(SYS_MIX_RECORD);
+ DestroyRecordMixingSprite();
+ DestroyTask(tSndEffTaskId);
+ }
+ break;
+ case 2:
+ data[10] = CreateTask(sub_80BA00C, 10);
+ tState = 3;
+ PlaySE(SE_W226);
+ break;
+ case 3: // wait for sub_80BA00C
+ if (!gTasks[data[10]].isActive)
+ {
+ tState = 4;
+ data[10] = sub_8083664();
+ sub_80720B0();
+ MenuPrint(gOtherText_MixingComplete, 2, 15);
+ data[8] = 0;
+ }
+ break;
+ case 4: // wait 60 frames
+ data[8]++;
+ if (data[8] > 60)
+ tState = 5;
+ break;
+ case 5:
+ if (!gTasks[data[10]].isActive)
+ {
+ sub_8055588();
+ MenuZeroFillScreen();
+ DestroyTask(taskId);
+ EnableBothScriptContexts();
+ }
+ break;
+ }
+}
+
+void sub_80B95F0(u8 taskId)
+{
+ struct Task *task = &gTasks[taskId];
+
+ switch (task->tState)
+ {
+ case 0:
+ sub_80B9A78();
+ MenuDisplayMessageBox();
+ MenuPrint(gOtherText_MixingRecordsWithFriend, 2, 15);
+ task->data[8] = 0x708;
+ task->tState = 400;
+ ClearLinkCallback_2();
+ break;
+ case 100: // wait 20 frames
+ task->data[12]++;
+ if (task->data[12] > 20)
+ {
+ task->data[12] = 0;
+ task->tState = 101;
+ }
+ break;
+ case 101:
+ {
+ u8 players = GetLinkPlayerCount_2();
+
+ if (IsLinkMaster() == 1)
+ {
+ if (players == sub_800820C())
+ {
+ PlaySE(SE_PIN);
+ task->tState = 201;
+ task->data[12] = 0;
+ }
+ }
+ else
+ {
+ PlaySE(SE_BOO);
+ task->tState = 301;
+ }
+ }
+ break;
+ case 201:
+ if (sub_800820C() == GetLinkPlayerCount_2())
+ {
+ if (++task->data[12] > GetLinkPlayerCount_2() * 30)
+ {
+ sub_8007F4C();
+ task->tState = 1;
+ }
+ }
+ break;
+ case 301:
+ if (sub_800820C() == GetLinkPlayerCount_2())
+ task->tState = 1;
+ break;
+ case 400: // wait 20 frames
+ task->data[12]++;
+ if (task->data[12] > 20)
+ {
+ task->tState = 1;
+ task->data[12] = 0;
+ }
+ break;
+ case 1: // wait for handshake
+ if (gReceivedRemoteLinkPlayers)
+ {
+ ConvertIntToDecimalStringN(gStringVar1, GetMultiplayerId_(), 2, 2);
+ task->tState = 5;
+ }
+ break;
+ case 2:
+ {
+ u8 subTaskId;
+
+ task->data[6] = GetLinkPlayerCount_2();
+ task->tState = 0;
+ task->data[5] = GetMultiplayerId_();
+ task->func = Task_RecordMixing_SendPacket;
+ StorePtrInTaskData(&unk_2018000, &task->data[2]);
+ subTaskId = CreateTask(Task_RecordMixing_CopyReceiveBuffer, 0x50);
+ task->data[10] = subTaskId;
+ gTasks[subTaskId].data[0] = taskId;
+ StorePtrInTaskData((u8 *)&unk_2008000, &gTasks[subTaskId].data[5]);
+ }
+ break;
+ case 5: // wait 60 frames
+ task->data[10]++;
+ if (task->data[10] > 60)
+ {
+ task->data[10] = 0;
+ task->tState = 2;
+ }
+ break;
+ }
+}
+
+void Task_RecordMixing_SendPacket(u8 taskId)
+{
+ struct Task *task = &gTasks[taskId];
+ // does this send the data 24 times?
+
+ switch (task->tState)
+ {
+ case 0: //Copy record data to send buffer
+ {
+ void *recordData = (u8 *)LoadPtrFromTaskData(&task->data[2]) + BUFFER_CHUNK_SIZE * task->data[4];
+
+ memcpy(gBlockSendBuffer, recordData, BUFFER_CHUNK_SIZE);
+ task->tState++;
+ }
+ break;
+ case 1:
+ if (GetMultiplayerId() == 0)
+ sub_8007E9C(1);
+ task->tState++;
+ break;
+ case 2:
+ break;
+ case 3:
+ task->data[4]++;
+ if ((u16)task->data[4] == 24)
+ task->tState++;
+ else
+ task->tState = 0;
+ break;
+ case 4:
+ if (!gTasks[task->data[10]].isActive)
+ task->func = Task_RecordMixing_SendPacket_SwitchToReceive;
+ }
+}
+
+void Task_RecordMixing_CopyReceiveBuffer(u8 taskId)
+{
+ struct Task *task = &gTasks[taskId];
+ s32 recvStatus = GetBlockReceivedStatus();
+ u8 handledPlayers = 0;
+
+ if (recvStatus == sub_8008198())
+ {
+ u8 player;
+
+ for (player = 0; player < GetLinkPlayerCount(); player++)
+ {
+ void *src;
+ u8 *dst;
+
+ if ((recvStatus >> player) & 1)
+ {
+ dst = LoadPtrFromTaskData(&task->data[5]) + task->data[player + 1] * BUFFER_CHUNK_SIZE + player * sizeof(struct PlayerRecords);
+ src = GetPlayerRecvBuffer(player);
+ if ((task->data[player + 1] + 1) * BUFFER_CHUNK_SIZE > sizeof(struct PlayerRecords))
+ memcpy(dst, src, sizeof(struct PlayerRecords) - task->data[player + 1] * BUFFER_CHUNK_SIZE);
+ else
+ memcpy(dst, src, BUFFER_CHUNK_SIZE);
+ ResetBlockReceivedFlag(player);
+ task->data[player + 1]++;
+ if ((u16)task->data[player + 1] == 0x18)
+ handledPlayers++;
+ }
+ }
+ gTasks[task->data[0]].data[0]++;
+ }
+ if (handledPlayers == GetLinkPlayerCount())
+ DestroyTask(taskId);
+}
+
+void sub_80B99B4(u8 taskId)
+{
+ struct Task *task = &gTasks[taskId];
+
+ if (!gTasks[task->data[10]].isActive)
+ DestroyTask(taskId);
+}
+
+void Task_RecordMixing_ReceivePacket(u8 taskId)
+{
+ struct Task *task = &gTasks[taskId];
+
+ task->func = sub_80B99B4;
+ if (gUnknown_03000718 == 1)
+ RecordMixing_ReceiveExchangePacket(task->data[5]);
+}
+
+void Task_RecordMixing_SendPacket_SwitchToReceive(u8 taskId)
+{
+ gTasks[taskId].func = Task_RecordMixing_ReceivePacket;
+ gUnknown_03000718 = 1;
+}
+
+void *LoadPtrFromTaskData(u16 *taskData)
+{
+ return (void *)(taskData[0] | (taskData[1] << 16));
+}
+
+void StorePtrInTaskData(void *ptr, u16 *taskData)
+{
+ taskData[0] = (u32)ptr;
+ taskData[1] = (u32)ptr >> 16;
+}
+
+u8 GetMultiplayerId_(void)
+{
+ return GetMultiplayerId();
+}
+
+u16 *GetPlayerRecvBuffer(u8 player)
+{
+ return gBlockRecvBuffer[player];
+}
+
+void sub_80B9A78(void)
+{
+ gUnknown_03005D2C = sizeof(struct PlayerRecords);
+}
+
+const u8 gUnknown_083D0288[2] = {1, 0};
+
+const u8 gUnknown_083D028A[2][3] =
+{
+ {1, 2, 0},
+ {2, 0, 1},
+};
+
+const u8 gUnknown_083D0290[9][4] =
+{
+ {1, 0, 3, 2},
+ {3, 0, 1, 2},
+ {2, 0, 3, 1},
+ {1, 3, 0, 2},
+ {2, 3, 0, 1},
+ {3, 2, 0, 1},
+ {1, 2, 3, 0},
+ {2, 3, 1, 0},
+ {3, 2, 1, 0},
+};
+
+void sub_80B9A88(u8 *a)
+{
+ u32 i;
+ u32 id;
+ u32 players = GetLinkPlayerCount();
+
+ switch (players)
+ {
+ case 2:
+ for (i = 0; i < 2; i++)
+ a[i] = gUnknown_083D0288[i];
+ break;
+ case 3:
+ id = GetLinkPlayerTrainerId(0) % 2;
+ for (i = 0; i < 3; i++)
+ a[i] = gUnknown_083D028A[id][i];
+ break;
+ case 4:
+ id = GetLinkPlayerTrainerId(0) % 9;
+ for (i = 0; i < 4; i++)
+ a[i] = gUnknown_083D0290[id][i];
+ break;
+ default:
+ break;
+ }
+}
+
+void sub_80B9B1C(u8 *a, size_t size, u8 index)
+{
+ u8 arr[4];
+ u8 *ptr;
+
+ sub_80B9A88(arr);
+ //Probably not how it was originally written, but this matches.
+ memcpy(a + index * size, (ptr = gUnknown_083D0278), 0x40);
+ memcpy(ptr, a + arr[index] * size, 0x40);
+ sub_80F7F30();
+}
+
+void sub_80B9B70(u8 *a, size_t size, u8 index)
+{
+ sub_80B9A88(gUnknown_0300071C);
+ memcpy(a + size * index, a + size * gUnknown_0300071C[index], 0xA4);
+ sub_8134AC0(a + size * index);
+}
+
+u8 sub_80B9BBC(u16 *a)
+{
+ return a[16];
+}
+
+void sub_80B9BC4(u8 *a, size_t b, u8 c[][2], u8 d, u8 e)
+{
+ struct DayCareMail *r6 = (struct DayCareMail *)(a + b * c[d][0]);
+ struct DayCareMail *src = r6 + c[d][1];
+ struct DayCareMail sp0 = *src;
+ struct DayCareMail *r8 = (struct DayCareMail *)(a + b * c[e][0]);
+
+ r6 += c[d][1];
+ *r6 = *(r8 + c[e][1]);
+
+ r8 += c[e][1];
+ *r8 = sp0;
+}
+
+u8 sub_80B9C4C(u8 *a)
+{
+ int i;
+ u8 r2 = 0;
+
+ for (i = 0; i < 0x100; i++)
+ r2 += a[i];
+ return r2;
+}
+
+const u8 gUnknown_083D02B4[][2] =
+{
+ {0, 1},
+ {1, 2},
+ {2, 0},
+};
+
+const u8 gUnknown_083D02BA[3][4] =
+{
+ {0, 1, 2, 3},
+ {0, 2, 1, 3},
+ {0, 3, 2, 1},
+};
+
+#ifdef NONMATCHING
+void sub_80B9C6C(u8 *a, u32 b, u8 c, void *d)
+{
+ u8 r8;
+ u8 sp4[4];
+ u8 sp8[4];
+ void *spC[4];
+ u8 sp1C[4][2];
+ u8 sp24[4][2];
+ u8 sp3C;
+ u16 sp40 = Random();
+ u16 i; // r3
+ u16 r7;
+ u8 r1;
+ struct DayCareMisc *r6;
+
+ //asm("":::"r8");
+ SeedRng(gLinkPlayers[0].trainerId);
+ r8 = GetLinkPlayerCount();
+ for (i = 0; i < 4; i++)
+ {
+ sp4[i] = 0xFF;
+ sp8[i] = 0;
+ sp1C[i][0] = 0;
+ sp1C[i][1] = 0;
+ }
+ sp3C = 0;
+ for (i = 0; i < r8; i++)
+ {
+ r6 = (struct DayCareMisc *)(a + b * i);
+ if (r6->unk70 != 0)
+ {
+ for (r7 = 0; r7 < r6->unk70; r7++)
+ {
+ if (r6->unk74[r7] == 0)
+ sp1C[i][r7] = 1;
+ }
+ }
+ //_080B9D3C
+ }
+ //_080B9D46
+ for (r7 = 0, i = 0; i < r8; i++)
+ {
+ r6 = (struct DayCareMisc *)(a + b * i);
+ if (sp1C[i][0] == 1 || sp1C[i][1] == 1)
+ sp3C++;
+ if (sp1C[i][0] == 1 && sp1C[i][1] == 0)
+ {
+ sp24[r7][0] = i;
+ sp24[r7][1] = 0;
+ r7++;
+ }
+ else if (sp1C[i][0] == 0 && sp1C[i][1] == 1)
+ {
+ sp24[r7][0] = i;
+ sp24[r7][1] = 0;
+ r7++;
+ }
+ //else if (sp1C[i][0] == 1 + 1 && sp1C[i][1] + 1 == 1 + 1)
+ else if (sp1C[i][0] == 1 && sp1C[i][1] == 1)
+ {
+ u8 r4, r1;
+
+ sp24[r7][0] = i;
+ r4 = sub_80B9BBC((u16 *)&r6->data[0]);
+ r1 = sub_80B9BBC((u16 *)&r6->data[1]);
+
+ asm("");
+ if (r4 == 0 && r1 != 0)
+ sp24[r7][1] = 1;
+ else if ((r4 == 0 && r1 == 0) || (r4 != 0 && r1 != 0))
+ sp24[r7][1] = Random() % 2;
+ else
+ sp24[r7][1] = 0;
+ /*
+ if (r4 == 0 && r1 != 0)
+ sp24[r7][1] = 1;
+ else if ((r4 == 0 && r1 == 0) || (r4 != 0 && r1 != 0))
+ sp24[r7][1] = Random() % 2;
+ else
+ sp24[r7][1] = 0;
+ */
+
+ /*
+ if (r4 == 0 && r1 != 0)
+ sp24[r7][1] = 1;
+ else if ((r4 == 0 && r1 == 0) || (r4 != 0 && r1 != 0))
+ //sp24[r7][1] = ((Random() << 16) >> 16) % 2;
+ sp24[r7][1] = Random() % 2;
+ else
+ sp24[r7][1] = 0;
+ */
+ //_080B9E2C:
+ r7++;
+ }
+ }
+ //_080B9E3E
+ for (i = 0; i < 4; i++)
+ {
+ r6 = (struct DayCareMisc *)a + b * c;
+ spC[i] = r6;
+ }
+ r1 = sub_80B9C4C(d) % 3;
+ switch (sp3C)
+ {
+ case 2:
+ sub_80B9BC4(a, b, (u8 *)sp24, 0, 1);
+ break;
+ case 3:
+ {
+ u8 var1 = gUnknown_083D02B4[r1][0];
+ u8 var2 = gUnknown_083D02B4[r1][1];
+ sub_80B9BC4(a, b, (u8 *)sp24, var1, var2);
+ }
+ break;
+ case 4:
+ {
+ u8 *r6 = (u8 *)sp24;
+ u8 var1 = gUnknown_083D02BA[r1][0];
+ u8 var2 = gUnknown_083D02BA[r1][1];
+ sub_80B9BC4(a, b, r6, var1, var2);
+ }
+ {
+ u8 *r6 = (u8 *)sp24;
+ u8 var1 = gUnknown_083D02BA[r1][2];
+ u8 var2 = gUnknown_083D02BA[r1][3];
+ sub_80B9BC4(a, b, r6, var1, var2);
+ }
+ break;
+ }
+ //_080B9EF0
+ //memcpy(&gSaveBlock1.filler_303C.data[0], a + b * c, 0x38);
+ //memcpy(&gSaveBlock1.filler_303C.data[1], a + b * c + 0x38, 0x38);
+ r6 = (struct DayCareMisc *)(a + b * c);
+ gSaveBlock1.filler_303C.data[0] = r6->data[0];
+ gSaveBlock1.filler_303C.data[1] = r6->data[1];
+ //memcpy(&gSaveBlock1.filler_303C.data[0], &r6->data[0], 0x38);
+ //memcpy(&gSaveBlock1.filler_303C.data[1], &r6->data[1], 0x38);
+ SeedRng(sp40);
+}
+#else
+__attribute__((naked))
+void sub_80B9C6C(u8 *a, u32 b, u8 c, void *d)
+{
+ asm(".syntax unified\n\
+ push {r4-r7,lr}\n\
+ mov r7, r10\n\
+ mov r6, r9\n\
+ mov r5, r8\n\
+ push {r5-r7}\n\
+ sub sp, 0x58\n\
+ str r0, [sp, 0x2C]\n\
+ str r1, [sp, 0x30]\n\
+ str r3, [sp, 0x38]\n\
+ lsls r2, 24\n\
+ lsrs r2, 24\n\
+ str r2, [sp, 0x34]\n\
+ bl Random\n\
+ lsls r0, 16\n\
+ lsrs r0, 16\n\
+ str r0, [sp, 0x40]\n\
+ ldr r0, _080B9DA8 @ =gLinkPlayers\n\
+ ldrh r0, [r0, 0x4]\n\
+ bl SeedRng\n\
+ bl GetLinkPlayerCount\n\
+ lsls r0, 24\n\
+ lsrs r0, 24\n\
+ mov r8, r0\n\
+ movs r3, 0\n\
+ add r0, sp, 0x1C\n\
+ mov r9, r0\n\
+ movs r1, 0x1D\n\
+ add r1, sp\n\
+ mov r10, r1\n\
+ mov r2, sp\n\
+ adds r2, 0xC\n\
+ str r2, [sp, 0x4C]\n\
+ movs r7, 0xFF\n\
+ add r4, sp, 0x8\n\
+ movs r2, 0\n\
+ mov r6, r9\n\
+ mov r5, r10\n\
+_080B9CBC:\n\
+ mov r1, sp\n\
+ adds r1, r3\n\
+ adds r1, 0x4\n\
+ ldrb r0, [r1]\n\
+ orrs r0, r7\n\
+ strb r0, [r1]\n\
+ adds r0, r4, r3\n\
+ strb r2, [r0]\n\
+ lsls r1, r3, 1\n\
+ adds r0, r6, r1\n\
+ strb r2, [r0]\n\
+ adds r1, r5, r1\n\
+ strb r2, [r1]\n\
+ adds r0, r3, 0x1\n\
+ lsls r0, 16\n\
+ lsrs r3, r0, 16\n\
+ cmp r3, 0x3\n\
+ bls _080B9CBC\n\
+ movs r4, 0\n\
+ str r4, [sp, 0x3C]\n\
+ movs r3, 0\n\
+ mov r1, r8\n\
+ lsls r0, r1, 16\n\
+ lsrs r1, r0, 16\n\
+ str r0, [sp, 0x50]\n\
+ ldr r4, [sp, 0x30]\n\
+ ldr r0, [sp, 0x34]\n\
+ adds r2, r4, 0\n\
+ muls r2, r0\n\
+ str r2, [sp, 0x48]\n\
+ ldr r2, [sp, 0x3C]\n\
+ cmp r2, r1\n\
+ bcs _080B9D46\n\
+ mov r8, r1\n\
+_080B9D00:\n\
+ ldr r4, [sp, 0x30]\n\
+ adds r0, r4, 0\n\
+ muls r0, r3\n\
+ ldr r1, [sp, 0x2C]\n\
+ adds r6, r1, r0\n\
+ ldr r0, [r6, 0x70]\n\
+ cmp r0, 0\n\
+ beq _080B9D3C\n\
+ movs r7, 0\n\
+ cmp r7, r0\n\
+ bcs _080B9D3C\n\
+ adds r4, r6, 0\n\
+ adds r4, 0x74\n\
+ mov r2, r9\n\
+ lsls r1, r3, 1\n\
+ movs r5, 0x1\n\
+_080B9D20:\n\
+ lsls r0, r7, 1\n\
+ adds r0, r4, r0\n\
+ ldrh r0, [r0]\n\
+ cmp r0, 0\n\
+ bne _080B9D30\n\
+ adds r0, r7, r1\n\
+ adds r0, r2, r0\n\
+ strb r5, [r0]\n\
+_080B9D30:\n\
+ adds r0, r7, 0x1\n\
+ lsls r0, 16\n\
+ lsrs r7, r0, 16\n\
+ ldr r0, [r6, 0x70]\n\
+ cmp r7, r0\n\
+ bcc _080B9D20\n\
+_080B9D3C:\n\
+ adds r0, r3, 0x1\n\
+ lsls r0, 16\n\
+ lsrs r3, r0, 16\n\
+ cmp r3, r8\n\
+ bcc _080B9D00\n\
+_080B9D46:\n\
+ movs r7, 0\n\
+ movs r3, 0\n\
+ ldr r2, [sp, 0x50]\n\
+ cmp r2, 0\n\
+ beq _080B9E3E\n\
+ mov r4, sp\n\
+ adds r4, 0x24\n\
+ str r4, [sp, 0x44]\n\
+ movs r0, 0x25\n\
+ add r0, sp\n\
+ mov r8, r0\n\
+_080B9D5C:\n\
+ ldr r1, [sp, 0x30]\n\
+ adds r0, r1, 0\n\
+ muls r0, r3\n\
+ ldr r2, [sp, 0x2C]\n\
+ adds r6, r2, r0\n\
+ lsls r1, r3, 1\n\
+ mov r4, r9\n\
+ adds r0, r4, r1\n\
+ ldrb r0, [r0]\n\
+ cmp r0, 0x1\n\
+ beq _080B9D7C\n\
+ mov r2, r10\n\
+ adds r0, r2, r1\n\
+ ldrb r0, [r0]\n\
+ cmp r0, 0x1\n\
+ bne _080B9D86\n\
+_080B9D7C:\n\
+ ldr r0, [sp, 0x3C]\n\
+ adds r0, 0x1\n\
+ lsls r0, 24\n\
+ lsrs r0, 24\n\
+ str r0, [sp, 0x3C]\n\
+_080B9D86:\n\
+ mov r4, r9\n\
+ adds r0, r4, r1\n\
+ ldrb r0, [r0]\n\
+ cmp r0, 0x1\n\
+ bne _080B9DAC\n\
+ mov r2, r10\n\
+ adds r0, r2, r1\n\
+ ldrb r2, [r0]\n\
+ cmp r2, 0\n\
+ bne _080B9DAC\n\
+_080B9D9A:\n\
+ lsls r1, r7, 1\n\
+ ldr r4, [sp, 0x44]\n\
+ adds r0, r4, r1\n\
+ strb r3, [r0]\n\
+ add r1, r8\n\
+ strb r2, [r1]\n\
+ b _080B9E2C\n\
+ .align 2, 0\n\
+_080B9DA8: .4byte gLinkPlayers\n\
+_080B9DAC:\n\
+ mov r2, r9\n\
+ adds r0, r2, r1\n\
+ ldrb r0, [r0]\n\
+ cmp r0, 0\n\
+ bne _080B9DC0\n\
+ mov r4, r10\n\
+ adds r0, r4, r1\n\
+ ldrb r2, [r0]\n\
+ cmp r2, 0x1\n\
+ beq _080B9D9A\n\
+_080B9DC0:\n\
+ mov r2, r9\n\
+ adds r0, r2, r1\n\
+ ldrb r0, [r0]\n\
+ cmp r0, 0x1\n\
+ bne _080B9E32\n\
+ mov r4, r10\n\
+ adds r0, r4, r1\n\
+ ldrb r0, [r0]\n\
+ cmp r0, 0x1\n\
+ bne _080B9E32\n\
+ lsls r5, r7, 1\n\
+ ldr r1, [sp, 0x44]\n\
+ adds r0, r1, r5\n\
+ strb r3, [r0]\n\
+ adds r0, r6, 0\n\
+ str r3, [sp, 0x54]\n\
+ bl sub_80B9BBC\n\
+ adds r4, r0, 0\n\
+ lsls r4, 24\n\
+ lsrs r4, 24\n\
+ adds r0, r6, 0\n\
+ adds r0, 0x38\n\
+ bl sub_80B9BBC\n\
+ lsls r0, 24\n\
+ lsrs r1, r0, 24\n\
+ ldr r3, [sp, 0x54]\n\
+ cmp r4, 0\n\
+ bne _080B9E0A\n\
+ cmp r1, 0\n\
+ beq _080B9E0E\n\
+ mov r2, r8\n\
+ adds r1, r2, r5\n\
+ movs r0, 0x1\n\
+ strb r0, [r1]\n\
+ b _080B9E2C\n\
+_080B9E0A:\n\
+ cmp r1, 0\n\
+ beq _080B9E26\n\
+_080B9E0E:\n\
+ str r3, [sp, 0x54]\n\
+ bl Random\n\
+ mov r4, r8\n\
+ adds r2, r4, r5\n\
+ lsls r0, 16\n\
+ lsrs r0, 16\n\
+ movs r1, 0x1\n\
+ ands r0, r1\n\
+ strb r0, [r2]\n\
+ ldr r3, [sp, 0x54]\n\
+ b _080B9E2C\n\
+_080B9E26:\n\
+ mov r2, r8\n\
+ adds r0, r2, r5\n\
+ strb r1, [r0]\n\
+_080B9E2C:\n\
+ adds r0, r7, 0x1\n\
+ lsls r0, 16\n\
+ lsrs r7, r0, 16\n\
+_080B9E32:\n\
+ adds r0, r3, 0x1\n\
+ lsls r0, 16\n\
+ lsrs r3, r0, 16\n\
+ ldr r4, [sp, 0x50]\n\
+ cmp r0, r4\n\
+ bcc _080B9D5C\n\
+_080B9E3E:\n\
+ movs r3, 0\n\
+ ldr r1, [sp, 0x48]\n\
+ lsls r0, r1, 4\n\
+ subs r0, r1\n\
+ lsls r0, 3\n\
+ ldr r2, [sp, 0x2C]\n\
+ adds r6, r2, r0\n\
+ ldr r1, [sp, 0x4C]\n\
+_080B9E4E:\n\
+ lsls r0, r3, 2\n\
+ adds r0, r1, r0\n\
+ str r6, [r0]\n\
+ adds r0, r3, 0x1\n\
+ lsls r0, 16\n\
+ lsrs r3, r0, 16\n\
+ cmp r3, 0x3\n\
+ bls _080B9E4E\n\
+ ldr r0, [sp, 0x38]\n\
+ bl sub_80B9C4C\n\
+ lsls r0, 24\n\
+ lsrs r0, 24\n\
+ movs r1, 0x3\n\
+ bl __umodsi3\n\
+ lsls r0, 24\n\
+ lsrs r1, r0, 24\n\
+ ldr r4, [sp, 0x3C]\n\
+ cmp r4, 0x3\n\
+ beq _080B9E9C\n\
+ cmp r4, 0x3\n\
+ bgt _080B9E82\n\
+ cmp r4, 0x2\n\
+ beq _080B9E8A\n\
+ b _080B9EF0\n\
+_080B9E82:\n\
+ ldr r0, [sp, 0x3C]\n\
+ cmp r0, 0x4\n\
+ beq _080B9EBC\n\
+ b _080B9EF0\n\
+_080B9E8A:\n\
+ add r2, sp, 0x24\n\
+ movs r0, 0x1\n\
+ str r0, [sp]\n\
+ ldr r0, [sp, 0x2C]\n\
+ ldr r1, [sp, 0x30]\n\
+ movs r3, 0\n\
+ bl sub_80B9BC4\n\
+ b _080B9EF0\n\
+_080B9E9C:\n\
+ ldr r0, _080B9EB8 @ =gUnknown_083D02B4\n\
+ lsls r1, 1\n\
+ adds r2, r1, r0\n\
+ ldrb r3, [r2]\n\
+ adds r0, 0x1\n\
+ adds r1, r0\n\
+ ldrb r0, [r1]\n\
+ add r2, sp, 0x24\n\
+ str r0, [sp]\n\
+ ldr r0, [sp, 0x2C]\n\
+ ldr r1, [sp, 0x30]\n\
+ bl sub_80B9BC4\n\
+ b _080B9EF0\n\
+ .align 2, 0\n\
+_080B9EB8: .4byte gUnknown_083D02B4\n\
+_080B9EBC:\n\
+ add r6, sp, 0x24\n\
+ ldr r4, _080B9F2C @ =gUnknown_083D02BA\n\
+ lsls r5, r1, 2\n\
+ adds r0, r5, r4\n\
+ ldrb r3, [r0]\n\
+ adds r0, r4, 0x1\n\
+ adds r0, r5, r0\n\
+ ldrb r0, [r0]\n\
+ str r0, [sp]\n\
+ ldr r0, [sp, 0x2C]\n\
+ ldr r1, [sp, 0x30]\n\
+ adds r2, r6, 0\n\
+ bl sub_80B9BC4\n\
+ adds r0, r4, 0x2\n\
+ adds r0, r5, r0\n\
+ ldrb r3, [r0]\n\
+ adds r4, 0x3\n\
+ adds r5, r4\n\
+ ldrb r0, [r5]\n\
+ str r0, [sp]\n\
+ ldr r0, [sp, 0x2C]\n\
+ ldr r1, [sp, 0x30]\n\
+ adds r2, r6, 0\n\
+ bl sub_80B9BC4\n\
+_080B9EF0:\n\
+ ldr r1, [sp, 0x2C]\n\
+ ldr r2, [sp, 0x48]\n\
+ adds r6, r1, r2\n\
+ ldr r4, _080B9F30 @ =gSaveBlock1\n\
+ ldr r1, _080B9F34 @ =0x0000303c\n\
+ adds r0, r4, r1\n\
+ adds r1, r6, 0\n\
+ movs r2, 0x38\n\
+ bl memcpy\n\
+ ldr r2, _080B9F38 @ =0x00003074\n\
+ adds r4, r2\n\
+ adds r1, r6, 0\n\
+ adds r1, 0x38\n\
+ adds r0, r4, 0\n\
+ movs r2, 0x38\n\
+ bl memcpy\n\
+ ldr r0, [sp, 0x40]\n\
+ bl SeedRng\n\
+ add sp, 0x58\n\
+ pop {r3-r5}\n\
+ mov r8, r3\n\
+ mov r9, r4\n\
+ mov r10, r5\n\
+ pop {r4-r7}\n\
+ pop {r0}\n\
+ bx r0\n\
+ .align 2, 0\n\
+_080B9F2C: .4byte gUnknown_083D02BA\n\
+_080B9F30: .4byte gSaveBlock1\n\
+_080B9F34: .4byte 0x0000303c\n\
+_080B9F38: .4byte 0x00003074\n\
+ .syntax divided\n");
+}
+#endif
+
+void sub_80B9F3C(u16 *a, u8 b)
+{
+ if (b != 0 && *a != 0)
+ {
+ if (GetPocketByItemId(*a) == 5)
+ {
+ if (!CheckBagHasItem(*a, 1) && !CheckPCHasItem(*a, 1) && AddBagItem(*a, 1))
+ {
+ VarSet(VAR_0x4001, *a);
+ StringCopy(gStringVar1, gLinkPlayers[0].name);
+ if (*a == ITEM_EON_TICKET)
+ FlagSet(SYS_HAS_EON_TICKET);
+ }
+ else
+ {
+ VarSet(VAR_0x4001, ITEM_NONE);
+ }
+ }
+ else
+ {
+ if (AddBagItem(*a, 1) == TRUE)
+ {
+ VarSet(VAR_0x4001, *a);
+ StringCopy(gStringVar1, gLinkPlayers[0].name);
+ }
+ else
+ {
+ VarSet(VAR_0x4001, ITEM_NONE);
+ }
+ }
+ }
+}
+
+void sub_80BA00C(u8 taskId)
+{
+ struct Task *task = &gTasks[taskId];
+
+ switch (task->data[0])
+ {
+ case 0:
+ task->data[0]++;
+ break;
+ case 1:
+ task->data[0]++;
+ break;
+ case 2:
+ SetSecretBase2Field_9_AndHideBG();
+ sub_8125E2C();
+ task->data[0]++;
+ break;
+ case 3:
+ if (sub_8125E6C() != 0)
+ {
+ ClearSecretBase2Field_9_2();
+ task->data[0]++;
+ task->data[1] = 0;
+ }
+ break;
+ case 4:
+ task->data[1]++;
+ if (task->data[1] > 10)
+ {
+ sub_800832C();
+ task->data[0]++;
+ }
+ break;
+ case 5:
+ if (!gReceivedRemoteLinkPlayers)
+ DestroyTask(taskId);
+ break;
+ }
+}
diff --git a/src/engine/reset_rtc_screen.c b/src/engine/reset_rtc_screen.c
new file mode 100644
index 000000000..d052992ad
--- /dev/null
+++ b/src/engine/reset_rtc_screen.c
@@ -0,0 +1,496 @@
+#include "global.h"
+#include "event_data.h"
+#include "main.h"
+#include "menu.h"
+#include "palette.h"
+#include "rtc.h"
+#include "save.h"
+#include "sprite.h"
+#include "songs.h"
+#include "sound.h"
+#include "string_util.h"
+#include "strings.h"
+#include "strings2.h"
+#include "task.h"
+#include "text.h"
+#include "unknown_task.h"
+
+struct ResetRtcStruct
+{
+ /*0x0*/ u8 dataIndex;
+ /*0x2*/ u16 minVal;
+ /*0x4*/ u16 maxVal;
+ /*0x6*/ u8 left;
+ /*0x7*/ u8 right;
+ /*0x8*/ u8 unk8;
+};
+
+extern u16 gSaveFileStatus;
+
+extern struct ResetRtcStruct gUnknown_08376420[];
+extern struct SpritePalette gUnknown_083764BC;
+extern struct SpriteTemplate gSpriteTemplate_83764E8;
+extern u8 gUnknown_08376500[];
+
+void CB2_ResetRtcScreen(void);
+void VBlankCB_ResetRtcScreen(void);
+void Task_ResetRtcScreen(u8);
+
+void SpriteCB_ResetRtcCusor0(struct Sprite *sprite)
+{
+ int state = gTasks[sprite->data0].data[2];
+ if (state != sprite->data1)
+ {
+ sprite->data1 = state;
+ switch (state)
+ {
+ case 1:
+ sprite->invisible = FALSE;
+ sprite->animNum = 1;
+ sprite->animDelayCounter = 0;
+ sprite->pos1.x = 53;
+ sprite->pos1.y = 68;
+ break;
+ case 2:
+ sprite->invisible = FALSE;
+ sprite->animNum = 1;
+ sprite->animDelayCounter = 0;
+ sprite->pos1.x = 86;
+ sprite->pos1.y = 68;
+ break;
+ case 3:
+ sprite->invisible = FALSE;
+ sprite->animNum = 1;
+ sprite->animDelayCounter = 0;
+ sprite->pos1.x = 107;
+ sprite->pos1.y = 68;
+ break;
+ case 4:
+ sprite->invisible = FALSE;
+ sprite->animNum = 1;
+ sprite->animDelayCounter = 0;
+ sprite->pos1.x = 128;
+ sprite->pos1.y = 68;
+ break;
+ case 5:
+ sprite->invisible = FALSE;
+ sprite->animNum = 2;
+ sprite->animDelayCounter = 0;
+ sprite->pos1.x = 155;
+ sprite->pos1.y = 80;
+ break;
+ case 6:
+ DestroySprite(sprite);
+ break;
+ }
+ }
+}
+
+void SpriteCB_ResetRtcCusor1(struct Sprite *sprite)
+{
+ int state = gTasks[sprite->data0].data[2];
+ if (state != sprite->data1)
+ {
+ sprite->data1 = state;
+ switch (state)
+ {
+ case 1:
+ sprite->invisible = FALSE;
+ sprite->animNum = 0;
+ sprite->animDelayCounter = 0;
+ sprite->pos1.x = 53;
+ sprite->pos1.y = 92;
+ break;
+ case 2:
+ sprite->invisible = FALSE;
+ sprite->animNum = 0;
+ sprite->animDelayCounter = 0;
+ sprite->pos1.x = 86;
+ sprite->pos1.y = 92;
+ break;
+ case 3:
+ sprite->invisible = FALSE;
+ sprite->animNum = 0;
+ sprite->animDelayCounter = 0;
+ sprite->pos1.x = 107;
+ sprite->pos1.y = 92;
+ break;
+ case 4:
+ sprite->invisible = FALSE;
+ sprite->animNum = 0;
+ sprite->animDelayCounter = 0;
+ sprite->pos1.x = 128;
+ sprite->pos1.y = 92;
+ break;
+ case 5:
+ sprite->invisible = TRUE;
+ break;
+ case 6:
+ DestroySprite(sprite);
+ break;
+ }
+ }
+}
+
+void ResetRtcScreen_CreateCursor(u8 taskId)
+{
+ int spriteId;
+
+ LoadSpritePalette(&gUnknown_083764BC);
+
+ spriteId = CreateSpriteAtEnd(&gSpriteTemplate_83764E8, 53, 68, 0);
+ gSprites[spriteId].callback = SpriteCB_ResetRtcCusor0;
+ gSprites[spriteId].data0 = taskId;
+ gSprites[spriteId].data1 = -1;
+
+ spriteId = CreateSpriteAtEnd(&gSpriteTemplate_83764E8, 53, 68, 0);
+ gSprites[spriteId].callback = SpriteCB_ResetRtcCusor1;
+ gSprites[spriteId].data0 = taskId;
+ gSprites[spriteId].data1 = -1;
+}
+
+void ResetRtcScreen_FreeCursorPalette(void)
+{
+ FreeSpritePaletteByTag(gUnknown_083764BC.tag);
+}
+
+void ResetRtcScreen_HideChooseTimeWindow(void)
+{
+ MenuZeroFillWindowRect(3, 8, 25, 11);
+}
+
+void ResetRtcScreen_PrintTime(u8 x, u8 y, u16 days, u8 hours, u8 minutes, u8 seconds)
+{
+ u8 *dest = gStringVar4;
+ days %= 10000;
+ hours %= 24;
+ minutes %= 60;
+ seconds %= 60;
+ sub_8072C44(gStringVar1, days, 24, 1);
+ dest = StringCopy(dest, gStringVar1);
+ dest = StringCopy(dest, gOtherText_Day);
+ sub_8072C44(gStringVar1, hours, 18, 1);
+ dest = StringCopy(dest, gStringVar1);
+ dest = StringCopy(dest, gUnknown_08376500);
+ dest = ConvertIntToDecimalStringN(dest, minutes, STR_CONV_MODE_LEADING_ZEROS, 2);
+ dest = StringCopy(dest, gUnknown_08376500);
+ ConvertIntToDecimalStringN(dest, seconds, STR_CONV_MODE_LEADING_ZEROS, 2);
+ MenuPrint(gStringVar4, x, y);
+}
+
+void ResetRtcScreen_ShowChooseTimeWindow(u16 days, u8 hours, u8 minutes, u8 seconds)
+{
+ MenuDrawTextWindow(3, 8, 25, 11);
+ MenuPrint(gOtherText_OK, 20, 9);
+ ResetRtcScreen_PrintTime(4, 9, days, hours, minutes, seconds);
+}
+
+bool32 ResetRtcScreen_MoveTimeUpDown(s16 *val, int minVal, int maxVal, u16 keys)
+{
+ if (keys & DPAD_DOWN)
+ {
+ (*val)--;
+ if (*val < minVal)
+ *val = maxVal;
+ }
+ else if (keys & DPAD_UP)
+ {
+ (*val)++;
+ if (*val > maxVal)
+ *val = minVal;
+ }
+ else if (keys & DPAD_LEFT)
+ {
+ *val -= 10;
+ if (*val < minVal)
+ *val = maxVal;
+ }
+ else if (keys & DPAD_RIGHT)
+ {
+ *val += 10;
+ if (*val > maxVal)
+ *val = minVal;
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void Task_ResetRtc_3(u8 taskId)
+{
+ gTasks[taskId].data[0] = 1;
+}
+
+void Task_ResetRtc_2(u8 taskId)
+{
+ ResetRtcScreen_HideChooseTimeWindow();
+ ResetRtcScreen_FreeCursorPalette();
+ gTasks[taskId].func = Task_ResetRtc_3;
+}
+
+void Task_ResetRtc_1(u8 taskId)
+{
+ s16 *data = gTasks[taskId].data;
+ u8 selection = data[2];
+ struct ResetRtcStruct *selectionInfo = &gUnknown_08376420[selection - 1];
+
+ if (gMain.newKeys & B_BUTTON)
+ {
+ gTasks[taskId].func = Task_ResetRtc_2;
+ data[1] = 0;
+ data[2] = 6;
+ PlaySE(SE_SELECT);
+ return;
+ }
+
+ if (gMain.newKeys & DPAD_RIGHT)
+ {
+ if (selectionInfo->right)
+ {
+ data[2] = selectionInfo->right;
+ PlaySE(SE_SELECT);
+ return;
+ }
+ }
+
+ if (gMain.newKeys & DPAD_LEFT)
+ {
+ if (selectionInfo->left)
+ {
+ data[2] = selectionInfo->left;
+ PlaySE(SE_SELECT);
+ return;
+ }
+ }
+
+ if (selection == 5)
+ {
+ if (gMain.newKeys & A_BUTTON)
+ {
+ gLocalTime.days = data[3];
+ gLocalTime.hours = data[4];
+ gLocalTime.minutes = data[5];
+ gLocalTime.seconds = data[6];
+ PlaySE(SE_SELECT);
+ gTasks[taskId].func = Task_ResetRtc_2;
+ data[1] = 1;
+ data[2] = 6;
+ }
+ }
+ else if (ResetRtcScreen_MoveTimeUpDown(&data[selectionInfo->dataIndex], selectionInfo->minVal, selectionInfo->maxVal, gMain.newAndRepeatedKeys & (DPAD_UP | DPAD_DOWN)))
+ {
+ PlaySE(SE_SELECT);
+ ResetRtcScreen_PrintTime(4, 9, data[3], data[4], data[5], data[6]);
+ }
+}
+
+void Task_ResetRtc_0(u8 taskId)
+{
+ s16 *data = gTasks[taskId].data;
+ data[0] = 0;
+ data[3] = gLocalTime.days;
+ data[4] = gLocalTime.hours;
+ data[5] = gLocalTime.minutes;
+ data[6] = gLocalTime.seconds;
+ ResetRtcScreen_ShowChooseTimeWindow(data[3], data[4], data[5], data[6]);
+ ResetRtcScreen_CreateCursor(taskId);
+ data[2] = 2;
+ gTasks[taskId].func = Task_ResetRtc_1;
+}
+
+void CB2_InitResetRtcScreen(void)
+{
+ u8 *addr;
+ u32 size;
+
+ REG_DISPCNT = 0;
+ SetVBlankCallback(NULL);
+
+ DmaClear16(3, PLTT, PLTT_SIZE);
+
+ addr = (u8 *)VRAM;
+ size = 0x18000;
+ while (1)
+ {
+ DmaFill16(3, 0, addr, 0x1000);
+ addr += 0x1000;
+ size -= 0x1000;
+ if (size <= 0x1000)
+ {
+ DmaFill16(3, 0, addr, size);
+ break;
+ }
+ }
+
+ ResetOamRange(0, 128);
+ LoadOam();
+ remove_some_task();
+ dp12_8087EA4();
+ ResetSpriteData();
+ ResetTasks();
+ ResetPaletteFade();
+ SetUpWindowConfig(&gWindowConfig_81E6CE4);
+ InitMenuWindow(&gWindowConfig_81E6CE4);
+ REG_DISPCNT = 4352;
+ SetVBlankCallback(VBlankCB_ResetRtcScreen);
+ SetMainCallback2(CB2_ResetRtcScreen);
+ CreateTask(Task_ResetRtcScreen, 80);
+}
+
+void CB2_ResetRtcScreen(void)
+{
+ RunTasks();
+ AnimateSprites();
+ BuildOamBuffer();
+ UpdatePaletteFade();
+}
+
+void VBlankCB_ResetRtcScreen(void)
+{
+ ProcessSpriteCopyRequests();
+ LoadOam();
+ TransferPlttBuffer();
+}
+
+void ResetRtcScreen_ShowMessage(const u8 *str)
+{
+ MenuDisplayMessageBox();
+ MenuPrint(str, 2, 15);
+}
+
+void Task_ShowResetRtcPrompt(u8 taskId)
+{
+ s16 *data = gTasks[taskId].data;
+
+ switch (data[0])
+ {
+ case 0:
+ MenuZeroFillScreen();
+ MenuDrawTextWindow(0, 0, 20, 10);
+ MenuPrint(gSystemText_PresentTime, 1, 1);
+ ResetRtcScreen_PrintTime(
+ 1,
+ 3,
+ gLocalTime.days,
+ gLocalTime.hours,
+ gLocalTime.minutes,
+ gLocalTime.seconds);
+ MenuPrint(gSystemText_PreviousTime, 1, 5);
+ ResetRtcScreen_PrintTime(
+ 1,
+ 7,
+ gSaveBlock2.lastBerryTreeUpdate.days,
+ gSaveBlock2.lastBerryTreeUpdate.hours,
+ gSaveBlock2.lastBerryTreeUpdate.minutes,
+ gSaveBlock2.lastBerryTreeUpdate.seconds);
+ ResetRtcScreen_ShowMessage(gSystemText_ResetRTCPrompt);
+ data[0]++;
+ case 1:
+ if (gMain.newKeys & B_BUTTON)
+ {
+ DestroyTask(taskId);
+ DoSoftReset();
+ }
+ else if (gMain.newKeys & A_BUTTON)
+ {
+ PlaySE(SE_SELECT);
+ DestroyTask(taskId);
+ }
+ break;
+ }
+}
+
+void Task_ResetRtcScreen(u8 taskId)
+{
+ s16 *data = gTasks[taskId].data;
+
+ switch (data[0])
+ {
+ case 0:
+ BeginNormalPaletteFade(0xFFFFFFFF, 1, 0x10, 0, 0xFFFF);
+ data[0] = 1;
+ break;
+ case 1:
+ if (!gPaletteFade.active)
+ {
+ if (gSaveFileStatus == 0 || gSaveFileStatus == 2)
+ {
+ ResetRtcScreen_ShowMessage(gSystemText_NoSaveFileNoTime);
+ data[0] = 5;
+ }
+ else
+ {
+ RtcCalcLocalTime();
+ data[1] = CreateTask(Task_ShowResetRtcPrompt, 80);
+ data[0] = 2;
+ }
+ }
+ break;
+ case 2:
+ if (gTasks[data[1]].isActive != TRUE)
+ {
+ MenuZeroFillScreen();
+ ResetRtcScreen_ShowMessage(gSystemText_PleaseResetTime);
+ gLocalTime = gSaveBlock2.lastBerryTreeUpdate;
+ data[1] = CreateTask(Task_ResetRtc_0, 80);
+ data[0] = 3;
+ }
+ break;
+ case 3:
+ if (gTasks[data[1]].data[0])
+ {
+ if (!gTasks[data[1]].data[1])
+ {
+ DestroyTask(data[1]);
+ data[0] = 2;
+ }
+ else
+ {
+ DestroyTask(data[1]);
+ RtcReset();
+ RtcCalcLocalTimeOffset(
+ gLocalTime.days,
+ gLocalTime.hours,
+ gLocalTime.minutes,
+ gLocalTime.seconds);
+ gSaveBlock2.lastBerryTreeUpdate = gLocalTime;
+ VarSet(VAR_DAYS, gLocalTime.days);
+ DisableResetRTC();
+ ResetRtcScreen_ShowMessage(gSystemText_ClockResetDataSave);
+ data[0] = 4;
+ }
+ }
+ break;
+ case 4:
+ if (TrySavingData(0) == TRUE)
+ {
+ ResetRtcScreen_ShowMessage(gSystemText_SaveCompleted);
+ PlaySE(SE_PINPON);
+ }
+ else
+ {
+ ResetRtcScreen_ShowMessage(gSystemText_SaveFailed);
+ PlaySE(SE_BOO);
+ }
+ data[0] = 5;
+ case 5:
+ if (gMain.newKeys & A_BUTTON)
+ {
+ BeginNormalPaletteFade(0xFFFFFFFF, 1, 0, 0x10, 0xFFFF);
+ data[0] = 6;
+ }
+ else
+ {
+ break;
+ }
+ case 6:
+ if (!gPaletteFade.active)
+ {
+ DestroyTask(taskId);
+ DoSoftReset();
+ }
+ }
+}
diff --git a/src/engine/rng.c b/src/engine/rng.c
new file mode 100644
index 000000000..7d4b5600e
--- /dev/null
+++ b/src/engine/rng.c
@@ -0,0 +1,18 @@
+#include "global.h"
+#include "rng.h"
+
+// The number 1103515245 comes from the example implementation of rand and srand
+// in the ISO C standard.
+
+u32 gRngValue;
+
+u16 Random(void)
+{
+ gRngValue = 1103515245 * gRngValue + 24691;
+ return gRngValue >> 16;
+}
+
+void SeedRng(u16 seed)
+{
+ gRngValue = seed;
+}
diff --git a/src/engine/rtc.c b/src/engine/rtc.c
new file mode 100644
index 000000000..d73f943d2
--- /dev/null
+++ b/src/engine/rtc.c
@@ -0,0 +1,349 @@
+#include "global.h"
+#include "rtc.h"
+#include "string_util.h"
+#include "text.h"
+
+static u16 sErrorStatus;
+static struct SiiRtcInfo sRtc;
+static u8 sProbeResult;
+static u16 sSavedIme;
+
+struct Time gLocalTime;
+
+static const struct SiiRtcInfo sRtcDummy = {0, MONTH_JAN, 1}; // 2000 Jan 1
+
+static const s32 sNumDaysInMonths[12] =
+{
+ 31,
+ 28,
+ 31,
+ 30,
+ 31,
+ 30,
+ 31,
+ 31,
+ 30,
+ 31,
+ 30,
+ 31,
+};
+
+void RtcDisableInterrupts()
+{
+ sSavedIme = REG_IME;
+ REG_IME = 0;
+}
+
+void RtcRestoreInterrupts()
+{
+ REG_IME = sSavedIme;
+}
+
+u32 ConvertBcdToBinary(u8 bcd)
+{
+ if (bcd > 0x9F)
+ return 0xFF;
+
+ if ((bcd & 0xF) <= 9)
+ return (10 * ((bcd >> 4) & 0xF)) + (bcd & 0xF);
+ else
+ return 0xFF;
+}
+
+bool8 IsLeapYear(u8 year)
+{
+ if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
+ return TRUE;
+
+ return FALSE;
+}
+
+u16 ConvertDateToDayCount(u8 year, u8 month, u8 day)
+{
+ s32 i;
+ u16 dayCount = 0;
+
+#ifndef BUGFIX_BERRY
+ // The berry glitch was caused by not adding days for the year 2000.
+ for (i = year - 1; i > 0; i--)
+ {
+ dayCount += 365;
+
+ if (IsLeapYear(i) == TRUE)
+ dayCount++;
+ }
+#else
+ // The fix was to use "i >= 0" as the condition instead of "i > 0".
+ for (i = year - 1; i >= 0; i--)
+ {
+ dayCount += 365;
+
+ if (IsLeapYear(i) == TRUE)
+ dayCount++;
+ }
+#endif // BUGFIX_BERRY
+
+ for (i = 0; i < month - 1; i++)
+ dayCount += sNumDaysInMonths[i];
+
+ if (month > MONTH_FEB && IsLeapYear(year) == TRUE)
+ dayCount++;
+
+ dayCount += day;
+
+ return dayCount;
+}
+
+u16 RtcGetDayCount(struct SiiRtcInfo *rtc)
+{
+ u8 year = ConvertBcdToBinary(rtc->year);
+ u8 month = ConvertBcdToBinary(rtc->month);
+ u8 day = ConvertBcdToBinary(rtc->day);
+ return ConvertDateToDayCount(year, month, day);
+}
+
+void RtcInit()
+{
+ sErrorStatus = 0;
+
+ RtcDisableInterrupts();
+ SiiRtcUnprotect();
+ sProbeResult = SiiRtcProbe();
+ RtcRestoreInterrupts();
+
+ if (!(sProbeResult & 0xF))
+ {
+ sErrorStatus = RTC_INIT_ERROR;
+ return;
+ }
+
+ if (sProbeResult & 0xF0)
+ sErrorStatus = RTC_INIT_WARNING;
+ else
+ sErrorStatus = 0;
+
+ RtcGetRawInfo(&sRtc);
+ sErrorStatus = RtcCheckInfo(&sRtc);
+}
+
+u16 RtcGetErrorStatus()
+{
+ return sErrorStatus;
+}
+
+void RtcGetInfo(struct SiiRtcInfo *rtc)
+{
+ if (sErrorStatus & RTC_ERR_FLAG_MASK)
+ *rtc = sRtcDummy;
+ else
+ RtcGetRawInfo(rtc);
+}
+
+void RtcGetDateTime(struct SiiRtcInfo *rtc)
+{
+ RtcDisableInterrupts();
+ SiiRtcGetDateTime(rtc);
+ RtcRestoreInterrupts();
+}
+
+void RtcGetStatus(struct SiiRtcInfo *rtc)
+{
+ RtcDisableInterrupts();
+ SiiRtcGetStatus(rtc);
+ RtcRestoreInterrupts();
+}
+
+void RtcGetRawInfo(struct SiiRtcInfo *rtc)
+{
+ RtcGetStatus(rtc);
+ RtcGetDateTime(rtc);
+}
+
+u16 RtcCheckInfo(struct SiiRtcInfo *rtc)
+{
+ u16 errorFlags = 0;
+ s32 year;
+ s32 month;
+ s32 value;
+
+ if (rtc->status & SIIRTCINFO_POWER)
+ errorFlags |= RTC_ERR_POWER_FAILURE;
+
+ if (!(rtc->status & SIIRTCINFO_24HOUR))
+ errorFlags |= RTC_ERR_12HOUR_CLOCK;
+
+ year = ConvertBcdToBinary(rtc->year);
+
+ if (year == 0xFF)
+ errorFlags |= RTC_ERR_INVALID_YEAR;
+
+ month = ConvertBcdToBinary(rtc->month);
+
+ if (month == 0xFF || month == 0 || month > 12)
+ errorFlags |= RTC_ERR_INVALID_MONTH;
+
+ value = ConvertBcdToBinary(rtc->day);
+
+ if (value == 0xFF)
+ errorFlags |= RTC_ERR_INVALID_DAY;
+
+ if (month == MONTH_FEB)
+ {
+ if (value > IsLeapYear(year) + sNumDaysInMonths[month - 1])
+ errorFlags |= RTC_ERR_INVALID_DAY;
+ }
+ else
+ {
+ if (value > sNumDaysInMonths[month - 1])
+ errorFlags |= RTC_ERR_INVALID_DAY;
+ }
+
+ value = ConvertBcdToBinary(rtc->hour);
+
+ if (value > 24)
+ errorFlags |= RTC_ERR_INVALID_HOUR;
+
+ value = ConvertBcdToBinary(rtc->minute);
+
+ if (value > 60)
+ errorFlags |= RTC_ERR_INVALID_MINUTE;
+
+ value = ConvertBcdToBinary(rtc->second);
+
+ if (value > 60)
+ errorFlags |= RTC_ERR_INVALID_SECOND;
+
+ return errorFlags;
+}
+
+void RtcReset()
+{
+ RtcDisableInterrupts();
+ SiiRtcReset();
+ RtcRestoreInterrupts();
+}
+
+void FormatDecimalTime(u8 *dest, s32 hour, s32 minute, s32 second)
+{
+ dest = ConvertIntToDecimalStringN(dest, hour, STR_CONV_MODE_LEADING_ZEROS, 2);
+ *dest++ = CHAR_COLON;
+ dest = ConvertIntToDecimalStringN(dest, minute, STR_CONV_MODE_LEADING_ZEROS, 2);
+ *dest++ = CHAR_COLON;
+ dest = ConvertIntToDecimalStringN(dest, second, STR_CONV_MODE_LEADING_ZEROS, 2);
+ *dest = EOS;
+}
+
+void FormatHexTime(u8 *dest, s32 hour, s32 minute, s32 second)
+{
+ dest = ConvertIntToHexStringN(dest, hour, STR_CONV_MODE_LEADING_ZEROS, 2);
+ *dest++ = CHAR_COLON;
+ dest = ConvertIntToHexStringN(dest, minute, STR_CONV_MODE_LEADING_ZEROS, 2);
+ *dest++ = CHAR_COLON;
+ dest = ConvertIntToHexStringN(dest, second, STR_CONV_MODE_LEADING_ZEROS, 2);
+ *dest = EOS;
+}
+
+void FormatHexRtcTime(u8 *dest)
+{
+ FormatHexTime(dest, sRtc.hour, sRtc.minute, sRtc.second);
+}
+
+void FormatDecimalDate(u8 *dest, s32 year, s32 month, s32 day)
+{
+ dest = ConvertIntToDecimalStringN(dest, year, STR_CONV_MODE_LEADING_ZEROS, 4);
+ *dest++ = CHAR_HYPHEN;
+ dest = ConvertIntToDecimalStringN(dest, month, STR_CONV_MODE_LEADING_ZEROS, 2);
+ *dest++ = CHAR_HYPHEN;
+ dest = ConvertIntToDecimalStringN(dest, day, STR_CONV_MODE_LEADING_ZEROS, 2);
+ *dest = EOS;
+}
+
+void FormatHexDate(u8 *dest, s32 year, s32 month, s32 day)
+{
+ dest = ConvertIntToHexStringN(dest, year, STR_CONV_MODE_LEADING_ZEROS, 4);
+ *dest++ = CHAR_HYPHEN;
+ dest = ConvertIntToHexStringN(dest, month, STR_CONV_MODE_LEADING_ZEROS, 2);
+ *dest++ = CHAR_HYPHEN;
+ dest = ConvertIntToHexStringN(dest, day, STR_CONV_MODE_LEADING_ZEROS, 2);
+ *dest = EOS;
+}
+
+void RtcCalcTimeDifference(struct SiiRtcInfo *rtc, struct Time *result, struct Time *t)
+{
+ u16 days = RtcGetDayCount(rtc);
+ result->seconds = ConvertBcdToBinary(rtc->second) - t->seconds;
+ result->minutes = ConvertBcdToBinary(rtc->minute) - t->minutes;
+ result->hours = ConvertBcdToBinary(rtc->hour) - t->hours;
+ result->days = days - t->days;
+
+ if (result->seconds < 0)
+ {
+ result->seconds += 60;
+ --result->minutes;
+ }
+
+ if (result->minutes < 0)
+ {
+ result->minutes += 60;
+ --result->hours;
+ }
+
+ if (result->hours < 0)
+ {
+ result->hours += 24;
+ --result->days;
+ }
+}
+
+void RtcCalcLocalTime()
+{
+ RtcGetInfo(&sRtc);
+ RtcCalcTimeDifference(&sRtc, &gLocalTime, &gSaveBlock2.localTimeOffset);
+}
+
+void RtcInitLocalTimeOffset(s32 hour, s32 minute)
+{
+ RtcCalcLocalTimeOffset(0, hour, minute, 0);
+}
+
+void RtcCalcLocalTimeOffset(s32 days, s32 hours, s32 minutes, s32 seconds)
+{
+ gLocalTime.days = days;
+ gLocalTime.hours = hours;
+ gLocalTime.minutes = minutes;
+ gLocalTime.seconds = seconds;
+ RtcGetInfo(&sRtc);
+ RtcCalcTimeDifference(&sRtc, &gSaveBlock2.localTimeOffset, &gLocalTime);
+}
+
+void CalcTimeDifference(struct Time *result, struct Time *t1, struct Time *t2)
+{
+ result->seconds = t2->seconds - t1->seconds;
+ result->minutes = t2->minutes - t1->minutes;
+ result->hours = t2->hours - t1->hours;
+ result->days = t2->days - t1->days;
+
+ if (result->seconds < 0)
+ {
+ result->seconds += 60;
+ --result->minutes;
+ }
+
+ if (result->minutes < 0)
+ {
+ result->minutes += 60;
+ --result->hours;
+ }
+
+ if (result->hours < 0)
+ {
+ result->hours += 24;
+ --result->days;
+ }
+}
+
+u32 RtcGetMinuteCount()
+{
+ RtcGetInfo(&sRtc);
+ return (24 * 60) * RtcGetDayCount(&sRtc) + 60 * sRtc.hour + sRtc.minute;
+}
diff --git a/src/engine/save.c b/src/engine/save.c
new file mode 100644
index 000000000..eae3f4470
--- /dev/null
+++ b/src/engine/save.c
@@ -0,0 +1,797 @@
+#include "global.h"
+#include "gba/gba.h"
+#include "gba/flash_internal.h"
+#include "save.h"
+#include "load_save.h"
+#include "overworld.h"
+#include "save_failed_screen.h"
+
+#define GETVALIDSTATUSBITFIELD ((1 << ARRAY_COUNT(gSaveSectionLocations)) - 1)
+#define GETCHUNKSIZE(chunk, n) ((sizeof(chunk) - (0xF80 * (n - 1))) >= 0xF80 ? 0xF80 : (sizeof(chunk) - (0xF80 * (n - 1))))
+#define GETBLOCKOFFSET(n) (0xF80 * (n - 1))
+#define TOTALNUMSECTORS ((ARRAY_COUNT(gSaveSectionLocations) * 2) + (ARRAY_COUNT(gHallOfFameSaveSectionLocations) * 2)) // there are 2 slots, so double each array count and get the sum.
+
+extern struct SaveSection unk_2000000; // slow save RAM
+
+u16 gLastWrittenSector;
+u32 gLastSaveCounter;
+u16 gLastKnownGoodSector;
+u32 gDamagedSaveSectors;
+u32 gSaveCounter;
+struct SaveSection *gFastSaveSection; // the pointer is in fast IWRAM but may sometimes point to the slower EWRAM.
+u16 gUnknown_03005EB4;
+u16 gSaveFileStatus;
+u32 gGameContinueCallback;
+
+extern struct PokemonStorage gPokemonStorage;
+extern struct HallOfFame gHallOfFame;
+
+static EWRAM_DATA u32 gLastSaveSectorStatus = 0; // used but in an unferenced function, so unused
+
+const struct SaveSectionLocation gSaveSectionLocations[] =
+{
+ {((u8 *) &gSaveBlock2) + GETBLOCKOFFSET(1), GETCHUNKSIZE(gSaveBlock2, 1)},
+ {((u8 *) &gSaveBlock1) + GETBLOCKOFFSET(1), GETCHUNKSIZE(gSaveBlock1, 1)},
+ {((u8 *) &gSaveBlock1) + GETBLOCKOFFSET(2), GETCHUNKSIZE(gSaveBlock1, 2)},
+ {((u8 *) &gSaveBlock1) + GETBLOCKOFFSET(3), GETCHUNKSIZE(gSaveBlock1, 3)},
+ {((u8 *) &gSaveBlock1) + GETBLOCKOFFSET(4), GETCHUNKSIZE(gSaveBlock1, 4)},
+ {((u8 *) &gPokemonStorage) + GETBLOCKOFFSET(1), GETCHUNKSIZE(gPokemonStorage, 1)},
+ {((u8 *) &gPokemonStorage) + GETBLOCKOFFSET(2), GETCHUNKSIZE(gPokemonStorage, 2)},
+ {((u8 *) &gPokemonStorage) + GETBLOCKOFFSET(3), GETCHUNKSIZE(gPokemonStorage, 3)},
+ {((u8 *) &gPokemonStorage) + GETBLOCKOFFSET(4), GETCHUNKSIZE(gPokemonStorage, 4)},
+ {((u8 *) &gPokemonStorage) + GETBLOCKOFFSET(5), GETCHUNKSIZE(gPokemonStorage, 5)},
+ {((u8 *) &gPokemonStorage) + GETBLOCKOFFSET(6), GETCHUNKSIZE(gPokemonStorage, 6)},
+ {((u8 *) &gPokemonStorage) + GETBLOCKOFFSET(7), GETCHUNKSIZE(gPokemonStorage, 7)},
+ {((u8 *) &gPokemonStorage) + GETBLOCKOFFSET(8), GETCHUNKSIZE(gPokemonStorage, 8)},
+ {((u8 *) &gPokemonStorage) + GETBLOCKOFFSET(9), GETCHUNKSIZE(gPokemonStorage, 9)}
+};
+
+const struct SaveSectionLocation gHallOfFameSaveSectionLocations[] =
+{
+ {((u8 *) &gHallOfFame) + GETBLOCKOFFSET(1), GETCHUNKSIZE(struct HallOfFame, 1)}, // gHallOfFame is not a proper sym, so the struct must be used.
+ {((u8 *) &gHallOfFame) + GETBLOCKOFFSET(2), GETCHUNKSIZE(struct HallOfFame, 2)}
+};
+
+const u8 gFlashSectors[] = { 0x1E, 0x1F };
+
+void ClearSaveData(void)
+{
+ u16 i;
+
+ for (i = 0; i < NUM_SECTORS; i++)
+ EraseFlashSector(i);
+}
+
+void ResetSaveCounters(void)
+{
+ gSaveCounter = 0;
+ gLastWrittenSector = 0;
+ gDamagedSaveSectors = 0;
+}
+
+bool32 SetDamagedSectorBits(u8 op, u8 bit)
+{
+ bool32 retVal = FALSE;
+
+ switch (op)
+ {
+ case ENABLE:
+ gDamagedSaveSectors |= (1 << bit);
+ break;
+ case DISABLE:
+ gDamagedSaveSectors &= ~(1 << bit);
+ break;
+ case CHECK: // unused
+ if (gDamagedSaveSectors & (1 << bit))
+ retVal = TRUE;
+ break;
+ }
+
+ return retVal;
+}
+
+u8 save_write_to_flash(u16 a1, const struct SaveSectionLocation *location)
+{
+ u32 retVal;
+ u16 i;
+
+ gFastSaveSection = &unk_2000000;
+
+ if (a1 != 0xFFFF) // for link
+ {
+ retVal = HandleWriteSector(a1, location);
+ }
+ else
+ {
+ gLastKnownGoodSector = gLastWrittenSector; // backup the current written sector before attempting to write.
+ gLastSaveCounter = gSaveCounter;
+ gLastWrittenSector++;
+ gLastWrittenSector = gLastWrittenSector % ARRAY_COUNT(gSaveSectionLocations);
+ gSaveCounter++;
+ retVal = 1;
+
+ for (i = 0; i < ARRAY_COUNT(gSaveSectionLocations); i++)
+ HandleWriteSector(i, location);
+
+ if (gDamagedSaveSectors != 0) // skip the damaged sector.
+ {
+ retVal = 0xFF;
+ gLastWrittenSector = gLastKnownGoodSector;
+ gSaveCounter = gLastSaveCounter;
+ }
+ }
+
+ return retVal;
+}
+
+u8 HandleWriteSector(u16 a1, const struct SaveSectionLocation *location)
+{
+ u16 i;
+ u16 sector;
+ u8 *data;
+ u16 size;
+
+ sector = a1 + gLastWrittenSector;
+ sector %= ARRAY_COUNT(gSaveSectionLocations);
+ sector += ARRAY_COUNT(gSaveSectionLocations) * (gSaveCounter % 2);
+
+ data = location[a1].data;
+ size = location[a1].size;
+
+ // clear save section.
+ for (i = 0; i < sizeof(struct SaveSection); i++)
+ ((char *)gFastSaveSection)[i] = 0;
+
+ gFastSaveSection->id = a1;
+ gFastSaveSection->security = UNKNOWN_CHECK_VALUE;
+ gFastSaveSection->counter = gSaveCounter;
+
+ for (i = 0; i < size; i++)
+ gFastSaveSection->data[i] = data[i];
+
+ gFastSaveSection->checksum = CalculateChecksum(data, size);
+ return TryWriteSector(sector, gFastSaveSection->data);
+}
+
+u8 HandleWriteSectorNBytes(u8 sector, u8 *data, u16 size)
+{
+ u16 i;
+ struct SaveSection *section = &unk_2000000;
+
+ for (i = 0; i < sizeof(struct SaveSection); i++)
+ ((char *)section)[i] = 0;
+
+ section->security = UNKNOWN_CHECK_VALUE;
+
+ for (i = 0; i < size; i++)
+ section->data[i] = data[i];
+
+ section->id = CalculateChecksum(data, size); // though this appears to be incorrect, it might be some sector checksum instead of a whole save checksum and only appears to be relevent to HOF data, if used.
+ return TryWriteSector(sector, section->data);
+}
+
+u8 TryWriteSector(u8 sector, u8 *data)
+{
+ if (ProgramFlashSectorAndVerify(sector, data) != 0) // is damaged?
+ {
+ SetDamagedSectorBits(ENABLE, sector); // set damaged sector bits.
+ return 0xFF;
+ }
+ else
+ {
+ SetDamagedSectorBits(DISABLE, sector); // unset damaged sector bits. it's safe now.
+ return 1;
+ }
+}
+
+u32 RestoreSaveBackupVarsAndIncrement(const struct SaveSectionLocation *location) // location is unused
+{
+ gFastSaveSection = &unk_2000000;
+ gLastKnownGoodSector = gLastWrittenSector;
+ gLastSaveCounter = gSaveCounter;
+ gLastWrittenSector++;
+ gLastWrittenSector = gLastWrittenSector % ARRAY_COUNT(gSaveSectionLocations);
+ gSaveCounter++;
+ gUnknown_03005EB4 = 0;
+ gDamagedSaveSectors = 0;
+ return 0;
+}
+
+u32 RestoreSaveBackupVars(const struct SaveSectionLocation *location) // only ever called once, and gSaveBlock2 is passed to this function. location is unused
+{
+ gFastSaveSection = &unk_2000000;
+ gLastKnownGoodSector = gLastWrittenSector;
+ gLastSaveCounter = gSaveCounter;
+ gUnknown_03005EB4 = 0;
+ gDamagedSaveSectors = 0;
+ return 0;
+}
+
+u8 sub_812550C(u16 a1, const struct SaveSectionLocation *location)
+{
+ u8 retVal;
+
+ if (gUnknown_03005EB4 < a1 - 1)
+ {
+ retVal = 1;
+ HandleWriteSector(gUnknown_03005EB4, location);
+ gUnknown_03005EB4++;
+ if (gDamagedSaveSectors)
+ {
+ retVal = 0xFF;
+ gLastWrittenSector = gLastKnownGoodSector;
+ gSaveCounter = gLastSaveCounter;
+ }
+ }
+ else
+ {
+ retVal = 0xFF;
+ }
+
+ return retVal;
+}
+
+u8 sub_812556C(u16 a1, const struct SaveSectionLocation *location)
+{
+ u8 retVal = 1;
+
+ sub_81255B8(a1 - 1, location);
+
+ if (gDamagedSaveSectors)
+ {
+ retVal = 0xFF;
+ gLastWrittenSector = gLastKnownGoodSector;
+ gSaveCounter = gLastSaveCounter;
+ }
+ return retVal;
+}
+
+u8 sub_81255B8(u16 a1, const struct SaveSectionLocation *location)
+{
+ u16 i;
+ u16 sector;
+ u8 *data;
+ u16 size;
+ u8 status;
+
+ sector = a1 + gLastWrittenSector;
+ sector %= ARRAY_COUNT(gSaveSectionLocations);
+ sector += ARRAY_COUNT(gSaveSectionLocations) * (gSaveCounter % 2);
+
+ data = location[a1].data;
+ size = location[a1].size;
+
+ // clear temp save section.
+ for (i = 0; i < sizeof(struct SaveSection); i++)
+ ((char *)gFastSaveSection)[i] = 0;
+
+ gFastSaveSection->id = a1;
+ gFastSaveSection->security = UNKNOWN_CHECK_VALUE;
+ gFastSaveSection->counter = gSaveCounter;
+
+ // set temp section's data.
+ for (i = 0; i < size; i++)
+ gFastSaveSection->data[i] = data[i];
+
+ // calculate checksum.
+ gFastSaveSection->checksum = CalculateChecksum(data, size);
+
+ EraseFlashSector(sector);
+
+ status = 1;
+
+ for (i = 0; i < sizeof(struct UnkSaveSection); i++)
+ {
+ if (ProgramFlashByte(sector, i, ((u8 *)gFastSaveSection)[i]))
+ {
+ status = 0xFF;
+ break;
+ }
+ }
+
+ if (status == 0xFF)
+ {
+ SetDamagedSectorBits(ENABLE, sector);
+ return 0xFF;
+ }
+ else
+ {
+ status = 1;
+
+ for (i = 0; i < 7; i++)
+ {
+ if (ProgramFlashByte(sector, 0xFF9 + i, ((u8 *)gFastSaveSection)[0xFF9 + i]))
+ {
+ status = 0xFF;
+ break;
+ }
+ }
+
+ if (status == 0xFF)
+ {
+ SetDamagedSectorBits(ENABLE, sector);
+ return 0xFF;
+ }
+ else
+ {
+ SetDamagedSectorBits(DISABLE, sector);
+ return 1;
+ }
+ }
+}
+
+u8 sub_8125758(u16 a1, const struct SaveSectionLocation *location)
+{
+ u16 sector;
+
+ sector = a1 + gLastWrittenSector - 1;
+ sector %= ARRAY_COUNT(gSaveSectionLocations);
+ sector += ARRAY_COUNT(gSaveSectionLocations) * (gSaveCounter % 2);
+
+ if (ProgramFlashByte(sector, sizeof(struct UnkSaveSection), ((u8 *)gFastSaveSection)[sizeof(struct UnkSaveSection)]))
+ {
+ // sector is damaged, so enable the bit in gDamagedSaveSectors and restore the last written sector and save counter.
+ SetDamagedSectorBits(ENABLE, sector);
+ gLastWrittenSector = gLastKnownGoodSector;
+ gSaveCounter = gLastSaveCounter;
+ return 0xFF;
+ }
+ else
+ {
+ SetDamagedSectorBits(DISABLE, sector);
+ return 1;
+ }
+}
+
+u8 sub_81257F0(u16 a1, const struct SaveSectionLocation *location)
+{
+ u16 sector;
+
+ sector = a1 + gLastWrittenSector - 1;
+ sector %= ARRAY_COUNT(gSaveSectionLocations);
+ sector += ARRAY_COUNT(gSaveSectionLocations) * (gSaveCounter % 2);
+
+ if (ProgramFlashByte(sector, sizeof(struct UnkSaveSection), 0x25))
+ {
+ // sector is damaged, so enable the bit in gDamagedSaveSectors and restore the last written sector and save counter.
+ SetDamagedSectorBits(ENABLE, sector);
+ gLastWrittenSector = gLastKnownGoodSector;
+ gSaveCounter = gLastSaveCounter;
+ return 0xFF;
+ }
+ else
+ {
+ SetDamagedSectorBits(DISABLE, sector);
+ return 1;
+ }
+}
+
+u8 sub_812587C(u16 a1, const struct SaveSectionLocation *location)
+{
+ u8 retVal;
+ gFastSaveSection = &unk_2000000;
+ if (a1 != 0xFFFF)
+ {
+ retVal = 0xFF;
+ }
+ else
+ {
+ retVal = GetSaveValidStatus(location);
+ sub_81258BC(0xFFFF, location);
+ }
+
+ return retVal;
+}
+
+u8 sub_81258BC(u16 a1, const struct SaveSectionLocation *location)
+{
+ u16 i;
+ u16 checksum;
+ u16 v3 = ARRAY_COUNT(gSaveSectionLocations) * (gSaveCounter % 2);
+ u16 id;
+
+ for (i = 0; i < ARRAY_COUNT(gSaveSectionLocations); i++)
+ {
+ DoReadFlashWholeSection(i + v3, gFastSaveSection);
+ id = gFastSaveSection->id;
+ if (id == 0)
+ gLastWrittenSector = i;
+ checksum = CalculateChecksum(gFastSaveSection->data, location[id].size);
+ if (gFastSaveSection->security == UNKNOWN_CHECK_VALUE
+ && gFastSaveSection->checksum == checksum)
+ {
+ u16 j;
+ for (j = 0; j < location[id].size; j++)
+ ((u8 *)location[id].data)[j] = gFastSaveSection->data[j];
+ }
+ }
+
+ return 1;
+}
+
+u8 GetSaveValidStatus(const struct SaveSectionLocation *location)
+{
+ u16 i;
+ u16 checksum;
+ u32 saveSlot1Counter = 0;
+ u32 saveSlot2Counter = 0;
+ u32 slotCheckField = 0;
+ bool8 securityPassed = FALSE;
+ u8 saveSlot1Status;
+ u8 saveSlot2Status;
+
+ // check save slot 1.
+ for (i = 0; i < ARRAY_COUNT(gSaveSectionLocations); i++)
+ {
+ DoReadFlashWholeSection(i, gFastSaveSection);
+ if (gFastSaveSection->security == UNKNOWN_CHECK_VALUE)
+ {
+ securityPassed = TRUE;
+ checksum = CalculateChecksum(gFastSaveSection->data, location[gFastSaveSection->id].size);
+ if (gFastSaveSection->checksum == checksum)
+ {
+ saveSlot1Counter = gFastSaveSection->counter;
+ slotCheckField |= 1 << gFastSaveSection->id;
+ }
+ }
+ }
+
+ if (securityPassed)
+ {
+ if (slotCheckField == GETVALIDSTATUSBITFIELD)
+ saveSlot1Status = 1;
+ else
+ saveSlot1Status = 255;
+ }
+ else
+ {
+ saveSlot1Status = 0;
+ }
+
+ slotCheckField = 0;
+ securityPassed = FALSE;
+
+ // check save slot 2.
+ for (i = 0; i < ARRAY_COUNT(gSaveSectionLocations); i++)
+ {
+ DoReadFlashWholeSection(i + ARRAY_COUNT(gSaveSectionLocations), gFastSaveSection);
+ if (gFastSaveSection->security == UNKNOWN_CHECK_VALUE)
+ {
+ securityPassed = TRUE;
+ checksum = CalculateChecksum(gFastSaveSection->data, location[gFastSaveSection->id].size);
+ if (gFastSaveSection->checksum == checksum)
+ {
+ saveSlot2Counter = gFastSaveSection->counter;
+ slotCheckField |= 1 << gFastSaveSection->id;
+ }
+ }
+ }
+
+ if (securityPassed)
+ {
+ if (slotCheckField == GETVALIDSTATUSBITFIELD)
+ saveSlot2Status = 1;
+ else
+ saveSlot2Status = 255;
+ }
+ else
+ {
+ saveSlot2Status = 0;
+ }
+
+ if (saveSlot1Status == 1 && saveSlot2Status == 1)
+ {
+ if ((saveSlot1Counter == -1 && saveSlot2Counter == 0) || (saveSlot1Counter == 0 && saveSlot2Counter == -1))
+ {
+ if ((unsigned)(saveSlot1Counter + 1) < (unsigned)(saveSlot2Counter + 1))
+ {
+ gSaveCounter = saveSlot2Counter;
+ }
+ else
+ {
+ gSaveCounter = saveSlot1Counter;
+ }
+ }
+ else
+ {
+ if (saveSlot1Counter < saveSlot2Counter)
+ {
+ gSaveCounter = saveSlot2Counter;
+ }
+ else
+ {
+ gSaveCounter = saveSlot1Counter;
+ }
+ }
+ return 1;
+ }
+
+ if (saveSlot1Status == 1)
+ {
+ gSaveCounter = saveSlot1Counter;
+ if (saveSlot2Status == 255)
+ return 255;
+ return 1;
+ }
+
+ if (saveSlot2Status == 1)
+ {
+ gSaveCounter = saveSlot2Counter;
+ if (saveSlot1Status == 255)
+ return 255;
+ return 1;
+ }
+
+ if (saveSlot1Status == 0 && saveSlot2Status == 0)
+ {
+ gSaveCounter = 0;
+ gLastWrittenSector = 0;
+ return 0;
+ }
+
+ gSaveCounter = 0;
+ gLastWrittenSector = 0;
+ return 2;
+}
+
+u8 sub_8125B88(u8 a1, u8 *data, u16 size)
+{
+ u16 i;
+ struct SaveSection *section = &unk_2000000;
+ DoReadFlashWholeSection(a1, section);
+ if (section->security == UNKNOWN_CHECK_VALUE)
+ {
+ u16 checksum = CalculateChecksum(section->data, size);
+ if (section->id == checksum)
+ {
+ for (i = 0; i < size; i++)
+ data[i] = section->data[i];
+ return 1;
+ }
+ else
+ {
+ return 2;
+ }
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+u8 DoReadFlashWholeSection(u8 sector, struct SaveSection *section)
+{
+ ReadFlash(sector, 0, section->data, sizeof(struct SaveSection));
+ return 1;
+}
+
+u16 CalculateChecksum(void *data, u16 size)
+{
+ u16 i;
+ u32 checksum = 0;
+
+ for (i = 0; i < (size / 4); i++)
+ checksum += *((u32 *)data)++;
+
+ return ((checksum >> 16) + checksum);
+}
+
+u8 HandleSavingData(u8 saveType)
+{
+ u8 i;
+ switch (saveType)
+ {
+ case HOF_DELETE_SAVE: // deletes HOF before overwriting HOF completely. unused
+ for (i = (ARRAY_COUNT(gSaveSectionLocations) * 2 + 0); i < TOTALNUMSECTORS; i++)
+ EraseFlashSector(i);
+ case HOF_SAVE: // hall of fame.
+ if (GetGameStat(10) < 999)
+ IncrementGameStat(10);
+ for (i = 0; i < ARRAY_COUNT(gHallOfFameSaveSectionLocations); i++)
+ HandleWriteSectorNBytes((ARRAY_COUNT(gSaveSectionLocations) * 2 + 0) + i, gHallOfFameSaveSectionLocations[i].data, gHallOfFameSaveSectionLocations[i].size);
+ SaveSerializedGame();
+ save_write_to_flash(0xFFFF, gSaveSectionLocations);
+ break;
+ case NORMAL_SAVE: // normal save. also called by overwriting your own save.
+ default:
+ SaveSerializedGame();
+ save_write_to_flash(0xFFFF, gSaveSectionLocations);
+ break;
+ case LINK_SAVE: // link save. updates only gSaveBlock1 and gSaveBlock2.
+ SaveSerializedGame();
+ for (i = 0; i < 5; i++)
+ save_write_to_flash(i, gSaveSectionLocations);
+ break;
+ case EREADER_SAVE: // used in mossdeep "game corner" before/after battling old man e-reader trainer
+ SaveSerializedGame();
+ save_write_to_flash(0, gSaveSectionLocations);
+ break;
+ case DIFFERENT_FILE_SAVE: // there is a different file, so erase the file and overwrite it completely.
+ for (i = (ARRAY_COUNT(gSaveSectionLocations) * 2 + 0); i < TOTALNUMSECTORS; i++)
+ EraseFlashSector(i); // erase HOF.
+ SaveSerializedGame();
+ save_write_to_flash(0xFFFF, gSaveSectionLocations);
+ break;
+ }
+ return 0;
+}
+
+u8 TrySavingData(u8 saveType) // TrySave
+{
+ if (gFlashMemoryPresent != TRUE)
+ return 0xFF;
+ HandleSavingData(saveType);
+ if (!gDamagedSaveSectors)
+ return 1;
+ DoSaveFailedScreen(saveType);
+ return 0xFF;
+}
+
+u8 sub_8125D80(void) // trade.s save
+{
+ if (gFlashMemoryPresent != TRUE)
+ return 1;
+ SaveSerializedGame();
+ RestoreSaveBackupVarsAndIncrement(gSaveSectionLocations);
+ return 0;
+}
+
+bool8 sub_8125DA8(void) // trade.s save
+{
+ u8 retVal = sub_812550C(ARRAY_COUNT(gSaveSectionLocations), gSaveSectionLocations);
+ if (gDamagedSaveSectors)
+ DoSaveFailedScreen(0);
+ if (retVal == 0xFF)
+ return 1;
+ else
+ return 0;
+}
+
+u8 sub_8125DDC(void) // trade.s save
+{
+ sub_812556C(ARRAY_COUNT(gSaveSectionLocations), gSaveSectionLocations);
+ if (gDamagedSaveSectors)
+ DoSaveFailedScreen(0);
+ return 0;
+}
+
+u8 sub_8125E04(void) // trade.s save
+{
+ sub_8125758(ARRAY_COUNT(gSaveSectionLocations), gSaveSectionLocations);
+ if (gDamagedSaveSectors)
+ DoSaveFailedScreen(0);
+ return 0;
+}
+
+u8 sub_8125E2C(void)
+{
+ if (gFlashMemoryPresent != TRUE)
+ return 1;
+
+ SaveSerializedGame();
+ RestoreSaveBackupVars(gSaveSectionLocations);
+ sub_812556C(gUnknown_03005EB4 + 1, gSaveSectionLocations);
+ return 0;
+}
+
+bool8 sub_8125E6C(void)
+{
+ u8 retVal = FALSE;
+ u16 val = ++gUnknown_03005EB4;
+ if (val <= 4)
+ {
+ sub_812556C(gUnknown_03005EB4 + 1, gSaveSectionLocations);
+ sub_81257F0(val, gSaveSectionLocations);
+ }
+ else
+ {
+ sub_81257F0(val, gSaveSectionLocations);
+ retVal = TRUE;
+ }
+ if (gDamagedSaveSectors)
+ DoSaveFailedScreen(1);
+ return retVal;
+}
+
+u8 sub_8125EC8(u8 a1)
+{
+ u8 result;
+
+ if (gFlashMemoryPresent != TRUE)
+ {
+ gSaveFileStatus = 4;
+ return 0xFF;
+ }
+
+ switch (a1)
+ {
+ case 0:
+ default:
+ result = sub_812587C(0xFFFF, gSaveSectionLocations);
+ LoadSerializedGame();
+ gSaveFileStatus = result;
+ gGameContinueCallback = 0;
+ break;
+ case 3:
+ result = sub_8125B88((ARRAY_COUNT(gSaveSectionLocations) * 2 + 0), gHallOfFameSaveSectionLocations[0].data, gHallOfFameSaveSectionLocations[0].size);
+ if (result == 1)
+ result = sub_8125B88((ARRAY_COUNT(gSaveSectionLocations) * 2 + 1), gHallOfFameSaveSectionLocations[1].data, gHallOfFameSaveSectionLocations[1].size);
+ break;
+ }
+
+ return result;
+}
+
+bool8 unref_sub_8125F4C(struct UnkSaveSection *a1)
+{
+ u16 i;
+ char *raw = (char *)a1;
+
+ for (i = 0; i < sizeof(struct SaveSection); i++)
+ raw[i] = 0;
+
+ ReadFlash(gFlashSectors[0], 0, a1->data, 4096);
+
+ if (a1->security != UNKNOWN_CHECK_VALUE)
+ return FALSE;
+
+ return TRUE;
+}
+
+u8 unref_sub_8125FA0(void)
+{
+ u16 i;
+ u8 v0 = TrySavingData(0);
+
+ for (i = 0; i < 2; i++)
+ EraseFlashSector(gFlashSectors[i]);
+
+ if (v0 == 255)
+ {
+ return 3;
+ }
+ else if (v0 == 3)
+ {
+ return 2;
+ }
+ else
+ {
+ sub_8125EC8(0);
+ return 1;
+ }
+}
+
+u8 unref_sub_8125FF0(u8 *data, u16 size)
+{
+ u16 i;
+ struct UnkSaveSection *section = (struct UnkSaveSection *)&unk_2000000;
+
+ for (i = 0; i < sizeof(struct SaveSection); i++)
+ ((char *)section)[i] = 0;
+
+ section->security = UNKNOWN_CHECK_VALUE;
+
+ for (i = 0; i < size; i++)
+ section->data[i] = data[i];
+
+ gLastSaveSectorStatus = ProgramFlashSectorAndVerifyNBytes(gFlashSectors[0], (u8 *)section, sizeof(struct SaveSection));
+
+ if (gLastSaveSectorStatus)
+ return 0xFF;
+ else
+ return 1;
+}
+
+u8 unref_sub_8126068(u8 sector, u8 *data, u32 size)
+{
+ if (ProgramFlashSectorAndVerify(sector, data))
+ return 255;
+ else
+ return 1;
+}
+
+u8 unref_sub_8126080(u8 sector, u8 *data)
+{
+ ReadFlash(sector, 0, data, sizeof(struct SaveSection));
+ return 1;
+}
diff --git a/src/engine/save_failed_screen.c b/src/engine/save_failed_screen.c
new file mode 100644
index 000000000..b91e8b5bf
--- /dev/null
+++ b/src/engine/save_failed_screen.c
@@ -0,0 +1,310 @@
+#include "global.h"
+#include "gba/flash_internal.h"
+#include "save_failed_screen.h"
+#include "m4a.h"
+#include "main.h"
+#include "menu.h"
+#include "palette.h"
+#include "save.h"
+#include "sprite.h"
+#include "starter_choose.h"
+#include "strings.h"
+#include "task.h"
+#include "text.h"
+
+// In English 1.0, the text window is too small, causing text to overflow.
+
+#ifdef BUGFIX_SAVEFAILEDSCREEN1
+#define MSG_WIN_TOP 10
+#else
+#define MSG_WIN_TOP 12
+#endif
+
+#define CLOCK_WIN_TOP (MSG_WIN_TOP - 4)
+
+extern u8 unk_2000000[];
+
+static EWRAM_DATA u16 gSaveFailedType = 0;
+static EWRAM_DATA u16 gSaveFailedClockInfo[9] = {0};
+
+extern u32 gDamagedSaveSectors;
+extern u32 gGameContinueCallback;
+
+static const struct OamData sClockOamData =
+{
+ 160, // Y
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0
+};
+
+static const u8 sClockFrames[8][3] =
+{
+ { 1, 0, 0 },
+ { 5, 0, 0 },
+ { 9, 0, 0 },
+ { 5, 0, 1 },
+ { 1, 0, 1 },
+ { 5, 1, 1 },
+ { 9, 1, 0 },
+ { 5, 1, 0 },
+};
+
+static const u8 gSaveFailedClockPal[] = INCBIN_U8("graphics/misc/clock_small.gbapal");
+static const u8 gSaveFailedClockGfx[] = INCBIN_U8("graphics/misc/clock_small.4bpp.lz");
+
+static void VBlankCB(void);
+static void CB2_SaveFailedScreen(void);
+static void CB2_WipeSave(void);
+static void CB2_GameplayCannotBeContinued(void);
+static void CB2_FadeAndReturnToTitleScreen(void);
+static void CB2_ReturnToTitleScreen(void);
+static void VBlankCB_UpdateClockGraphics(void);
+static bool8 VerifySectorWipe(u16 sector);
+static bool8 WipeSector(u16 sector);
+static bool8 WipeSectors(u32 sectorBits);
+
+void DoSaveFailedScreen(u8 saveType)
+{
+ SetMainCallback2(CB2_SaveFailedScreen);
+ gSaveFailedType = saveType;
+ gSaveFailedClockInfo[0] = FALSE;
+}
+
+static void VBlankCB(void)
+{
+ LoadOam();
+ ProcessSpriteCopyRequests();
+ TransferPlttBuffer();
+}
+
+static void CB2_SaveFailedScreen(void)
+{
+ u16 ime;
+
+ switch (gMain.state)
+ {
+ case 0:
+ default:
+ SetVBlankCallback(0);
+ REG_DISPCNT = 0;
+ REG_BG3CNT = 0;
+ REG_BG2CNT = 0;
+ REG_BG1CNT = 0;
+ REG_BG0CNT = 0;
+ REG_BG3HOFS = 0;
+ REG_BG3VOFS = 0;
+ REG_BG2HOFS = 0;
+ REG_BG2VOFS = 0;
+ REG_BG1HOFS = 0;
+ REG_BG1VOFS = 0;
+ REG_BG0HOFS = 0;
+ REG_BG0VOFS = 0;
+ DmaFill16(3, 0, VRAM, VRAM_SIZE);
+ DmaFill32(3, 0, OAM, OAM_SIZE);
+ DmaFill16(3, 0, PLTT, PLTT_SIZE);
+ LZ77UnCompVram(&gBirchHelpGfx, (void *)VRAM);
+ LZ77UnCompVram(&gBirchBagTilemap, (void *)(VRAM + 0x3000));
+ LZ77UnCompVram(&gBirchGrassTilemap, (void *)(VRAM + 0x3800));
+ LZ77UnCompVram(&gSaveFailedClockGfx, (void *)(VRAM + 0x10020));
+ ResetSpriteData();
+ ResetTasks();
+ ResetPaletteFade();
+ LoadPalette(&gBirchBagGrassPal, 0, sizeof(gBirchBagGrassPal));
+ LoadPalette(&gSaveFailedClockPal, 0x100, sizeof(gSaveFailedClockPal));
+ SetUpWindowConfig(&gWindowConfig_81E6C3C);
+ InitMenuWindow(&gWindowConfig_81E6CE4);
+ MenuDrawTextWindow(13, CLOCK_WIN_TOP, 16, CLOCK_WIN_TOP + 3); // clock window
+ MenuDrawTextWindow(1, MSG_WIN_TOP, 28, 19); // message window
+ MenuPrint(gSystemText_SaveFailedBackupCheck, 2, MSG_WIN_TOP + 1);
+ BeginNormalPaletteFade(0xFFFFFFFF, 0, 16, 0, 0);
+ ime = REG_IME;
+ REG_IME = 0;
+ REG_IE |= INTR_FLAG_VBLANK;
+ REG_IME = ime;
+ REG_DISPSTAT |= DISPSTAT_VBLANK_INTR;
+ SetVBlankCallback(VBlankCB);
+ REG_BG3CNT = BGCNT_PRIORITY(3) | BGCNT_CHARBASE(0) | BGCNT_SCREENBASE(7) | BGCNT_16COLOR | BGCNT_TXT256x256;
+ REG_BG2CNT = BGCNT_PRIORITY(2) | BGCNT_CHARBASE(0) | BGCNT_SCREENBASE(6) | BGCNT_16COLOR | BGCNT_TXT256x256;
+ REG_BG0CNT = BGCNT_PRIORITY(0) | BGCNT_CHARBASE(2) | BGCNT_SCREENBASE(31) | BGCNT_16COLOR | BGCNT_TXT256x256;
+ REG_DISPCNT = DISPCNT_OBJ_ON | DISPCNT_BG3_ON | DISPCNT_BG2_ON | DISPCNT_BG0_ON | DISPCNT_OBJ_1D_MAP | DISPCNT_MODE_0;
+ gMain.state++;
+ break;
+ case 1:
+ if (!UpdatePaletteFade())
+ {
+ SetMainCallback2(CB2_WipeSave);
+ SetVBlankCallback(VBlankCB_UpdateClockGraphics);
+ }
+ break;
+ }
+}
+
+static void CB2_WipeSave(void)
+{
+ u8 wipeTries = 0;
+
+ gSaveFailedClockInfo[0] = TRUE;
+
+ while (gDamagedSaveSectors != 0 && wipeTries < 3) // while there are still attempts left, keep trying to fix the save sectors.
+ {
+ if (WipeSectors(gDamagedSaveSectors) != FALSE)
+ {
+ MenuDrawTextWindow(1, MSG_WIN_TOP, 28, 19);
+ MenuPrint(gSystemText_BackupDamagedGameContinue, 2, MSG_WIN_TOP + 1);
+ SetMainCallback2(CB2_GameplayCannotBeContinued);
+ return;
+ }
+
+ MenuDrawTextWindow(1, MSG_WIN_TOP, 28, 19);
+ MenuPrint(gSystemText_CheckCompleteSaveAttempt, 2, MSG_WIN_TOP + 1);
+ HandleSavingData(gSaveFailedType);
+
+ if (gDamagedSaveSectors != 0)
+ {
+#ifdef BUGFIX_SAVEFAILEDSCREEN2
+ MenuDrawTextWindow(1, MSG_WIN_TOP, 28, 19);
+#endif
+ MenuPrint(gSystemText_SaveFailedBackupCheck, 2, MSG_WIN_TOP + 1);
+ }
+
+ wipeTries++;
+ }
+
+ if (wipeTries == 3)
+ {
+ MenuDrawTextWindow(1, MSG_WIN_TOP, 28, 19);
+ MenuPrint(gSystemText_BackupDamagedGameContinue, 2, MSG_WIN_TOP + 1);
+ SetMainCallback2(CB2_FadeAndReturnToTitleScreen); // called again below
+ }
+ else
+ {
+ MenuDrawTextWindow(1, MSG_WIN_TOP, 28, 19);
+
+ // no callback exists, so the game cannot continue.
+ if (gGameContinueCallback == 0)
+ MenuPrint(gSystemText_SaveCompletedGameEnd, 2, MSG_WIN_TOP + 1);
+ else // callback exists, so continue
+ MenuPrint(gSystemText_SaveCompletedPressA, 2, MSG_WIN_TOP + 1);
+ }
+
+ SetMainCallback2(CB2_FadeAndReturnToTitleScreen);
+}
+
+static void CB2_GameplayCannotBeContinued(void)
+{
+ gSaveFailedClockInfo[0] = FALSE;
+
+ if (gMain.newKeys & A_BUTTON)
+ {
+ MenuDrawTextWindow(1, MSG_WIN_TOP, 28, 19);
+ MenuPrint(gSystemText_GameplayEnded, 2, MSG_WIN_TOP + 1);
+ SetVBlankCallback(VBlankCB);
+ SetMainCallback2(CB2_FadeAndReturnToTitleScreen);
+ }
+}
+
+static void CB2_FadeAndReturnToTitleScreen(void)
+{
+ gSaveFailedClockInfo[0] = FALSE;
+
+ if (gMain.newKeys & A_BUTTON)
+ {
+ BeginNormalPaletteFade(0xFFFFFFFF, 0, 0, 16, 0);
+ SetVBlankCallback(VBlankCB);
+ SetMainCallback2(CB2_ReturnToTitleScreen);
+ }
+}
+
+static void CB2_ReturnToTitleScreen(void)
+{
+ if (!UpdatePaletteFade())
+ {
+ if (gGameContinueCallback == 0) // no callback exists, so do a soft reset.
+ {
+ DoSoftReset();
+ }
+ else
+ {
+ SetMainCallback2((MainCallback)gGameContinueCallback);
+ gGameContinueCallback = 0;
+ }
+ }
+}
+
+static void VBlankCB_UpdateClockGraphics(void)
+{
+ unsigned int n = (gMain.vblankCounter2 >> 3) & 7;
+
+ gMain.oamBuffer[0] = sClockOamData;
+ gMain.oamBuffer[0].x = 112;
+ gMain.oamBuffer[0].y = (CLOCK_WIN_TOP + 1) * 8;
+
+ if (gSaveFailedClockInfo[0] != FALSE)
+ {
+ gMain.oamBuffer[0].tileNum = sClockFrames[n][0];
+ gMain.oamBuffer[0].matrixNum = (sClockFrames[n][2] << 4) | (sClockFrames[n][1] << 3);
+ }
+ else
+ {
+ gMain.oamBuffer[0].tileNum = 1;
+ }
+
+ CpuFastCopy(gMain.oamBuffer, (void *)OAM, 4);
+
+ if (gSaveFailedClockInfo[1]) // maybe was used for debugging?
+ gSaveFailedClockInfo[1]--;
+}
+
+static bool8 VerifySectorWipe(u16 sector)
+{
+ u32 *ptr = (u32 *)unk_2000000;
+ u16 i;
+
+ ReadFlash(sector, 0, (u8 *)ptr, 4096);
+
+ for (i = 0; i < 0x400; i++, ptr++)
+ if (*ptr)
+ return TRUE;
+
+ return FALSE;
+}
+
+static bool8 WipeSector(u16 sector)
+{
+ u16 i, j;
+ bool8 failed = TRUE;
+
+ for (i = 0; failed && i < 130; i++)
+ {
+ for (j = 0; j < 0x1000; j++)
+ ProgramFlashByte(sector, j, 0);
+
+ failed = VerifySectorWipe(sector);
+ }
+
+ return failed;
+}
+
+static bool8 WipeSectors(u32 sectorBits)
+{
+ u16 i;
+
+ for (i = 0; i < 0x20; i++)
+ if ((sectorBits & (1 << i)) && !WipeSector(i))
+ sectorBits &= ~(1 << i);
+
+ if (sectorBits == 0)
+ return FALSE;
+ else
+ return TRUE;
+}
diff --git a/src/engine/save_menu_util.c b/src/engine/save_menu_util.c
new file mode 100644
index 000000000..a2d17bd36
--- /dev/null
+++ b/src/engine/save_menu_util.c
@@ -0,0 +1,148 @@
+#include "global.h"
+#include "save_menu_util.h"
+#include "event_data.h"
+#include "menu.h"
+#include "pokedex.h"
+#include "region_map.h"
+#include "string_util.h"
+#include "strings2.h"
+
+void HandleDrawSaveWindowInfo(s16 left, s16 top)
+{
+ u32 width = 12;
+
+ // old handle for setting window width?
+ if (IsResizeSaveWindowEnabled())
+ width = 13;
+
+ if (FlagGet(SYS_POKEDEX_GET))
+ {
+ // print info + dex information.
+ MenuDrawTextWindow(left, top, left + width, top + 11);
+ PrintSaveMapName(++left, ++top); // MAP NAME
+ PrintSavePlayerName(left, top + 2); // PLAYER
+ PrintSaveBadges(left, top + 4); // BADGES
+ PrintSavePokedexCount(left, top + 6); // POKEDEX
+ PrintSavePlayTime(left, top + 8); // PLAY TIME
+ }
+ else
+ {
+ // print everything besides dex.
+ MenuDrawTextWindow(left, top, left + width, top + 9);
+ PrintSaveMapName(++left, ++top); // MAP NAME
+ PrintSavePlayerName(left, top + 2); // PLAYER
+ PrintSaveBadges(left, top + 4); // BADGES
+ PrintSavePlayTime(left, top + 6); // PLAY TIME
+ }
+}
+
+void HandleCloseSaveWindow(u16 left, u16 top)
+{
+ u32 width = 12;
+
+ // old handle for setting window width?
+ if (IsResizeSaveWindowEnabled())
+ width = 13;
+
+ if (FlagGet(SYS_POKEDEX_GET))
+ MenuZeroFillWindowRect(left, top, left + width, top + 11);
+ else
+ MenuZeroFillWindowRect(left, top, left + width, top + 9);
+}
+
+/*
+theory: This function was used to handle the save menu window's width being auto sized from
+either 12 or 13 in an older source. Whatever was here might have either been optimized out by
+GF's compiler or was dummied out to always return a TRUE at some point.
+*/
+u8 IsResizeSaveWindowEnabled(void) // i don't know what else to name it..
+{
+ return TRUE;
+}
+
+void PrintSavePlayerName(s16 x, s16 y)
+{
+ MenuPrint(gOtherText_Player, x, y);
+ MenuPrint_RightAligned(gSaveBlock2.playerName, x + 12, y);
+}
+
+void PrintSaveMapName(s16 x, s16 y)
+{
+ char name[32];
+
+ CopyMapName(name, gMapHeader.regionMapSectionId);
+ MenuPrint(name, x, y);
+}
+
+void PrintSaveBadges(s16 x, s16 y)
+{
+ char badges[16];
+
+ MenuPrint(gOtherText_Badges, x, y);
+ ConvertIntToDecimalString(badges, GetBadgeCount());
+ MenuPrint_RightAligned(badges, x + 12, y);
+}
+
+void PrintSavePokedexCount(s16 x, s16 y)
+{
+ char pokedex[16];
+
+ MenuPrint(gOtherText_Pokedex, x, y);
+ ConvertIntToDecimalStringN(pokedex, GetPokedexSeenCount(), 1, 3);
+ MenuPrint_RightAligned(pokedex, x + 12, y);
+}
+
+void PrintSavePlayTime(s16 x, s16 y)
+{
+ char playtime[16];
+
+ MenuPrint(gOtherText_PlayTime, x, y);
+ FormatPlayTime(playtime, gSaveBlock2.playTimeHours, gSaveBlock2.playTimeMinutes, 1);
+ MenuPrint_RightAligned(playtime, x + 12, y);
+}
+
+u8 GetBadgeCount(void)
+{
+ u8 badgeCount = 0;
+ int badgeFlag;
+
+ for (badgeFlag = BADGE01_GET; badgeFlag <= BADGE08_GET; badgeFlag++)
+ if (FlagGet(badgeFlag))
+ badgeCount++;
+
+ return badgeCount;
+}
+
+u16 GetPokedexSeenCount()
+{
+ u16 pokedexSeenCount;
+
+ if (IsNationalPokedexEnabled())
+ pokedexSeenCount = GetNationalPokedexCount(1);
+ else
+ pokedexSeenCount = GetHoennPokedexCount(1);
+
+ return pokedexSeenCount;
+}
+
+void FormatPlayTime(char *playtime, u16 hours, u16 minutes, u16 unk)
+{
+ s16 colon = unk;
+ playtime = ConvertIntToDecimalString(playtime, hours);
+
+ // playtime[0] is hours.
+ // playtime[1] is the character to render between hours and minutes.
+ // playtime[2] is minutes.
+
+ playtime[0] = 0;
+
+ if (colon)
+ playtime[1] = 0xF0; // set middle character to ":"
+ else
+ playtime[1] = 0;
+
+ playtime[2] = 0;
+ playtime += 3;
+
+ ConvertIntToDecimalStringN(playtime, minutes, 2, 2);
+}
diff --git a/src/engine/script.c b/src/engine/script.c
new file mode 100644
index 000000000..f049b96fc
--- /dev/null
+++ b/src/engine/script.c
@@ -0,0 +1,368 @@
+#include "global.h"
+#include "script.h"
+#include "event_data.h"
+
+#define RAM_SCRIPT_MAGIC 51
+
+EWRAM_DATA u8 *gUnknown_0202E8AC = NULL;
+
+static u8 sScriptContext1Status;
+static struct ScriptContext sScriptContext1;
+static struct ScriptContext sScriptContext2;
+static bool8 sScriptContext2Enabled;
+
+extern ScrCmdFunc gScriptCmdTable[];
+extern ScrCmdFunc gScriptCmdTableEnd[];
+extern void *gNullScriptPtr;
+
+void InitScriptContext(struct ScriptContext *ctx, void *cmdTable, void *cmdTableEnd)
+{
+ s32 i;
+
+ ctx->mode = 0;
+ ctx->scriptPtr = 0;
+ ctx->stackDepth = 0;
+ ctx->nativePtr = 0;
+ ctx->cmdTable = cmdTable;
+ ctx->cmdTableEnd = cmdTableEnd;
+
+ for (i = 0; i < 4; i++)
+ ctx->data[i] = 0;
+
+ for (i = 0; i < 20; i++)
+ ctx->stack[i] = 0;
+}
+
+u8 SetupBytecodeScript(struct ScriptContext *ctx, const u8 *ptr)
+{
+ ctx->scriptPtr = ptr;
+ ctx->mode = 1;
+ return 1;
+}
+
+void SetupNativeScript(struct ScriptContext *ctx, bool8 (*ptr)(void))
+{
+ ctx->mode = 2;
+ ctx->nativePtr = ptr;
+}
+
+void StopScript(struct ScriptContext *ctx)
+{
+ ctx->mode = 0;
+ ctx->scriptPtr = 0;
+}
+
+u8 RunScriptCommand(struct ScriptContext *ctx)
+{
+ if (ctx->mode == 0)
+ return 0;
+
+ switch (ctx->mode)
+ {
+ case 0:
+ return 0;
+ case 2:
+ if (ctx->nativePtr)
+ {
+ if (ctx->nativePtr() == TRUE)
+ ctx->mode = 1;
+ return 1;
+ }
+ ctx->mode = 1;
+ case 1:
+ while (1)
+ {
+ u8 cmdCode;
+ ScrCmdFunc *func;
+
+ if (!ctx->scriptPtr)
+ {
+ ctx->mode = 0;
+ return 0;
+ }
+
+ if (ctx->scriptPtr == gNullScriptPtr)
+ {
+ while (1)
+ asm("svc 2"); // HALT
+ }
+
+ cmdCode = *(ctx->scriptPtr);
+ ctx->scriptPtr++;
+ func = &ctx->cmdTable[cmdCode];
+
+ if (func >= ctx->cmdTableEnd)
+ {
+ ctx->mode = 0;
+ return 0;
+ }
+
+ if ((*func)(ctx) == 1)
+ return 1;
+ }
+ }
+
+ return 1;
+}
+
+u8 ScriptPush(struct ScriptContext *ctx, const u8 *ptr)
+{
+ if (ctx->stackDepth + 1 >= 20)
+ {
+ return 1;
+ }
+ else
+ {
+ ctx->stack[ctx->stackDepth] = ptr;
+ ctx->stackDepth++;
+ return 0;
+ }
+}
+
+const u8 *ScriptPop(struct ScriptContext *ctx)
+{
+ if (ctx->stackDepth == 0)
+ return NULL;
+
+ ctx->stackDepth--;
+ return ctx->stack[ctx->stackDepth];
+}
+
+void ScriptJump(struct ScriptContext *ctx, u8 *ptr)
+{
+ ctx->scriptPtr = ptr;
+}
+
+void ScriptCall(struct ScriptContext *ctx, u8 *ptr)
+{
+ ScriptPush(ctx, ctx->scriptPtr);
+ ctx->scriptPtr = ptr;
+}
+
+void ScriptReturn(struct ScriptContext *ctx)
+{
+ ctx->scriptPtr = ScriptPop(ctx);
+}
+
+u16 ScriptReadHalfword(struct ScriptContext *ctx)
+{
+ u16 value = *(ctx->scriptPtr++);
+ value |= *(ctx->scriptPtr++) << 8;
+ return value;
+}
+
+u32 ScriptReadWord(struct ScriptContext *ctx)
+{
+ u32 value0 = *(ctx->scriptPtr++);
+ u32 value1 = *(ctx->scriptPtr++);
+ u32 value2 = *(ctx->scriptPtr++);
+ u32 value3 = *(ctx->scriptPtr++);
+ return (((((value3 << 8) + value2) << 8) + value1) << 8) + value0;
+}
+
+void ScriptContext2_Enable(void)
+{
+ sScriptContext2Enabled = TRUE;
+}
+
+void ScriptContext2_Disable(void)
+{
+ sScriptContext2Enabled = FALSE;
+}
+
+bool8 ScriptContext2_IsEnabled(void)
+{
+ return sScriptContext2Enabled;
+}
+
+void ScriptContext1_Init(void)
+{
+ InitScriptContext(&sScriptContext1, gScriptCmdTable, gScriptCmdTableEnd);
+ sScriptContext1Status = 2;
+}
+
+bool8 ScriptContext2_RunScript(void)
+{
+ if (sScriptContext1Status == 2)
+ return 0;
+
+ if (sScriptContext1Status == 1)
+ return 0;
+
+ ScriptContext2_Enable();
+
+ if (!RunScriptCommand(&sScriptContext1))
+ {
+ sScriptContext1Status = 2;
+ ScriptContext2_Disable();
+ return 0;
+ }
+
+ return 1;
+}
+
+void ScriptContext1_SetupScript(const u8 *ptr)
+{
+ InitScriptContext(&sScriptContext1, gScriptCmdTable, gScriptCmdTableEnd);
+ SetupBytecodeScript(&sScriptContext1, ptr);
+ ScriptContext2_Enable();
+ sScriptContext1Status = 0;
+}
+
+void ScriptContext1_Stop(void)
+{
+ sScriptContext1Status = 1;
+}
+
+void EnableBothScriptContexts()
+{
+ sScriptContext1Status = 0;
+ ScriptContext2_Enable();
+}
+
+void ScriptContext2_RunNewScript(const u8 *ptr)
+{
+ InitScriptContext(&sScriptContext2, &gScriptCmdTable, &gScriptCmdTableEnd);
+ SetupBytecodeScript(&sScriptContext2, ptr);
+ while (RunScriptCommand(&sScriptContext2) == 1)
+ ;
+}
+
+static u8 *mapheader_get_tagged_pointer(u8 tag)
+{
+ u8 *mapScripts = gMapHeader.mapScripts;
+
+ if (mapScripts == NULL)
+ return NULL;
+
+ while (1)
+ {
+ if (*mapScripts == 0)
+ return NULL;
+ if (*mapScripts == tag)
+ {
+ mapScripts++;
+ return (u8 *)(mapScripts[0] + (mapScripts[1] << 8) + (mapScripts[2] << 16) + (mapScripts[3] << 24));
+ }
+ mapScripts += 5;
+ }
+}
+
+static void mapheader_run_script_by_tag(u8 tag)
+{
+ u8 *ptr = mapheader_get_tagged_pointer(tag);
+ if (ptr)
+ ScriptContext2_RunNewScript(ptr);
+}
+
+static u8 *mapheader_get_first_match_from_tagged_ptr_list(u8 tag)
+{
+ u8 *ptr = mapheader_get_tagged_pointer(tag);
+
+ if (!ptr)
+ return NULL;
+
+ while (1)
+ {
+ u16 varIndex1;
+ u16 varIndex2;
+ varIndex1 = ptr[0] | (ptr[1] << 8);
+ if (!varIndex1)
+ return NULL;
+ ptr += 2;
+ varIndex2 = ptr[0] | (ptr[1] << 8);
+ ptr += 2;
+ if (VarGet(varIndex1) == VarGet(varIndex2))
+ return (u8 *)(ptr[0] + (ptr[1] << 8) + (ptr[2] << 16) + (ptr[3] << 24));
+ ptr += 4;
+ }
+}
+
+void mapheader_run_script_with_tag_x1(void)
+{
+ mapheader_run_script_by_tag(1);
+}
+
+void mapheader_run_script_with_tag_x3(void)
+{
+ mapheader_run_script_by_tag(3);
+}
+
+void mapheader_run_script_with_tag_x5(void)
+{
+ mapheader_run_script_by_tag(5);
+}
+
+void mapheader_run_script_with_tag_x6(void)
+{
+ mapheader_run_script_by_tag(6);
+}
+
+bool8 mapheader_run_first_tag2_script_list_match(void)
+{
+ u8 *ptr = mapheader_get_first_match_from_tagged_ptr_list(2);
+
+ if (!ptr)
+ return 0;
+
+ ScriptContext1_SetupScript(ptr);
+ return 1;
+}
+
+void mapheader_run_first_tag4_script_list_match(void)
+{
+ u8 *ptr = mapheader_get_first_match_from_tagged_ptr_list(4);
+ if (ptr)
+ ScriptContext2_RunNewScript(ptr);
+}
+
+static u32 CalculateRamScriptChecksum(void)
+{
+ u32 i;
+ u32 sum = 0;
+ for (i = 0; i < sizeof(struct RamScriptData); i++)
+ sum += ((u8 *)&gSaveBlock1.ramScript.data)[i];
+ return sum;
+}
+
+void ClearRamScript(void)
+{
+ CpuFill32(0, &gSaveBlock1.ramScript, sizeof(struct RamScript));
+}
+
+bool8 InitRamScript(u8 *script, u16 scriptSize, u8 mapGroup, u8 mapNum, u8 objectId)
+{
+ struct RamScriptData *scriptData = &gSaveBlock1.ramScript.data;
+
+ ClearRamScript();
+
+ if (scriptSize > sizeof(scriptData->script))
+ return FALSE;
+
+ scriptData->magic = RAM_SCRIPT_MAGIC;
+ scriptData->mapGroup = mapGroup;
+ scriptData->mapNum = mapNum;
+ scriptData->objectId = objectId;
+ memcpy(scriptData->script, script, scriptSize);
+ gSaveBlock1.ramScript.checksum = CalculateRamScriptChecksum();
+ return TRUE;
+}
+
+u8 *GetRamScript(u8 objectId, u8 *script)
+{
+ struct RamScriptData *scriptData = &gSaveBlock1.ramScript.data;
+ gUnknown_0202E8AC = 0;
+ if (scriptData->magic == RAM_SCRIPT_MAGIC
+ && scriptData->mapGroup == gSaveBlock1.location.mapGroup
+ && scriptData->mapNum == gSaveBlock1.location.mapNum
+ && scriptData->objectId == objectId)
+ {
+ if (CalculateRamScriptChecksum() == gSaveBlock1.ramScript.checksum)
+ {
+ gUnknown_0202E8AC = script;
+ return scriptData->script;
+ }
+ ClearRamScript();
+ }
+ return script;
+}
diff --git a/src/engine/sound.c b/src/engine/sound.c
new file mode 100644
index 000000000..91f5e06a3
--- /dev/null
+++ b/src/engine/sound.c
@@ -0,0 +1,570 @@
+#include "global.h"
+#include "gba/m4a_internal.h"
+#include "sound.h"
+#include "battle.h"
+#include "m4a.h"
+#include "main.h"
+#include "pokemon.h"
+#include "songs.h"
+#include "task.h"
+
+struct Fanfare
+{
+ u16 songNum;
+ u16 duration;
+};
+
+extern u16 gBattleTypeFlags;
+
+static EWRAM_DATA struct MusicPlayerInfo *gMPlay_PokemonCry = NULL;
+static EWRAM_DATA u8 gPokemonCryBGMDuckingCounter = 0;
+
+static u16 sCurrentMapMusic;
+static u16 sNextMapMusic;
+static u8 sMapMusicState;
+static u8 sMapMusicFadeInSpeed;
+static u16 sFanfareCounter;
+
+bool8 gDisableMusic;
+
+extern struct MusicPlayerInfo gMPlay_BGM;
+extern struct MusicPlayerInfo gMPlay_SE1;
+extern struct MusicPlayerInfo gMPlay_SE2;
+extern struct MusicPlayerInfo gMPlay_SE3;
+
+extern struct ToneData voicegroup_8452590[];
+extern struct ToneData voicegroup_8452B90[];
+extern struct ToneData voicegroup_8453190[];
+extern struct ToneData voicegroup_8453790[];
+
+extern struct ToneData voicegroup_84537C0[];
+extern struct ToneData voicegroup_8453DC0[];
+extern struct ToneData voicegroup_84543C0[];
+extern struct ToneData voicegroup_84549C0[];
+
+static const struct Fanfare sFanfares[] =
+{
+ { BGM_FANFA1, 80 },
+ { BGM_FANFA4, 160 },
+ { BGM_FANFA5, 220 },
+ { BGM_ME_WAZA, 220 },
+ { BGM_ME_ASA, 160 },
+ { BGM_ME_BACHI, 340 },
+ { BGM_ME_WASURE, 180 },
+ { BGM_ME_KINOMI, 120 },
+ { BGM_ME_TAMA, 710 },
+ { BGM_ME_B_BIG, 250 },
+ { BGM_ME_B_SMALL, 150 },
+ { BGM_ME_ZANNEN, 160 },
+};
+
+static void Task_Fanfare(u8 taskId);
+static void CreateFanfareTask(void);
+static void PlayCryInternal(u16 species, s8 pan, s8 volume, u8 priority, u8 mode);
+static void Task_DuckBGMForPokemonCry(u8 taskId);
+static void RestoreBGMVolumeAfterPokemonCry(void);
+
+void InitMapMusic(void)
+{
+ gDisableMusic = FALSE;
+ ResetMapMusic();
+}
+
+void MapMusicMain(void)
+{
+ switch (sMapMusicState)
+ {
+ case 0:
+ break;
+ case 1:
+ sMapMusicState = 2;
+ PlayBGM(sCurrentMapMusic);
+ break;
+ case 2:
+ case 3:
+ case 4:
+ break;
+ case 5:
+ if (IsBGMStopped())
+ {
+ sNextMapMusic = 0;
+ sMapMusicState = 0;
+ }
+ break;
+ case 6:
+ if (IsBGMStopped() && IsFanfareTaskInactive())
+ {
+ sCurrentMapMusic = sNextMapMusic;
+ sNextMapMusic = 0;
+ sMapMusicState = 2;
+ PlayBGM(sCurrentMapMusic);
+ }
+ break;
+ case 7:
+ if (IsBGMStopped() && IsFanfareTaskInactive())
+ {
+ FadeInNewBGM(sNextMapMusic, sMapMusicFadeInSpeed);
+ sCurrentMapMusic = sNextMapMusic;
+ sNextMapMusic = 0;
+ sMapMusicState = 2;
+ sMapMusicFadeInSpeed = 0;
+ }
+ break;
+ }
+}
+
+void ResetMapMusic(void)
+{
+ sCurrentMapMusic = 0;
+ sNextMapMusic = 0;
+ sMapMusicState = 0;
+ sMapMusicFadeInSpeed = 0;
+}
+
+u16 GetCurrentMapMusic(void)
+{
+ return sCurrentMapMusic;
+}
+
+void PlayNewMapMusic(u16 songNum)
+{
+ sCurrentMapMusic = songNum;
+ sNextMapMusic = 0;
+ sMapMusicState = 1;
+}
+
+void StopMapMusic(void)
+{
+ sCurrentMapMusic = 0;
+ sNextMapMusic = 0;
+ sMapMusicState = 1;
+}
+
+void FadeOutMapMusic(u8 speed)
+{
+ if (IsNotWaitingForBGMStop())
+ FadeOutBGM(speed);
+ sCurrentMapMusic = 0;
+ sNextMapMusic = 0;
+ sMapMusicState = 5;
+}
+
+void FadeOutAndPlayNewMapMusic(u16 songNum, u8 speed)
+{
+ FadeOutMapMusic(speed);
+ sCurrentMapMusic = 0;
+ sNextMapMusic = songNum;
+ sMapMusicState = 6;
+}
+
+void FadeOutAndFadeInNewMapMusic(u16 songNum, u8 fadeOutSpeed, u8 fadeInSpeed)
+{
+ FadeOutMapMusic(fadeOutSpeed);
+ sCurrentMapMusic = 0;
+ sNextMapMusic = songNum;
+ sMapMusicState = 7;
+ sMapMusicFadeInSpeed = fadeInSpeed;
+}
+
+void FadeInNewMapMusic(u16 songNum, u8 speed)
+{
+ FadeInNewBGM(songNum, speed);
+ sCurrentMapMusic = songNum;
+ sNextMapMusic = 0;
+ sMapMusicState = 2;
+ sMapMusicFadeInSpeed = 0;
+}
+
+bool8 IsNotWaitingForBGMStop(void)
+{
+ if (sMapMusicState == 6)
+ return FALSE;
+ if (sMapMusicState == 5)
+ return FALSE;
+ if (sMapMusicState == 7)
+ return FALSE;
+ return TRUE;
+}
+
+void PlayFanfareByFanfareNum(u8 fanfareNum)
+{
+ u16 songNum;
+ m4aMPlayStop(&gMPlay_BGM);
+ songNum = sFanfares[fanfareNum].songNum;
+ sFanfareCounter = sFanfares[fanfareNum].duration;
+ m4aSongNumStart(songNum);
+}
+
+bool8 WaitFanfare(bool8 stop)
+{
+ if (sFanfareCounter)
+ {
+ sFanfareCounter--;
+ return FALSE;
+ }
+ else
+ {
+ if (!stop)
+ m4aMPlayContinue(&gMPlay_BGM);
+ else
+ m4aSongNumStart(SE_STOP);
+
+ return TRUE;
+ }
+}
+
+void StopFanfareByFanfareNum(u8 fanfareNum)
+{
+ m4aSongNumStop(sFanfares[fanfareNum].songNum);
+}
+
+void PlayFanfare(u16 songNum)
+{
+ s32 i;
+ for (i = 0; (u32)i < 12; i++)
+ {
+ if (sFanfares[i].songNum == songNum)
+ {
+ PlayFanfareByFanfareNum(i);
+ CreateFanfareTask();
+ return;
+ }
+ }
+
+ PlayFanfareByFanfareNum(0);
+ CreateFanfareTask();
+}
+
+bool8 IsFanfareTaskInactive(void)
+{
+ if (FuncIsActiveTask(Task_Fanfare) == TRUE)
+ return FALSE;
+ return TRUE;
+}
+
+static void Task_Fanfare(u8 taskId)
+{
+ if (sFanfareCounter)
+ {
+ sFanfareCounter--;
+ }
+ else
+ {
+ m4aMPlayContinue(&gMPlay_BGM);
+ DestroyTask(taskId);
+ }
+}
+
+static void CreateFanfareTask(void)
+{
+ if (FuncIsActiveTask(Task_Fanfare) != TRUE)
+ CreateTask(Task_Fanfare, 80);
+}
+
+void FadeInNewBGM(u16 songNum, u8 speed)
+{
+ if (gDisableMusic)
+ songNum = 0;
+ m4aSongNumStart(songNum);
+ m4aMPlayImmInit(&gMPlay_BGM);
+ m4aMPlayVolumeControl(&gMPlay_BGM, 0xFFFF, 0);
+ m4aSongNumStop(songNum);
+ m4aMPlayFadeIn(&gMPlay_BGM, speed);
+}
+
+void FadeOutBGMTemporarily(u8 speed)
+{
+ m4aMPlayFadeOutTemporarily(&gMPlay_BGM, speed);
+}
+
+bool8 IsBGMPausedOrStopped(void)
+{
+ if (gMPlay_BGM.status & MUSICPLAYER_STATUS_PAUSE)
+ return TRUE;
+ if (!(gMPlay_BGM.status & MUSICPLAYER_STATUS_TRACK))
+ return TRUE;
+ return FALSE;
+}
+
+void FadeInBGM(u8 speed)
+{
+ m4aMPlayFadeIn(&gMPlay_BGM, speed);
+}
+
+void FadeOutBGM(u8 speed)
+{
+ m4aMPlayFadeOut(&gMPlay_BGM, speed);
+}
+
+bool8 IsBGMStopped(void)
+{
+ if (!(gMPlay_BGM.status & MUSICPLAYER_STATUS_TRACK))
+ return TRUE;
+ return FALSE;
+}
+
+void PlayCry1(u16 species, s8 pan)
+{
+ m4aMPlayVolumeControl(&gMPlay_BGM, 0xFFFF, 85);
+ PlayCryInternal(species, pan, 125, 10, 0);
+ gPokemonCryBGMDuckingCounter = 2;
+ RestoreBGMVolumeAfterPokemonCry();
+}
+
+void PlayCry2(u16 species, s8 pan, s8 volume, u8 priority)
+{
+ PlayCryInternal(species, pan, volume, priority, 0);
+}
+
+void PlayCry3(u16 species, s8 pan, u8 mode)
+{
+ if (mode == 1)
+ {
+ PlayCryInternal(species, pan, 125, 10, 1);
+ }
+ else
+ {
+ m4aMPlayVolumeControl(&gMPlay_BGM, 0xFFFF, 85);
+ PlayCryInternal(species, pan, 125, 10, mode);
+ gPokemonCryBGMDuckingCounter = 2;
+ RestoreBGMVolumeAfterPokemonCry();
+ }
+}
+
+void PlayCry4(u16 species, s8 pan, u8 mode)
+{
+ if (mode == 1)
+ {
+ PlayCryInternal(species, pan, 125, 10, 1);
+ }
+ else
+ {
+ if (!(gBattleTypeFlags & BATTLE_TYPE_MULTI))
+ m4aMPlayVolumeControl(&gMPlay_BGM, 0xFFFF, 85);
+ PlayCryInternal(species, pan, 125, 10, mode);
+ }
+}
+
+void PlayCry5(u16 species, u8 mode)
+{
+ m4aMPlayVolumeControl(&gMPlay_BGM, 0xFFFF, 85);
+ PlayCryInternal(species, 0, 125, 10, mode);
+ gPokemonCryBGMDuckingCounter = 2;
+ RestoreBGMVolumeAfterPokemonCry();
+}
+
+static void PlayCryInternal(u16 species, s8 pan, s8 volume, u8 priority, u8 mode)
+{
+ bool32 v0;
+ u32 release;
+ u32 length;
+ u32 pitch;
+ u32 chorus;
+ u32 index;
+ u8 table;
+
+ species--;
+ v0 = FALSE;
+ release = 0;
+ length = 140;
+ pitch = 15360;
+ chorus = 0;
+
+ switch (mode)
+ {
+ case 0:
+ break;
+ case 1:
+ length = 20;
+ release = 225;
+ break;
+ case 2:
+ length = 30;
+ release = 225;
+ pitch = 15600;
+ chorus = 20;
+ volume = 80;
+ break;
+ case 3:
+ length = 50;
+ release = 200;
+ pitch = 14800;
+ break;
+ case 4:
+ length = 20;
+ release = 220;
+ pitch = 15800;
+ break;
+ case 5:
+ release = 200;
+ pitch = 14500;
+ break;
+ }
+
+ SetPokemonCryVolume(volume);
+ SetPokemonCryPanpot(pan);
+ SetPokemonCryPitch(pitch);
+ SetPokemonCryLength(length);
+ SetPokemonCryProgress(0);
+ SetPokemonCryRelease(release);
+ SetPokemonCryChorus(chorus);
+ SetPokemonCryPriority(priority);
+
+ species = SpeciesToCryId(species);
+ index = species & 0x7F;
+ table = species >> 7;
+
+ switch (table)
+ {
+ case 0:
+ gMPlay_PokemonCry = SetPokemonCryTone(
+ v0 ? &voicegroup_84537C0[index] : &voicegroup_8452590[index]);
+ break;
+ case 1:
+ gMPlay_PokemonCry = SetPokemonCryTone(
+ v0 ? &voicegroup_8453DC0[index] : &voicegroup_8452B90[index]);
+ break;
+ case 2:
+ gMPlay_PokemonCry = SetPokemonCryTone(
+ v0 ? &voicegroup_84543C0[index] : &voicegroup_8453190[index]);
+ break;
+ case 3:
+ gMPlay_PokemonCry = SetPokemonCryTone(
+ v0 ? &voicegroup_84549C0[index] : &voicegroup_8453790[index]);
+ break;
+ }
+}
+
+bool8 IsCryFinished(void)
+{
+ if (FuncIsActiveTask(Task_DuckBGMForPokemonCry) == TRUE)
+ {
+ return FALSE;
+ }
+ else
+ {
+ ClearPokemonCrySongs();
+ return TRUE;
+ }
+}
+
+void StopCryAndClearCrySongs(void)
+{
+ m4aMPlayStop(gMPlay_PokemonCry);
+ ClearPokemonCrySongs();
+}
+
+void StopCry(void)
+{
+ m4aMPlayStop(gMPlay_PokemonCry);
+}
+
+bool8 IsCryPlayingOrClearCrySongs(void)
+{
+ if (IsPokemonCryPlaying(gMPlay_PokemonCry))
+ {
+ return TRUE;
+ }
+ else
+ {
+ ClearPokemonCrySongs();
+ return FALSE;
+ }
+}
+
+bool8 IsCryPlaying(void)
+{
+ if (IsPokemonCryPlaying(gMPlay_PokemonCry))
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static void Task_DuckBGMForPokemonCry(u8 taskId)
+{
+ if (gPokemonCryBGMDuckingCounter)
+ {
+ gPokemonCryBGMDuckingCounter--;
+ return;
+ }
+
+ if (!IsPokemonCryPlaying(gMPlay_PokemonCry))
+ {
+ m4aMPlayVolumeControl(&gMPlay_BGM, 0xFFFF, 256);
+ DestroyTask(taskId);
+ }
+}
+
+static void RestoreBGMVolumeAfterPokemonCry(void)
+{
+ if (FuncIsActiveTask(Task_DuckBGMForPokemonCry) != TRUE)
+ CreateTask(Task_DuckBGMForPokemonCry, 80);
+}
+
+void PlayBGM(u16 songNum)
+{
+ if (gDisableMusic)
+ songNum = 0;
+ m4aSongNumStart(songNum);
+}
+
+void PlaySE(u16 songNum)
+{
+ m4aSongNumStart(songNum);
+}
+
+void PlaySE12WithPanning(u16 songNum, s8 pan)
+{
+ m4aSongNumStart(songNum);
+ m4aMPlayImmInit(&gMPlay_SE1);
+ m4aMPlayImmInit(&gMPlay_SE2);
+ m4aMPlayPanpotControl(&gMPlay_SE1, 0xFFFF, pan);
+ m4aMPlayPanpotControl(&gMPlay_SE2, 0xFFFF, pan);
+}
+
+void PlaySE1WithPanning(u16 songNum, s8 pan)
+{
+ m4aSongNumStart(songNum);
+ m4aMPlayImmInit(&gMPlay_SE1);
+ m4aMPlayPanpotControl(&gMPlay_SE1, 0xFFFF, pan);
+}
+
+void PlaySE2WithPanning(u16 songNum, s8 pan)
+{
+ m4aSongNumStart(songNum);
+ m4aMPlayImmInit(&gMPlay_SE2);
+ m4aMPlayPanpotControl(&gMPlay_SE2, 0xFFFF, pan);
+}
+
+void SE12PanpotControl(s8 pan)
+{
+ m4aMPlayPanpotControl(&gMPlay_SE1, 0xFFFF, pan);
+ m4aMPlayPanpotControl(&gMPlay_SE2, 0xFFFF, pan);
+}
+
+bool8 IsSEPlaying(void)
+{
+ if ((gMPlay_SE1.status & MUSICPLAYER_STATUS_PAUSE) && (gMPlay_SE2.status & MUSICPLAYER_STATUS_PAUSE))
+ return FALSE;
+ if (!(gMPlay_SE1.status & MUSICPLAYER_STATUS_TRACK) && !(gMPlay_SE2.status & MUSICPLAYER_STATUS_TRACK))
+ return FALSE;
+ return TRUE;
+}
+
+bool8 IsBGMPlaying(void)
+{
+ if (gMPlay_BGM.status & MUSICPLAYER_STATUS_PAUSE)
+ return FALSE;
+ if (!(gMPlay_BGM.status & MUSICPLAYER_STATUS_TRACK))
+ return FALSE;
+ return TRUE;
+}
+
+bool8 IsSpecialSEPlaying(void)
+{
+ if (gMPlay_SE3.status & MUSICPLAYER_STATUS_PAUSE)
+ return FALSE;
+ if (!(gMPlay_SE3.status & MUSICPLAYER_STATUS_TRACK))
+ return FALSE;
+ return TRUE;
+}
diff --git a/src/engine/sound_check_menu.c b/src/engine/sound_check_menu.c
new file mode 100644
index 000000000..d97ae6d86
--- /dev/null
+++ b/src/engine/sound_check_menu.c
@@ -0,0 +1,2199 @@
+#include "global.h"
+#include "sprite.h"
+#include "palette.h"
+#include "task.h"
+#include "m4a.h"
+#include "main.h"
+#include "text.h"
+#include "menu.h"
+#include "songs.h"
+#include "title_screen.h"
+#include "sound.h"
+#include "pokedex_cry_screen.h"
+
+// local task defines
+#define WINDOW_SELECTED data[0]
+#define BGM_INDEX data[1]
+#define SE_INDEX data[2]
+#define UNK_DATA3 data[3]
+#define UNK_DATA4 data[4]
+// data 5-7 are not used
+// i dont have a define for data 8 yet because its used in a nonmatching and I can't be sure yet its actually used.
+
+// window selections
+enum
+{
+ BGM_WINDOW,
+ SE_WINDOW
+};
+
+// driver test cry enums
+enum
+{
+ CRY_TEST_UNK0,
+ CRY_TEST_VOLUME,
+ CRY_TEST_PANPOT,
+ CRY_TEST_PITCH,
+ CRY_TEST_LENGTH,
+ CRY_TEST_RELEASE,
+ CRY_TEST_PROGRESS,
+ CRY_TEST_CHORUS,
+ CRY_TEST_PRIORITY
+};
+
+// minmax range enums
+enum
+{
+ MIN,
+ MAX
+};
+
+extern struct ToneData voicegroup_84537C0[];
+extern struct ToneData voicegroup_8452590[];
+extern struct ToneData voicegroup_8453DC0[];
+extern struct ToneData voicegroup_8452B90[];
+extern struct ToneData voicegroup_84543C0[];
+extern struct ToneData voicegroup_8453190[];
+extern struct ToneData voicegroup_84549C0[];
+extern struct ToneData voicegroup_8453790[];
+
+static EWRAM_DATA u8 gUnknown_020387B0 = 0;
+static EWRAM_DATA u8 gUnknown_020387B1 = 0;
+static EWRAM_DATA u8 gUnknown_020387B2 = 0;
+static EWRAM_DATA s8 gUnknown_020387B3 = 0;
+static EWRAM_DATA int gUnknown_020387B4[9] = {0};
+static EWRAM_DATA u8 gUnknown_020387D8 = 0;
+static EWRAM_DATA u8 gUnknown_020387D9 = 0;
+
+extern u16 gUnknown_03005D34;
+extern u8 gUnknown_03005E98;
+
+struct MusicPlayerInfo *gUnknown_03005D30;
+
+extern struct MusicPlayerInfo gMPlay_BGM;
+
+void sub_80BA258(u8);
+void sub_80BA384(u8);
+void sub_80BA65C(u8);
+void sub_80BA68C(u8);
+void sub_80BA6B8(u8);
+void sub_80BA700(u16, u16, u16);
+void sub_80BA79C(const u8 *const, u16, u16);
+void sub_80BA800(u8);
+void sub_80BAA48(u8);
+void sub_80BACDC(s8);
+void sub_80BAD5C(void);
+void sub_80BAE10(u8, u8);
+void sub_80BAE78(int, u16, u16, u8);
+void sub_80BAF84(u8);
+void sub_80BB038(u8);
+void sub_80BB1D4(void);
+void sub_80BB25C(u8);
+void sub_80BB3B4(u8);
+void sub_80BB494(void);
+
+static const u8 gDebugText_SoundCheckJap[] = _("サウンドチェック");
+static const u8 gDebugText_BGM[] = _("BGM");
+static const u8 gDebugText_SE[] = _("SE ");
+static const u8 gDebugText_ABDesc[] = _("A‥さいせい B‥おわり");
+static const u8 gDebugText_UpDown[] = _("L‥UP R‥DOWN");
+static const u8 gDebugText_DriverTest[] = _("R‥DRIVER-TEST");
+
+// ideally this should be a multi Coords8 struct, but it wont match when its treated like a struct.
+static const u8 gUnknown_083D0300[] = { 1, 1, 1, 3, 1, 5, 1, 7, 1, 9, 1, 11, 1, 13, 1, 15, 1, 17 };
+
+static const u8 gDebugText_BBack[] = _("Bぼたんで もどる");
+static const u8 gDebugText_APlay[] = _("Aぼたんで さいせい");
+static const u8 gDebugText_Voice[] = _("VOICE‥‥‥‥");
+static const u8 gDebugText_Volume[] = _("VOLUME‥‥‥");
+static const u8 gDebugText_Panpot[] = _("PANPOT‥‥‥");
+static const u8 gDebugText_Pitch[] = _("PITCH‥‥‥‥");
+static const u8 gDebugText_Length[] = _("LENGTH‥‥‥");
+static const u8 gDebugText_Release[] = _("RELEASE‥‥");
+static const u8 gDebugText_Progress[] = _("PROGRESS‥");
+static const u8 gDebugText_Chorus[] = _("CHORUS‥‥‥");
+static const u8 gDebugText_Priority[] = _("PRIORITY‥");
+static const u8 gDebugText_Playing[] = _("さいせいちゆう‥"); // 再生中 (playing)
+static const u8 gDebugText_Reverse[] = _("はんてん‥‥‥‥"); // 反転 (reverse)
+static const u8 gDebugText_Stereo[] = _("すてれお‥‥‥‥"); // stereo
+
+// also ideally should be a MinMax struct, but any attempt to make this into a struct causes it to not match due to the weird multi dim access.
+static const int gUnknown_083D039C[16] =
+{
+ 0, 387,
+ 0, 127,
+ -127, 127,
+ -128, 32639,
+ 0, 65535,
+ 0, 255,
+ 0, 65535,
+ -64, 63
+};
+
+static const u8 gUnknown_083D03DC[] = _("▶");
+static const u8 gUnknown_083D03DE[] = _(" ");
+
+// why not just use Powers of ten from string_util?
+static const int gUnknown_083D03E0[6] =
+{
+ 1,
+ 10,
+ 100,
+ 1000,
+ 10000,
+ 100000
+};
+
+static const s8 gUnknown_083D03F8[5] = { 0x3F, 0x00, 0xC0, 0x7F, 0x80 };
+
+static const u8 gOtherText_SE[] = _("SE");
+static const u8 gOtherText_Pan[] = _("PAN");
+static const u8 gOtherText_LR[] = _(" LR");
+static const u8 gOtherText_RL[] = _(" RL");
+
+// bgm names
+static const u8 BGMName_STOP[] = _("STOP");
+static const u8 BGMName_TETSUJI[] = _("TETSUJI");
+static const u8 BGMName_FIELD13[] = _("FIELD13");
+static const u8 BGMName_KACHI22[] = _("KACHI22");
+static const u8 BGMName_KACHI2[] = _("KACHI2");
+static const u8 BGMName_KACHI3[] = _("KACHI3");
+static const u8 BGMName_KACHI5[] = _("KACHI5");
+static const u8 BGMName_PCC[] = _("PCC");
+static const u8 BGMName_NIBI[] = _("NIBI");
+static const u8 BGMName_SUIKUN[] = _("SUIKUN");
+static const u8 BGMName_DOORO1[] = _("DOORO1");
+static const u8 BGMName_DOORO_X1[] = _("DOORO-X1");
+static const u8 BGMName_DOORO_X3[] = _("DOORO-X3");
+static const u8 BGMName_MACHI_S2[] = _("MACHI-S2");
+static const u8 BGMName_MACHI_S4[] = _("MACHI-S4");
+static const u8 BGMName_GIM[] = _("GIM");
+static const u8 BGMName_NAMINORI[] = _("NAMINORI");
+static const u8 BGMName_DAN01[] = _("DAN01");
+static const u8 BGMName_FANFA1[] = _("FANFA1");
+static const u8 BGMName_ME_ASA[] = _("ME-ASA");
+static const u8 BGMName_ME_BACHI[] = _("ME-BACHI");
+static const u8 BGMName_FANFA4[] = _("FANFA4");
+static const u8 BGMName_FANFA5[] = _("FANFA5");
+static const u8 BGMName_ME_WAZA[] = _("ME-WAZA");
+static const u8 BGMName_BIJYUTU[] = _("BIJYUTU");
+static const u8 BGMName_DOORO_X4[] = _("DOORO-X4");
+static const u8 BGMName_FUNE_KAN[] = _("FUNE-KAN");
+static const u8 BGMName_ME_SHINKA[] = _("ME-SHINKA");
+static const u8 BGMName_SHINKA[] = _("SHINKA");
+static const u8 BGMName_ME_WASURE[] = _("ME-WASURE");
+static const u8 BGMName_SYOUJOEYE[] = _("SYOUJOEYE");
+static const u8 BGMName_BOYEYE[] = _("BOYEYE");
+static const u8 BGMName_DAN02[] = _("DAN02");
+static const u8 BGMName_MACHI_S3[] = _("MACHI-S3");
+static const u8 BGMName_ODAMAKI[] = _("ODAMAKI");
+static const u8 BGMName_B_TOWER[] = _("B-TOWER");
+static const u8 BGMName_SWIMEYE[] = _("SWIMEYE");
+static const u8 BGMName_DAN03[] = _("DAN03");
+static const u8 BGMName_ME_KINOMI[] = _("ME-KINOMI");
+static const u8 BGMName_ME_TAMA[] = _("ME-TAMA");
+static const u8 BGMName_ME_B_BIG[] = _("ME-B-BIG");
+static const u8 BGMName_ME_B_SMALL[] = _("ME-B-SMALL");
+static const u8 BGMName_ME_ZANNEN[] = _("ME-ZANNEN");
+static const u8 BGMName_BD_TIME[] = _("BD-TIME");
+static const u8 BGMName_TEST1[] = _("TEST1");
+static const u8 BGMName_TEST2[] = _("TEST2");
+static const u8 BGMName_TEST3[] = _("TEST3");
+static const u8 BGMName_TEST4[] = _("TEST4");
+static const u8 BGMName_TEST[] = _("TEST");
+static const u8 BGMName_GOMACHI0[] = _("GOMACHI0");
+static const u8 BGMName_GOTOWN[] = _("GOTOWN");
+static const u8 BGMName_POKECEN[] = _("POKECEN");
+static const u8 BGMName_NEXTROAD[] = _("NEXTROAD");
+static const u8 BGMName_GRANROAD[] = _("GRANROAD");
+static const u8 BGMName_CYCLING[] = _("CYCLING");
+static const u8 BGMName_FRIENDLY[] = _("FRIENDLY");
+static const u8 BGMName_MISHIRO[] = _("MISHIRO");
+static const u8 BGMName_TOZAN[] = _("TOZAN");
+static const u8 BGMName_GIRLEYE[] = _("GIRLEYE");
+static const u8 BGMName_MINAMO[] = _("MINAMO");
+static const u8 BGMName_ASHROAD[] = _("ASHROAD");
+static const u8 BGMName_EVENT0[] = _("EVENT0");
+static const u8 BGMName_DEEPDEEP[] = _("DEEPDEEP");
+static const u8 BGMName_KACHI1[] = _("KACHI1");
+static const u8 BGMName_TITLE3[] = _("TITLE3");
+static const u8 BGMName_DEMO1[] = _("DEMO1");
+static const u8 BGMName_GIRL_SUP[] = _("GIRL-SUP");
+static const u8 BGMName_HAGESHII[] = _("HAGESHII");
+static const u8 BGMName_KAKKOII[] = _("KAKKOII");
+static const u8 BGMName_KAZANBAI[] = _("KAZANBAI");
+static const u8 BGMName_AQA_0[] = _("AQA-0");
+static const u8 BGMName_TSURETEK[] = _("TSURETEK");
+static const u8 BGMName_BOY_SUP[] = _("BOY-SUP");
+static const u8 BGMName_RAINBOW[] = _("RAINBOW");
+static const u8 BGMName_AYASII[] = _("AYASII");
+static const u8 BGMName_KACHI4[] = _("KACHI4");
+static const u8 BGMName_ROPEWAY[] = _("ROPEWAY");
+static const u8 BGMName_CASINO[] = _("CASINO");
+static const u8 BGMName_HIGHTOWN[] = _("HIGHTOWN");
+static const u8 BGMName_SAFARI[] = _("SAFARI");
+static const u8 BGMName_C_ROAD[] = _("C-ROAD");
+static const u8 BGMName_AJITO[] = _("AJITO");
+static const u8 BGMName_M_BOAT[] = _("M-BOAT");
+static const u8 BGMName_M_DUNGON[] = _("M-DUNGON");
+static const u8 BGMName_FINECITY[] = _("FINECITY");
+static const u8 BGMName_MACHUPI[] = _("MACHUPI");
+static const u8 BGMName_P_SCHOOL[] = _("P-SCHOOL");
+static const u8 BGMName_DENDOU[] = _("DENDOU");
+static const u8 BGMName_TONEKUSA[] = _("TONEKUSA");
+static const u8 BGMName_MABOROSI[] = _("MABOROSI");
+static const u8 BGMName_CON_FAN[] = _("CON-FAN");
+static const u8 BGMName_CONTEST0[] = _("CONTEST0");
+static const u8 BGMName_MGM0[] = _("MGM0");
+static const u8 BGMName_T_BATTLE[] = _("T-BATTLE");
+static const u8 BGMName_OOAME[] = _("OOAME");
+static const u8 BGMName_HIDERI[] = _("HIDERI");
+static const u8 BGMName_RUNECITY[] = _("RUNECITY");
+static const u8 BGMName_CON_K[] = _("CON-K");
+static const u8 BGMName_EIKOU_R[] = _("EIKOU-R");
+static const u8 BGMName_KARAKURI[] = _("KARAKURI");
+static const u8 BGMName_HUTAGO[] = _("HUTAGO");
+static const u8 BGMName_SITENNOU[] = _("SITENNOU");
+static const u8 BGMName_YAMA_EYE[] = _("YAMA-EYE");
+static const u8 BGMName_CONLOBBY[] = _("CONLOBBY");
+static const u8 BGMName_INTER_V[] = _("INTER-V");
+static const u8 BGMName_DAIGO[] = _("DAIGO");
+static const u8 BGMName_THANKFOR[] = _("THANKFOR");
+static const u8 BGMName_END[] = _("END");
+static const u8 BGMName_BATTLE27[] = _("BATTLE27");
+static const u8 BGMName_BATTLE31[] = _("BATTLE31");
+static const u8 BGMName_BATTLE20[] = _("BATTLE20");
+static const u8 BGMName_BATTLE32[] = _("BATTLE32");
+static const u8 BGMName_BATTLE33[] = _("BATTLE33");
+static const u8 BGMName_BATTLE36[] = _("BATTLE36");
+static const u8 BGMName_BATTLE34[] = _("BATTLE34");
+static const u8 BGMName_BATTLE35[] = _("BATTLE35");
+static const u8 BGMName_BATTLE38[] = _("BATTLE38");
+static const u8 BGMName_BATTLE30[] = _("BATTLE30");
+
+static const u8 *const gBGMNames[] =
+{
+ BGMName_STOP,
+ BGMName_TETSUJI,
+ BGMName_FIELD13,
+ BGMName_KACHI22,
+ BGMName_KACHI2,
+ BGMName_KACHI3,
+ BGMName_KACHI5,
+ BGMName_PCC,
+ BGMName_NIBI,
+ BGMName_SUIKUN,
+ BGMName_DOORO1,
+ BGMName_DOORO_X1,
+ BGMName_DOORO_X3,
+ BGMName_MACHI_S2,
+ BGMName_MACHI_S4,
+ BGMName_GIM,
+ BGMName_NAMINORI,
+ BGMName_DAN01,
+ BGMName_FANFA1,
+ BGMName_ME_ASA,
+ BGMName_ME_BACHI,
+ BGMName_FANFA4,
+ BGMName_FANFA5,
+ BGMName_ME_WAZA,
+ BGMName_BIJYUTU,
+ BGMName_DOORO_X4,
+ BGMName_FUNE_KAN,
+ BGMName_ME_SHINKA,
+ BGMName_SHINKA,
+ BGMName_ME_WASURE,
+ BGMName_SYOUJOEYE,
+ BGMName_BOYEYE,
+ BGMName_DAN02,
+ BGMName_MACHI_S3,
+ BGMName_ODAMAKI,
+ BGMName_B_TOWER,
+ BGMName_SWIMEYE,
+ BGMName_DAN03,
+ BGMName_ME_KINOMI,
+ BGMName_ME_TAMA,
+ BGMName_ME_B_BIG,
+ BGMName_ME_B_SMALL,
+ BGMName_ME_ZANNEN,
+ BGMName_BD_TIME,
+ BGMName_TEST1,
+ BGMName_TEST2,
+ BGMName_TEST3,
+ BGMName_TEST4,
+ BGMName_TEST,
+ BGMName_GOMACHI0,
+ BGMName_GOTOWN,
+ BGMName_POKECEN,
+ BGMName_NEXTROAD,
+ BGMName_GRANROAD,
+ BGMName_CYCLING,
+ BGMName_FRIENDLY,
+ BGMName_MISHIRO,
+ BGMName_TOZAN,
+ BGMName_GIRLEYE,
+ BGMName_MINAMO,
+ BGMName_ASHROAD,
+ BGMName_EVENT0,
+ BGMName_DEEPDEEP,
+ BGMName_KACHI1,
+ BGMName_TITLE3,
+ BGMName_DEMO1,
+ BGMName_GIRL_SUP,
+ BGMName_HAGESHII,
+ BGMName_KAKKOII,
+ BGMName_KAZANBAI,
+ BGMName_AQA_0,
+ BGMName_TSURETEK,
+ BGMName_BOY_SUP,
+ BGMName_RAINBOW,
+ BGMName_AYASII,
+ BGMName_KACHI4,
+ BGMName_ROPEWAY,
+ BGMName_CASINO,
+ BGMName_HIGHTOWN,
+ BGMName_SAFARI,
+ BGMName_C_ROAD,
+ BGMName_AJITO,
+ BGMName_M_BOAT,
+ BGMName_M_DUNGON,
+ BGMName_FINECITY,
+ BGMName_MACHUPI,
+ BGMName_P_SCHOOL,
+ BGMName_DENDOU,
+ BGMName_TONEKUSA,
+ BGMName_MABOROSI,
+ BGMName_CON_FAN,
+ BGMName_CONTEST0,
+ BGMName_MGM0,
+ BGMName_T_BATTLE,
+ BGMName_OOAME,
+ BGMName_HIDERI,
+ BGMName_RUNECITY,
+ BGMName_CON_K,
+ BGMName_EIKOU_R,
+ BGMName_KARAKURI,
+ BGMName_HUTAGO,
+ BGMName_SITENNOU,
+ BGMName_YAMA_EYE,
+ BGMName_CONLOBBY,
+ BGMName_INTER_V,
+ BGMName_DAIGO,
+ BGMName_THANKFOR,
+ BGMName_END,
+ BGMName_BATTLE27,
+ BGMName_BATTLE31,
+ BGMName_BATTLE20,
+ BGMName_BATTLE32,
+ BGMName_BATTLE33,
+ BGMName_BATTLE36,
+ BGMName_BATTLE34,
+ BGMName_BATTLE35,
+ BGMName_BATTLE38,
+ BGMName_BATTLE30
+};
+
+// SE names
+static const u8 SEName_STOP[] = _("STOP");
+static const u8 SEName_KAIFUKU[] = _("KAIFUKU");
+static const u8 SEName_PC_LOGON[] = _("PC-LOGON");
+static const u8 SEName_PC_OFF[] = _("PC-OFF");
+static const u8 SEName_PC_ON[] = _("PC-ON");
+static const u8 SEName_SELECT[] = _("SELECT");
+static const u8 SEName_WIN_OPEN[] = _("WIN-OPEN");
+static const u8 SEName_WALL_HIT[] = _("WALL-HIT");
+static const u8 SEName_DOOR[] = _("DOOR");
+static const u8 SEName_KAIDAN[] = _("KAIDAN");
+static const u8 SEName_DANSA[] = _("DANSA");
+static const u8 SEName_JITENSYA[] = _("JITENSYA");
+static const u8 SEName_KOUKA_L[] = _("KOUKA-L");
+static const u8 SEName_KOUKA_M[] = _("KOUKA-M");
+static const u8 SEName_KOUKA_H[] = _("KOUKA-H");
+static const u8 SEName_BOWA2[] = _("BOWA2");
+static const u8 SEName_POKE_DEAD[] = _("POKE-DEAD");
+static const u8 SEName_NIGERU[] = _("NIGERU");
+static const u8 SEName_JIDO_DOA[] = _("JIDO-DOA");
+static const u8 SEName_NAMINORI[] = _("NAMINORI");
+static const u8 SEName_BAN[] = _("BAN");
+static const u8 SEName_PIN[] = _("PIN");
+static const u8 SEName_BOO[] = _("BOO");
+static const u8 SEName_BOWA[] = _("BOWA");
+static const u8 SEName_JYUNI[] = _("JYUNI");
+static const u8 SEName_A[] = _("A");
+static const u8 SEName_I[] = _("I");
+static const u8 SEName_U[] = _("U");
+static const u8 SEName_E[] = _("E");
+static const u8 SEName_O[] = _("O");
+static const u8 SEName_N[] = _("N");
+static const u8 SEName_SEIKAI[] = _("SEIKAI");
+static const u8 SEName_HAZURE[] = _("HAZURE");
+static const u8 SEName_EXP[] = _("EXP");
+static const u8 SEName_JITE_PYOKO[] = _("JITE-PYOKO");
+static const u8 SEName_MU_PACHI[] = _("MU-PACHI");
+static const u8 SEName_TK_KASYA[] = _("TK-KASYA");
+static const u8 SEName_FU_ZAKU[] = _("FU-ZAKU");
+static const u8 SEName_FU_ZAKU2[] = _("FU-ZAKU2");
+static const u8 SEName_FU_ZUZUZU[] = _("FU-ZUZUZU");
+static const u8 SEName_RU_GASHIN[] = _("RU-GASHIN");
+static const u8 SEName_RU_GASYAN[] = _("RU-GASYAN");
+static const u8 SEName_RU_BARI[] = _("RU-BARI");
+static const u8 SEName_RU_HYUU[] = _("RU-HYUU");
+static const u8 SEName_KI_GASYAN[] = _("KI-GASYAN");
+static const u8 SEName_TK_WARPIN[] = _("TK-WARPIN");
+static const u8 SEName_TK_WARPOUT[] = _("TK-WARPOUT");
+static const u8 SEName_TU_SAA[] = _("TU-SAA");
+static const u8 SEName_HI_TURUN[] = _("HI-TURUN");
+static const u8 SEName_TRACK_MOVE[] = _("TRACK-MOVE");
+static const u8 SEName_TRACK_STOP[] = _("TRACK-STOP");
+static const u8 SEName_TRACK_HAIK[] = _("TRACK-HAIK");
+static const u8 SEName_TRACK_DOOR[] = _("TRACK-DOOR");
+static const u8 SEName_MOTER[] = _("MOTER");
+static const u8 SEName_CARD[] = _("CARD");
+static const u8 SEName_SAVE[] = _("SAVE");
+static const u8 SEName_KON[] = _("KON");
+static const u8 SEName_KON2[] = _("KON2");
+static const u8 SEName_KON3[] = _("KON3");
+static const u8 SEName_KON4[] = _("KON4");
+static const u8 SEName_SUIKOMU[] = _("SUIKOMU");
+static const u8 SEName_NAGERU[] = _("NAGERU");
+static const u8 SEName_TOY_C[] = _("TOY-C");
+static const u8 SEName_TOY_D[] = _("TOY-D");
+static const u8 SEName_TOY_E[] = _("TOY-E");
+static const u8 SEName_TOY_F[] = _("TOY-F");
+static const u8 SEName_TOY_G[] = _("TOY-G");
+static const u8 SEName_TOY_A[] = _("TOY-A");
+static const u8 SEName_TOY_B[] = _("TOY-B");
+static const u8 SEName_TOY_C1[] = _("TOY-C1");
+static const u8 SEName_MIZU[] = _("MIZU");
+static const u8 SEName_HASHI[] = _("HASHI");
+static const u8 SEName_DAUGI[] = _("DAUGI");
+static const u8 SEName_PINPON[] = _("PINPON");
+static const u8 SEName_FUUSEN1[] = _("FUUSEN1");
+static const u8 SEName_FUUSEN2[] = _("FUUSEN2");
+static const u8 SEName_FUUSEN3[] = _("FUUSEN3");
+static const u8 SEName_TOY_KABE[] = _("TOY-KABE");
+static const u8 SEName_TOY_DANGO[] = _("TOY-DANGO");
+static const u8 SEName_DOKU[] = _("DOKU");
+static const u8 SEName_ESUKA[] = _("ESUKA");
+static const u8 SEName_T_AME[] = _("T-AME");
+static const u8 SEName_T_AME_E[] = _("T-AME-E");
+static const u8 SEName_T_OOAME[] = _("T-OOAME");
+static const u8 SEName_T_OOAME_E[] = _("T-OOAME-E");
+static const u8 SEName_T_KOAME[] = _("T-KOAME");
+static const u8 SEName_T_KOAME_E[] = _("T-KOAME-E");
+static const u8 SEName_T_KAMI[] = _("T-KAMI");
+static const u8 SEName_T_KAMI2[] = _("T-KAMI2");
+static const u8 SEName_ELEBETA[] = _("ELEBETA");
+static const u8 SEName_HINSI[] = _("HINSI");
+static const u8 SEName_EXPMAX[] = _("EXPMAX");
+static const u8 SEName_TAMAKORO[] = _("TAMAKORO");
+static const u8 SEName_TAMAKORO_E[] = _("TAMAKORO-E");
+static const u8 SEName_BASABASA[] = _("BASABASA");
+static const u8 SEName_REGI[] = _("REGI");
+static const u8 SEName_C_GAJI[] = _("C-GAJI");
+static const u8 SEName_C_MAKU_U[] = _("C-MAKU-U");
+static const u8 SEName_C_MAKU_D[] = _("C-MAKU-D");
+static const u8 SEName_C_PASI[] = _("C-PASI");
+static const u8 SEName_C_SYU[] = _("C-SYU");
+static const u8 SEName_C_PIKON[] = _("C-PIKON");
+static const u8 SEName_REAPOKE[] = _("REAPOKE");
+static const u8 SEName_OP_BASYU[] = _("OP-BASYU");
+static const u8 SEName_BT_START[] = _("BT-START");
+static const u8 SEName_DENDOU[] = _("DENDOU");
+static const u8 SEName_JIHANKI[] = _("JIHANKI");
+static const u8 SEName_TAMA[] = _("TAMA");
+static const u8 SEName_Z_SCROLL[] = _("Z-SCROLL");
+static const u8 SEName_Z_PAGE[] = _("Z-PAGE");
+static const u8 SEName_PN_ON[] = _("PN-ON");
+static const u8 SEName_PN_OFF[] = _("PN-OFF");
+static const u8 SEName_Z_SEARCH[] = _("Z-SEARCH");
+static const u8 SEName_TAMAGO[] = _("TAMAGO");
+static const u8 SEName_TB_START[] = _("TB-START");
+static const u8 SEName_TB_KON[] = _("TB-KON");
+static const u8 SEName_TB_KARA[] = _("TB-KARA");
+static const u8 SEName_BIDORO[] = _("BIDORO");
+static const u8 SEName_W085[] = _("W085");
+static const u8 SEName_W085B[] = _("W085B");
+static const u8 SEName_W231[] = _("W231");
+static const u8 SEName_W171[] = _("W171");
+static const u8 SEName_W233[] = _("W233");
+static const u8 SEName_W233B[] = _("W233B");
+static const u8 SEName_W145[] = _("W145");
+static const u8 SEName_W145B[] = _("W145B");
+static const u8 SEName_W145C[] = _("W145C");
+static const u8 SEName_W240[] = _("W240");
+static const u8 SEName_W015[] = _("W015");
+static const u8 SEName_W081[] = _("W081");
+static const u8 SEName_W081B[] = _("W081B");
+static const u8 SEName_W088[] = _("W088");
+static const u8 SEName_W016[] = _("W016");
+static const u8 SEName_W016B[] = _("W016B");
+static const u8 SEName_W003[] = _("W003");
+static const u8 SEName_W104[] = _("W104");
+static const u8 SEName_W013[] = _("W013");
+static const u8 SEName_W196[] = _("W196");
+static const u8 SEName_W086[] = _("W086");
+static const u8 SEName_W004[] = _("W004");
+static const u8 SEName_W025[] = _("W025");
+static const u8 SEName_W025B[] = _("W025B");
+static const u8 SEName_W152[] = _("W152");
+static const u8 SEName_W026[] = _("W026");
+static const u8 SEName_W172[] = _("W172");
+static const u8 SEName_W172B[] = _("W172B");
+static const u8 SEName_W053[] = _("W053");
+static const u8 SEName_W007[] = _("W007");
+static const u8 SEName_W092[] = _("W092");
+static const u8 SEName_W221[] = _("W221");
+static const u8 SEName_W221B[] = _("W221B");
+static const u8 SEName_W052[] = _("W052");
+static const u8 SEName_W036[] = _("W036");
+static const u8 SEName_W059[] = _("W059");
+static const u8 SEName_W059B[] = _("W059B");
+static const u8 SEName_W010[] = _("W010");
+static const u8 SEName_W011[] = _("W011");
+static const u8 SEName_W017[] = _("W017");
+static const u8 SEName_W019[] = _("W019");
+static const u8 SEName_W028[] = _("W028");
+static const u8 SEName_W013B[] = _("W013B");
+static const u8 SEName_W044[] = _("W044");
+static const u8 SEName_W029[] = _("W029");
+static const u8 SEName_W057[] = _("W057");
+static const u8 SEName_W056[] = _("W056");
+static const u8 SEName_W250[] = _("W250");
+static const u8 SEName_W030[] = _("W030");
+static const u8 SEName_W039[] = _("W039");
+static const u8 SEName_W054[] = _("W054");
+static const u8 SEName_W077[] = _("W077");
+static const u8 SEName_W020[] = _("W020");
+static const u8 SEName_W082[] = _("W082");
+static const u8 SEName_W047[] = _("W047");
+static const u8 SEName_W195[] = _("W195");
+static const u8 SEName_W006[] = _("W006");
+static const u8 SEName_W091[] = _("W091");
+static const u8 SEName_W146[] = _("W146");
+static const u8 SEName_W120[] = _("W120");
+static const u8 SEName_W153[] = _("W153");
+static const u8 SEName_W071B[] = _("W071B");
+static const u8 SEName_W071[] = _("W071");
+static const u8 SEName_W103[] = _("W103");
+static const u8 SEName_W062[] = _("W062");
+static const u8 SEName_W062B[] = _("W062B");
+static const u8 SEName_W048[] = _("W048");
+static const u8 SEName_W187[] = _("W187");
+static const u8 SEName_W118[] = _("W118");
+static const u8 SEName_W155[] = _("W155");
+static const u8 SEName_W122[] = _("W122");
+static const u8 SEName_W060[] = _("W060");
+static const u8 SEName_W185[] = _("W185");
+static const u8 SEName_W014[] = _("W014");
+static const u8 SEName_W043[] = _("W043");
+static const u8 SEName_W207[] = _("W207");
+static const u8 SEName_W207B[] = _("W207B");
+static const u8 SEName_W215[] = _("W215");
+static const u8 SEName_W109[] = _("W109");
+static const u8 SEName_W173[] = _("W173");
+static const u8 SEName_W280[] = _("W280");
+static const u8 SEName_W202[] = _("W202");
+static const u8 SEName_W060B[] = _("W060B");
+static const u8 SEName_W076[] = _("W076");
+static const u8 SEName_W080[] = _("W080");
+static const u8 SEName_W100[] = _("W100");
+static const u8 SEName_W107[] = _("W107");
+static const u8 SEName_W166[] = _("W166");
+static const u8 SEName_W129[] = _("W129");
+static const u8 SEName_W115[] = _("W115");
+static const u8 SEName_W112[] = _("W112");
+static const u8 SEName_W197[] = _("W197");
+static const u8 SEName_W199[] = _("W199");
+static const u8 SEName_W236[] = _("W236");
+static const u8 SEName_W204[] = _("W204");
+static const u8 SEName_W268[] = _("W268");
+static const u8 SEName_W070[] = _("W070");
+static const u8 SEName_W063[] = _("W063");
+static const u8 SEName_W127[] = _("W127");
+static const u8 SEName_W179[] = _("W179");
+static const u8 SEName_W151[] = _("W151");
+static const u8 SEName_W201[] = _("W201");
+static const u8 SEName_W161[] = _("W161");
+static const u8 SEName_W161B[] = _("W161B");
+static const u8 SEName_W227[] = _("W227");
+static const u8 SEName_W227B[] = _("W227B");
+static const u8 SEName_W226[] = _("W226");
+static const u8 SEName_W208[] = _("W208");
+static const u8 SEName_W213[] = _("W213");
+static const u8 SEName_W213B[] = _("W213B");
+static const u8 SEName_W234[] = _("W234");
+static const u8 SEName_W260[] = _("W260");
+static const u8 SEName_W328[] = _("W328");
+static const u8 SEName_W320[] = _("W320");
+static const u8 SEName_W255[] = _("W255");
+static const u8 SEName_W291[] = _("W291");
+static const u8 SEName_W089[] = _("W089");
+static const u8 SEName_W239[] = _("W239");
+static const u8 SEName_W230[] = _("W230");
+static const u8 SEName_W281[] = _("W281");
+static const u8 SEName_W327[] = _("W327");
+static const u8 SEName_W287[] = _("W287");
+static const u8 SEName_W257[] = _("W257");
+static const u8 SEName_W253[] = _("W253");
+static const u8 SEName_W258[] = _("W258");
+static const u8 SEName_W322[] = _("W322");
+static const u8 SEName_W298[] = _("W298");
+static const u8 SEName_W287B[] = _("W287B");
+static const u8 SEName_W114[] = _("W114");
+static const u8 SEName_W063B[] = _("W063B");
+
+static const u8 *const gSENames[] =
+{
+ SEName_STOP,
+ SEName_KAIFUKU,
+ SEName_PC_LOGON,
+ SEName_PC_OFF,
+ SEName_PC_ON,
+ SEName_SELECT,
+ SEName_WIN_OPEN,
+ SEName_WALL_HIT,
+ SEName_DOOR,
+ SEName_KAIDAN,
+ SEName_DANSA,
+ SEName_JITENSYA,
+ SEName_KOUKA_L,
+ SEName_KOUKA_M,
+ SEName_KOUKA_H,
+ SEName_BOWA2,
+ SEName_POKE_DEAD,
+ SEName_NIGERU,
+ SEName_JIDO_DOA,
+ SEName_NAMINORI,
+ SEName_BAN,
+ SEName_PIN,
+ SEName_BOO,
+ SEName_BOWA,
+ SEName_JYUNI,
+ SEName_A,
+ SEName_I,
+ SEName_U,
+ SEName_E,
+ SEName_O,
+ SEName_N,
+ SEName_SEIKAI,
+ SEName_HAZURE,
+ SEName_EXP,
+ SEName_JITE_PYOKO,
+ SEName_MU_PACHI,
+ SEName_TK_KASYA,
+ SEName_FU_ZAKU,
+ SEName_FU_ZAKU2,
+ SEName_FU_ZUZUZU,
+ SEName_RU_GASHIN,
+ SEName_RU_GASYAN,
+ SEName_RU_BARI,
+ SEName_RU_HYUU,
+ SEName_KI_GASYAN,
+ SEName_TK_WARPIN,
+ SEName_TK_WARPOUT,
+ SEName_TU_SAA,
+ SEName_HI_TURUN,
+ SEName_TRACK_MOVE,
+ SEName_TRACK_STOP,
+ SEName_TRACK_HAIK,
+ SEName_TRACK_DOOR,
+ SEName_MOTER,
+ SEName_CARD,
+ SEName_SAVE,
+ SEName_KON,
+ SEName_KON2,
+ SEName_KON3,
+ SEName_KON4,
+ SEName_SUIKOMU,
+ SEName_NAGERU,
+ SEName_TOY_C,
+ SEName_TOY_D,
+ SEName_TOY_E,
+ SEName_TOY_F,
+ SEName_TOY_G,
+ SEName_TOY_A,
+ SEName_TOY_B,
+ SEName_TOY_C1,
+ SEName_MIZU,
+ SEName_HASHI,
+ SEName_DAUGI,
+ SEName_PINPON,
+ SEName_FUUSEN1,
+ SEName_FUUSEN2,
+ SEName_FUUSEN3,
+ SEName_TOY_KABE,
+ SEName_TOY_DANGO,
+ SEName_DOKU,
+ SEName_ESUKA,
+ SEName_T_AME,
+ SEName_T_AME_E,
+ SEName_T_OOAME,
+ SEName_T_OOAME_E,
+ SEName_T_KOAME,
+ SEName_T_KOAME_E,
+ SEName_T_KAMI,
+ SEName_T_KAMI2,
+ SEName_ELEBETA,
+ SEName_HINSI,
+ SEName_EXPMAX,
+ SEName_TAMAKORO,
+ SEName_TAMAKORO_E,
+ SEName_BASABASA,
+ SEName_REGI,
+ SEName_C_GAJI,
+ SEName_C_MAKU_U,
+ SEName_C_MAKU_D,
+ SEName_C_PASI,
+ SEName_C_SYU,
+ SEName_C_PIKON,
+ SEName_REAPOKE,
+ SEName_OP_BASYU,
+ SEName_BT_START,
+ SEName_DENDOU,
+ SEName_JIHANKI,
+ SEName_TAMA,
+ SEName_Z_SCROLL,
+ SEName_Z_PAGE,
+ SEName_PN_ON,
+ SEName_PN_OFF,
+ SEName_Z_SEARCH,
+ SEName_TAMAGO,
+ SEName_TB_START,
+ SEName_TB_KON,
+ SEName_TB_KARA,
+ SEName_BIDORO,
+ SEName_W085,
+ SEName_W085B,
+ SEName_W231,
+ SEName_W171,
+ SEName_W233,
+ SEName_W233B,
+ SEName_W145,
+ SEName_W145B,
+ SEName_W145C,
+ SEName_W240,
+ SEName_W015,
+ SEName_W081,
+ SEName_W081B,
+ SEName_W088,
+ SEName_W016,
+ SEName_W016B,
+ SEName_W003,
+ SEName_W104,
+ SEName_W013,
+ SEName_W196,
+ SEName_W086,
+ SEName_W004,
+ SEName_W025,
+ SEName_W025B,
+ SEName_W152,
+ SEName_W026,
+ SEName_W172,
+ SEName_W172B,
+ SEName_W053,
+ SEName_W007,
+ SEName_W092,
+ SEName_W221,
+ SEName_W221B,
+ SEName_W052,
+ SEName_W036,
+ SEName_W059,
+ SEName_W059B,
+ SEName_W010,
+ SEName_W011,
+ SEName_W017,
+ SEName_W019,
+ SEName_W028,
+ SEName_W013B,
+ SEName_W044,
+ SEName_W029,
+ SEName_W057,
+ SEName_W056,
+ SEName_W250,
+ SEName_W030,
+ SEName_W039,
+ SEName_W054,
+ SEName_W077,
+ SEName_W020,
+ SEName_W082,
+ SEName_W047,
+ SEName_W195,
+ SEName_W006,
+ SEName_W091,
+ SEName_W146,
+ SEName_W120,
+ SEName_W153,
+ SEName_W071B,
+ SEName_W071,
+ SEName_W103,
+ SEName_W062,
+ SEName_W062B,
+ SEName_W048,
+ SEName_W187,
+ SEName_W118,
+ SEName_W155,
+ SEName_W122,
+ SEName_W060,
+ SEName_W185,
+ SEName_W014,
+ SEName_W043,
+ SEName_W207,
+ SEName_W207B,
+ SEName_W215,
+ SEName_W109,
+ SEName_W173,
+ SEName_W280,
+ SEName_W202,
+ SEName_W060B,
+ SEName_W076,
+ SEName_W080,
+ SEName_W100,
+ SEName_W107,
+ SEName_W166,
+ SEName_W129,
+ SEName_W115,
+ SEName_W112,
+ SEName_W197,
+ SEName_W199,
+ SEName_W236,
+ SEName_W204,
+ SEName_W268,
+ SEName_W070,
+ SEName_W063,
+ SEName_W127,
+ SEName_W179,
+ SEName_W151,
+ SEName_W201,
+ SEName_W161,
+ SEName_W161B,
+ SEName_W227,
+ SEName_W227B,
+ SEName_W226,
+ SEName_W208,
+ SEName_W213,
+ SEName_W213B,
+ SEName_W234,
+ SEName_W260,
+ SEName_W328,
+ SEName_W320,
+ SEName_W255,
+ SEName_W291,
+ SEName_W089,
+ SEName_W239,
+ SEName_W230,
+ SEName_W281,
+ SEName_W327,
+ SEName_W287,
+ SEName_W257,
+ SEName_W253,
+ SEName_W258,
+ SEName_W322,
+ SEName_W298,
+ SEName_W287B,
+ SEName_W114,
+ SEName_W063B
+};
+
+void sub_80BA0A8(void)
+{
+ RunTasks();
+ AnimateSprites();
+ BuildOamBuffer();
+ UpdatePaletteFade();
+}
+
+void sub_80BA0C0(void)
+{
+ LoadOam();
+ ProcessSpriteCopyRequests();
+ TransferPlttBuffer();
+
+ if(gUnknown_020387B0 != 0)
+ {
+ m4aSoundMain();
+ m4aSoundMain();
+ m4aSoundMain();
+ }
+}
+
+// unused
+void CB2_StartSoundCheckMenu(void)
+{
+ u8 taskId;
+
+ SetVBlankCallback(NULL);
+ REG_DISPCNT = 0;
+ REG_BG2CNT = 0;
+ REG_BG1CNT = 0;
+ REG_BG0CNT = 0;
+ REG_BG2HOFS = 0;
+ REG_BG2VOFS = 0;
+ REG_BG1HOFS = 0;
+ REG_BG1VOFS = 0;
+ REG_BG0HOFS = 0;
+ REG_BG0VOFS = 0;
+ DmaFill16(3, 0, VRAM, VRAM_SIZE);
+ DmaFill32(3, 0, OAM, OAM_SIZE);
+ DmaFill16(3, 0, PLTT, PLTT_SIZE);
+ ResetPaletteFade();
+ ResetTasks();
+ ResetSpriteData();
+ SetUpWindowConfig(&gWindowConfig_81E6C3C);
+ InitMenuWindow(&gWindowConfig_81E6CE4);
+ BeginNormalPaletteFade(0xFFFFFFFF, 0, 16, 0, 0);
+ REG_WIN0H = WIN_RANGE(0, 0);
+ REG_WIN0V = WIN_RANGE(0, 0);
+ REG_WIN1H = WIN_RANGE(0, 0);
+ REG_WIN1V = WIN_RANGE(0, 0);
+ REG_WININ = 0x1111;
+ REG_WINOUT = 0x31;
+ REG_BLDCNT = 0xE1;
+ REG_BLDALPHA = 0;
+ REG_BLDY = 7;
+ REG_IE = 1; // could be a typo of REG_IME
+ REG_IE |= 1;
+ REG_DISPSTAT |= 8;
+ SetVBlankCallback(sub_80BA0C0);
+ SetMainCallback2(sub_80BA0A8);
+ REG_DISPCNT = 0x7140;
+ taskId = CreateTask(sub_80BA258, 0);
+ TASK.WINDOW_SELECTED = BGM_WINDOW;
+ TASK.BGM_INDEX = 0;
+ TASK.SE_INDEX = 0;
+ TASK.UNK_DATA3 = 0;
+ gUnknown_020387B0 = 0;
+ TASK.UNK_DATA3 = 0; // why?
+ m4aSoundInit();
+}
+
+// Task_InitSoundCheckMenu
+void sub_80BA258(u8 taskId)
+{
+ u8 soundcheckStr[sizeof(gDebugText_SoundCheckJap)];
+ u8 bgmStr[sizeof(gDebugText_BGM)];
+ u8 seStr[sizeof(gDebugText_SE)];
+ u8 abDescStr[sizeof(gDebugText_ABDesc)];
+ u8 upDownStr[sizeof(gDebugText_UpDown)];
+ u8 driverStr[sizeof(gDebugText_DriverTest)];
+
+ memcpy(soundcheckStr, gDebugText_SoundCheckJap, sizeof(gDebugText_SoundCheckJap));
+ memcpy(bgmStr, gDebugText_BGM, sizeof(gDebugText_BGM));
+ memcpy(seStr, gDebugText_SE, sizeof(gDebugText_SE));
+ memcpy(abDescStr, gDebugText_ABDesc, sizeof(gDebugText_ABDesc));
+ memcpy(upDownStr, gDebugText_UpDown, sizeof(gDebugText_UpDown));
+ memcpy(driverStr, gDebugText_DriverTest, sizeof(gDebugText_DriverTest));
+
+ if(!gPaletteFade.active)
+ {
+ MenuDrawTextWindow(0x2, 0, 0x1B, 0x3);
+ MenuDrawTextWindow(0x2, 0x5, 0x1B, 0xA);
+ MenuDrawTextWindow(0x2, 0xC, 0x1B, 0x11);
+ MenuPrint(soundcheckStr, 4, 1);
+ MenuPrint(abDescStr, 14, 1);
+ MenuPrint(bgmStr, 4, 6);
+ MenuPrint(upDownStr, 14, 6);
+ MenuPrint(seStr, 4, 13);
+ MenuPrint(upDownStr, 14, 13);
+ MenuPrint(driverStr, 14, 18);
+ TASK.FUNC = sub_80BA384;
+ REG_WIN0H = WIN_RANGE(17, 223);
+ REG_WIN0V = WIN_RANGE(1, 31);
+ }
+}
+
+void sub_80BA384(u8 taskId) // Task_HandleDrawingSoundCheckMenuText
+{
+ sub_80BA6B8(TASK.WINDOW_SELECTED);
+ sub_80BA700(TASK.BGM_INDEX + BGM_STOP, 7, 8); // print by BGM index
+ sub_80BA79C(gBGMNames[TASK.BGM_INDEX], 11, 8);
+ sub_80BA700(TASK.SE_INDEX, 7, 15);
+ sub_80BA79C(gSENames[TASK.SE_INDEX], 11, 15);
+ TASK.FUNC = sub_80BA65C;
+}
+
+#ifdef NONMATCHING
+bool8 sub_80BA400(u8 taskId) // Task_ProcessSoundCheckMenuInput
+{
+ if(gMain.newKeys & R_BUTTON) // driver test
+ {
+ TASK.FUNC = sub_80BA800;
+ return FALSE;
+ }
+ if(gMain.newKeys & L_BUTTON)
+ {
+ TASK.FUNC = sub_80BAF84;
+ return FALSE;
+ }
+ if(gMain.newKeys & START_BUTTON)
+ {
+ TASK.FUNC = sub_80BB25C;
+ return FALSE;
+ }
+ if(gMain.newKeys & A_BUTTON) // both these cases insist on non reuses of certain data variables and cause the function to not match.
+ {
+ if(TASK.WINDOW_SELECTED != 0) // is playing?
+ {
+ if(TASK.UNK_DATA4 != 0)
+ {
+ if(TASK.SE_INDEX != 0) // why are you insiting on a non signed halfword?
+ {
+ m4aSongNumStop(TASK.UNK_DATA4);
+ }
+ else
+ {
+ m4aSongNumStop(TASK.SE_INDEX);
+ TASK.UNK_DATA4 = TASK.SE_INDEX;
+ return FALSE;
+ }
+ }
+ else if(TASK.SE_INDEX == 0) // _080BA4BA
+ {
+ return FALSE;
+ }
+ // _080BA4C4
+ m4aSongNumStart(TASK.SE_INDEX);
+ TASK.UNK_DATA4 = TASK.SE_INDEX;
+ return FALSE;
+ }
+ else // _080BA4D0
+ {
+ if(TASK.UNK_DATA3 != 0)
+ {
+ if(TASK.BGM_INDEX != 0)
+ {
+ m4aSongNumStop(TASK.UNK_DATA3 + BGM_STOP);
+ }
+ else // _080BA500
+ {
+ m4aSongNumStop(TASK.UNK_DATA3 + BGM_STOP);
+ TASK.UNK_DATA3 = TASK.BGM_INDEX;
+ return FALSE;
+ }
+ }
+ else if(TASK.BGM_INDEX == 0) // _080BA514
+ return FALSE;
+
+ m4aSongNumStart(TASK.BGM_INDEX + BGM_STOP);
+ TASK.UNK_DATA3 = TASK.BGM_INDEX;
+ }
+ return FALSE;
+ }
+ if(gMain.newKeys & B_BUTTON)
+ {
+ m4aSongNumStart(5);
+ BeginNormalPaletteFade(0xFFFFFFFF, 0, 0, 16, 0);
+ TASK.FUNC = sub_80BA68C;
+ return FALSE;
+ }
+ if(gMain.newAndRepeatedKeys & DPAD_UP)
+ {
+ TASK.data[8] ^= A_BUTTON; // huh?
+ return TRUE;
+ }
+ if(gMain.newAndRepeatedKeys & DPAD_DOWN)
+ {
+ TASK.data[8] ^= A_BUTTON; // huh?
+ return TRUE;
+ }
+ else
+ {
+ u16 keys = gMain.newAndRepeatedKeys & DPAD_RIGHT;
+ if(keys)
+ {
+ if(TASK.WINDOW_SELECTED != 0)
+ {
+ if(TASK.SE_INDEX > 0)
+ {
+ TASK.SE_INDEX--;
+ }
+ else
+ {
+ TASK.SE_INDEX = 0xF7;
+ }
+ }
+ else if(TASK.BGM_INDEX > 0)
+ {
+ TASK.BGM_INDEX--;
+ }
+ else
+ {
+ TASK.BGM_INDEX = 0x75;
+ }
+ return TRUE;
+ }
+ if(gMain.newAndRepeatedKeys & DPAD_LEFT)
+ {
+ if(TASK.WINDOW_SELECTED != 0)
+ {
+ if(TASK.SE_INDEX < 0xF7)
+ {
+ TASK.SE_INDEX++;
+ }
+ else
+ {
+ TASK.SE_INDEX = keys; // ??
+ }
+ }
+ else if(TASK.BGM_INDEX < 0x75)
+ {
+ TASK.BGM_INDEX++;
+ return TRUE;
+ }
+ else
+ {
+ TASK.BGM_INDEX = TASK.SE_INDEX;
+ return TRUE;
+ }
+ return TRUE;
+ }
+ if(gMain.heldKeys & SELECT_BUTTON)
+ {
+ gUnknown_020387B0 = A_BUTTON;
+ return FALSE;
+ }
+ else
+ {
+ gUnknown_020387B0 = (gMain.heldKeys & SELECT_BUTTON);
+ return FALSE;
+ }
+ }
+}
+#else
+__attribute__((naked))
+bool8 sub_80BA400(u8 taskId)
+{
+ asm(".syntax unified\n\
+ push {r4-r6,lr}\n\
+ sub sp, 0x4\n\
+ lsls r0, 24\n\
+ lsrs r4, r0, 24\n\
+ ldr r2, _080BA428 @ =gMain\n\
+ ldrh r1, [r2, 0x2E]\n\
+ movs r0, 0x80\n\
+ lsls r0, 1\n\
+ ands r0, r1\n\
+ cmp r0, 0\n\
+ beq _080BA434\n\
+ ldr r0, _080BA42C @ =gTasks\n\
+ lsls r1, r4, 2\n\
+ adds r1, r4\n\
+ lsls r1, 3\n\
+ adds r1, r0\n\
+ ldr r0, _080BA430 @ =sub_80BA800\n\
+ str r0, [r1]\n\
+ b _080BA64C\n\
+ .align 2, 0\n\
+_080BA428: .4byte gMain\n\
+_080BA42C: .4byte gTasks\n\
+_080BA430: .4byte sub_80BA800\n\
+_080BA434:\n\
+ movs r0, 0x80\n\
+ lsls r0, 2\n\
+ ands r0, r1\n\
+ cmp r0, 0\n\
+ beq _080BA458\n\
+ ldr r0, _080BA450 @ =gTasks\n\
+ lsls r1, r4, 2\n\
+ adds r1, r4\n\
+ lsls r1, 3\n\
+ adds r1, r0\n\
+ ldr r0, _080BA454 @ =sub_80BAF84\n\
+ str r0, [r1]\n\
+ b _080BA64C\n\
+ .align 2, 0\n\
+_080BA450: .4byte gTasks\n\
+_080BA454: .4byte sub_80BAF84\n\
+_080BA458:\n\
+ movs r0, 0x8\n\
+ ands r0, r1\n\
+ cmp r0, 0\n\
+ beq _080BA478\n\
+ ldr r0, _080BA470 @ =gTasks\n\
+ lsls r1, r4, 2\n\
+ adds r1, r4\n\
+ lsls r1, 3\n\
+ adds r1, r0\n\
+ ldr r0, _080BA474 @ =sub_80BB25C\n\
+ str r0, [r1]\n\
+ b _080BA64C\n\
+ .align 2, 0\n\
+_080BA470: .4byte gTasks\n\
+_080BA474: .4byte sub_80BB25C\n\
+_080BA478:\n\
+ movs r6, 0x1\n\
+ movs r5, 0x1\n\
+ ands r5, r1\n\
+ cmp r5, 0\n\
+ beq _080BA538\n\
+ ldr r0, _080BA4AC @ =gTasks\n\
+ lsls r1, r4, 2\n\
+ adds r1, r4\n\
+ lsls r1, 3\n\
+ adds r5, r1, r0\n\
+ movs r1, 0x8\n\
+ ldrsh r0, [r5, r1]\n\
+ cmp r0, 0\n\
+ beq _080BA4D0\n\
+ movs r2, 0x10\n\
+ ldrsh r0, [r5, r2]\n\
+ cmp r0, 0\n\
+ beq _080BA4BA\n\
+ movs r3, 0xC\n\
+ ldrsh r4, [r5, r3]\n\
+ cmp r4, 0\n\
+ beq _080BA4B0\n\
+ ldrh r0, [r5, 0x10]\n\
+ bl m4aSongNumStop\n\
+ b _080BA4C4\n\
+ .align 2, 0\n\
+_080BA4AC: .4byte gTasks\n\
+_080BA4B0:\n\
+ ldrh r0, [r5, 0x10]\n\
+ bl m4aSongNumStop\n\
+ strh r4, [r5, 0x10]\n\
+ b _080BA64C\n\
+_080BA4BA:\n\
+ movs r4, 0xC\n\
+ ldrsh r0, [r5, r4]\n\
+ cmp r0, 0\n\
+ bne _080BA4C4\n\
+ b _080BA64C\n\
+_080BA4C4:\n\
+ ldrh r0, [r5, 0xC]\n\
+ bl m4aSongNumStart\n\
+ ldrh r0, [r5, 0xC]\n\
+ strh r0, [r5, 0x10]\n\
+ b _080BA64C\n\
+_080BA4D0:\n\
+ ldrh r1, [r5, 0xE]\n\
+ movs r2, 0xE\n\
+ ldrsh r0, [r5, r2]\n\
+ cmp r0, 0\n\
+ beq _080BA514\n\
+ movs r3, 0xA\n\
+ ldrsh r4, [r5, r3]\n\
+ cmp r4, 0\n\
+ beq _080BA500\n\
+ ldr r0, _080BA4FC @ =0x0000015d\n\
+ adds r4, r0, 0\n\
+ adds r0, r1, r4\n\
+ lsls r0, 16\n\
+ lsrs r0, 16\n\
+ bl m4aSongNumStop\n\
+ ldrh r1, [r5, 0xA]\n\
+ adds r4, r1\n\
+ lsls r4, 16\n\
+ lsrs r4, 16\n\
+ adds r0, r4, 0\n\
+ b _080BA528\n\
+ .align 2, 0\n\
+_080BA4FC: .4byte 0x0000015d\n\
+_080BA500:\n\
+ ldr r2, _080BA510 @ =0x0000015d\n\
+ adds r0, r1, r2\n\
+ lsls r0, 16\n\
+ lsrs r0, 16\n\
+ bl m4aSongNumStop\n\
+ strh r4, [r5, 0xE]\n\
+ b _080BA64C\n\
+ .align 2, 0\n\
+_080BA510: .4byte 0x0000015d\n\
+_080BA514:\n\
+ ldrh r1, [r5, 0xA]\n\
+ movs r3, 0xA\n\
+ ldrsh r0, [r5, r3]\n\
+ cmp r0, 0\n\
+ bne _080BA520\n\
+ b _080BA64C\n\
+_080BA520:\n\
+ ldr r4, _080BA534 @ =0x0000015d\n\
+ adds r0, r1, r4\n\
+ lsls r0, 16\n\
+ lsrs r0, 16\n\
+_080BA528:\n\
+ bl m4aSongNumStart\n\
+ ldrh r0, [r5, 0xA]\n\
+ strh r0, [r5, 0xE]\n\
+ b _080BA64C\n\
+ .align 2, 0\n\
+_080BA534: .4byte 0x0000015d\n\
+_080BA538:\n\
+ movs r0, 0x2\n\
+ ands r0, r1\n\
+ cmp r0, 0\n\
+ beq _080BA570\n\
+ movs r0, 0x5\n\
+ bl m4aSongNumStart\n\
+ movs r0, 0x1\n\
+ negs r0, r0\n\
+ str r5, [sp]\n\
+ movs r1, 0\n\
+ movs r2, 0\n\
+ movs r3, 0x10\n\
+ bl BeginNormalPaletteFade\n\
+ ldr r1, _080BA568 @ =gTasks\n\
+ lsls r0, r4, 2\n\
+ adds r0, r4\n\
+ lsls r0, 3\n\
+ adds r0, r1\n\
+ ldr r1, _080BA56C @ =sub_80BA68C\n\
+ str r1, [r0]\n\
+ b _080BA64C\n\
+ .align 2, 0\n\
+_080BA568: .4byte gTasks\n\
+_080BA56C: .4byte sub_80BA68C\n\
+_080BA570:\n\
+ ldrh r1, [r2, 0x30]\n\
+ movs r0, 0x40\n\
+ ands r0, r1\n\
+ cmp r0, 0\n\
+ bne _080BA582\n\
+ movs r0, 0x80\n\
+ ands r0, r1\n\
+ cmp r0, 0\n\
+ beq _080BA59C\n\
+_080BA582:\n\
+ ldr r0, _080BA598 @ =gTasks\n\
+ lsls r1, r4, 2\n\
+ adds r1, r4\n\
+ lsls r1, 3\n\
+ adds r1, r0\n\
+ ldrh r0, [r1, 0x8]\n\
+ eors r0, r6\n\
+ strh r0, [r1, 0x8]\n\
+_080BA592:\n\
+ movs r0, 0x1\n\
+ b _080BA64E\n\
+ .align 2, 0\n\
+_080BA598: .4byte gTasks\n\
+_080BA59C:\n\
+ movs r0, 0x10\n\
+ ands r0, r1\n\
+ lsls r0, 16\n\
+ lsrs r3, r0, 16\n\
+ cmp r3, 0\n\
+ beq _080BA5EA\n\
+ ldr r0, _080BA5CC @ =gTasks\n\
+ lsls r1, r4, 2\n\
+ adds r1, r4\n\
+ lsls r1, 3\n\
+ adds r1, r0\n\
+ movs r2, 0x8\n\
+ ldrsh r0, [r1, r2]\n\
+ cmp r0, 0\n\
+ beq _080BA5D6\n\
+ ldrh r2, [r1, 0xC]\n\
+ movs r3, 0xC\n\
+ ldrsh r0, [r1, r3]\n\
+ cmp r0, 0\n\
+ ble _080BA5D0\n\
+ subs r0, r2, 0x1\n\
+ strh r0, [r1, 0xC]\n\
+ b _080BA592\n\
+ .align 2, 0\n\
+_080BA5CC: .4byte gTasks\n\
+_080BA5D0:\n\
+ movs r0, 0xF7\n\
+ strh r0, [r1, 0xC]\n\
+ b _080BA592\n\
+_080BA5D6:\n\
+ ldrh r2, [r1, 0xA]\n\
+ movs r4, 0xA\n\
+ ldrsh r0, [r1, r4]\n\
+ cmp r0, 0\n\
+ ble _080BA5E4\n\
+ subs r0, r2, 0x1\n\
+ b _080BA5E6\n\
+_080BA5E4:\n\
+ movs r0, 0x75\n\
+_080BA5E6:\n\
+ strh r0, [r1, 0xA]\n\
+ b _080BA592\n\
+_080BA5EA:\n\
+ movs r0, 0x20\n\
+ ands r0, r1\n\
+ cmp r0, 0\n\
+ beq _080BA630\n\
+ ldr r1, _080BA614 @ =gTasks\n\
+ lsls r0, r4, 2\n\
+ adds r0, r4\n\
+ lsls r0, 3\n\
+ adds r1, r0, r1\n\
+ movs r0, 0x8\n\
+ ldrsh r2, [r1, r0]\n\
+ cmp r2, 0\n\
+ beq _080BA61C\n\
+ ldrh r2, [r1, 0xC]\n\
+ movs r4, 0xC\n\
+ ldrsh r0, [r1, r4]\n\
+ cmp r0, 0xF6\n\
+ bgt _080BA618\n\
+ adds r0, r2, 0x1\n\
+ strh r0, [r1, 0xC]\n\
+ b _080BA592\n\
+ .align 2, 0\n\
+_080BA614: .4byte gTasks\n\
+_080BA618:\n\
+ strh r3, [r1, 0xC]\n\
+ b _080BA592\n\
+_080BA61C:\n\
+ ldrh r3, [r1, 0xA]\n\
+ movs r4, 0xA\n\
+ ldrsh r0, [r1, r4]\n\
+ cmp r0, 0x74\n\
+ bgt _080BA62C\n\
+ adds r0, r3, 0x1\n\
+ strh r0, [r1, 0xA]\n\
+ b _080BA592\n\
+_080BA62C:\n\
+ strh r2, [r1, 0xA]\n\
+ b _080BA592\n\
+_080BA630:\n\
+ ldrh r1, [r2, 0x2C]\n\
+ movs r0, 0x4\n\
+ ands r0, r1\n\
+ lsls r0, 16\n\
+ lsrs r1, r0, 16\n\
+ cmp r1, 0\n\
+ beq _080BA648\n\
+ ldr r0, _080BA644 @ =gUnknown_020387B0\n\
+ strb r6, [r0]\n\
+ b _080BA64C\n\
+ .align 2, 0\n\
+_080BA644: .4byte gUnknown_020387B0\n\
+_080BA648:\n\
+ ldr r0, _080BA658 @ =gUnknown_020387B0\n\
+ strb r1, [r0]\n\
+_080BA64C:\n\
+ movs r0, 0\n\
+_080BA64E:\n\
+ add sp, 0x4\n\
+ pop {r4-r6}\n\
+ pop {r1}\n\
+ bx r1\n\
+ .align 2, 0\n\
+_080BA658: .4byte gUnknown_020387B0\n\
+ .syntax divided");
+}
+#endif
+
+void sub_80BA65C(u8 taskId)
+{
+ if(sub_80BA400(taskId) != FALSE)
+ TASK.FUNC = sub_80BA384;
+}
+
+void sub_80BA68C(u8 taskId)
+{
+ if(!gPaletteFade.active)
+ {
+ DestroyTask(taskId);
+ SetMainCallback2(CB2_InitTitleScreen);
+ }
+}
+
+void sub_80BA6B8(u8 windowType)
+{
+ switch(windowType)
+ {
+ case BGM_WINDOW:
+ default:
+ REG_WIN1H = WIN_RANGE(17, 223);
+ REG_WIN1V = WIN_RANGE(41, 87);
+ break;
+ case SE_WINDOW:
+ REG_WIN1H = WIN_RANGE(17, 223);
+ REG_WIN1V = WIN_RANGE(97, 143);
+ break;
+ }
+}
+
+void sub_80BA700(u16 soundIndex, u16 x, u16 y) // PrintSoundNumber ?
+{
+ u8 i;
+ u8 str[5];
+ bool8 someBool;
+ u8 divisorValue;
+
+ for(i = 0; i < 3; i++)
+ str[i] = 0; // initialize array
+
+ str[3] = CHAR_ELLIPSIS;
+ str[4] = EOS;
+ someBool = FALSE;
+
+ divisorValue = soundIndex / 100;
+ if(divisorValue)
+ {
+ str[0] = divisorValue + CHAR_0;
+ someBool = TRUE;
+ }
+
+ divisorValue = (soundIndex % 100) / 10;
+ if(divisorValue || someBool != FALSE)
+ str[1] = divisorValue + CHAR_0;
+
+ str[2] = ((soundIndex % 100) % 10) + CHAR_0;
+ MenuPrint(str, x, y);
+}
+
+void sub_80BA79C(const u8 *const string, u16 x, u16 y)
+{
+ u8 i;
+ u8 str[11];
+
+ for(i = 0; i < 11; i++)
+ str[i] = 0; // format string.
+
+ str[10] = EOS; // the above for loop formats the last element of the array unnecessarily.
+
+ for(i = 0; string[i] != EOS && i < 10; i++)
+ str[i] = string[i];
+
+ MenuPrint(str, x, y);
+}
+
+void sub_80BA800(u8 taskId) // Task_DrawDriverTestMenu
+{
+ u8 bbackStr[sizeof(gDebugText_BBack)];
+ u8 aplayStr[sizeof(gDebugText_APlay)];
+ u8 voiceStr[sizeof(gDebugText_Voice)];
+ u8 volumeStr[sizeof(gDebugText_Volume)];
+ u8 panpotStr[sizeof(gDebugText_Panpot)];
+ u8 pitchStr[sizeof(gDebugText_Pitch)];
+ u8 lengthStr[sizeof(gDebugText_Length)];
+ u8 releaseStr[sizeof(gDebugText_Release)];
+ u8 progressStr[sizeof(gDebugText_Progress)];
+ u8 chorusStr[sizeof(gDebugText_Chorus)];
+ u8 priorityStr[sizeof(gDebugText_Priority)];
+ u8 playingStr[sizeof(gDebugText_Playing)];
+ u8 reverseStr[sizeof(gDebugText_Reverse)];
+ u8 stereoStr[sizeof(gDebugText_Stereo)];
+
+ memcpy(bbackStr, gDebugText_BBack, sizeof(gDebugText_BBack));
+ memcpy(aplayStr, gDebugText_APlay, sizeof(gDebugText_APlay));
+ memcpy(voiceStr, gDebugText_Voice, sizeof(gDebugText_Voice));
+ memcpy(volumeStr, gDebugText_Volume, sizeof(gDebugText_Volume));
+ memcpy(panpotStr, gDebugText_Panpot, sizeof(gDebugText_Panpot));
+ memcpy(pitchStr, gDebugText_Pitch, sizeof(gDebugText_Pitch));
+ memcpy(lengthStr, gDebugText_Length, sizeof(gDebugText_Length));
+ memcpy(releaseStr, gDebugText_Release, sizeof(gDebugText_Release));
+ memcpy(progressStr, gDebugText_Progress, sizeof(gDebugText_Progress));
+ memcpy(chorusStr, gDebugText_Chorus, sizeof(gDebugText_Chorus));
+ memcpy(priorityStr, gDebugText_Priority, sizeof(gDebugText_Priority));
+ memcpy(playingStr, gDebugText_Playing, sizeof(gDebugText_Playing));
+ memcpy(reverseStr, gDebugText_Reverse, sizeof(gDebugText_Reverse));
+ memcpy(stereoStr, gDebugText_Stereo, sizeof(gDebugText_Stereo));
+
+ REG_DISPCNT = 0x3140;
+ MenuDrawTextWindow(0, 0, 0x1D, 0x13);
+ MenuPrint(bbackStr, 0x13, 0x4);
+ MenuPrint(aplayStr, 0x13, 0x2);
+ MenuPrint(voiceStr, 0x2, 0x1);
+ MenuPrint(volumeStr, 0x2, 0x3);
+ MenuPrint(panpotStr, 0x2, 0x5);
+ MenuPrint(pitchStr, 0x2, 0x7);
+ MenuPrint(lengthStr, 0x2, 0x9);
+ MenuPrint(releaseStr, 0x2, 0xB);
+ MenuPrint(progressStr, 0x2, 0xD);
+ MenuPrint(chorusStr, 0x2, 0xF);
+ MenuPrint(priorityStr, 0x2, 0x11);
+ MenuPrint(playingStr, 0x13, 0x10);
+ MenuPrint(reverseStr, 0x13, 0xE);
+ MenuPrint(stereoStr, 0x13, 0xC);
+ REG_WIN0H = WIN_RANGE(0, 240);
+ REG_WIN0V = WIN_RANGE(0, 160);
+ gUnknown_020387B3 = 0;
+ gUnknown_020387B1 = 0;
+ gUnknown_020387B2 = 0;
+ gUnknown_03005D30 = NULL;
+ gUnknown_020387D8 = 0;
+ gUnknown_020387D9 = 1;
+ gUnknown_020387B4[CRY_TEST_UNK0] = 0;
+ gUnknown_020387B4[CRY_TEST_VOLUME] = 0x78;
+ gUnknown_020387B4[CRY_TEST_PANPOT] = 0;
+ gUnknown_020387B4[CRY_TEST_PITCH] = 0x3C00;
+ gUnknown_020387B4[CRY_TEST_LENGTH] = 0xB4;
+ gUnknown_020387B4[CRY_TEST_PROGRESS] = 0;
+ gUnknown_020387B4[CRY_TEST_RELEASE] = 0;
+ gUnknown_020387B4[CRY_TEST_CHORUS] = 0;
+ gUnknown_020387B4[CRY_TEST_PRIORITY] = 2;
+ sub_80BAD5C();
+ sub_80BAE10(0, 0);
+ TASK.FUNC = sub_80BAA48;
+}
+
+void sub_80BAA48(u8 taskId) // Task_ProcessDriverTestInput
+{
+ if(gMain.newKeys & B_BUTTON)
+ {
+ REG_DISPCNT = 0x7140;
+ REG_WIN0H = WIN_RANGE(17, 223);
+ REG_WIN0V = WIN_RANGE(1, 31);
+ MenuZeroFillWindowRect(0, 0, 0x1D, 0x13);
+ TASK.FUNC = sub_80BA258;
+ return;
+ }
+ if(gMain.newAndRepeatedKeys & DPAD_UP) // _080BAAA8
+ {
+ u8 backupVar = gUnknown_020387B3;
+ if(--gUnknown_020387B3 < 0)
+ gUnknown_020387B3 = 8;
+
+ sub_80BAE10(backupVar, gUnknown_020387B3);
+ return;
+ }
+ if(gMain.newAndRepeatedKeys & DPAD_DOWN) // _080BAAD0
+ {
+ u8 backupVar = gUnknown_020387B3;
+ if(++gUnknown_020387B3 > 8)
+ gUnknown_020387B3 = 0;
+
+ sub_80BAE10(backupVar, gUnknown_020387B3);
+ return;
+ }
+ if(gMain.newKeys & START_BUTTON) // _080BAAF8
+ {
+ gUnknown_020387D8 ^= 1;
+ sub_80BAD5C();
+ return;
+ }
+ if(gMain.newKeys & SELECT_BUTTON) // _080BAB14
+ {
+ gUnknown_020387D9 ^= 1;
+ sub_80BAD5C();
+ SetPokemonCryStereo(gUnknown_020387D9);
+ return;
+ }
+ if(gMain.newAndRepeatedKeys & R_BUTTON) // _080BAB38
+ {
+ sub_80BACDC(10);
+ sub_80BAD5C();
+ return;
+ }
+ if(gMain.newAndRepeatedKeys & L_BUTTON) // _080BAB46
+ {
+ sub_80BACDC(-10);
+ sub_80BAD5C();
+ return;
+ }
+ if(gMain.newAndRepeatedKeys & DPAD_LEFT) // _080BAB56
+ {
+ sub_80BACDC(-1);
+ sub_80BAD5C();
+ return;
+ }
+ if(gMain.newAndRepeatedKeys & DPAD_RIGHT) // _080BAB64
+ {
+ sub_80BACDC(1);
+ sub_80BAD5C();
+ return;
+ }
+ if(gMain.newKeys & A_BUTTON) // _080BAB78
+ {
+ u8 divide, remaining;
+
+ SetPokemonCryVolume(gUnknown_020387B4[CRY_TEST_VOLUME]);
+ SetPokemonCryPanpot(gUnknown_020387B4[CRY_TEST_PANPOT]);
+ SetPokemonCryPitch(gUnknown_020387B4[CRY_TEST_PITCH]);
+ SetPokemonCryLength(gUnknown_020387B4[CRY_TEST_LENGTH]);
+ SetPokemonCryProgress(gUnknown_020387B4[CRY_TEST_PROGRESS]);
+ SetPokemonCryRelease(gUnknown_020387B4[CRY_TEST_RELEASE]);
+ SetPokemonCryChorus(gUnknown_020387B4[CRY_TEST_CHORUS]);
+ SetPokemonCryPriority(gUnknown_020387B4[CRY_TEST_PRIORITY]);
+
+ remaining = gUnknown_020387B4[CRY_TEST_UNK0] % 128;
+ divide = gUnknown_020387B4[CRY_TEST_UNK0] / 128;
+
+ switch(divide)
+ {
+ case 0:
+ if(gUnknown_020387D8)
+ gUnknown_03005D30 = SetPokemonCryTone(&voicegroup_84537C0[remaining]);
+ else
+ gUnknown_03005D30 = SetPokemonCryTone(&voicegroup_8452590[remaining]);
+ break;
+ case 1:
+ if(gUnknown_020387D8)
+ gUnknown_03005D30 = SetPokemonCryTone(&voicegroup_8453DC0[remaining]);
+ else
+ gUnknown_03005D30 = SetPokemonCryTone(&voicegroup_8452B90[remaining]);
+ break;
+ case 2:
+ if(gUnknown_020387D8)
+ gUnknown_03005D30 = SetPokemonCryTone(&voicegroup_84543C0[remaining]);
+ else
+ gUnknown_03005D30 = SetPokemonCryTone(&voicegroup_8453190[remaining]);
+ break;
+ case 3:
+ if(gUnknown_020387D8)
+ gUnknown_03005D30 = SetPokemonCryTone(&voicegroup_84549C0[remaining]);
+ else
+ gUnknown_03005D30 = SetPokemonCryTone(&voicegroup_8453790[remaining]);
+ break;
+ }
+ }
+
+ // _080BACA2
+ if(gUnknown_03005D30 != NULL)
+ {
+ gUnknown_020387B1 = IsPokemonCryPlaying(gUnknown_03005D30);
+
+ if(gUnknown_020387B1 != gUnknown_020387B2)
+ sub_80BAD5C();
+
+ gUnknown_020387B2 = gUnknown_020387B1;
+ }
+}
+
+void sub_80BACDC(s8 var)
+{
+ int minMaxArray[ARRAY_COUNT(gUnknown_083D039C)];
+
+ memcpy(minMaxArray, gUnknown_083D039C, sizeof(gUnknown_083D039C));
+ gUnknown_020387B4[gUnknown_020387B3] += var;
+
+ if(gUnknown_020387B4[gUnknown_020387B3] > minMaxArray[MULTI_DIM_ARR(gUnknown_020387B3, B_16, MAX)])
+ gUnknown_020387B4[gUnknown_020387B3] = minMaxArray[MULTI_DIM_ARR(gUnknown_020387B3, B_16, MIN)];
+
+ if(gUnknown_020387B4[gUnknown_020387B3] < minMaxArray[MULTI_DIM_ARR(gUnknown_020387B3, B_16, MIN)])
+ gUnknown_020387B4[gUnknown_020387B3] = minMaxArray[MULTI_DIM_ARR(gUnknown_020387B3, B_16, MAX)];
+}
+
+void sub_80BAD5C(void)
+{
+ sub_80BAE78(gUnknown_020387B4[CRY_TEST_UNK0] + 1, 0xB, 0x1, 0x5);
+ sub_80BAE78(gUnknown_020387B4[CRY_TEST_VOLUME], 0xB, 0x3, 0x5);
+ sub_80BAE78(gUnknown_020387B4[CRY_TEST_PANPOT], 0xB, 0x5, 0x5);
+ sub_80BAE78(gUnknown_020387B4[CRY_TEST_PITCH], 0xB, 0x7, 0x5);
+ sub_80BAE78(gUnknown_020387B4[CRY_TEST_LENGTH], 0xB, 0x9, 0x5);
+ sub_80BAE78(gUnknown_020387B4[CRY_TEST_RELEASE], 0xB, 0xB, 0x5);
+ sub_80BAE78(gUnknown_020387B4[CRY_TEST_PROGRESS], 0xB, 0xD, 0x5);
+ sub_80BAE78(gUnknown_020387B4[CRY_TEST_CHORUS], 0xB, 0xF, 0x5);
+ sub_80BAE78(gUnknown_020387B4[CRY_TEST_PRIORITY], 0xB, 0x11, 0x5);
+ sub_80BAE78(gUnknown_020387B1, 0x1B, 0x10, 0x1);
+ sub_80BAE78(gUnknown_020387D8, 0x1B, 0xE, 0x1);
+ sub_80BAE78(gUnknown_020387D9, 0x1B, 0xC, 0x1);
+}
+
+void sub_80BAE10(u8 var1, u8 var2)
+{
+ u8 str1[sizeof(gUnknown_083D03DC)];
+ u8 str2[sizeof(gUnknown_083D03DE)];
+
+ memcpy(str1, gUnknown_083D03DC, sizeof(gUnknown_083D03DC));
+ memcpy(str2, gUnknown_083D03DE, sizeof(gUnknown_083D03DE));
+
+ MenuPrint(str2, gUnknown_083D0300[MULTI_DIM_ARR(var1, B_16, 0)], gUnknown_083D0300[MULTI_DIM_ARR(var1, B_16, 1)]);
+ MenuPrint(str1, gUnknown_083D0300[MULTI_DIM_ARR(var2, B_16, 0)], gUnknown_083D0300[MULTI_DIM_ARR(var2, B_16, 1)]);
+}
+
+/*void sub_80BAE78(int var1, u16 var2, u16 var3, u8 var4)
+{
+ u32 powers[6];
+ u8 str[8];
+ u8 i;
+ u8 someVar, someVar2;
+
+ memcpy(powers, gUnknown_083D03E0, sizeof(powers);
+
+ for(i = 0; i < var4; i++)
+ str[i] = 0;
+
+ str[var4 + 1] = CHAR_0;
+ someVar = 0;
+
+ if(var1 < 0) // make absolute value? wtf
+ {
+ var1 = -var1; // just use abs?
+ someVar = 1;
+ }
+
+ // _080BAED6
+ someVar2 = 0;
+ if(var4 == 1)
+ someVar2 = 1;
+
+ // _080BAEE2
+ for(;;)
+ {
+
+ }
+}*/
+
+// no.
+__attribute__((naked))
+void sub_80BAE78(int var1, u16 var2, u16 var3, u8 var4)
+{
+ asm(".syntax unified\n\
+ push {r4-r7,lr}\n\
+ mov r7, r10\n\
+ mov r6, r9\n\
+ mov r5, r8\n\
+ push {r5-r7}\n\
+ sub sp, 0x2C\n\
+ mov r8, r0\n\
+ lsls r1, 16\n\
+ lsrs r6, r1, 16\n\
+ lsls r2, 16\n\
+ lsrs r2, 16\n\
+ lsls r3, 24\n\
+ lsrs r7, r3, 24\n\
+ mov r1, sp\n\
+ ldr r0, _080BAF80 @ =gUnknown_083D03E0\n\
+ ldm r0!, {r3-r5}\n\
+ stm r1!, {r3-r5}\n\
+ ldm r0!, {r3-r5}\n\
+ stm r1!, {r3-r5}\n\
+ movs r5, 0\n\
+ add r0, sp, 0x18\n\
+ mov r9, r0\n\
+ cmp r5, r7\n\
+ bgt _080BAEC0\n\
+ mov r4, r9\n\
+ movs r3, 0\n\
+_080BAEAC:\n\
+ lsls r0, r5, 24\n\
+ asrs r0, 24\n\
+ adds r1, r4, r0\n\
+ strb r3, [r1]\n\
+ adds r0, 0x1\n\
+ lsls r0, 24\n\
+ lsrs r5, r0, 24\n\
+ asrs r0, 24\n\
+ cmp r0, r7\n\
+ ble _080BAEAC\n\
+_080BAEC0:\n\
+ adds r0, r7, 0x1\n\
+ add r0, r9\n\
+ movs r1, 0xFF\n\
+ strb r1, [r0]\n\
+ movs r1, 0\n\
+ mov r3, r8\n\
+ cmp r3, 0\n\
+ bge _080BAED6\n\
+ negs r3, r3\n\
+ mov r8, r3\n\
+ movs r1, 0x1\n\
+_080BAED6:\n\
+ movs r4, 0\n\
+ mov r10, r4\n\
+ cmp r7, 0x1\n\
+ bne _080BAEE2\n\
+ movs r5, 0x1\n\
+ mov r10, r5\n\
+_080BAEE2:\n\
+ subs r0, r7, 0x1\n\
+ lsls r0, 24\n\
+ lsrs r5, r0, 24\n\
+ lsls r0, r5, 24\n\
+ lsls r6, 24\n\
+ str r6, [sp, 0x24]\n\
+ lsls r2, 24\n\
+ str r2, [sp, 0x28]\n\
+ cmp r0, 0\n\
+ blt _080BAF62\n\
+ str r1, [sp, 0x20]\n\
+_080BAEF8:\n\
+ asrs r6, r0, 24\n\
+ lsls r0, r6, 2\n\
+ add r0, sp\n\
+ ldr r1, [r0]\n\
+ mov r0, r8\n\
+ bl __divsi3\n\
+ lsls r0, 24\n\
+ lsrs r2, r0, 24\n\
+ cmp r0, 0\n\
+ bne _080BAF1A\n\
+ mov r0, r10\n\
+ cmp r0, 0\n\
+ bne _080BAF1A\n\
+ lsls r4, r5, 24\n\
+ cmp r6, 0\n\
+ bne _080BAF46\n\
+_080BAF1A:\n\
+ lsls r4, r5, 24\n\
+ ldr r3, [sp, 0x20]\n\
+ cmp r3, 0\n\
+ beq _080BAF34\n\
+ mov r5, r10\n\
+ cmp r5, 0\n\
+ bne _080BAF34\n\
+ asrs r0, r4, 24\n\
+ subs r0, r7, r0\n\
+ subs r0, 0x1\n\
+ add r0, r9\n\
+ movs r1, 0xAE\n\
+ strb r1, [r0]\n\
+_080BAF34:\n\
+ asrs r1, r4, 24\n\
+ subs r1, r7, r1\n\
+ add r1, r9\n\
+ lsls r0, r2, 24\n\
+ asrs r0, 24\n\
+ subs r0, 0x5F\n\
+ strb r0, [r1]\n\
+ movs r0, 0x1\n\
+ mov r10, r0\n\
+_080BAF46:\n\
+ asrs r4, 24\n\
+ lsls r0, r4, 2\n\
+ add r0, sp\n\
+ ldr r1, [r0]\n\
+ mov r0, r8\n\
+ bl __modsi3\n\
+ mov r8, r0\n\
+ subs r4, 0x1\n\
+ lsls r4, 24\n\
+ lsrs r5, r4, 24\n\
+ lsls r0, r5, 24\n\
+ cmp r0, 0\n\
+ bge _080BAEF8\n\
+_080BAF62:\n\
+ ldr r3, [sp, 0x24]\n\
+ lsrs r1, r3, 24\n\
+ ldr r4, [sp, 0x28]\n\
+ lsrs r2, r4, 24\n\
+ mov r0, r9\n\
+ bl MenuPrint\n\
+ add sp, 0x2C\n\
+ pop {r3-r5}\n\
+ mov r8, r3\n\
+ mov r9, r4\n\
+ mov r10, r5\n\
+ pop {r4-r7}\n\
+ pop {r0}\n\
+ bx r0\n\
+ .align 2, 0\n\
+_080BAF80: .4byte gUnknown_083D03E0\n\
+ .syntax divided");
+}
+
+void sub_80BAF84(u8 taskId)
+{
+ u8 seStr[sizeof(gOtherText_SE)];
+ u8 panStr[sizeof(gOtherText_Pan)];
+ u8 playingStr[sizeof(gDebugText_Playing)];
+
+ memcpy(seStr, gOtherText_SE, sizeof(gOtherText_SE));
+ memcpy(panStr, gOtherText_Pan, sizeof(gOtherText_Pan));
+ memcpy(playingStr, gDebugText_Playing, sizeof(gDebugText_Playing));
+
+ REG_DISPCNT = 0x3140;
+ MenuDrawTextWindow(0, 0, 0x1D, 0x13);
+ MenuPrint(seStr, 3, 2);
+ MenuPrint(panStr, 3, 4);
+ MenuPrint(playingStr, 3, 8);
+ REG_WIN0H = WIN_RANGE(0, 240);
+ REG_WIN0V = WIN_RANGE(0, 160);
+ gUnknown_020387B4[CRY_TEST_UNK0] = 1;
+ gUnknown_020387B4[CRY_TEST_PANPOT] = 0;
+ gUnknown_020387B4[CRY_TEST_CHORUS] = 0;
+ gUnknown_020387B4[CRY_TEST_PROGRESS] = 0;
+ gUnknown_020387B4[CRY_TEST_RELEASE] = 0;
+ sub_80BB1D4();
+ TASK.FUNC = sub_80BB038;
+}
+
+void sub_80BB038(u8 taskId)
+{
+ sub_80BB1D4();
+ if(gUnknown_020387B4[CRY_TEST_PROGRESS])
+ {
+ if(gUnknown_020387B4[CRY_TEST_RELEASE])
+ {
+ gUnknown_020387B4[CRY_TEST_RELEASE]--;
+ }
+ else // _080BB05C
+ {
+ s8 panpot = gUnknown_083D03F8[gUnknown_020387B4[CRY_TEST_PANPOT]];
+ if(panpot != -128)
+ {
+ if(panpot == 0x7F)
+ {
+ gUnknown_020387B4[CRY_TEST_CHORUS] += 2;
+ if(gUnknown_020387B4[CRY_TEST_CHORUS] < 0x3F)
+ SE12PanpotControl(gUnknown_020387B4[CRY_TEST_CHORUS]);
+ }
+ }
+ else // _080BB08C
+ {
+ gUnknown_020387B4[CRY_TEST_CHORUS] -= 2;
+ if(gUnknown_020387B4[CRY_TEST_CHORUS] > -0x40)
+ SE12PanpotControl(gUnknown_020387B4[CRY_TEST_CHORUS]);
+ }
+ }
+ }
+ // _080BB0A2
+ if(gMain.newKeys & B_BUTTON)
+ {
+ REG_DISPCNT = 0x7140;
+ REG_WIN0H = WIN_RANGE(17, 223);
+ REG_WIN0V = WIN_RANGE(1, 31);
+ MenuZeroFillWindowRect(0, 0, 0x1D, 0x13);
+ TASK.FUNC = sub_80BA258;
+ return;
+ }
+ if(gMain.newKeys & A_BUTTON) // _080BB104
+ {
+ s8 panpot = gUnknown_083D03F8[gUnknown_020387B4[CRY_TEST_PANPOT]];
+ if(panpot != -128)
+ {
+ if(panpot == 0x7F)
+ {
+ PlaySE12WithPanning(gUnknown_020387B4[CRY_TEST_UNK0], -0x40);
+ gUnknown_020387B4[CRY_TEST_CHORUS] = -0x40;
+ gUnknown_020387B4[CRY_TEST_PROGRESS] = 1;
+ gUnknown_020387B4[CRY_TEST_RELEASE] = 0x1E;
+ return;
+ }
+ }
+ else // _080BB140
+ {
+ PlaySE12WithPanning(gUnknown_020387B4[CRY_TEST_UNK0], 0x3F);
+ gUnknown_020387B4[CRY_TEST_CHORUS] = 0x3F;
+ gUnknown_020387B4[CRY_TEST_PROGRESS] = 1;
+ gUnknown_020387B4[CRY_TEST_RELEASE] = 0x1E;
+ return;
+ }
+ // _080BB154
+ PlaySE12WithPanning(gUnknown_020387B4[CRY_TEST_UNK0], panpot);
+ gUnknown_020387B4[CRY_TEST_PROGRESS] = 0;
+ return;
+ }
+ if(gMain.newKeys & L_BUTTON) // _080BB15E
+ {
+ gUnknown_020387B4[CRY_TEST_PANPOT]++;
+ if(gUnknown_020387B4[CRY_TEST_PANPOT] > 4)
+ gUnknown_020387B4[CRY_TEST_PANPOT] = 0;
+ }
+ if(gMain.newKeys & R_BUTTON) // _080BB176
+ {
+ gUnknown_020387B4[CRY_TEST_PANPOT]--;
+ if(gUnknown_020387B4[CRY_TEST_PANPOT] < 0)
+ gUnknown_020387B4[CRY_TEST_PANPOT] = 4;
+ }
+ if(gMain.newAndRepeatedKeys & DPAD_RIGHT) // _080BB192
+ {
+ gUnknown_020387B4[CRY_TEST_UNK0]++;
+ if(gUnknown_020387B4[CRY_TEST_UNK0] > 0xF7)
+ gUnknown_020387B4[CRY_TEST_UNK0] = 0;
+ }
+ else if(gMain.newAndRepeatedKeys & DPAD_LEFT) // _080BB1B0
+ {
+ gUnknown_020387B4[CRY_TEST_UNK0]--;
+ if(gUnknown_020387B4[CRY_TEST_UNK0] < 0)
+ gUnknown_020387B4[CRY_TEST_UNK0] = 0xF7;
+ }
+}
+
+void sub_80BB1D4(void)
+{
+ u8 lrStr[sizeof(gOtherText_LR)];
+ u8 rlStr[sizeof(gOtherText_RL)];
+
+ memcpy(lrStr, gOtherText_LR, sizeof(lrStr));
+ memcpy(rlStr, gOtherText_RL, sizeof(rlStr));
+
+ sub_80BAE78(gUnknown_020387B4[CRY_TEST_UNK0], 7, 2, 3);
+
+ switch(gUnknown_083D03F8[gUnknown_020387B4[CRY_TEST_PANPOT]])
+ {
+ case 0x7F:
+ MenuPrint(lrStr, 7, 4);
+ break;
+ case -0x80:
+ MenuPrint(rlStr, 7, 4);
+ break;
+ default:
+ sub_80BAE78(gUnknown_083D03F8[gUnknown_020387B4[CRY_TEST_PANPOT]], 7, 4, 3);
+ break;
+ }
+ sub_80BAE78(IsSEPlaying(), 12, 8, 1);
+}
+
+void sub_80BB25C(u8 taskId)
+{
+ struct CryRelatedStruct cryStruct, cryStruct2;
+ u8 zero;
+
+ SetUpWindowConfig(&gWindowConfig_81E6C3C);
+ InitMenuWindow(&gWindowConfig_81E6CE4);
+ gUnknown_03005D34 = 1;
+ ResetSpriteData();
+ FreeAllSpritePalettes();
+
+ cryStruct.unk0 = 0x2000;
+ cryStruct.unk2 = 29;
+ cryStruct.paletteNo = 12;
+ cryStruct.yPos = 30;
+ cryStruct.xPos = 4;
+
+ zero = 0; // wtf?
+ gUnknown_03005E98 = 0;
+
+ while(sub_8119E3C(&cryStruct, 3) == FALSE);
+
+ cryStruct2.unk0 = 0;
+ cryStruct2.unk2 = 15;
+ cryStruct2.paletteNo = 13;
+ cryStruct2.xPos = 12;
+ cryStruct2.yPos = 12;
+
+ zero = 0; // wtf?
+ gUnknown_03005E98 = 0;
+
+ while(ShowPokedexCryScreen(&cryStruct2, 2) == FALSE);
+
+ MenuDrawTextWindow(0, 16, 5, 19);
+ sub_80BB494();
+ BeginNormalPaletteFade(0xFFFFFFFF, 0, 16, 0, 0);
+ REG_BG2HOFS = 0;
+ REG_BG2VOFS = 0;
+ REG_BG2CNT = 0xF01;
+ REG_BG3CNT = 0x1D03;
+ REG_DISPCNT = 0x1d40;
+ m4aMPlayFadeOutTemporarily(&gMPlay_BGM, 2);
+ TASK.FUNC = sub_80BB3B4;
+}
+
+void sub_80BB3B4(u8 taskId)
+{
+ sub_8119F88(3);
+
+ if(gMain.newKeys & A_BUTTON)
+ {
+ sub_811A050(gUnknown_03005D34);
+ }
+ if(gMain.newKeys & R_BUTTON)
+ {
+ StopCryAndClearCrySongs();
+ }
+ if(gMain.newAndRepeatedKeys & DPAD_UP)
+ {
+ if(--gUnknown_03005D34 == 0)
+ gUnknown_03005D34 = 384; // total species
+ sub_80BB494();
+ }
+ if(gMain.newAndRepeatedKeys & DPAD_DOWN)
+ {
+ if(++gUnknown_03005D34 > 384)
+ gUnknown_03005D34 = 1;
+ sub_80BB494();
+ }
+ if(gMain.newKeys & B_BUTTON)
+ {
+ REG_DISPCNT = 0x7140;
+ REG_WIN0H = WIN_RANGE(17, 223);
+ REG_WIN0V = WIN_RANGE(1, 31);
+ MenuZeroFillWindowRect(0, 0, 0x1D, 0x13);
+ TASK.FUNC = sub_80BA258;
+ DestroyCryMeterNeedleSprite();
+ }
+}
+
+void sub_80BB494(void)
+{
+ sub_80BAE78(gUnknown_03005D34, 1, 17, 3);
+}
diff --git a/src/engine/sprite.c b/src/engine/sprite.c
new file mode 100644
index 000000000..fb8c2b648
--- /dev/null
+++ b/src/engine/sprite.c
@@ -0,0 +1,1802 @@
+#include "global.h"
+#include "sprite.h"
+#include "main.h"
+#include "menu_cursor.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) \
+{ \
+ gSpriteTileAllocBitmap[(n) / 8] |= (1 << ((n) % 8)); \
+}
+
+#define FREE_SPRITE_TILE(n) \
+{ \
+ gSpriteTileAllocBitmap[(n) / 8] &= ~(1 << ((n) % 8)); \
+}
+
+#define SPRITE_TILE_IS_ALLOCATED(n) ((gSpriteTileAllocBitmap[(n) / 8] >> ((n) % 8)) & 1)
+
+struct OamMatrix
+{
+ s16 a;
+ s16 b;
+ s16 c;
+ s16 d;
+};
+
+struct SpriteCopyRequest
+{
+ const u8 *src;
+ u8 *dest;
+ u16 size;
+};
+
+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 ClearSpriteCopyRequests(void);
+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 void ResetAffineAnimData(void);
+static u8 IndexOfSpriteTileTag(u16 tag);
+static void AllocSpriteTileRange(u16 tag, u16 start, u16 count);
+static void DoLoadSpritePalette(const u16 *src, u16 paletteOffset);
+
+typedef void (*AnimFunc)(struct Sprite *);
+typedef void (*AnimCmdFunc)(struct Sprite *);
+typedef void (*AffineAnimCmdFunc)(u8 matrixNum, struct Sprite *);
+
+#define DUMMY_OAM_DATA \
+{ \
+ 160, /* Y (off-screen) */ \
+ 0, \
+ 0, \
+ 0, \
+ 0, \
+ 0, \
+ 304, /* X */ \
+ 0, \
+ 0, \
+ 0, \
+ 3, /* lowest priority */ \
+ 0, \
+ 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 error message.
+// It means "The DMA transfer request table has exceeded its limit."
+static const u8 sDmaOverErrorMsg[] =
+ _(
+ "DMA OVER\n"
+ "DMAてんそう\n"
+ "リクエストテーブルが\n"
+ "オーバーしました"
+ );
+
+// Unreferenced data.
+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,
+ .data0 = 0,
+ .data1 = 0,
+ .data2 = 0,
+ .data3 = 0,
+ .data4 = 0,
+ .data5 = 0,
+ .data6 = 0,
+ .data7 = 0,
+ .inUse = 0,
+ .coordOffsetEnabled = 0,
+ .invisible = 0,
+ .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
+};
+
+// TODO: Find out what these are used for.
+static const u16 sOamBitmasks[9] =
+{
+ 0xFF00, 0x00FF, 0x001F,
+ 0xFE00, 0x01FF, 0x03E0,
+ 0xFC00, 0x03FF, 0xFC00,
+};
+
+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 OamDimensions sOamDimensions[3][4] =
+{
+ { // square
+ { 8, 8 },
+ { 16, 16 },
+ { 32, 32 },
+ { 64, 64 },
+ },
+ { // horizontal rectangle
+ { 16, 8 },
+ { 32, 8 },
+ { 32, 16 },
+ { 64, 32 },
+ },
+ { // vertical rectangle
+ { 8, 16 },
+ { 8, 32 },
+ { 16, 32 },
+ { 32, 64 },
+ },
+};
+
+static u16 sSpriteTileRangeTags[MAX_SPRITES];
+static u16 sSpriteTileRanges[MAX_SPRITES * 2];
+static struct AffineAnimState sAffineAnimStates[OAM_MATRIX_COUNT];
+static u16 sSpritePaletteTags[16];
+
+u8 gSpriteOrder[MAX_SPRITES];
+bool8 gShouldProcessSpriteCopyRequests;
+u8 gSpriteCopyRequestCount;
+struct SpriteCopyRequest gSpriteCopyRequests[MAX_SPRITE_COPY_REQUESTS];
+u8 gOamLimit;
+u16 gReservedSpriteTileCount;
+u8 gSpriteTileAllocBitmap[128];
+s16 gSpriteCoordOffsetX;
+s16 gSpriteCoordOffsetY;
+u32 gOamMatrixAllocBitmap;
+struct OamMatrix gOamMatrices[OAM_MATRIX_COUNT];
+u8 gReservedSpritePaletteCount;
+
+EWRAM_DATA struct Sprite gSprites[MAX_SPRITES + 1] = {0};
+EWRAM_DATA u16 gSpritePriorities[MAX_SPRITES] = {0};
+EWRAM_DATA u8 gAffineAnimsDisabled = {0};
+
+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;
+ gShouldProcessSpriteCopyRequests = TRUE;
+}
+
+static 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;
+ }
+ }
+ }
+}
+
+static 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);
+ gSpritePriorities[i] = priority;
+ }
+}
+
+static void SortSprites(void)
+{
+ u8 i;
+ for (i = 1; i < MAX_SPRITES; i++)
+ {
+ u8 j = i;
+ struct Sprite *sprite1 = &gSprites[gSpriteOrder[i - 1]];
+ struct Sprite *sprite2 = &gSprites[gSpriteOrder[i]];
+ u16 sprite1Priority = gSpritePriorities[gSpriteOrder[i - 1]];
+ u16 sprite2Priority = gSpritePriorities[gSpriteOrder[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 == 3)
+ {
+ u32 shape = sprite1->oam.shape;
+ if (shape == ST_OAM_SQUARE || shape == 2)
+ {
+ if (sprite1Y > 128)
+ sprite1Y = sprite1Y - 256;
+ }
+ }
+
+ if (sprite2->oam.affineMode == ST_OAM_AFFINE_DOUBLE
+ && sprite2->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 = gSpriteOrder[j];
+ gSpriteOrder[j] = gSpriteOrder[j - 1];
+ gSpriteOrder[j - 1] = temp;
+
+ // UB: If j equals 1, then j-- makes j equal 0.
+ // Then, gSpriteOrder[-1] gets accessed below.
+ // Although this doesn't result in a bug in the ROM,
+ // the behavior is undefined.
+ j--;
+
+ sprite1 = &gSprites[gSpriteOrder[j - 1]];
+ sprite2 = &gSprites[gSpriteOrder[j]];
+ sprite1Priority = gSpritePriorities[gSpriteOrder[j - 1]];
+ sprite2Priority = gSpritePriorities[gSpriteOrder[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 == 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 == 3)
+ {
+ u32 shape = sprite2->oam.shape;
+ if (shape == ST_OAM_SQUARE || shape == ST_OAM_V_RECTANGLE)
+ {
+ if (sprite2Y > 128)
+ sprite2Y = sprite2Y - 256;
+ }
+ }
+ }
+ }
+}
+
+static 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;
+ }
+}
+
+static void AddSpritesToOamBuffer(void)
+{
+ u8 i = 0;
+ u8 oamIndex = 0;
+
+ while (i < MAX_SPRITES)
+ {
+ struct Sprite *sprite = &gSprites[gSpriteOrder[i]];
+ if (sprite->inUse && !sprite->invisible && AddSpriteToOamBuffer(sprite, &oamIndex))
+ break;
+ i++;
+ }
+
+ gMain.objCount = oamIndex;
+
+ 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;
+ }
+}
+
+static 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(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++)
+ {
+ struct OamData *oamBuffer = gMain.oamBuffer;
+ oamBuffer[i] = *(struct OamData *)&gDummyOamData;
+ }
+}
+
+void LoadOam(void)
+{
+ if (!gMain.oamLoadDisabled)
+ CpuCopy32(gMain.oamBuffer, (void *)OAM, sizeof(gMain.oamBuffer));
+}
+
+static void ClearSpriteCopyRequests(void)
+{
+ u8 i;
+
+ gShouldProcessSpriteCopyRequests = FALSE;
+ gSpriteCopyRequestCount = 0;
+
+ for (i = 0; i < MAX_SPRITE_COPY_REQUESTS; i++)
+ {
+ gSpriteCopyRequests[i].src = 0;
+ gSpriteCopyRequests[i].dest = 0;
+ gSpriteCopyRequests[i].size = 0;
+ }
+}
+
+static 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;
+}
+
+static 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;
+}
+
+static 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);
+ gSpriteTileAllocBitmap[index] &= val;
+ }
+ else if (op == 1)
+ {
+ val = (1 << val);
+ gSpriteTileAllocBitmap[index] |= val;
+ }
+ else
+ {
+ retVal = 1 << shift;
+ retVal &= gSpriteTileAllocBitmap[index];
+ }
+
+ return retVal;
+}
+
+void SpriteCallbackDummy(struct Sprite *sprite)
+{
+}
+
+void ProcessSpriteCopyRequests(void)
+{
+ if (gShouldProcessSpriteCopyRequests)
+ {
+ u8 i = 0;
+
+ while (gSpriteCopyRequestCount > 0)
+ {
+ CpuCopy16(gSpriteCopyRequests[i].src, gSpriteCopyRequests[i].dest, gSpriteCopyRequests[i].size);
+ gSpriteCopyRequestCount--;
+ i++;
+ }
+
+ gShouldProcessSpriteCopyRequests = FALSE;
+ }
+}
+
+static void RequestSpriteFrameImageCopy(u16 index, u16 tileNum, const struct SpriteFrameImage *images)
+{
+ if (gSpriteCopyRequestCount < MAX_SPRITE_COPY_REQUESTS)
+ {
+ gSpriteCopyRequests[gSpriteCopyRequestCount].src = images[index].data;
+ gSpriteCopyRequests[gSpriteCopyRequestCount].dest = (u8 *)OBJ_VRAM0 + TILE_SIZE_4BPP * tileNum;
+ gSpriteCopyRequests[gSpriteCopyRequestCount].size = images[index].size;
+ gSpriteCopyRequestCount++;
+ }
+}
+
+void RequestSpriteCopy(const u8 *src, u8 *dest, u16 size)
+{
+ if (gSpriteCopyRequestCount < MAX_SPRITE_COPY_REQUESTS)
+ {
+ gSpriteCopyRequests[gSpriteCopyRequestCount].src = src;
+ gSpriteCopyRequests[gSpriteCopyRequestCount].dest = dest;
+ gSpriteCopyRequests[gSpriteCopyRequestCount].size = size;
+ gSpriteCopyRequestCount++;
+ }
+}
+
+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++;
+ }
+}
+
+static void ResetAllSprites(void)
+{
+ u8 i;
+
+ for (i = 0; i < MAX_SPRITES; i++)
+ {
+ ResetSprite(&gSprites[i]);
+ gSpriteOrder[i] = i;
+ }
+
+ ResetSprite(&gSprites[i]);
+ sub_814A590();
+}
+
+void FreeSpriteTiles(struct Sprite *sprite)
+{
+ if (sprite->template->tileTag != 0xFFFF)
+ FreeSpriteTilesByTag(sprite->template->tileTag);
+}
+
+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 sub_800142C(u32 a1, u32 a2, u16 *a3, u16 a4, u32 a5)
+{
+ u16 *d = a3;
+ struct OamData *oam = &gMain.oamBuffer[gMain.objCount];
+ while (!(gMain.objCount & 0x80) && (s16)(d[0] + 1) != 0)
+ {
+ u16 *x = (u16 *)oam;
+ x[0] = (d[0] & sOamBitmasks[0]) | ((d[0] + a2) & sOamBitmasks[1]) | ((a4 & sOamBitmasks[2]) << 8);
+ x[1] = (d[1] & sOamBitmasks[3]) | ((d[1] + a1) & sOamBitmasks[4]) | ((a4 & sOamBitmasks[5]) << 4);
+ x[2] = (d[2] & sOamBitmasks[6]) | ((d[2] + a5) & sOamBitmasks[7]) | (a4 & sOamBitmasks[8]);
+ oam++;
+ gMain.objCount++;
+ d += 3;
+ }
+}
+
+void AnimateSprite(struct Sprite *sprite)
+{
+ sAnimFuncs[sprite->animBeginning](sprite);
+
+ if (!gAffineAnimsDisabled)
+ sAffineAnimFuncs[sprite->affineAnimBeginning](sprite);
+}
+
+static 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);
+ }
+}
+
+static 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);
+ }
+}
+
+static 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);
+}
+
+static void AnimCmd_end(struct Sprite *sprite)
+{
+ sprite->animCmdIndex--;
+ sprite->animEnded = TRUE;
+}
+
+static 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);
+}
+
+static void AnimCmd_loop(struct Sprite *sprite)
+{
+ if (sprite->animLoopCounter)
+ ContinueAnimLoop(sprite);
+ else
+ BeginAnimLoop(sprite);
+}
+
+static void BeginAnimLoop(struct Sprite *sprite)
+{
+ sprite->animLoopCounter = sprite->anims[sprite->animNum][sprite->animCmdIndex].loop.count;
+ JumpToTopOfAnimLoop(sprite);
+ ContinueAnim(sprite);
+}
+
+static void ContinueAnimLoop(struct Sprite *sprite)
+{
+ sprite->animLoopCounter--;
+ JumpToTopOfAnimLoop(sprite);
+ ContinueAnim(sprite);
+}
+
+static 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--;
+ }
+}
+
+static 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;
+ }
+}
+
+static 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)
+ {
+ 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);
+ }
+ }
+}
+
+static void AffineAnimDelay(u8 matrixNum, struct Sprite *sprite)
+{
+ if (!DecrementAffineAnimDelayCounter(sprite, matrixNum))
+ {
+ struct AffineAnimFrameCmd frameCmd;
+ GetAffineAnimFrame(matrixNum, sprite, &frameCmd);
+ ApplyAffineAnimFrameRelativeAndUpdateMatrix(matrixNum, &frameCmd);
+ }
+}
+
+static void AffineAnimCmd_loop(u8 matrixNum, struct Sprite *sprite)
+{
+ if (sAffineAnimStates[matrixNum].loopCounter)
+ ContinueAffineAnimLoop(matrixNum, sprite);
+ else
+ BeginAffineAnimLoop(matrixNum, sprite);
+}
+
+static 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);
+}
+
+static void ContinueAffineAnimLoop(u8 matrixNum, struct Sprite *sprite)
+{
+ sAffineAnimStates[matrixNum].loopCounter--;
+ JumpToTopOfAffineAnimLoop(matrixNum, sprite);
+ ContinueAffineAnim(sprite);
+}
+
+static 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--;
+ }
+}
+
+static 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;
+}
+
+static void AffineAnimCmd_end(u8 matrixNum, struct Sprite *sprite)
+{
+ struct AffineAnimFrameCmd dummyFrameCmd = {0};
+ sprite->affineAnimEnded = TRUE;
+ sAffineAnimStates[matrixNum].animCmdIndex--;
+ ApplyAffineAnimFrameRelativeAndUpdateMatrix(matrixNum, &dummyFrameCmd);
+}
+
+static void AffineAnimCmd_frame(u8 matrixNum, struct Sprite *sprite)
+{
+ struct AffineAnimFrameCmd frameCmd;
+ GetAffineAnimFrame(matrixNum, sprite, &frameCmd);
+ ApplyAffineAnimFrame(matrixNum, &frameCmd);
+ sAffineAnimStates[matrixNum].delayCounter = frameCmd.duration;
+}
+
+static 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;
+}
+
+static u8 GetSpriteMatrixNum(struct Sprite *sprite)
+{
+ u8 matrixNum = 0;
+ if (sprite->oam.affineMode & ST_OAM_AFFINE_ON_MASK)
+ matrixNum = sprite->oam.matrixNum;
+ return matrixNum;
+}
+
+static 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);
+}
+
+static void AffineAnimStateRestartAnim(u8 matrixNum)
+{
+ sAffineAnimStates[matrixNum].animCmdIndex = 0;
+ sAffineAnimStates[matrixNum].delayCounter = 0;
+ sAffineAnimStates[matrixNum].loopCounter = 0;
+}
+
+static 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;
+}
+
+static 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;
+}
+
+static void ApplyAffineAnimFrameAbsolute(u8 matrixNum, struct AffineAnimFrameCmd *frameCmd)
+{
+ sAffineAnimStates[matrixNum].xScale = frameCmd->xScale;
+ sAffineAnimStates[matrixNum].yScale = frameCmd->yScale;
+ sAffineAnimStates[matrixNum].rotation = frameCmd->rotation << 8;
+}
+
+static void DecrementAnimDelayCounter(struct Sprite *sprite)
+{
+ if (!sprite->animPaused)
+ sprite->animDelayCounter--;
+}
+
+static bool8 DecrementAffineAnimDelayCounter(struct Sprite *sprite, u8 matrixNum)
+{
+ if (!sprite->affineAnimPaused)
+ --sAffineAnimStates[matrixNum].delayCounter;
+ return sprite->affineAnimPaused;
+}
+
+static 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);
+}
+
+static s16 ConvertScaleParam(s16 scale)
+{
+ s32 val = 0x10000;
+ return val / scale;
+}
+
+static 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;
+}
+
+static 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;
+ }
+}
+
+static void ResetAffineAnimData(void)
+{
+ u8 i;
+
+ gAffineAnimsDisabled = 0;
+ 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]);
+}
+
+u16 AllocTilesForSpriteSheet(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);
+ return (u16)tileStart;
+ }
+}
+
+void AllocTilesForSpriteSheets(struct SpriteSheet *sheets)
+{
+ u8 i;
+ for (i = 0; sheets[i].data != NULL; i++)
+ AllocTilesForSpriteSheet(&sheets[i]);
+}
+
+void LoadTilesForSpriteSheet(const struct SpriteSheet *sheet)
+{
+ const u8 *data = sheet->data;
+ u16 tileStart = GetSpriteTileStartByTag(sheet->tag);
+ CpuCopy16(data, (u8 *)OBJ_VRAM0 + TILE_SIZE_4BPP * tileStart, sheet->size);
+}
+
+void LoadTilesForSpriteSheets(struct SpriteSheet *sheets)
+{
+ u8 i;
+ for (i = 0; sheets[i].data != NULL; i++)
+ LoadTilesForSpriteSheet(&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];
+}
+
+static 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;
+}
+
+static void AllocSpriteTileRange(u16 tag, u16 start, u16 count)
+{
+ u8 freeIndex = IndexOfSpriteTileTag(0xFFFF);
+ sSpriteTileRangeTags[freeIndex] = tag;
+ SET_SPRITE_TILE_RANGE(freeIndex, start, count);
+}
+
+void RequestSpriteSheetCopy(const struct SpriteSheet *sheet)
+{
+ const u8 *data = sheet->data;
+ u16 tileStart = GetSpriteTileStartByTag(sheet->tag);
+ RequestSpriteCopy(data, (u8 *)OBJ_VRAM0 + tileStart * TILE_SIZE_4BPP, sheet->size);
+}
+
+u16 LoadSpriteSheetDeferred(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);
+ RequestSpriteSheetCopy(sheet);
+ return (u16)tileStart;
+ }
+}
+
+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;
+}
+
+static 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/engine/string_util.c b/src/engine/string_util.c
new file mode 100644
index 000000000..9686256a1
--- /dev/null
+++ b/src/engine/string_util.c
@@ -0,0 +1,561 @@
+#include "global.h"
+#include "string_util.h"
+#include "strings.h"
+#include "text.h"
+
+u8 gUnknownStringVar[16];
+
+const u8 gEmptyString_81E72B0[] = _("");
+const u8 gRightPointingTriangleString[] = _("▶");
+
+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 *StringCopy8(u8 *dest, const u8 *src)
+{
+ s32 i;
+
+ for (i = 0; i < 8; 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)
+ {
+ u16 temp = length;
+ length++;
+ if (str[temp] == EXT_CTRL_CODE_BEGIN)
+ length += GetExtCtrlCodeLength(str[length]);
+ }
+
+ return length;
+}
+
+#ifdef GERMAN
+s32 StringLengthN(const u8 *str, s32 n)
+{
+ s32 i;
+
+ for (i = 0; i < n && str[i] != EOS; i++)
+ ;
+
+ if (i == n)
+ i = 0;
+
+ return i;
+}
+#endif
+
+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;
+}
+
+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)
+ {
+ char *out;
+ u8 c;
+ u16 digit = value / powerOfTen;
+ s32 temp = value - (powerOfTen * digit);
+
+ if (state == WRITING_DIGITS)
+ {
+ out = dest++;
+
+ if (digit <= 9)
+ c = sDigits[digit];
+ else
+ c = CHAR_QUESTION_MARK;
+
+ *out = c;
+ }
+ else if (digit != 0 || powerOfTen == 1)
+ {
+ state = WRITING_DIGITS;
+ out = dest++;
+
+ if (digit <= 9)
+ c = sDigits[digit];
+ else
+ c = CHAR_QUESTION_MARK;
+
+ *out = c;
+ }
+ else if (state == WRITING_SPACES)
+ {
+ *dest++ = CHAR_SPACE;
+ }
+
+ value = temp;
+ }
+
+ *dest = EOS;
+ return dest;
+}
+
+u8 *ConvertIntToDecimalStringN_DigitWidth6(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];
+
+ *dest++ = EXT_CTRL_CODE_BEGIN;
+ *dest++ = 0x14;
+ *dest++ = 6;
+
+ 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)
+ {
+ char *out;
+ u8 c;
+ u16 digit = value / powerOfTen;
+ s32 temp = value - (powerOfTen * digit);
+
+ if (state == WRITING_DIGITS)
+ {
+ out = dest++;
+
+ if (digit <= 9)
+ c = sDigits[digit];
+ else
+ c = CHAR_QUESTION_MARK;
+
+ *out = c;
+ }
+ else if (digit != 0 || powerOfTen == 1)
+ {
+ state = WRITING_DIGITS;
+ out = dest++;
+
+ if (digit <= 9)
+ c = sDigits[digit];
+ else
+ c = CHAR_QUESTION_MARK;
+
+ *out = c;
+ }
+ else if (state == WRITING_SPACES)
+ {
+ *dest++ = CHAR_SPACE;
+ }
+
+ value = temp;
+ }
+
+ *dest++ = EXT_CTRL_CODE_BEGIN;
+ *dest++ = 0x14;
+ *dest++ = 0x00;
+
+ *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)
+ {
+ char *out;
+ u8 c;
+ u32 digit = value / powerOfSixteen;
+ s32 temp = value % powerOfSixteen;
+
+ if (state == WRITING_DIGITS)
+ {
+ out = dest++;
+
+ if (digit <= 0xF)
+ c = sDigits[digit];
+ else
+ c = CHAR_QUESTION_MARK;
+
+ *out = c;
+ }
+ else if (digit != 0 || powerOfSixteen == 1)
+ {
+ state = WRITING_DIGITS;
+ out = dest++;
+
+ if (digit <= 0xF)
+ c = sDigits[digit];
+ else
+ c = CHAR_QUESTION_MARK;
+
+ *out = c;
+ }
+ else if (state == WRITING_SPACES)
+ {
+ *dest++ = CHAR_SPACE;
+ }
+
+ value = temp;
+ }
+
+ *dest = EOS;
+ return dest;
+}
+
+u8 *ConvertIntToDecimalString(u8 *dest, s32 value)
+{
+ char temp[12];
+ s32 length = 0;
+
+ do
+ {
+ temp[length++] = sDigits[value % 10];
+ value /= 10;
+ } while (value != 0);
+
+ length--;
+
+ while (length != -1)
+ {
+ *dest++ = temp[length];
+ length--;
+ }
+
+ *dest = EOS;
+ return dest;
+}
+
+u8 *StringExpandPlaceholders(u8 *dest, const u8 *src)
+{
+ for (;;)
+ {
+ u8 c = *src++;
+ u8 placeholderId;
+ u8 *expandedString;
+ u8 length;
+
+ switch (c)
+ {
+ case PLACEHOLDER_BEGIN:
+ placeholderId = *src++;
+ expandedString = GetExpandedPlaceholder(placeholderId);
+ dest = StringExpandPlaceholders(dest, expandedString);
+ break;
+ case EXT_CTRL_CODE_BEGIN:
+ *dest++ = c;
+ length = GetExtCtrlCodeLength(*src);
+ memcpy(dest, src, length);
+ dest += length;
+ src += length;
+ break;
+ case EOS:
+ *dest = EOS;
+ return dest;
+ case 0xFA:
+ case 0xFB:
+ case 0xFE:
+ default:
+ *dest++ = c;
+ }
+ }
+}
+
+u8 *StringBraille(u8 *dest, const u8 *src)
+{
+ u8 setBrailleFont[] = { 0xFC, 0x06, 0x06, 0xFF };
+ u8 gotoLine2[] = { 0xFE, 0xFC, 0x0E, 0x02, 0xFF };
+
+ dest = StringCopy(dest, setBrailleFont);
+
+ for (;;)
+ {
+ u8 c = *src++;
+
+ switch (c)
+ {
+ case EOS:
+ *dest = c;
+ return dest;
+ case 0xFE:
+ dest = StringCopy(dest, gotoLine2);
+ break;
+ default:
+ *dest++ = c;
+ *dest++ = c + 0x40;
+ break;
+ }
+ }
+}
+
+static u8 *ExpandPlaceholder_UnknownStringVar(void)
+{
+ return gUnknownStringVar;
+}
+
+static u8 *ExpandPlaceholder_PlayerName(void)
+{
+ return gSaveBlock2.playerName;
+}
+
+static u8 *ExpandPlaceholder_StringVar1(void)
+{
+ return gStringVar1;
+}
+
+static u8 *ExpandPlaceholder_StringVar2(void)
+{
+ return gStringVar2;
+}
+
+static u8 *ExpandPlaceholder_StringVar3(void)
+{
+ return gStringVar3;
+}
+
+static u8 *ExpandPlaceholder_KunChan(void)
+{
+ if (gSaveBlock2.playerGender == MALE)
+ return (u8 *) gExpandedPlaceholder_Kun;
+ else
+ return (u8 *) gExpandedPlaceholder_Chan;
+}
+
+static u8 *ExpandPlaceholder_RivalName(void)
+{
+ if (gSaveBlock2.playerGender == MALE)
+ return (u8 *) gExpandedPlaceholder_May;
+ else
+ return (u8 *) gExpandedPlaceholder_Brendan;
+}
+
+#define VERSION_DEPENDENT_PLACEHOLDER_LIST \
+ X(Version, Ruby, Sapphire) \
+ X(EvilTeam, Magma, Aqua) \
+ X(GoodTeam, Aqua, Magma) \
+ X(EvilLeader, Maxie, Archie) \
+ X(GoodLeader, Archie, Maxie) \
+ X(EvilLegendary, Groudon, Kyogre) \
+ X(GoodLegendary, Kyogre, Groudon)
+
+#ifdef SAPPHIRE
+#define X(ph, r, s) \
+static u8 *ExpandPlaceholder_##ph(void) { return (u8 *) gExpandedPlaceholder_##s; }
+VERSION_DEPENDENT_PLACEHOLDER_LIST
+#else
+#define X(ph, r, s) \
+static u8 *ExpandPlaceholder_##ph(void) { return (u8 *) gExpandedPlaceholder_##r; }
+VERSION_DEPENDENT_PLACEHOLDER_LIST
+#endif
+
+#undef X
+
+u8 *GetExpandedPlaceholder(u32 id)
+{
+ typedef u8 *(*ExpandPlaceholderFunc)(void);
+
+ static const ExpandPlaceholderFunc funcs[] =
+ {
+ ExpandPlaceholder_UnknownStringVar,
+ ExpandPlaceholder_PlayerName,
+ ExpandPlaceholder_StringVar1,
+ ExpandPlaceholder_StringVar2,
+ ExpandPlaceholder_StringVar3,
+ ExpandPlaceholder_KunChan,
+ ExpandPlaceholder_RivalName,
+ ExpandPlaceholder_Version,
+ ExpandPlaceholder_EvilTeam,
+ ExpandPlaceholder_GoodTeam,
+ ExpandPlaceholder_EvilLeader,
+ ExpandPlaceholder_GoodLeader,
+ ExpandPlaceholder_EvilLegendary,
+ ExpandPlaceholder_GoodLegendary,
+ };
+
+ if (id >= ARRAY_COUNT(funcs))
+ return (u8 *) gExpandedPlaceholder_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);
+}
diff --git a/src/engine/task.c b/src/engine/task.c
new file mode 100644
index 000000000..7bd2b5937
--- /dev/null
+++ b/src/engine/task.c
@@ -0,0 +1,209 @@
+#include "global.h"
+#include "task.h"
+
+#define ACTIVE_SENTINEL 0x10
+#define HEAD_SENTINEL 0xFE
+#define TAIL_SENTINEL 0xFF
+
+// gTasks is a queue of the active 16 tasks
+struct Task gTasks[ACTIVE_SENTINEL];
+
+static void InsertTask(u8 newTaskId);
+static u8 FindFirstActiveTask();
+
+// Unused string
+const u8 sTaskOverString[] = _("TASK OVER\nタスクがオーバーしました");
+
+void ResetTasks()
+{
+ u8 taskId;
+
+ for (taskId = 0; taskId < ACTIVE_SENTINEL; taskId++)
+ {
+ gTasks[taskId].isActive = FALSE;
+ gTasks[taskId].func = TaskDummy;
+ gTasks[taskId].prev = taskId;
+ gTasks[taskId].next = taskId + 1;
+ gTasks[taskId].priority = -1;
+ memset(gTasks[taskId].data, 0, sizeof(gTasks[taskId].data));
+ }
+
+ gTasks[0].prev = HEAD_SENTINEL;
+ gTasks[ACTIVE_SENTINEL - 1].next = TAIL_SENTINEL;
+}
+
+u8 CreateTask(TaskFunc func, u8 priority)
+{
+ u8 taskId;
+
+ for (taskId = 0; taskId < ACTIVE_SENTINEL; taskId++)
+ {
+ if (!gTasks[taskId].isActive)
+ {
+ gTasks[taskId].func = func;
+ gTasks[taskId].priority = priority;
+ InsertTask(taskId);
+ memset(gTasks[taskId].data, 0, sizeof(gTasks[taskId].data));
+ gTasks[taskId].isActive = TRUE;
+ return taskId;
+ }
+ }
+
+ return 0;
+}
+
+static void InsertTask(u8 newTaskId)
+{
+ u8 taskId = FindFirstActiveTask();
+
+ if (taskId == ACTIVE_SENTINEL)
+ {
+ // The task system inserts from the top downwards starting from the end (0xFF) to 0. If FindFirstActiveTask returned the value equivalent to ACTIVE_SENTINEL, it means it is the only task because it searched the entire queue.
+ gTasks[newTaskId].prev = HEAD_SENTINEL;
+ gTasks[newTaskId].next = TAIL_SENTINEL;
+ return;
+ }
+
+ while (1)
+ {
+ if (gTasks[newTaskId].priority < gTasks[taskId].priority)
+ {
+ // We've found a task with a higher priority value,
+ // so we insert the new task before it.
+ gTasks[newTaskId].prev = gTasks[taskId].prev;
+ gTasks[newTaskId].next = taskId;
+
+ if (gTasks[taskId].prev != HEAD_SENTINEL)
+ gTasks[gTasks[taskId].prev].next = newTaskId; // as long as we are not at the end, insert the newTask appropriately.
+
+ gTasks[taskId].prev = newTaskId;
+ return;
+ }
+ if (gTasks[taskId].next == TAIL_SENTINEL) // we did not find a space for the task, so overwrite the last task as it is the lowest priority.
+ {
+ // We've reached the end.
+ gTasks[newTaskId].prev = taskId;
+ gTasks[newTaskId].next = gTasks[taskId].next;
+ gTasks[taskId].next = newTaskId;
+ return;
+ }
+ taskId = gTasks[taskId].next; // neither the priority was lower, nor the end. check the next task.
+ }
+}
+
+void DestroyTask(u8 taskId)
+{
+ if (gTasks[taskId].isActive)
+ {
+ gTasks[taskId].isActive = FALSE;
+
+ if (gTasks[taskId].prev == HEAD_SENTINEL)
+ {
+ if (gTasks[taskId].next != TAIL_SENTINEL)
+ gTasks[gTasks[taskId].next].prev = HEAD_SENTINEL;
+ }
+ else
+ {
+ if (gTasks[taskId].next == TAIL_SENTINEL)
+ {
+ gTasks[gTasks[taskId].prev].next = TAIL_SENTINEL;
+ }
+ else
+ {
+ gTasks[gTasks[taskId].prev].next = gTasks[taskId].next;
+ gTasks[gTasks[taskId].next].prev = gTasks[taskId].prev;
+ }
+ }
+ }
+}
+
+void RunTasks(void)
+{
+ u8 taskId = FindFirstActiveTask();
+
+ if (taskId != ACTIVE_SENTINEL)
+ {
+ do
+ {
+ gTasks[taskId].func(taskId);
+ taskId = gTasks[taskId].next;
+ } while (taskId != TAIL_SENTINEL);
+ }
+}
+
+static u8 FindFirstActiveTask(void)
+{
+ u8 taskId;
+
+ for (taskId = 0; taskId < ACTIVE_SENTINEL; taskId++)
+ if (gTasks[taskId].isActive == TRUE && gTasks[taskId].prev == HEAD_SENTINEL)
+ break;
+
+ return taskId;
+}
+
+void TaskDummy(u8 taskId)
+{
+}
+
+#define TASK_DATA_OP(taskId, offset, op) \
+{ \
+ u32 tasksAddr = (u32)gTasks; \
+ u32 addr = taskId * sizeof(struct Task) + offset; \
+ u32 dataAddr = tasksAddr + offsetof(struct Task, data); \
+ addr += dataAddr; \
+ op; \
+}
+
+void SetTaskFuncWithFollowupFunc(u8 taskId, TaskFunc func, TaskFunc followupFunc)
+{
+ TASK_DATA_OP(taskId, 28, *((u16 *)addr) = (u32)followupFunc)
+ TASK_DATA_OP(taskId, 30, *((u16 *)addr) = (u32)followupFunc >> 16)
+ gTasks[taskId].func = func;
+}
+
+void SwitchTaskToFollowupFunc(u8 taskId)
+{
+ s32 func;
+
+ gTasks[taskId].func = NULL;
+
+ TASK_DATA_OP(taskId, 28, func = *((u16 *)addr))
+ TASK_DATA_OP(taskId, 30, func |= *((s16 *)addr) << 16)
+
+ gTasks[taskId].func = (TaskFunc)func;
+}
+
+bool8 FuncIsActiveTask(TaskFunc func)
+{
+ u8 i;
+
+ for (i = 0; i < ACTIVE_SENTINEL; i++)
+ if (gTasks[i].isActive == TRUE && gTasks[i].func == func)
+ return TRUE;
+
+ return FALSE;
+}
+
+u8 FindTaskIdByFunc(TaskFunc func)
+{
+ s32 i;
+
+ for (i = 0; i < ACTIVE_SENTINEL; i++)
+ if (gTasks[i].isActive == TRUE && gTasks[i].func == func)
+ return (u8)i;
+
+ return -1;
+}
+
+u8 GetTaskCount(void)
+{
+ u8 i;
+ u8 count = 0;
+
+ for (i = 0; i < ACTIVE_SENTINEL; i++)
+ if (gTasks[i].isActive == TRUE)
+ count++;
+
+ return count;
+}
diff --git a/src/engine/text.c b/src/engine/text.c
new file mode 100644
index 000000000..abde4484a
--- /dev/null
+++ b/src/engine/text.c
@@ -0,0 +1,4324 @@
+#include "global.h"
+#include "text.h"
+#include "battle.h"
+#include "main.h"
+#include "palette.h"
+#include "songs.h"
+#include "sound.h"
+#include "string_util.h"
+
+enum
+{
+ WIN_STATE_END,
+ WIN_STATE_BEGIN,
+ WIN_STATE_NORMAL,
+ WIN_STATE_INTERRUPTIBLE_PAUSE,
+ WIN_STATE_PAUSE,
+ WIN_STATE_WAIT_BUTTON,
+ WIN_STATE_NEWLINE,
+ WIN_STATE_PLACEHOLDER,
+ WIN_STATE_PARAGRAPH,
+ WIN_STATE_NEWLINE_WAIT,
+ WIN_STATE_WAIT_SOUND,
+};
+
+struct Font
+{
+ u32 type;
+ u8 *glyphs;
+ u16 glyphSize;
+ u16 lowerTileOffset;
+};
+
+struct GlyphBuffer
+{
+ // 0-7 left tile
+ // 8-15 right tile (if it exists)
+ u32 pixelRows[16];
+
+ // pixel row filled with BG color
+ u32 background;
+
+ // table of color indices (not a palette)
+ u32 colors[16];
+};
+
+struct GlyphTileInfo
+{
+ u8 textMode;
+ u8 startPixel;
+ u8 width;
+ u8 *src;
+ u32 *dest;
+ u32 *colors;
+};
+
+struct ShiftAmount
+{
+ u32 left;
+ u32 right;
+};
+
+static u16 InitVariableWidthFontTileData(struct Window *, u16);
+static u16 LoadFixedWidthFont(struct Window *, u16);
+static u16 LoadFixedWidthFont_Font1Latin(struct Window *, u16);
+static u16 LoadFixedWidthFont_Font4Latin(struct Window *, u16);
+static u16 LoadFixedWidthFont_Braille(struct Window *, u16);
+static void MultistepLoadFont_LoadGlyph(struct Window *, u16, u8);
+static u8 sub_8002FA0(struct Window *, const u8 *);
+static u8 InterpretText(struct Window *);
+static u8 HandleExtCtrlCode(struct Window *);
+static u8 UpdateWindowText(struct Window *);
+static u8 DrawGlyph_TextMode0(struct Window *, u32);
+static void DrawGlyph_TextMode2(struct Window *, u32);
+static void LoadFixedWidthGlyph(struct Window *, u32, u8 *);
+static void WriteGlyphTilemap(struct Window *, u16, u16);
+static void GetGlyphTilePointers(u8, u8, u16, u8 **, u8 **);
+static u16 *GetCursorTilemapPointer(struct Window *);
+static void ApplyColors_UnshadowedFont(const u8 *, u32 *, u8, u8);
+static void ApplyColors_ShadowedFont(const void *, void *, u8, u8, u8);
+static void SetCursorX(struct Window *, u8);
+static void AddToCursorX(struct Window *, u8);
+static void AddToCursorY(struct Window *, u8);
+static void ClipLeft(struct Window *);
+static void ClipRight(struct Window *);
+static void InitColors(struct Window *);
+static void SetBackgroundColor(struct Window *, u8);
+static void SetShadowColor(struct Window *, u8);
+static void SetForegroundColor(struct Window *, u8);
+static u8 GetTextDelay(struct Window *);
+static bool8 PlayerCanInterruptWait(struct Window *);
+static void ScrollWindowTextLines(struct Window *);
+static void ScrollWindowTextLines_TextMode0(struct Window *);
+static void DoScroll_TextMode0(struct Window *, u16);
+static void ScrollWindowTextLines_TextMode1(struct Window *);
+static void DoScroll_TextMode1(struct Window *, u16);
+static void ScrollWindowTextLines_TextMode2(struct Window *);
+static void DoScroll_TextMode2(struct Window *, u8);
+void ClearWindowTextLines(struct Window *);
+static void ClearWindowTextLines_TextMode0_TextMode1(struct Window *, u8);
+static void ClearWindowTextLines_TextMode2(struct Window *, u8);
+static void TryEraseDownArrow(struct Window *);
+static u16 GetBlankTileNum(struct Window *);
+static u8 WaitWithDownArrow(struct Window *);
+static void DrawInitialDownArrow(struct Window *);
+static void DrawMovingDownArrow(struct Window *);
+static u16 GetCursorTileNum(struct Window *, u32, u32);
+static s32 DrawGlyphTiles(struct Window *, u32, u32);
+static void UpdateTilemap(struct Window *, u32);
+static u8 GetGlyphWidth(struct Window *, u32);
+static s32 DrawGlyphTile_ShadowedFont(struct GlyphTileInfo *);
+
+static void PrintGlyph_TextMode0(struct Window *, u32);
+static void PrintGlyph_TextMode1(struct Window *, u32);
+static void PrintGlyph_TextMode2(struct Window *, u32);
+
+static void WriteGlyphTilemap_Font0_Font3(struct Window *, u32);
+static void WriteGlyphTilemap_Font1_Font4(struct Window *, u32);
+static void WriteGlyphTilemap_Font2_Font5(struct Window *, u32);
+static void WriteGlyphTilemap_Font1_Font4(struct Window *, u32);
+static void WriteGlyphTilemap_Font2_Font5(struct Window *, u32);
+static void WriteGlyphTilemap_Font6(struct Window *, u32);
+
+static u8 ExtCtrlCode_Nop(struct Window *);
+static u8 ExtCtrlCode_ForegroundColor(struct Window *);
+static u8 ExtCtrlCode_BackgroundColor(struct Window *);
+static u8 ExtCtrlCode_ShadowColor(struct Window *);
+static u8 ExtCtrlCode_AllColors(struct Window *);
+static u8 ExtCtrlCode_Palette(struct Window *);
+static u8 ExtCtrlCode_Font(struct Window *);
+static u8 ExtCtrlCode_DefaultFont(struct Window *);
+static u8 ExtCtrlCode_Pause(struct Window *);
+static u8 ExtCtrlCode_WaitButton(struct Window *);
+static u8 ExtCtrlCode_WaitSound(struct Window *);
+static u8 ExtCtrlCode_PlayBGM(struct Window *);
+static u8 ExtCtrlCode_Escape(struct Window *);
+static u8 ExtCtrlCode_Nop2(struct Window *);
+static u8 ExtCtrlCode_SetCursorY(struct Window *);
+static u8 ExtCtrlCode_ClearWindowTextLines(struct Window *);
+static u8 ExtCtrlCode_PlaySE(struct Window *);
+static u8 ExtCtrlCode_Skip(struct Window *);
+static u8 ExtCtrlCode_SetCursorX(struct Window *);
+static u8 ExtCtrlCode_SkipTo(struct Window *);
+static u8 ExtCtrlCode_Spacing(struct Window *);
+static u8 ExtCtrlCode_Japanese(struct Window *);
+static u8 ExtCtrlCode_Latin(struct Window *);
+
+static void ShiftGlyphTile_UnshadowedFont_Width0(struct GlyphBuffer *, u8 *, u32 *, u8);
+static void ShiftGlyphTile_UnshadowedFont_Width1(struct GlyphBuffer *, u8 *, u32 *, u8);
+static void ShiftGlyphTile_UnshadowedFont_Width2(struct GlyphBuffer *, u8 *, u32 *, u8);
+static void ShiftGlyphTile_UnshadowedFont_Width3(struct GlyphBuffer *, u8 *, u32 *, u8);
+static void ShiftGlyphTile_UnshadowedFont_Width4(struct GlyphBuffer *, u8 *, u32 *, u8);
+static void ShiftGlyphTile_UnshadowedFont_Width5(struct GlyphBuffer *, u8 *, u32 *, u8);
+static void ShiftGlyphTile_UnshadowedFont_Width6(struct GlyphBuffer *, u8 *, u32 *, u8);
+static void ShiftGlyphTile_UnshadowedFont_Width7(struct GlyphBuffer *, u8 *, u32 *, u8);
+static void ShiftGlyphTile_UnshadowedFont_Width8(struct GlyphBuffer *, u8 *, u32 *, u8);
+
+static void ShiftGlyphTile_ShadowedFont_Width0(struct GlyphBuffer *, u32 *, u32 *, u8);
+static void ShiftGlyphTile_ShadowedFont_Width1(struct GlyphBuffer *, u32 *, u32 *, u8);
+static void ShiftGlyphTile_ShadowedFont_Width2(struct GlyphBuffer *, u32 *, u32 *, u8);
+static void ShiftGlyphTile_ShadowedFont_Width3(struct GlyphBuffer *, u32 *, u32 *, u8);
+static void ShiftGlyphTile_ShadowedFont_Width4(struct GlyphBuffer *, u32 *, u32 *, u8);
+static void ShiftGlyphTile_ShadowedFont_Width5(struct GlyphBuffer *, u32 *, u32 *, u8);
+static void ShiftGlyphTile_ShadowedFont_Width6(struct GlyphBuffer *, u32 *, u32 *, u8);
+static void ShiftGlyphTile_ShadowedFont_Width7(struct GlyphBuffer *, u32 *, u32 *, u8);
+static void ShiftGlyphTile_ShadowedFont_Width8(struct GlyphBuffer *, u32 *, u32 *, u8);
+
+static struct Window *sMultistepLoadFont_Window;
+static u16 sMultistepLoadFont_StartOffset;
+static u16 sMultistepLoadFont_Index;
+static struct Window sTempWindow;
+static u8 sWaitType;
+static u8 sLineLength;
+static struct GlyphBuffer sGlyphBuffer;
+
+EWRAM_DATA u16 gBGTilemapBuffers[4][0x400] = {0};
+
+EWRAM_DATA u8 gStringVar1[0x100] = {0};
+EWRAM_DATA u8 gStringVar2[0x100] = {0};
+EWRAM_DATA u8 gStringVar3[0x100] = {0};
+EWRAM_DATA u8 gStringVar4[0x100] = {0};
+
+extern u16 gBattleTypeFlags;
+extern u8 gIsLinkContest;
+extern u8 gTileBuffer[];
+
+vu16 *const gBGControlRegs[] =
+{
+ &REG_BG0CNT,
+ &REG_BG1CNT,
+ &REG_BG2CNT,
+ &REG_BG3CNT,
+};
+
+vu16 *const gBGHOffsetRegs[] =
+{
+ &REG_BG0HOFS,
+ &REG_BG1HOFS,
+ &REG_BG2HOFS,
+ &REG_BG3HOFS,
+};
+
+vu16 *const gBGVOffsetRegs[] =
+{
+ &REG_BG0VOFS,
+ &REG_BG1VOFS,
+ &REG_BG2VOFS,
+ &REG_BG3VOFS,
+};
+
+const u16 gUnknown_081E29D8[] = { 0x100, 0x200, 0x400, 0x800 };
+const u16 gUnknown_081E29E0[] = { 0x100, 0x200, 0x400, 0x800 };
+const u16 gUnknown_081E29E8[] = { 1, 2, 4, 8 };
+
+static const u8 sFont0LatinGlyphs[] = INCBIN_U8("graphics/fonts/font0_lat.1bpp");
+static const u8 sFont1LatinGlyphs[] = INCBIN_U8("graphics/fonts/font1_lat.1bpp");
+static const u8 sFont0JapaneseGlyphs[] = INCBIN_U8("graphics/fonts/font0_jpn.1bpp");
+static const u8 sFont1JapaneseGlyphs[] = INCBIN_U8("graphics/fonts/font1_jpn.1bpp");
+static const u8 sBrailleGlyphs[] = INCBIN_U8("graphics/fonts/font6_braille.1bpp");
+static const u32 sDownArrowTiles[] = INCBIN_U32("graphics/fonts/down_arrow.4bpp");
+
+// clang-format off
+#include "../data/text/type1_map.h"
+#include "../data/text/type3_map.h"
+#include "../data/text/font1_widths.h"
+#include "../data/text/font4_widths.h"
+#include "../data/text/font0_widths.h"
+#include "../data/text/font3_widths.h"
+// clang-format on
+
+const u16 gUnknownPalette_81E6692[] = INCBIN_U16("graphics/fonts/unknown_81E6692.gbapal");
+const u16 gFontDefaultPalette[] = INCBIN_U16("graphics/fonts/default.gbapal");
+
+const u8 sBlankTile[8] = { 0, 0, 0, 0, 0, 0, 0, 0, };
+
+static const u32 sGlyphMasks[9][8][3] =
+{
+ {
+ { 0xFFFFFFFF,0xFFFFFFFF,0x00000000, },
+ { 0xFFFFFFFF,0xFFFFFFFF,0x00000000, },
+ { 0xFFFFFFFF,0xFFFFFFFF,0x00000000, },
+ { 0xFFFFFFFF,0xFFFFFFFF,0x00000000, },
+ { 0xFFFFFFFF,0xFFFFFFFF,0x00000000, },
+ { 0xFFFFFFFF,0xFFFFFFFF,0x00000000, },
+ { 0xFFFFFFFF,0xFFFFFFFF,0x00000000, },
+ { 0xFFFFFFFF,0xFFFFFFFF,0x00000000, },
+ },
+ {
+ { 0x00000000,0xFFFFFFFF,0xFFFFFFF0, },
+ { 0x0000000F,0xFFFFFFFF,0xFFFFFF00, },
+ { 0x000000FF,0xFFFFFFFF,0xFFFFF000, },
+ { 0x00000FFF,0xFFFFFFFF,0xFFFF0000, },
+ { 0x0000FFFF,0xFFFFFFFF,0xFFF00000, },
+ { 0x000FFFFF,0xFFFFFFFF,0xFF000000, },
+ { 0x00FFFFFF,0xFFFFFFFF,0xF0000000, },
+ { 0x0FFFFFFF,0xFFFFFFFF,0x00000000, },
+ },
+ {
+ { 0x00000000,0xFFFFFFFF,0xFFFFFF00, },
+ { 0x0000000F,0xFFFFFFFF,0xFFFFF000, },
+ { 0x000000FF,0xFFFFFFFF,0xFFFF0000, },
+ { 0x00000FFF,0xFFFFFFFF,0xFFF00000, },
+ { 0x0000FFFF,0xFFFFFFFF,0xFF000000, },
+ { 0x000FFFFF,0xFFFFFFFF,0xF0000000, },
+ { 0x00FFFFFF,0xFFFFFFFF,0x00000000, },
+ { 0x0FFFFFFF,0xFFFFFFF0,0x00000000, },
+ },
+ {
+ { 0x00000000,0xFFFFFFFF,0xFFFFF000, },
+ { 0x0000000F,0xFFFFFFFF,0xFFFF0000, },
+ { 0x000000FF,0xFFFFFFFF,0xFFF00000, },
+ { 0x00000FFF,0xFFFFFFFF,0xFF000000, },
+ { 0x0000FFFF,0xFFFFFFFF,0xF0000000, },
+ { 0x000FFFFF,0xFFFFFFFF,0x00000000, },
+ { 0x00FFFFFF,0xFFFFFFF0,0x00000000, },
+ { 0x0FFFFFFF,0xFFFFFF00,0x00000000, },
+ },
+ {
+ { 0x00000000,0xFFFFFFFF,0xFFFF0000, },
+ { 0x0000000F,0xFFFFFFFF,0xFFF00000, },
+ { 0x000000FF,0xFFFFFFFF,0xFF000000, },
+ { 0x00000FFF,0xFFFFFFFF,0xF0000000, },
+ { 0x0000FFFF,0xFFFFFFFF,0x00000000, },
+ { 0x000FFFFF,0xFFFFFFF0,0x00000000, },
+ { 0x00FFFFFF,0xFFFFFF00,0x00000000, },
+ { 0x0FFFFFFF,0xFFFFF000,0x00000000, },
+ },
+ {
+ { 0x00000000,0xFFFFFFFF,0xFFF00000, },
+ { 0x0000000F,0xFFFFFFFF,0xFF000000, },
+ { 0x000000FF,0xFFFFFFFF,0xF0000000, },
+ { 0x00000FFF,0xFFFFFFFF,0x00000000, },
+ { 0x0000FFFF,0xFFFFFFF0,0x00000000, },
+ { 0x000FFFFF,0xFFFFFF00,0x00000000, },
+ { 0x00FFFFFF,0xFFFFF000,0x00000000, },
+ { 0x0FFFFFFF,0xFFFF0000,0x00000000, },
+ },
+ {
+ { 0x00000000,0xFFFFFFFF,0xFF000000, },
+ { 0x0000000F,0xFFFFFFFF,0xF0000000, },
+ { 0x000000FF,0xFFFFFFFF,0x00000000, },
+ { 0x00000FFF,0xFFFFFFF0,0x00000000, },
+ { 0x0000FFFF,0xFFFFFF00,0x00000000, },
+ { 0x000FFFFF,0xFFFFF000,0x00000000, },
+ { 0x00FFFFFF,0xFFFF0000,0x00000000, },
+ { 0x0FFFFFFF,0xFFF00000,0x00000000, },
+ },
+ {
+ { 0x00000000,0xFFFFFFFF,0xF0000000, },
+ { 0x0000000F,0xFFFFFFFF,0x00000000, },
+ { 0x000000FF,0xFFFFFFF0,0x00000000, },
+ { 0x00000FFF,0xFFFFFF00,0x00000000, },
+ { 0x0000FFFF,0xFFFFF000,0x00000000, },
+ { 0x000FFFFF,0xFFFF0000,0x00000000, },
+ { 0x00FFFFFF,0xFFF00000,0x00000000, },
+ { 0x0FFFFFFF,0xFF000000,0x00000000, },
+ },
+ {
+ { 0x00000000,0xFFFFFFFF,0x00000000, },
+ { 0x0000000F,0xFFFFFFF0,0x00000000, },
+ { 0x000000FF,0xFFFFFF00,0x00000000, },
+ { 0x00000FFF,0xFFFFF000,0x00000000, },
+ { 0x0000FFFF,0xFFFF0000,0x00000000, },
+ { 0x000FFFFF,0xFFF00000,0x00000000, },
+ { 0x00FFFFFF,0xFF000000,0x00000000, },
+ { 0x0FFFFFFF,0xF0000000,0x00000000, },
+ },
+};
+
+static const struct ShiftAmount sGlyphShiftAmounts[8] =
+{
+ { 0, 32 },
+ { 4, 28 },
+ { 8, 24 },
+ { 12, 20 },
+ { 16, 16 },
+ { 20, 12 },
+ { 24, 8 },
+ { 28, 4 },
+};
+
+typedef void (*PrintGlyphFunc)(struct Window *, u32);
+
+static const PrintGlyphFunc sPrintGlyphFuncs[] =
+{
+ PrintGlyph_TextMode0,
+ PrintGlyph_TextMode1,
+ PrintGlyph_TextMode2,
+};
+
+typedef void (*WriteGlyphTilemapFunc)(struct Window *, u32);
+
+static const WriteGlyphTilemapFunc sWriteGlyphTilemapFuncs[] =
+{
+ WriteGlyphTilemap_Font0_Font3,
+ WriteGlyphTilemap_Font1_Font4,
+ WriteGlyphTilemap_Font2_Font5,
+ WriteGlyphTilemap_Font0_Font3,
+ WriteGlyphTilemap_Font1_Font4,
+ WriteGlyphTilemap_Font2_Font5,
+ WriteGlyphTilemap_Font6,
+};
+
+static const struct Window sDefaultWindow = { .language = GAME_LANGUAGE };
+
+typedef u8 (*ExtCtrlCodeFunc)(struct Window *);
+
+static const ExtCtrlCodeFunc sExtCtrlCodeFuncs[] =
+{
+ ExtCtrlCode_Nop,
+ ExtCtrlCode_ForegroundColor,
+ ExtCtrlCode_BackgroundColor,
+ ExtCtrlCode_ShadowColor,
+ ExtCtrlCode_AllColors,
+ ExtCtrlCode_Palette,
+ ExtCtrlCode_Font,
+ ExtCtrlCode_DefaultFont,
+ ExtCtrlCode_Pause,
+ ExtCtrlCode_WaitButton,
+ ExtCtrlCode_WaitSound,
+ ExtCtrlCode_PlayBGM,
+ ExtCtrlCode_Escape,
+ ExtCtrlCode_Nop2,
+ ExtCtrlCode_SetCursorY,
+ ExtCtrlCode_ClearWindowTextLines,
+ ExtCtrlCode_PlaySE,
+ ExtCtrlCode_Skip,
+ ExtCtrlCode_SetCursorX,
+ ExtCtrlCode_SkipTo,
+ ExtCtrlCode_Spacing,
+ ExtCtrlCode_Japanese,
+ ExtCtrlCode_Latin,
+};
+
+extern const u32 gFont3LatinGlyphs[];
+extern const u32 gFont4LatinGlyphs[];
+extern const u32 gFont3JapaneseGlyphs[];
+extern const u32 gFont4JapaneseGlyphs[];
+
+static const struct Font sFonts[] =
+{
+ // Japanese fonts
+ { 0, (u8 *)sFont0JapaneseGlyphs, 16, 8 },
+ { 1, (u8 *)sFont1JapaneseGlyphs, 8, 0 },
+ { 2, (u8 *)sFont1JapaneseGlyphs, 8, 0 },
+ { 4, (u8 *)gFont3JapaneseGlyphs, 64, 512 },
+ { 1, (u8 *)gFont4JapaneseGlyphs, 32, 0 },
+ { 2, (u8 *)gFont4JapaneseGlyphs, 32, 0 },
+ { 3, (u8 *)sBrailleGlyphs, 8, 0 },
+ // Latin
+ { 0, (u8 *)sFont0LatinGlyphs, 16, 8 },
+ { 1, (u8 *)sFont1LatinGlyphs, 8, 0 },
+ { 2, (u8 *)sFont1LatinGlyphs, 8, 0 },
+ { 0, (u8 *)gFont3LatinGlyphs, 64, 32 },
+ { 1, (u8 *)gFont4LatinGlyphs, 32, 0 },
+ { 2, (u8 *)gFont4LatinGlyphs, 32, 0 },
+ { 3, (u8 *)sBrailleGlyphs, 8, 0 },
+};
+
+static const u8 sTextSpeedDelays[] = { 6, 3, 1 }; // slow, mid, fast
+
+static const u8 sExtCtrlCodeLengths[] =
+{
+ 1,
+ 2,
+ 2,
+ 2,
+ 4,
+ 2,
+ 2,
+ 1,
+ 2,
+ 1,
+ 1,
+ 3,
+ 2,
+ 2,
+ 2,
+ 1,
+ 3,
+ 2,
+ 2,
+ 2,
+ 2,
+ 1,
+ 1,
+};
+
+typedef void (*ShiftGlyphTileUnshadowedFunc)(struct GlyphBuffer *, u8 *, u32 *, u8);
+
+static const ShiftGlyphTileUnshadowedFunc sShiftGlyphTileUnshadowedFuncs[] =
+{
+ ShiftGlyphTile_UnshadowedFont_Width0,
+ ShiftGlyphTile_UnshadowedFont_Width1,
+ ShiftGlyphTile_UnshadowedFont_Width2,
+ ShiftGlyphTile_UnshadowedFont_Width3,
+ ShiftGlyphTile_UnshadowedFont_Width4,
+ ShiftGlyphTile_UnshadowedFont_Width5,
+ ShiftGlyphTile_UnshadowedFont_Width6,
+ ShiftGlyphTile_UnshadowedFont_Width7,
+ ShiftGlyphTile_UnshadowedFont_Width8,
+};
+
+typedef void (*ShiftGlyphTileShadowedFunc)(struct GlyphBuffer *, u32 *, u32 *, u8);
+
+static const ShiftGlyphTileShadowedFunc sShiftGlyphTileShadowedFuncs[] =
+{
+ ShiftGlyphTile_ShadowedFont_Width0,
+ ShiftGlyphTile_ShadowedFont_Width1,
+ ShiftGlyphTile_ShadowedFont_Width2,
+ ShiftGlyphTile_ShadowedFont_Width3,
+ ShiftGlyphTile_ShadowedFont_Width4,
+ ShiftGlyphTile_ShadowedFont_Width5,
+ ShiftGlyphTile_ShadowedFont_Width6,
+ ShiftGlyphTile_ShadowedFont_Width7,
+ ShiftGlyphTile_ShadowedFont_Width8,
+};
+
+const struct WindowConfig gWindowConfig_81E6C3C =
+{
+ 0, // BG number
+ 2, // BG character base block
+ 31, // BG screen base block
+ 0, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 0, // font
+ 0, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(31), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6C58 =
+{
+ 0, // BG number
+ 0, // BG character base block
+ 24, // BG screen base block
+ 0, // BG priority
+ 0, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 0, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 26, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(24), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6C74 =
+{
+ 0, // BG number
+ 0, // BG character base block
+ 0, // BG screen base block
+ 0, // BG priority
+ 0, // palette number
+ 1, // foreground color
+ 0, // background color
+ 3, // shadow color
+ 4, // font
+ 0, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)OBJ_VRAM0, // tile data
+ NULL, // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6C90 =
+{
+ 0, // BG number
+ 1, // BG character base block
+ 30, // BG screen base block
+ 2, // BG priority
+ 31, // palette number
+ 15, // foreground color
+ 0, // background color
+ 1, // shadow color
+ 4, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(1), // tile data
+ (u16 *)BG_SCREEN_ADDR(30), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6CAC =
+{
+ 0, // BG number
+ 0, // BG character base block
+ 0, // BG screen base block
+ 0, // BG priority
+ 0, // palette number
+ 15, // foreground color
+ 0, // background color
+ 1, // shadow color
+ 4, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 8, // width
+ 60, // height
+ gTileBuffer, // tile data
+ NULL, // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6CC8 =
+{
+ 2, // BG number
+ 2, // BG character base block
+ 15, // BG screen base block
+ 0, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(15), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6CE4 =
+{
+ 0, // BG number
+ 2, // BG character base block
+ 31, // BG screen base block
+ 0, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(31), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6D00 =
+{
+ 0, // BG number
+ 0, // BG character base block
+ 31, // BG screen base block
+ 0, // BG priority
+ 15, // palette number
+ 2, // foreground color
+ 15, // background color
+ 3, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(31), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6D1C =
+{
+ 1, // BG number
+ 0, // BG character base block
+ 31, // BG screen base block
+ 1, // BG priority
+ 15, // palette number
+ 2, // foreground color
+ 15, // background color
+ 3, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(31), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6D38 =
+{
+ 0, // BG number
+ 0, // BG character base block
+ 0, // BG screen base block
+ 0, // BG priority
+ 0, // palette number
+ 15, // foreground color
+ 0, // background color
+ 14, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 8, // width
+ 64, // height
+ gTileBuffer, // tile data
+ NULL, // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6D54 =
+{
+ 3, // BG number
+ 3, // BG character base block
+ 15, // BG screen base block
+ 3, // BG priority
+ 2, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(3), // tile data
+ (u16 *)BG_SCREEN_ADDR(15), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6D70 =
+{
+ 3, // BG number
+ 3, // BG character base block
+ 15, // BG screen base block
+ 3, // BG priority
+ 3, // palette number
+ 1, // foreground color
+ 3, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(3), // tile data
+ (u16 *)BG_SCREEN_ADDR(15), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6D8C =
+{
+ 1, // BG number
+ 0, // BG character base block
+ 14, // BG screen base block
+ 1, // BG priority
+ 0, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(14), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6DA8 =
+{
+ 0, // BG number
+ 0, // BG character base block
+ 12, // BG screen base block
+ 0, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(11), // tilemap
+};
+
+const struct WindowConfig WindowConfig_TrainerCard_Back_Values =
+{
+ 0, // BG number
+ 2, // BG character base block
+ 30, // BG screen base block
+ 0, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 0, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(30), // tilemap
+};
+
+const struct WindowConfig WindowConfig_TrainerCard_Back_Labels =
+{
+ 0, // BG number
+ 2, // BG character base block
+ 30, // BG screen base block
+ 0, // BG priority
+ 14, // palette number
+ 1, // foreground color
+ 0, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(30), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6DFC =
+{
+ 0, // BG number
+ 2, // BG character base block
+ 31, // BG screen base block
+ 0, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 0, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(31), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6E18 =
+{
+ 0, // BG number
+ 2, // BG character base block
+ 31, // BG screen base block
+ 1, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 0, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(31), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6E34 =
+{
+ 1, // BG number
+ 0, // BG character base block
+ 31, // BG screen base block
+ 1, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 0, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(31), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6E50 =
+{
+ 0, // BG number
+ 2, // BG character base block
+ 28, // BG screen base block
+ 0, // BG priority
+ 13, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(28), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6E6C =
+{
+ 0, // BG number
+ 2, // BG character base block
+ 30, // BG screen base block
+ 0, // BG priority
+ 13, // palette number
+ 15, // foreground color
+ 0, // background color
+ 10, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(30), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6E88 =
+{
+ 0, // BG number
+ 0, // BG character base block
+ 31, // BG screen base block
+ 0, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(31), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6EA4 =
+{
+ 1, // BG number
+ 0, // BG character base block
+ 28, // BG screen base block
+ 1, // BG priority
+ 1, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 8, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(28), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6EC0 =
+{
+ 2, // BG number
+ 2, // BG character base block
+ 29, // BG screen base block
+ 2, // BG priority
+ 1, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 8, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(29), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6EDC =
+{
+ 1, // BG number
+ 0, // BG character base block
+ 28, // BG screen base block
+ 1, // BG priority
+ 2, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 8, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(28), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6EF8 =
+{
+ 2, // BG number
+ 2, // BG character base block
+ 29, // BG screen base block
+ 2, // BG priority
+ 2, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 8, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(29), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6F14 =
+{
+ 1, // BG number
+ 0, // BG character base block
+ 28, // BG screen base block
+ 1, // BG priority
+ 3, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 8, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(28), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6F30 =
+{
+ 2, // BG number
+ 2, // BG character base block
+ 29, // BG screen base block
+ 2, // BG priority
+ 3, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 8, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(29), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6F4C =
+{
+ 3, // BG number
+ 0, // BG character base block
+ 30, // BG screen base block
+ 3, // BG priority
+ 0, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(30), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6F68 =
+{
+ 0, // BG number
+ 2, // BG character base block
+ 13, // BG screen base block
+ 0, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(13), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6F84 =
+{
+ 0, // BG number
+ 2, // BG character base block
+ 31, // BG screen base block
+ 0, // BG priority
+ 15, // palette number
+ 15, // foreground color
+ 0, // background color
+ 1, // shadow color
+ 3, // font
+ 0, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(31), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6FA0 =
+{
+ 1, // BG number
+ 0, // BG character base block
+ 24, // BG screen base block
+ 3, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 0, // background color
+ 8, // shadow color
+ 3, // font
+ 0, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(24), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6FBC =
+{
+ 0, // BG number
+ 0, // BG character base block
+ 30, // BG screen base block
+ 0, // BG priority
+ 15, // palette number
+ 15, // foreground color
+ 0, // background color
+ 8, // shadow color
+ 3, // font
+ 0, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(30), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6FD8 =
+{
+ 0, // BG number
+ 0, // BG character base block
+ 24, // BG screen base block
+ 0, // BG priority
+ 15, // palette number
+ 15, // foreground color
+ 0, // background color
+ 8, // shadow color
+ 3, // font
+ 0, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(24), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E6FF4 =
+{
+ 0, // BG number
+ 0, // BG character base block
+ 24, // BG screen base block
+ 0, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 0, // background color
+ 8, // shadow color
+ 3, // font
+ 0, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(24), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E7010 =
+{
+ 0, // BG number
+ 0, // BG character base block
+ 30, // BG screen base block
+ 0, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 0, // background color
+ 8, // shadow color
+ 3, // font
+ 0, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(30), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E702C =
+{
+ 3, // BG number
+ 2, // BG character base block
+ 15, // BG screen base block
+ 0, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(15), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E7048 =
+{
+ 2, // BG number
+ 2, // BG character base block
+ 14, // BG screen base block
+ 2, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 0, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 16, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 16, // width
+ 32, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(14), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E7064 =
+{
+ 2, // BG number
+ 2, // BG character base block
+ 14, // BG screen base block
+ 2, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 0, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(14), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E7080 =
+{
+ 3, // BG number
+ 0, // BG character base block
+ 30, // BG screen base block
+ 3, // BG priority
+ 11, // palette number
+ 1, // foreground color
+ 15, // background color
+ 5, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(30), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E709C =
+{
+ 0, // BG number
+ 0, // BG character base block
+ 31, // BG screen base block
+ 1, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(31), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E70B8 =
+{
+ 2, // BG number
+ 0, // BG character base block
+ 30, // BG screen base block
+ 2, // BG priority
+ 11, // palette number
+ 1, // foreground color
+ 0, // background color
+ 5, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(30), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E70D4 =
+{
+ 3, // BG number
+ 0, // BG character base block
+ 30, // BG screen base block
+ 3, // BG priority
+ 11, // palette number
+ 1, // foreground color
+ 15, // background color
+ 5, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(30), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E70F0 =
+{
+ 0, // BG number
+ 0, // BG character base block
+ 0, // BG screen base block
+ 0, // BG priority
+ 0, // palette number
+ 15, // foreground color
+ 1, // background color
+ 14, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 32, // width
+ 32, // height
+ gTileBuffer, // tile data
+ NULL, // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E710C =
+{
+ 0, // BG number
+ 0, // BG character base block
+ 31, // BG screen base block
+ 1, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 0, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(31), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E7128 =
+{
+ 0, // BG number
+ 2, // BG character base block
+ 31, // BG screen base block
+ 0, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(31), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E7144 =
+{
+ 0, // BG number
+ 2, // BG character base block
+ 31, // BG screen base block
+ 0, // BG priority
+ 13, // palette number
+ 1, // foreground color
+ 0, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(31), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E7160 =
+{
+ 1, // BG number
+ 1, // BG character base block
+ 10, // BG screen base block
+ 1, // BG priority
+ 14, // palette number
+ 1, // foreground color
+ 0, // background color
+ 8, // shadow color
+ 3, // font
+ 0, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(1), // tile data
+ (u16 *)BG_SCREEN_ADDR(10), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E717C =
+{
+ 0, // BG number
+ 3, // BG character base block
+ 31, // BG screen base block
+ 0, // BG priority
+ 15, // palette number
+ 15, // foreground color
+ 0, // background color
+ 8, // shadow color
+ 3, // font
+ 0, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(3), // tile data
+ (u16 *)BG_SCREEN_ADDR(31), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E7198 =
+{
+ 0, // BG number
+ 2, // BG character base block
+ 15, // BG screen base block
+ 0, // BG priority
+ 15, // palette number
+ 15, // foreground color
+ 0, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(15), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E71B4 =
+{
+ 0, // BG number
+ 2, // BG character base block
+ 15, // BG screen base block
+ 0, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(15), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E71D0 =
+{
+ 1, // BG number
+ 1, // BG character base block
+ 28, // BG screen base block
+ 0, // BG priority
+ 5, // palette number
+ 13, // foreground color
+ 14, // background color
+ 15, // shadow color
+ 3, // font
+ 0, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(1), // tile data
+ (u16 *)BG_SCREEN_ADDR(28), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E71EC =
+{
+ 2, // BG number
+ 1, // BG character base block
+ 30, // BG screen base block
+ 0, // BG priority
+ 5, // palette number
+ 13, // foreground color
+ 14, // background color
+ 15, // shadow color
+ 3, // font
+ 0, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(1), // tile data
+ (u16 *)BG_SCREEN_ADDR(30), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E7208 =
+{
+ 0, // BG number
+ 2, // BG character base block
+ 28, // BG screen base block
+ 0, // BG priority
+ 8, // palette number
+ 1, // foreground color
+ 0, // background color
+ 2, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(28), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E7224 =
+{
+ 0, // BG number
+ 0, // BG character base block
+ 31, // BG screen base block
+ 0, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(0), // tile data
+ (u16 *)BG_SCREEN_ADDR(31), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E7240 =
+{
+ 1, // BG number
+ 2, // BG character base block
+ 30, // BG screen base block
+ 0, // BG priority
+ 15, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)BG_CHAR_ADDR(2), // tile data
+ (u16 *)BG_SCREEN_ADDR(30), // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E725C =
+{
+ 0, // BG number
+ 0, // BG character base block
+ 0, // BG screen base block
+ 0, // BG priority
+ 0, // palette number
+ 15, // foreground color
+ 0, // background color
+ 14, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 30, // width
+ 20, // height
+ (u8 *)OBJ_VRAM0, // tile data
+ NULL, // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E7278 =
+{
+ 0, // BG number
+ 0, // BG character base block
+ 0, // BG screen base block
+ 0, // BG priority
+ 0, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 3, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 16, // width
+ 32, // height
+ gTileBuffer, // tile data
+ NULL, // tilemap
+};
+
+const struct WindowConfig gWindowConfig_81E7294 =
+{
+ 0, // BG number
+ 0, // BG character base block
+ 0, // BG screen base block
+ 0, // BG priority
+ 0, // palette number
+ 1, // foreground color
+ 15, // background color
+ 8, // shadow color
+ 4, // font
+ 2, // text mode
+ 0, // spacing
+ 0, // tilemap left coordinate
+ 0, // tilemap top coordinate
+ 16, // width
+ 32, // height
+ gTileBuffer, // tile data
+ NULL, // tilemap
+};
+
+static void UpdateBGRegs(const struct WindowConfig *winConfig)
+{
+ u8 bgNum = winConfig->bgNum;
+ *gBGHOffsetRegs[bgNum] = 0;
+ *gBGVOffsetRegs[bgNum] = 0;
+ *gBGControlRegs[bgNum] = winConfig->priority | (winConfig->screenBaseBlock << 8) | (winConfig->charBaseBlock << 2);
+}
+
+static void ClearBGMem(const struct WindowConfig *winConfig)
+{
+ CpuFastFill(0, winConfig->tileData, 32);
+
+ if (winConfig->tilemap)
+ CpuFastFill(0, winConfig->tilemap, 0x800);
+}
+
+void LoadFontDefaultPalette(const struct WindowConfig *winConfig)
+{
+ LoadPalette(gFontDefaultPalette, 16 * winConfig->paletteNum, 32);
+}
+
+void SetUpWindowConfig(const struct WindowConfig *winConfig)
+{
+ UpdateBGRegs(winConfig);
+ ClearBGMem(winConfig);
+ LoadFontDefaultPalette(winConfig);
+}
+
+u16 InitWindowTileData(struct Window *win, u16 startOffset)
+{
+ u16 retVal;
+
+ win->tileDataStartOffset = startOffset;
+ retVal = 0;
+
+ switch (win->config->textMode)
+ {
+ case 2:
+ retVal = InitVariableWidthFontTileData(win, startOffset);
+ break;
+ case 1:
+ switch (win->config->fontNum)
+ {
+ case 0:
+ case 3:
+ retVal = LoadFixedWidthFont(win, startOffset);
+ break;
+ case 1:
+ case 2:
+ retVal = LoadFixedWidthFont_Font1Latin(win, startOffset);
+ break;
+ case 4:
+ case 5:
+ retVal = LoadFixedWidthFont_Font4Latin(win, startOffset);
+ break;
+ case 6:
+ retVal = LoadFixedWidthFont_Braille(win, startOffset);
+ break;
+ }
+ break;
+ }
+
+ return retVal;
+}
+
+static u16 InitVariableWidthFontTileData(struct Window *win, u16 startOffset)
+{
+ u8 *buffer;
+
+ win->tileDataStartOffset = startOffset;
+ win->tileDataOffset = 2;
+ buffer = win->tileData + 32 * win->tileDataStartOffset;
+ CpuFastFill(0, buffer, 32);
+ ApplyColors_UnshadowedFont(sBlankTile, (u32 *)(buffer + 32), win->config->foregroundColor, win->config->backgroundColor);
+ return win->tileDataStartOffset + win->tileDataOffset + win->width * win->height;
+}
+
+static u16 LoadFixedWidthFont(struct Window *win, u16 startOffset)
+{
+ s32 glyph;
+ u8 *buffer = win->tileData + 32 * startOffset;
+ for (glyph = 0; glyph < 256; glyph++)
+ {
+ LoadFixedWidthGlyph(win, glyph, buffer);
+ buffer += 64;
+ }
+ return 2 * glyph;
+}
+
+static u16 LoadFixedWidthFont_Font1Latin(struct Window *win, u16 startOffset)
+{
+ s32 i;
+ u8 *buffer = win->tileData + 32 * startOffset;
+ for (i = 0; i < 256; i++)
+ {
+ ApplyColors_UnshadowedFont(&sFont1LatinGlyphs[8 * i], (u32 *)buffer, win->foregroundColor, win->backgroundColor);
+ buffer += 32;
+ }
+ return i;
+}
+
+static u16 LoadFixedWidthFont_Font4Latin(struct Window *win, u16 startOffset)
+{
+ s32 i;
+ u8 *buffer = win->tileData + 32 * startOffset;
+ for (i = 0; i < 256; i++)
+ {
+ ApplyColors_ShadowedFont(&gFont4LatinGlyphs[8 * i], buffer, win->foregroundColor, win->shadowColor, win->backgroundColor);
+ buffer += 32;
+ }
+ return i;
+}
+
+static u16 LoadFixedWidthFont_Braille(struct Window *win, u16 startOffset)
+{
+ s32 i;
+ u8 *buffer = win->tileData + 32 * startOffset;
+ for (i = 0; i < 256; i++)
+ {
+ ApplyColors_UnshadowedFont(&sBrailleGlyphs[8 * i], (u32 *)buffer, win->foregroundColor, win->backgroundColor);
+ buffer += 32;
+ }
+ return i;
+}
+
+u32 MultistepInitWindowTileData(struct Window *win, u16 startOffset)
+{
+ u32 retVal;
+ sMultistepLoadFont_Window = win;
+ sMultistepLoadFont_Index = 0;
+ sMultistepLoadFont_StartOffset = startOffset;
+ win->tileDataStartOffset = startOffset;
+ retVal = 0;
+
+ switch (win->config->textMode)
+ {
+ case 2:
+ retVal = InitVariableWidthFontTileData(win, startOffset);
+ break;
+ case 1:
+ retVal = 256;
+ if (win->config->fontNum == 0
+ || win->config->fontNum == 3)
+ retVal *= 2;
+ break;
+ }
+
+ return retVal;
+}
+
+bool32 MultistepLoadFont(void)
+{
+ bool32 retVal = TRUE;
+
+ if (sMultistepLoadFont_Window->config->textMode == 1)
+ {
+ s32 i;
+
+ for (i = sMultistepLoadFont_Index; i < sMultistepLoadFont_Index + 16; i++)
+ MultistepLoadFont_LoadGlyph(sMultistepLoadFont_Window, sMultistepLoadFont_StartOffset, i);
+
+ sMultistepLoadFont_Index += 16;
+
+ if (sMultistepLoadFont_Index < 256)
+ retVal = FALSE;
+ }
+
+ return retVal;
+}
+
+static void MultistepLoadFont_LoadGlyph(struct Window *win, u16 startOffset, u8 glyph)
+{
+ u8 *buffer;
+
+ switch (win->config->fontNum)
+ {
+ case 0:
+ case 3:
+ buffer = win->tileData + 32 * startOffset + 64 * glyph;
+ LoadFixedWidthGlyph(win, glyph, buffer);
+ break;
+ case 1:
+ case 2:
+ buffer = win->tileData + 32 * (glyph + startOffset);
+ ApplyColors_UnshadowedFont(
+ &sFont1LatinGlyphs[8 * glyph],
+ (u32 *)buffer,
+ win->foregroundColor,
+ win->backgroundColor);
+ break;
+ case 4:
+ case 5:
+ buffer = win->tileData + 32 * (glyph + startOffset);
+ ApplyColors_ShadowedFont(
+ &gFont4LatinGlyphs[8 * glyph],
+ buffer,
+ win->foregroundColor,
+ win->shadowColor,
+ win->backgroundColor);
+ break;
+ }
+}
+
+void EmptyFunc(void)
+{
+}
+
+void InitWindowFromConfig(struct Window *win, const struct WindowConfig *winConfig)
+{
+ *win = sDefaultWindow;
+ win->config = (struct WindowConfig *)winConfig;
+ win->textMode = winConfig->textMode;
+ win->spacing = winConfig->spacing;
+ win->fontNum = winConfig->fontNum;
+ win->paletteNum = winConfig->paletteNum;
+ win->tilemapLeft = winConfig->tilemapLeft;
+ win->tilemapTop = winConfig->tilemapTop;
+ win->width = winConfig->width;
+ win->height = winConfig->height;
+ win->tileData = winConfig->tileData;
+ win->tilemap = winConfig->tilemap;
+ InitColors(win);
+ SetBackgroundColor(win, winConfig->backgroundColor);
+ SetShadowColor(win, winConfig->shadowColor);
+ SetForegroundColor(win, winConfig->foregroundColor);
+}
+
+void InitWindow(struct Window *win, const u8 *text, u16 tileDataStartOffset, u8 left, u8 top)
+{
+ struct WindowConfig *winConfig = win->config;
+ win->textMode = winConfig->textMode;
+ win->fontNum = winConfig->fontNum;
+ win->language = GAME_LANGUAGE;
+ win->paletteNum = winConfig->paletteNum;
+ win->win_field_B = 0;
+ win->win_field_C = 0;
+ win->delayCounter = 0;
+ win->spacing = winConfig->spacing;
+ win->win_field_F = 0;
+ win->tilemapLeft = winConfig->tilemapLeft;
+ win->tilemapTop = winConfig->tilemapTop;
+ win->width = winConfig->width;
+ win->height = winConfig->height;
+ win->text = text;
+ win->textIndex = 0;
+ win->tileDataStartOffset = tileDataStartOffset;
+ win->tileDataOffset = 0;
+ win->left = 8 * left;
+ win->cursorX = 0;
+ win->top = 8 * top;
+ win->cursorY = 0;
+ win->state = WIN_STATE_BEGIN;
+ win->downArrowCounter = 0;
+ win->tileData = winConfig->tileData;
+ win->tilemap = winConfig->tilemap;
+ InitColors(win);
+ SetBackgroundColor(win, winConfig->backgroundColor);
+ SetShadowColor(win, winConfig->shadowColor);
+ SetForegroundColor(win, winConfig->foregroundColor);
+}
+
+void sub_8002E4C(struct Window *win, const u8 *text, u16 tileDataStartOffset, u8 left, u16 top, u32 a6)
+{
+ u8 val;
+
+ InitWindow(win, text, tileDataStartOffset, 0, 0);
+ win->left = left;
+ win->top = top;
+ val = 0;
+ if (a6)
+ val = 255;
+ win->win_field_F = val;
+ if (val)
+ ClipLeft(win);
+}
+
+void sub_8002E90(struct Window *win, const u8 *text)
+{
+ win->state = WIN_STATE_NORMAL;
+ win->text = text;
+ win->textIndex = 0;
+ win->downArrowCounter = 0;
+ win->win_field_B = -1;
+ win->win_field_C = 0;
+ win->delayCounter = 0;
+}
+
+void sub_8002EB0(struct Window *win, const u8 *text, u16 tileDataStartOffset, u8 left, u8 top)
+{
+ gMain.watchedKeysMask = A_BUTTON | B_BUTTON;
+ gMain.watchedKeysPressed = 0;
+ sWaitType = 0;
+ sLineLength = 26;
+ InitWindow(win, text, tileDataStartOffset, left, top);
+ win->win_field_B = -1;
+ if (win->textMode == 0)
+ {
+ u16 val = GetCursorTileNum(win, 0, 0);
+ u8 *buffer = win->tileData + 32 * val;
+ ApplyColors_UnshadowedFont(sBlankTile, (u32 *)buffer, win->backgroundColor, win->backgroundColor);
+ ApplyColors_UnshadowedFont(&sFont1LatinGlyphs[8 * 0x6E], (u32 *)(buffer + 32), win->foregroundColor, win->backgroundColor);
+ win->tileDataOffset = 2;
+ }
+}
+
+u8 sub_8002F44(struct Window *win)
+{
+ while (win->state)
+ {
+ if (win->state == WIN_STATE_NEWLINE)
+ {
+ AddToCursorY(win, 16);
+ win->cursorX = 0;
+ if (win->win_field_F)
+ ClipLeft(win);
+ win->state = WIN_STATE_NORMAL;
+ }
+ else if (win->state == WIN_STATE_PLACEHOLDER)
+ {
+ sub_8002FA0(win, GetExpandedPlaceholder(win->text[win->textIndex++]));
+ }
+
+ InterpretText(win);
+ }
+ return 1;
+}
+
+static u8 sub_8002FA0(struct Window *win, const u8 *text)
+{
+ u8 retVal;
+ u8 savedLanguage = win->language;
+ const u8 *savedText = win->text;
+ u16 savedTextIndex = win->textIndex;
+ win->text = text;
+ win->textIndex = 0;
+ win->state = WIN_STATE_NORMAL;
+ retVal = sub_8002F44(win);
+ win->text = savedText;
+ win->textIndex = savedTextIndex;
+ win->state = WIN_STATE_NORMAL;
+ win->language = savedLanguage;
+ return retVal;
+}
+
+static u8 InterpretText(struct Window *win)
+{
+ u8 c = win->text[win->textIndex++];
+
+ switch (c)
+ {
+ case 0xFF:
+ ClipRight(win);
+ win->state = WIN_STATE_END;
+ return 0;
+ case 0xFD:
+ win->state = WIN_STATE_PLACEHOLDER;
+ return 2;
+ case 0xFE:
+ ClipRight(win);
+ win->state = WIN_STATE_NEWLINE;
+ return 2;
+ case 0xFB:
+ DrawInitialDownArrow(win);
+ win->state = WIN_STATE_PARAGRAPH;
+ return 2;
+ case 0xFA:
+ DrawInitialDownArrow(win);
+ win->state = WIN_STATE_NEWLINE_WAIT;
+ return 2;
+ case 0xFC:
+ return HandleExtCtrlCode(win);
+ }
+
+ sPrintGlyphFuncs[win->textMode](win, c);
+ return 1;
+}
+
+static u8 HandleExtCtrlCode(struct Window *win)
+{
+ return sExtCtrlCodeFuncs[win->text[win->textIndex++]](win);
+}
+
+static u8 ExtCtrlCode_Nop(struct Window *win)
+{
+ return 2;
+}
+
+static u8 ExtCtrlCode_ForegroundColor(struct Window *win)
+{
+ SetForegroundColor(win, win->text[win->textIndex++]);
+ return 2;
+}
+
+static u8 ExtCtrlCode_BackgroundColor(struct Window *win)
+{
+ SetBackgroundColor(win, win->text[win->textIndex++]);
+ return 2;
+}
+
+static u8 ExtCtrlCode_ShadowColor(struct Window *win)
+{
+ SetShadowColor(win, win->text[win->textIndex++]);
+ return 2;
+}
+
+static u8 ExtCtrlCode_AllColors(struct Window *win)
+{
+ SetForegroundColor(win, win->text[win->textIndex++]);
+ SetBackgroundColor(win, win->text[win->textIndex++]);
+ SetShadowColor(win, win->text[win->textIndex++]);
+ return 2;
+}
+
+static u8 ExtCtrlCode_Palette(struct Window *win)
+{
+ win->paletteNum = win->text[win->textIndex++];
+ return 2;
+}
+
+static u8 ExtCtrlCode_Font(struct Window *win)
+{
+ win->fontNum = win->text[win->textIndex++];
+ return 2;
+}
+
+static u8 ExtCtrlCode_DefaultFont(struct Window *win)
+{
+ win->fontNum = win->config->fontNum;
+ return 2;
+}
+
+static u8 ExtCtrlCode_Pause(struct Window *win)
+{
+ ClipRight(win);
+ win->state = WIN_STATE_PAUSE;
+ win->delayCounter = win->text[win->textIndex++];
+ return 2;
+}
+
+static u8 ExtCtrlCode_WaitButton(struct Window *win)
+{
+ ClipRight(win);
+ win->state = WIN_STATE_WAIT_BUTTON;
+ return 2;
+}
+
+static u8 ExtCtrlCode_WaitSound(struct Window *win)
+{
+ ClipRight(win);
+ win->state = WIN_STATE_WAIT_SOUND;
+ return 2;
+}
+
+static u8 ExtCtrlCode_PlayBGM(struct Window *win)
+{
+ u16 loByte = win->text[win->textIndex++];
+ u16 hiByte = win->text[win->textIndex++] << 8;
+ PlayBGM(loByte | hiByte);
+ return 2;
+}
+
+static u8 ExtCtrlCode_Escape(struct Window *win)
+{
+ sPrintGlyphFuncs[win->textMode](win, win->text[win->textIndex++]);
+ return 1;
+}
+
+u8 ExtCtrlCode_Nop2(struct Window *win)
+{
+ return 1;
+}
+
+static u8 ExtCtrlCode_SetCursorY(struct Window *win)
+{
+ ClipRight(win);
+ AddToCursorY(win, 8 * win->text[win->textIndex++]);
+ return 1;
+}
+
+static u8 ExtCtrlCode_ClearWindowTextLines(struct Window *win)
+{
+ ClearWindowTextLines(win);
+ return 2;
+}
+
+static u8 ExtCtrlCode_PlaySE(struct Window *win)
+{
+ u16 loByte = win->text[win->textIndex++];
+ u16 hiByte = win->text[win->textIndex++] << 8;
+ PlaySE(loByte | hiByte);
+ return 2;
+}
+
+static void DrawSpace(struct Window *win)
+{
+ if (win->textMode == 1 || (win->left + win->cursorX) & 7 || win->spacing <= 7)
+ {
+ sPrintGlyphFuncs[win->textMode](win, 0);
+ }
+ else
+ {
+ u32 val = sGlyphBuffer.background;
+ u16 index = GetCursorTileNum(win, 0, 0);
+ u32 *buffer = (u32 *)(win->tileData + 32 * index);
+ buffer[0] = val;
+ buffer[1] = val;
+ buffer[2] = val;
+ buffer[3] = val;
+ buffer[4] = val;
+ buffer[5] = val;
+ buffer[6] = val;
+ buffer[7] = val;
+ index = GetCursorTileNum(win, 0, 1);
+ buffer = (u32 *)(win->tileData + 32 * index);
+ buffer[0] = val;
+ buffer[1] = val;
+ buffer[2] = val;
+ buffer[3] = val;
+ buffer[4] = val;
+ buffer[5] = val;
+ buffer[6] = val;
+ buffer[7] = val;
+ UpdateTilemap(win, 1);
+ AddToCursorX(win, 8);
+ }
+}
+
+static void sub_8003344(struct Window *win, u8 newX)
+{
+ u8 savedSpacing = win->spacing;
+
+ if (newX - win->cursorX <= 8)
+ win->spacing = newX - win->cursorX;
+ else
+ win->spacing = 8 - ((win->left + win->cursorX) & 7);
+
+ while (win->cursorX < newX)
+ {
+ s32 spacing;
+ DrawSpace(win);
+ spacing = newX - win->cursorX;
+ if (spacing >= 8)
+ spacing = 8;
+ win->spacing = spacing;
+ }
+
+ win->spacing = savedSpacing;
+}
+
+static u8 ExtCtrlCode_Skip(struct Window *win)
+{
+ sub_8003344(win, win->cursorX + win->text[win->textIndex++]);
+ return 1;
+}
+
+static u8 ExtCtrlCode_SetCursorX(struct Window *win)
+{
+ ClipRight(win);
+ SetCursorX(win, win->text[win->textIndex++]);
+ return 1;
+}
+
+static u8 ExtCtrlCode_SkipTo(struct Window *win)
+{
+ sub_8003344(win, win->text[win->textIndex++]);
+ return 1;
+}
+
+static u8 ExtCtrlCode_Spacing(struct Window *win)
+{
+ win->spacing = win->text[win->textIndex++];
+ return 2;
+}
+
+static u8 ExtCtrlCode_Japanese(struct Window *win)
+{
+ win->language = LANGUAGE_JAPANESE;
+ return 2;
+}
+
+static u8 ExtCtrlCode_Latin(struct Window *win)
+{
+ win->language = GAME_LANGUAGE;
+ return 2;
+}
+
+u8 sub_8003418(struct Window *win)
+{
+ u8 retVal = 1;
+ while (win->state)
+ {
+ if (win->state == WIN_STATE_NEWLINE)
+ {
+ AddToCursorY(win, 16);
+ win->cursorX = 0;
+ if (win->win_field_F)
+ ClipLeft(win);
+ win->state = WIN_STATE_NORMAL;
+ }
+ if (InterpretText(win) == 1)
+ {
+ retVal = 0;
+ break;
+ }
+ }
+ return retVal;
+}
+
+u8 sub_8003460(struct Window *win, const u8 *text, u16 tileDataStartOffset, u8 left, u8 top)
+{
+ InitWindow(win, text, tileDataStartOffset, left, top);
+ return sub_8002F44(win);
+}
+
+u8 sub_8003490(struct Window *win, u8 c, u16 tileDataStartOffset, u8 left, u8 top)
+{
+ u8 retVal;
+ u8 text[2];
+ text[0] = c;
+ text[1] = EOS;
+ InitWindow(win, text, tileDataStartOffset, left, top);
+ retVal = InterpretText(win);
+ ClipRight(win);
+ return retVal;
+}
+
+void sub_80034D4(u8 *tileData, const u8 *text)
+{
+ sub_8004E3C((struct WindowConfig *)&gWindowConfig_81E6C74, tileData, text);
+}
+
+u8 sub_80034EC(u8 *str)
+{
+ return GetStringWidthGivenWindowConfig((struct WindowConfig *)&gWindowConfig_81E6C74, str);
+}
+
+u8 *sub_8003504(u8 *dest, s32 value, u8 alignAmount, u8 alignType)
+{
+ sTempWindow.config = (struct WindowConfig *)&gWindowConfig_81E6C74;
+ InitWindow(&sTempWindow, 0, 0, 0, 0);
+ return AlignInt2(&sTempWindow, dest, value, alignAmount, alignType);
+}
+
+u8 *sub_8003558(u8 *dest, const u8 *src, u8 alignAmount, u8 alignType)
+{
+ sTempWindow.config = (struct WindowConfig *)&gWindowConfig_81E6C74;
+ InitWindow(&sTempWindow, src, 0, 0, 0);
+ return AlignString(&sTempWindow, dest, src, alignAmount, alignType);
+}
+
+u8 sub_80035AC(struct Window *win)
+{
+ sWaitType = 0;
+ return UpdateWindowText(win);
+}
+
+static u8 UpdateWindowText(struct Window *win)
+{
+ switch (win->state)
+ {
+ case WIN_STATE_WAIT_BUTTON:
+ if (PlayerCanInterruptWait(win))
+ {
+ if (gMain.newKeys & (A_BUTTON | B_BUTTON))
+ {
+ PlaySE(SE_SELECT);
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ else
+ {
+ win->delayCounter--;
+ if (win->delayCounter)
+ return 0;
+ }
+ win->state = WIN_STATE_NORMAL;
+ return 0;
+ case WIN_STATE_INTERRUPTIBLE_PAUSE:
+ if (PlayerCanInterruptWait(win) && (gMain.heldKeys & (A_BUTTON | B_BUTTON)) && gMain.watchedKeysPressed == TRUE)
+ {
+ win->delayCounter = 0;
+ win->state = WIN_STATE_NORMAL;
+ break;
+ }
+ case WIN_STATE_PAUSE:
+ if (win->delayCounter)
+ {
+ win->delayCounter--;
+ if (win->delayCounter)
+ return 0;
+ }
+
+ win->state = WIN_STATE_NORMAL;
+ break;
+ case WIN_STATE_PARAGRAPH:
+ if (!WaitWithDownArrow(win))
+ return 0;
+ ClearWindowTextLines(win);
+ win->state = WIN_STATE_NORMAL;
+ BLOCK_CROSS_JUMP
+ return 0;
+ case WIN_STATE_NEWLINE_WAIT:
+ if (!WaitWithDownArrow(win))
+ return 0;
+ ScrollWindowTextLines(win);
+ win->state = WIN_STATE_NORMAL;
+ BLOCK_CROSS_JUMP
+ return 0;
+ case WIN_STATE_PLACEHOLDER:
+ win->textIndex++;
+ win->state = WIN_STATE_NORMAL;
+ break;
+ case WIN_STATE_NEWLINE:
+ ScrollWindowTextLines(win);
+ win->state = WIN_STATE_NORMAL;
+ BLOCK_CROSS_JUMP
+ return 0;
+ case WIN_STATE_BEGIN:
+ ClearWindowTextLines(win);
+ break;
+ case WIN_STATE_WAIT_SOUND:
+ if (IsSEPlaying())
+ return 0;
+ win->state = WIN_STATE_NORMAL;
+ break;
+ case WIN_STATE_END:
+ return 1;
+ case WIN_STATE_NORMAL:
+ break;
+ default:
+ win->state = WIN_STATE_END;
+ return 1;
+ }
+
+ InterpretText(win);
+
+ switch (win->state)
+ {
+ case WIN_STATE_END:
+ return 1;
+ case WIN_STATE_WAIT_BUTTON:
+ case WIN_STATE_PARAGRAPH:
+ case WIN_STATE_NEWLINE_WAIT:
+ if (PlayerCanInterruptWait(win))
+ return 0;
+ win->delayCounter = 60;
+ break;
+ case WIN_STATE_PAUSE:
+ case WIN_STATE_NEWLINE:
+ case WIN_STATE_WAIT_SOUND:
+ break;
+ default:
+ win->state = WIN_STATE_INTERRUPTIBLE_PAUSE;
+ win->delayCounter = GetTextDelay(win);
+ }
+
+ return 0;
+}
+
+#if defined(ENGLISH)
+#define SUB_800374C_LINE_LENGTH 26
+#elif defined(GERMAN)
+#define SUB_800374C_LINE_LENGTH 27
+#endif
+
+u8 sub_800374C(struct Window *win)
+{
+ u8 retVal;
+
+ sWaitType = 1;
+ sLineLength = SUB_800374C_LINE_LENGTH;
+ retVal = UpdateWindowText(win);
+ sLineLength = 26;
+ sWaitType = 0;
+ return retVal;
+}
+
+u8 sub_8003778(struct Window *win)
+{
+ u8 retVal;
+
+ sWaitType = 2;
+ sLineLength = 26;
+ retVal = UpdateWindowText(win);
+ sWaitType = 0;
+ return retVal;
+}
+
+u8 sub_80037A0(struct Window *win)
+{
+ u8 retVal;
+
+ sWaitType = 3;
+ sLineLength = 17;
+ retVal = UpdateWindowText(win);
+ sLineLength = 26;
+ return retVal;
+}
+
+u32 sub_80037C8(struct Window *win, u8 lineLength)
+{
+ u8 retVal;
+
+ sWaitType = 0;
+ sLineLength = lineLength;
+ retVal = UpdateWindowText(win);
+ sLineLength = 26;
+ return retVal;
+}
+
+static void PrintGlyph_TextMode0(struct Window *win, u32 glyph)
+{
+ AddToCursorX(win, DrawGlyph_TextMode0(win, glyph));
+ if (win->win_field_B)
+ ClipRight(win);
+}
+
+static u8 DrawGlyph_TextMode0(struct Window *win, u32 glyph)
+{
+ u8 pixelsWidth = GetGlyphWidth(win, glyph);
+ u32 tilesWidth = DrawGlyphTiles(win, glyph, pixelsWidth);
+ UpdateTilemap(win, tilesWidth);
+ return pixelsWidth;
+}
+
+static void PrintGlyph_TextMode1(struct Window *win, u32 glyph)
+{
+ sWriteGlyphTilemapFuncs[win->fontNum](win, glyph);
+ win->cursorX += 8;
+}
+
+static void WriteGlyphTilemap_Font0_Font3(struct Window *win, u32 glyph)
+{
+ s16 val = win->tileDataStartOffset + 2 * glyph;
+ WriteGlyphTilemap(win, val, val + 1);
+}
+
+static void WriteGlyphTilemap_Font1_Font4(struct Window *win, u32 glyph)
+{
+ u32 val = glyph * 2;
+ WriteGlyphTilemap(
+ win,
+ win->tileDataStartOffset + sFontType1Map[val],
+ win->tileDataStartOffset + sFontType1Map[val + 1]);
+}
+
+static void WriteGlyphTilemap_Font2_Font5(struct Window *win, u32 glyph)
+{
+ WriteGlyphTilemap(
+ win,
+ win->tileDataStartOffset + 212,
+ win->tileDataStartOffset + glyph);
+}
+
+static void WriteGlyphTilemap_Font6(struct Window *win, u32 glyph)
+{
+ u32 val = glyph * 2;
+ WriteGlyphTilemap(
+ win,
+ win->tileDataStartOffset + sFontType3Map[val],
+ win->tileDataStartOffset + sFontType3Map[val + 1]);
+}
+
+static void PrintGlyph_TextMode2(struct Window *win, u32 glyph)
+{
+ u8 width = GetGlyphWidth(win, glyph);
+ DrawGlyph_TextMode2(win, glyph);
+ AddToCursorX(win, width);
+ if (win->win_field_B)
+ ClipRight(win);
+}
+
+static void DrawGlyph_TextMode2(struct Window *win, u32 glyph)
+{
+ u8 pixelsWidth = GetGlyphWidth(win, glyph);
+ u32 tilesWidth = DrawGlyphTiles(win, glyph, pixelsWidth);
+ UpdateTilemap(win, tilesWidth);
+}
+
+static void LoadFixedWidthGlyph(struct Window *win, u32 glyph, u8 *dest)
+{
+ u8 *upperTile;
+ u8 *lowerTile;
+
+ GetGlyphTilePointers(win->fontNum, win->language, glyph, &upperTile, &lowerTile);
+
+ switch (win->fontNum)
+ {
+ case 0:
+ case 1:
+ case 2:
+ case 6:
+ ApplyColors_UnshadowedFont(upperTile, (u32 *)dest, win->foregroundColor, win->backgroundColor);
+ ApplyColors_UnshadowedFont(lowerTile, (u32 *)(dest + 32), win->foregroundColor, win->backgroundColor);
+ break;
+ case 3:
+ case 4:
+ case 5:
+ ApplyColors_ShadowedFont(upperTile, dest, win->foregroundColor, win->shadowColor, win->backgroundColor);
+ ApplyColors_ShadowedFont(lowerTile, dest + 32, win->foregroundColor, win->shadowColor, win->backgroundColor);
+ break;
+ }
+}
+
+static void WriteGlyphTilemap(struct Window *win, u16 upperTileNum, u16 lowerTileNum)
+{
+ u16 *buffer = GetCursorTilemapPointer(win);
+ if (buffer)
+ {
+ u16 palette = (win->paletteNum << 12);
+ buffer[0] = upperTileNum | palette;
+ buffer[32] = lowerTileNum | palette;
+ }
+}
+
+static void GetGlyphTilePointers(u8 fontNum, u8 language, u16 glyph, u8 **upperTilePtr, u8 **lowerTilePtr)
+{
+ u16 index;
+ const struct Font *font;
+
+ if (language == LANGUAGE_JAPANESE)
+ language = 0;
+ else
+ language = 7;
+
+ font = &sFonts[language + fontNum];
+
+ switch (font->type)
+ {
+ case 0:
+ *upperTilePtr = font->glyphs + glyph * font->glyphSize;
+ *lowerTilePtr = *upperTilePtr + font->lowerTileOffset;
+ break;
+ case 1:
+ index = 2 * glyph;
+ *upperTilePtr = font->glyphs + sFontType1Map[index] * font->glyphSize;
+ *lowerTilePtr = font->glyphs + sFontType1Map[index + 1] * font->glyphSize;
+ break;
+ case 2:
+ *upperTilePtr = font->glyphs + 212 * font->glyphSize;
+ *lowerTilePtr = font->glyphs + glyph * font->glyphSize;
+ break;
+ case 3:
+ index = 2 * glyph;
+ *upperTilePtr = font->glyphs + sFontType3Map[index] * font->glyphSize;
+ *lowerTilePtr = font->glyphs + sFontType3Map[index + 1] * font->glyphSize;
+ break;
+ case 4:
+ *upperTilePtr = font->glyphs
+ + (glyph & 0xFFF0) * font->glyphSize
+ + (((glyph &= 0xF) * font->glyphSize) >> 1);
+ *lowerTilePtr = *upperTilePtr + font->lowerTileOffset;
+ break;
+ }
+}
+
+static u16 *GetCursorTilemapPointer(struct Window *win)
+{
+ u16 *ptr = NULL;
+ if (win->tilemap)
+ {
+ u8 x = ((win->left + win->cursorX) >> 3) + win->tilemapLeft;
+ u8 y = ((win->top + win->cursorY) >> 3) + win->tilemapTop;
+ ptr = &win->tilemap[(y * 32) + x];
+ }
+ return ptr;
+}
+
+static void ApplyColors_UnshadowedFont(const u8 *src, u32 *dest, u8 foreground, u8 background)
+{
+ u32 a[2];
+ s32 i;
+ const u8 *srcRows = src;
+
+ a[0] = background;
+ a[1] = foreground;
+
+ for (i = 0; i < 8; i++)
+ {
+ u32 destRow = a[srcRows[i] & 1]
+ | (a[(srcRows[i] >> 1) & 1] << 4)
+ | (a[(srcRows[i] >> 2) & 1] << 8)
+ | (a[(srcRows[i] >> 3) & 1] << 12)
+ | (a[(srcRows[i] >> 4) & 1] << 16)
+ | (a[(srcRows[i] >> 5) & 1] << 20)
+ | (a[(srcRows[i] >> 6) & 1] << 24)
+ | (a[(srcRows[i] >> 7) ] << 28);
+ dest[i] = destRow;
+ }
+}
+
+static void ApplyColors_ShadowedFont(const void *src, void *dest, u8 foreground, u8 shadow, u8 background)
+{
+ u32 a[0x10];
+ s32 i;
+ const u32 *curSrc;
+ u32 *curDest;
+ u32 colorMask;
+
+ a[0x1] = 0x1;
+ a[0x2] = 0x2;
+ a[0x3] = 0x3;
+ a[0x4] = 0x4;
+ a[0x5] = 0x5;
+ a[0x6] = 0x6;
+ a[0x7] = 0x7;
+ a[0x8] = 0x8;
+ a[0x9] = 0x9;
+ a[0xA] = 0xA;
+ a[0xB] = 0xB;
+ a[0xC] = 0xC;
+ a[0xD] = 0xD;
+ a[0x0] = background;
+ a[0xE] = shadow;
+ a[0xF] = foreground;
+
+ colorMask = 0xF;
+
+ curSrc = src;
+ curDest = dest;
+
+ for (i = 7; i >= 0; i--)
+ {
+ u32 row = *curSrc++;
+ u32 recoloredRow = a[row & colorMask]
+ | (a[(row >> 4) & colorMask] << 4)
+ | (a[(row >> 8) & colorMask] << 8)
+ | (a[(row >> 12) & colorMask] << 12)
+ | (a[(row >> 16) & colorMask] << 16)
+ | (a[(row >> 20) & colorMask] << 20)
+ | (a[(row >> 24) & colorMask] << 24)
+ | (a[(row >> 28) ] << 28);
+ *curDest++ = recoloredRow;
+ }
+}
+
+static void SetCursorX(struct Window *win, u8 x)
+{
+ if (win->textMode == 0 && ((win->left + win->cursorX) & 7))
+ win->tileDataOffset += 2;
+ win->cursorX = x;
+}
+
+static void AddToCursorX(struct Window *win, u8 deltaX)
+{
+ if (win->textMode == 0)
+ {
+ u8 x = win->cursorX;
+ win->cursorX += deltaX;
+ if (((win->left + win->cursorX) & 0xF8) != ((win->left + x) & 0xF8))
+ win->tileDataOffset += 2;
+ }
+ else
+ {
+ win->cursorX += deltaX;
+ }
+}
+
+static void AddToCursorY(struct Window *win, u8 deltaY)
+{
+ if (win->textMode == 0 && ((win->left + win->cursorX) & 7))
+ win->tileDataOffset += 2;
+ win->cursorY += deltaY;
+}
+
+static void EraseAtCursor(struct Window *win)
+{
+ switch (win->textMode)
+ {
+ case 0:
+ case 2:
+ DrawGlyphTiles(win, 0, 8);
+ break;
+ case 1:
+ sWriteGlyphTilemapFuncs[win->fontNum](win, 0);
+ break;
+ }
+}
+
+static void ClipLeft(struct Window *win)
+{
+ u32 pixel = win->left & 7;
+ if (win->textMode != 1 && pixel)
+ {
+ const u32 *masks = sGlyphMasks[8][pixel];
+ u32 outsideMask = masks[0];
+ u32 insideMask = ~outsideMask;
+ u32 outside = sGlyphBuffer.background & outsideMask;
+ u16 tileNum = GetCursorTileNum(win, 0, 0);
+ u32 *buffer = (u32 *)(win->tileData + 32 * tileNum);
+ buffer[0] = (buffer[0] & insideMask) | outside;
+ buffer[1] = (buffer[1] & insideMask) | outside;
+ buffer[2] = (buffer[2] & insideMask) | outside;
+ buffer[3] = (buffer[3] & insideMask) | outside;
+ buffer[4] = (buffer[4] & insideMask) | outside;
+ buffer[5] = (buffer[5] & insideMask) | outside;
+ buffer[6] = (buffer[6] & insideMask) | outside;
+ buffer[7] = (buffer[7] & insideMask) | outside;
+ tileNum = GetCursorTileNum(win, 0, 1);
+ buffer = (u32 *)(win->tileData + 32 * tileNum);
+ buffer[0] = (buffer[0] & insideMask) | outside;
+ buffer[1] = (buffer[1] & insideMask) | outside;
+ buffer[2] = (buffer[2] & insideMask) | outside;
+ buffer[3] = (buffer[3] & insideMask) | outside;
+ buffer[4] = (buffer[4] & insideMask) | outside;
+ buffer[5] = (buffer[5] & insideMask) | outside;
+ buffer[6] = (buffer[6] & insideMask) | outside;
+ buffer[7] = (buffer[7] & insideMask) | outside;
+ }
+}
+
+static void ClipRight(struct Window *win)
+{
+ register u8 cursorX asm("r0") = win->cursorX;
+ u8 left = win->left;
+ u32 pixel = (cursorX + left) & 7;
+ if (win->textMode != 1 && pixel)
+ {
+ const u32 *masks = sGlyphMasks[8 - pixel][pixel];
+ u32 insideMask = masks[0];
+ u32 outside = (sGlyphBuffer.background & ~insideMask);
+ u16 tileNum = GetCursorTileNum(win, 0, 0);
+ u32 *buffer = (u32 *)(win->tileData + 32 * tileNum);
+ buffer[0] = (buffer[0] & insideMask) | outside;
+ buffer[1] = (buffer[1] & insideMask) | outside;
+ buffer[2] = (buffer[2] & insideMask) | outside;
+ buffer[3] = (buffer[3] & insideMask) | outside;
+ buffer[4] = (buffer[4] & insideMask) | outside;
+ buffer[5] = (buffer[5] & insideMask) | outside;
+ buffer[6] = (buffer[6] & insideMask) | outside;
+ buffer[7] = (buffer[7] & insideMask) | outside;
+ tileNum = GetCursorTileNum(win, 0, 1);
+ buffer = (u32 *)(win->tileData + 32 * tileNum);
+ buffer[0] = (buffer[0] & insideMask) | outside;
+ buffer[1] = (buffer[1] & insideMask) | outside;
+ buffer[2] = (buffer[2] & insideMask) | outside;
+ buffer[3] = (buffer[3] & insideMask) | outside;
+ buffer[4] = (buffer[4] & insideMask) | outside;
+ buffer[5] = (buffer[5] & insideMask) | outside;
+ buffer[6] = (buffer[6] & insideMask) | outside;
+ buffer[7] = (buffer[7] & insideMask) | outside;
+ UpdateTilemap(win, 1);
+ }
+}
+
+static void InitColors(struct Window *win)
+{
+ u32 i;
+
+ win->backgroundColor = 0;
+ win->shadowColor = 14;
+ win->foregroundColor = 15;
+
+ for (i = 0; i < 16; i++)
+ sGlyphBuffer.colors[i] = i;
+}
+
+static void SetBackgroundColor(struct Window *win, u8 color)
+{
+ u32 val1;
+ u32 val2;
+ u32 val3;
+ win->backgroundColor = color;
+ sGlyphBuffer.colors[0] = color;
+ val1 = color | (color << 4);
+ val2 = val1 | (val1 << 8);
+ val3 = val2 | (val2 << 16);
+ sGlyphBuffer.background = val3;
+}
+
+static void SetShadowColor(struct Window *win, u8 color)
+{
+ win->shadowColor = color;
+ sGlyphBuffer.colors[14] = color;
+}
+
+static void SetForegroundColor(struct Window *win, u8 color)
+{
+ win->foregroundColor = color;
+ sGlyphBuffer.colors[15] = color;
+}
+
+static u8 GetTextDelay(struct Window *win)
+{
+ if (!PlayerCanInterruptWait(win))
+ return 3;
+
+ return sTextSpeedDelays[gSaveBlock2.optionsTextSpeed];
+}
+
+static bool8 PlayerCanInterruptWait(struct Window *win)
+{
+ bool8 retVal = TRUE;
+
+ switch (sWaitType)
+ {
+ case 2:
+ retVal = FALSE;
+ break;
+ case 3:
+ retVal = gIsLinkContest ? FALSE : TRUE;
+ break;
+ case 1:
+ retVal = (gBattleTypeFlags & BATTLE_TYPE_LINK) ? FALSE : TRUE;
+ break;
+ }
+
+ return retVal;
+}
+
+static void ScrollWindowTextLines(struct Window *win)
+{
+ switch (win->textMode)
+ {
+ case 0:
+ ScrollWindowTextLines_TextMode0(win);
+ break;
+ case 1:
+ ScrollWindowTextLines_TextMode1(win);
+ break;
+ case 2:
+ ScrollWindowTextLines_TextMode2(win);
+ break;
+ }
+}
+
+static void ScrollWindowTextLines_TextMode0(struct Window *win)
+{
+ if (win->cursorY == 0)
+ {
+ win->tileDataOffset = 2 * sLineLength + 2;
+ win->cursorX = 0;
+ win->cursorY += 16;
+ }
+ else
+ {
+ if (win->win_field_C & 2)
+ win->tileDataOffset = 2 * sLineLength + 2;
+ else
+ win->tileDataOffset = 2;
+ win->win_field_C = win->win_field_C ^ 2;
+ win->cursorX = 0;
+ DoScroll_TextMode0(win, sLineLength);
+ }
+}
+
+static void DoScroll_TextMode0(struct Window *win, u16 lineLength)
+{
+ u16 *buffer = win->tilemap;
+ u32 val1 = 32 * (win->top >> 3);
+ u32 val2 = (win->left >> 3);
+ u16 fill;
+ buffer += val1 + val2;
+ fill = (win->paletteNum << 12) | GetBlankTileNum(win);
+ CpuCopy16(buffer + 64, buffer, lineLength * 2);
+ CpuCopy16(buffer + 96, buffer + 32, lineLength * 2);
+ CpuFill16(fill, buffer + 64, lineLength * 2);
+ CpuFill16(fill, buffer + 96, lineLength * 2);
+}
+
+static void ScrollWindowTextLines_TextMode1(struct Window *win)
+{
+ if (win->cursorY == 0)
+ {
+ win->cursorX = 0;
+ win->cursorY += 16;
+ }
+ else
+ {
+ win->win_field_C ^= 2;
+ win->cursorX = 0;
+ DoScroll_TextMode1(win, sLineLength);
+ }
+}
+
+static void DoScroll_TextMode1(struct Window *win, u16 lineLength)
+{
+ u16 *buffer = GetCursorTilemapPointer(win);
+ u16 *dest = buffer - 32;
+ u16 fill = (win->paletteNum << 12) | GetBlankTileNum(win);
+ CpuCopy16(buffer + 32, dest, lineLength * 2);
+ CpuCopy16(buffer + 64, buffer, lineLength * 2);
+ CpuFill16(fill, buffer + 32, lineLength * 2);
+ CpuFill16(fill, buffer + 64, lineLength * 2);
+}
+
+static void ScrollWindowTextLines_TextMode2(struct Window *win)
+{
+ if (win->cursorY == 0)
+ {
+ win->cursorX = 0;
+ win->cursorY += 16;
+ }
+ else
+ {
+ win->win_field_C ^= 2;
+ win->cursorX = 0;
+ DoScroll_TextMode2(win, sLineLength);
+ }
+}
+
+static void DoScroll_TextMode2(struct Window *win, u8 lineLength)
+{
+ u8 i;
+ u8 *buf1 = win->tileData + 32 * GetCursorTileNum(win, 0, -2);
+ u8 *buf2 = win->tileData + 32 * GetCursorTileNum(win, 0, 0);
+ u8 *buf4;
+ u16 *buf3;
+ u16 a[4];
+
+ CpuFastCopy(buf2, buf1, 32 * lineLength);
+ CpuFastFill(sGlyphBuffer.background, buf2, 32 * lineLength);
+ buf4 = buf2 + 32 * win->width;
+ CpuFastCopy(buf4, buf1 + 32 * win->width, 32 * lineLength);
+ CpuFastFill(sGlyphBuffer.background, buf4, 32 * lineLength);
+
+ buf3 = GetCursorTilemapPointer(win) - 64;
+
+ a[0] = (win->tileDataStartOffset + win->tileDataOffset
+ + ((win->top >> 3) * win->width)
+ + (win->left >> 3))
+ | (win->paletteNum << 12);
+ a[1] = a[0] + win->width;
+ a[2] = a[1] + win->width;
+ a[3] = a[2] + win->width;
+
+ for (i = 0; i < lineLength; i++)
+ {
+ buf3[0] = a[0]++;
+ buf3[32] = a[1]++;
+ buf3[64] = a[2]++;
+ buf3[96] = a[3]++;
+ buf3++;
+ }
+}
+
+void ClearWindowTextLines(struct Window *win)
+{
+ switch (win->textMode)
+ {
+ case 0:
+ ClearWindowTextLines_TextMode0_TextMode1(win, sLineLength);
+ win->tileDataOffset = 2;
+ break;
+ case 1:
+ ClearWindowTextLines_TextMode0_TextMode1(win, sLineLength);
+ break;
+ case 2:
+ ClearWindowTextLines_TextMode2(win, sLineLength);
+ break;
+ }
+}
+
+static void ClearWindowTextLines_TextMode0_TextMode1(struct Window *win, u8 lineLength)
+{
+ u8 i;
+ u16 *buffer;
+ u16 fill;
+
+ win->cursorX = 0;
+ win->cursorY = 0;
+ win->win_field_C = 0;
+
+ buffer = GetCursorTilemapPointer(win);
+ fill = GetBlankTileNum(win) | (win->paletteNum << 12);
+
+ for (i = 0; i < 4; i++)
+ {
+ u8 j;
+ for (j = 0; j < lineLength; j++)
+ buffer[j] = fill;
+ buffer += 32;
+ }
+}
+
+static void ClearWindowTextLines_TextMode2(struct Window *win, u8 lineLength)
+{
+ u8 *buffer;
+
+ win->cursorX = 0;
+ win->cursorY = 0;
+ win->win_field_C = 0;
+
+ buffer = win->tileData + 32 * GetCursorTileNum(win, 0, 0);
+ CpuFastFill(sGlyphBuffer.background, buffer, 32 * lineLength);
+ buffer += 32 * win->width;
+ CpuFastFill(sGlyphBuffer.background, buffer, 32 * lineLength);
+ buffer += 32 * win->width;
+ CpuFastFill(sGlyphBuffer.background, buffer, 32 * lineLength);
+ buffer += 32 * win->width;
+ CpuFastFill(sGlyphBuffer.background, buffer, 32 * lineLength);
+}
+
+static void DrawDownArrow(struct Window *win)
+{
+ if (PlayerCanInterruptWait(win))
+ {
+ const u32 *downArrowTiles = &sDownArrowTiles[((win->downArrowCounter & 0x0F00) >> 8) * 16];
+
+ switch (win->textMode)
+ {
+ case 1:
+ {
+ u8 *buffer;
+ u16 tileNum = win->tileDataStartOffset + 254;
+ if (win->fontNum == 0 || win->fontNum == 3)
+ tileNum *= 2;
+ buffer = win->tileData + 32 * tileNum;
+ ApplyColors_ShadowedFont(downArrowTiles, buffer, win->foregroundColor, win->shadowColor, win->backgroundColor);
+ ApplyColors_ShadowedFont(downArrowTiles + 8, buffer + 32, win->foregroundColor, win->shadowColor, win->backgroundColor);
+ WriteGlyphTilemap(win, tileNum, tileNum + 1);
+ break;
+ }
+ case 0:
+ case 2:
+ {
+ struct GlyphTileInfo glyphTileInfo;
+ glyphTileInfo.textMode = win->textMode;
+ glyphTileInfo.startPixel = (win->left + win->cursorX) & 7;
+ if (glyphTileInfo.startPixel != 0)
+ {
+ u8 *upperTile;
+ u8 *lowerTile;
+ GetGlyphTilePointers(win->fontNum, win->language, 0, &upperTile, &lowerTile);
+ glyphTileInfo.width = 8 - glyphTileInfo.startPixel;
+ glyphTileInfo.src = upperTile;
+ glyphTileInfo.dest = (u32 *)(win->tileData + 32 * GetCursorTileNum(win, 1, 0));
+ glyphTileInfo.colors = sGlyphBuffer.colors;
+ DrawGlyphTile_ShadowedFont(&glyphTileInfo);
+ glyphTileInfo.src = lowerTile;
+ glyphTileInfo.dest = (u32 *)(win->tileData + 32 * GetCursorTileNum(win, 1, 1));
+ DrawGlyphTile_ShadowedFont(&glyphTileInfo);
+ }
+ glyphTileInfo.width = 8;
+ glyphTileInfo.src = (u8 *)downArrowTiles;
+ glyphTileInfo.dest = (u32 *)(win->tileData + 32 * GetCursorTileNum(win, 0, 0));
+ glyphTileInfo.colors = sGlyphBuffer.colors;
+ DrawGlyphTile_ShadowedFont(&glyphTileInfo);
+ glyphTileInfo.src += 32;
+ glyphTileInfo.dest = (u32 *)(win->tileData + 32 * GetCursorTileNum(win, 0, 1));
+ DrawGlyphTile_ShadowedFont(&glyphTileInfo);
+ if (glyphTileInfo.startPixel != 0)
+ UpdateTilemap(win, 2);
+ else
+ UpdateTilemap(win, 1);
+ break;
+ }
+ }
+ }
+}
+
+static u8 WaitWithDownArrow(struct Window *win)
+{
+ u8 retVal = 1;
+
+ if (!PlayerCanInterruptWait(win))
+ {
+ win->delayCounter--;
+ if (!win->delayCounter)
+ {
+ TryEraseDownArrow(win);
+ }
+ else
+ {
+ DrawMovingDownArrow(win);
+ retVal = 0;
+ }
+ }
+ else
+ {
+ if (gMain.newKeys & (A_BUTTON | B_BUTTON))
+ {
+ PlaySE(SE_SELECT);
+ TryEraseDownArrow(win);
+ }
+ else
+ {
+ DrawMovingDownArrow(win);
+ retVal = 0;
+ }
+ }
+
+ return retVal;
+}
+
+static void DrawInitialDownArrow(struct Window *win)
+{
+ win->downArrowCounter = 0;
+ DrawDownArrow(win);
+}
+
+static void DrawMovingDownArrow(struct Window *win)
+{
+ u16 downArrowPos = (win->downArrowCounter & 0x0F00) >> 8;
+ u16 wait = win->downArrowCounter & 0x000F;
+ u16 newVal;
+
+ wait++;
+
+ if (wait == 6)
+ {
+ wait = 0;
+ downArrowPos++;
+ if (downArrowPos > 3)
+ downArrowPos = 0;
+ win->downArrowCounter = downArrowPos << 8;
+ DrawDownArrow(win);
+ }
+
+ newVal = downArrowPos << 8;
+ newVal |= wait;
+
+ win->downArrowCounter = newVal;
+}
+
+static void TryEraseDownArrow(struct Window *win)
+{
+ win->downArrowCounter = 0;
+ if (PlayerCanInterruptWait(win) == TRUE)
+ EraseAtCursor(win);
+}
+
+u16 GetWindowTilemapEntry(struct Window *win, u8 x, u8 y)
+{
+ u16 *tilemap = win->tilemap;
+ return tilemap[32 * y + x];
+}
+
+void DrawWindowRect(struct Window *win, u16 tilemapEntry, u8 left, u8 top, u8 right, u8 bottom)
+{
+ u8 i;
+ u16 *buffer = &win->tilemap[top * 32];
+
+ for (i = left; i <= right; i++)
+ buffer[i] = tilemapEntry;
+
+ for (i = top + 1; i < bottom - 1; i++)
+ {
+ buffer += 32;
+ buffer[left] = tilemapEntry;
+ buffer[right] = tilemapEntry;
+ }
+
+ if (top != bottom)
+ {
+ buffer += 32;
+ for (i = left; i <= right; i++)
+ buffer[i] = tilemapEntry;
+ }
+}
+
+void DrawWindowRect_DefaultPalette(struct Window *win, u16 tileNum, u8 left, u8 top, u8 right, u8 bottom)
+{
+ DrawWindowRect(win, (win->paletteNum << 12) | tileNum, left, top, right, bottom);
+}
+
+void FillWindowRect(struct Window *win, u16 tilemapEntry, u8 left, u8 top, u8 right, u8 bottom)
+{
+ u16 *buffer = &win->tilemap[top * 32];
+ while (top++ <= bottom)
+ {
+ u8 j;
+ for (j = left; j <= right; j++)
+ buffer[j] = tilemapEntry;
+ buffer += 32;
+ }
+}
+
+void FillWindowRect_DefaultPalette(struct Window *win, u16 tileNum, u8 left, u8 top, u8 right, u8 bottom)
+{
+ FillWindowRect(win, (win->paletteNum << 12) | tileNum, left, top, right, bottom);
+}
+
+void ZeroFillWindowRect(struct Window *win, u8 left, u8 top, u8 right, u8 bottom)
+{
+ FillWindowRect_DefaultPalette(win, 0, left, top, right, bottom);
+}
+
+void FillWindowRectWithBlankTile(struct Window *win, u8 left, u8 top, u8 right, u8 bottom)
+{
+ u16 tileNum = GetBlankTileNum(win);
+ FillWindowRect_DefaultPalette(win, tileNum, left, top, right, bottom);
+}
+
+static u16 GetBlankTileNum(struct Window *win)
+{
+ u16 retVal = win->tileDataStartOffset;
+
+ switch (win->textMode)
+ {
+ case 0:
+ break;
+ case 2:
+ retVal++;
+ break;
+ case 1:
+ switch (win->fontNum)
+ {
+ case 1:
+ case 2:
+ case 4:
+ case 5:
+ retVal += 212;
+ case 0:
+ case 3:
+ case 6:
+ break;
+ default:
+ retVal = 0;
+ }
+ break;
+ }
+
+ return retVal;
+}
+
+static s32 sub_80048D8(struct Window *win, u8 x, u8 y)
+{
+ win->cursorX = x;
+ win->cursorY = y & 0xF8;
+}
+
+static u8 GetGlyphWidth(struct Window *win, u32 glyph)
+{
+ u8 width = 8;
+
+#ifdef BUGFIX_GLYPHWIDTH
+ if (win->language != LANGUAGE_JAPANESE)
+#else
+ if (win->language == LANGUAGE_ENGLISH)
+#endif
+ {
+ width = win->spacing;
+ if (!win->spacing)
+ {
+ switch (win->fontNum)
+ {
+ case 3:
+ width = sFont3Widths[glyph];
+ break;
+ case 4:
+ case 5:
+ width = sFont4Widths[sFontType1Map[2 * glyph + 1]];
+ break;
+ case 0:
+ width = sFont0Widths[glyph];
+ break;
+ case 1:
+ case 2:
+ width = sFont1Widths[sFontType1Map[2 * glyph + 1]];
+ break;
+ case 6:
+ width = 8;
+ break;
+ default:
+ width = 8;
+ }
+ }
+ }
+
+ return width;
+}
+
+u8 GetExtCtrlCodeLength(u8 code)
+{
+ u8 length = 0;
+ if (code <= 0x16)
+ length = sExtCtrlCodeLengths[code];
+ return length;
+}
+
+u8 *AlignInt1(struct Window *win, u8 *dest, s32 value, u8 alignAmount, u8 alignType)
+{
+ u8 temp[16];
+ u8 width;
+ switch (alignType)
+ {
+ case 0:
+ ConvertIntToDecimalString(temp, value);
+ dest = StringCopy(dest, temp);
+ dest[0] = 0xFC;
+ dest[1] = 19;
+ dest[2] = alignAmount;
+ dest += 3;
+ *dest = 0xFF;
+ break;
+ case 1:
+ ConvertIntToDecimalString(temp, value);
+ width = GetStringWidth(win, temp);
+ if (alignAmount > width)
+ {
+ dest[0] = 0xFC;
+ dest[1] = 19;
+ dest[2] = alignAmount - width;
+ dest += 3;
+ }
+ dest = StringCopy(dest, temp);
+ break;
+ case 2:
+ ConvertIntToDecimalString(temp, value);
+ width = GetStringWidth(win, temp);
+ if (alignAmount > width)
+ {
+ dest[0] = 0xFC;
+ dest[1] = 19;
+ dest[2] = (alignAmount - width) / 2;
+ dest += 3;
+ }
+ dest = StringCopy(dest, temp);
+ if (alignAmount > width)
+ {
+ dest[0] = 0xFC;
+ dest[1] = 19;
+ dest[2] = alignAmount;
+ dest += 3;
+ *dest = 0xFF;
+ }
+ break;
+ }
+ return dest;
+}
+
+u8 *AlignInt2(struct Window *win, u8 *dest, s32 value, u8 alignAmount, u8 alignType)
+{
+ u8 temp[16];
+ u8 width;
+ switch (alignType)
+ {
+ case 0:
+ ConvertIntToDecimalString(temp, value);
+ width = GetStringWidth(win, temp);
+ dest = StringCopy(dest, temp);
+ dest[0] = 0xFC;
+ dest[1] = 17;
+ dest[2] = alignAmount - width;
+ dest += 3;
+ *dest = 0xFF;
+ break;
+ case 1:
+ ConvertIntToDecimalString(temp, value);
+ width = GetStringWidth(win, temp);
+ if (alignAmount > width)
+ {
+ dest[0] = 0xFC;
+ dest[1] = 17;
+ dest[2] = alignAmount - width;
+ dest += 3;
+ }
+ dest = StringCopy(dest, temp);
+ break;
+ case 2:
+ ConvertIntToDecimalString(temp, value);
+ width = GetStringWidth(win, temp);
+ if (alignAmount > width)
+ {
+ dest[0] = 0xFC;
+ dest[1] = 17;
+ dest[2] = (alignAmount - width) / 2;
+ dest += 3;
+ }
+ dest = StringCopy(dest, temp);
+ if (alignAmount > width)
+ {
+ dest[0] = 0xFC;
+ dest[1] = 17;
+ dest[2] = (alignAmount - width) / 2;
+ dest += 3;
+ *dest = 0xFF;
+ }
+ break;
+ }
+ return dest;
+}
+
+u8 *AlignString(struct Window *win, u8 *dest, const u8 *src, u8 alignAmount, u8 alignType)
+{
+ u8 width;
+ switch (alignType)
+ {
+ case 0:
+ dest = StringCopy(dest, src);
+ dest[0] = 0xFC;
+ dest[1] = 19;
+ dest[2] = alignAmount;
+ dest += 3;
+ *dest = 0xFF;
+ break;
+ case 1:
+ width = GetStringWidth(win, src);
+ if (alignAmount > width)
+ {
+ dest[0] = 0xFC;
+ dest[1] = 19;
+ dest[2] = alignAmount - width;
+ dest += 3;
+ }
+ dest = StringCopy(dest, src);
+ break;
+ case 2:
+ width = GetStringWidth(win, src);
+ if (alignAmount > width)
+ {
+ dest[0] = 0xFC;
+ dest[1] = 19;
+ dest[2] = (alignAmount - width) / 2;
+ dest += 3;
+ }
+ dest = StringCopy(dest, src);
+ if (alignAmount > width)
+ {
+ dest[0] = 0xFC;
+ dest[1] = 19;
+ dest[2] = alignAmount;
+ dest += 3;
+ *dest = 0xFF;
+ }
+ break;
+ }
+ return dest;
+}
+
+u8 GetStringWidth(struct Window *win, const u8 *s)
+{
+ u8 width = 0;
+ u8 savedFontNum = win->fontNum;
+ u8 savedCharset = win->language;
+ u8 savedSpacing = win->spacing;
+ s32 i = 0;
+
+ while (s[i] != 0xFF)
+ {
+ u8 c = s[i];
+ switch (c)
+ {
+ case 0xFD:
+ {
+ u8 temp;
+ i++;
+ temp = win->language;
+ width += GetStringWidth(win, GetExpandedPlaceholder(s[i]));
+ win->language = temp;
+ i++;
+ break;
+ }
+ case 0xFC:
+ i++;
+ switch (s[i])
+ {
+ case 6:
+ win->fontNum = s[i + 1];
+ break;
+ case 7:
+ win->fontNum = win->config->fontNum;
+ break;
+ case 0x11:
+ width += s[i + 1];
+ break;
+ case 0x12:
+ case 0x13:
+ if (width < s[i + 1])
+ width = s[i + 1];
+ break;
+ case 0x14:
+ win->spacing = s[i + 1];
+ break;
+ case 0x15:
+ win->language = LANGUAGE_JAPANESE;
+ break;
+ case 0x16:
+ win->language = GAME_LANGUAGE;
+ break;
+ }
+
+ i += GetExtCtrlCodeLength(s[i]);
+ break;
+ default:
+ i++;
+ width += GetGlyphWidth(win, c);
+ }
+ }
+
+ win->spacing = savedSpacing;
+ win->language = savedCharset;
+ win->fontNum = savedFontNum;
+
+ return width;
+}
+
+u8 sub_8004D04(struct Window *win, const u8 *text, u16 tileDataStartOffset, u8 left, u16 top, u32 a6)
+{
+ sub_8002E4C(win, text, tileDataStartOffset, left, top, a6);
+ return sub_8002F44(win);
+}
+
+u8 sub_8004D38(struct Window *win, const u8 *text, u16 tileDataStartOffset, u8 left, u8 top)
+{
+ u8 width = GetStringWidth(win, text);
+ InitWindow(win, text, tileDataStartOffset, left - ((u32)(width + 7) >> 3), top);
+ EraseAtCursor(win);
+ width &= 7;
+ if (width)
+ width = 8 - width;
+ sub_80048D8(win, width, 0);
+ return sub_8002F44(win);
+}
+
+u8 sub_8004DB0(struct Window *win, const u8 *text, u16 tileDataStartOffset, u8 left, u8 top, u16 a6)
+{
+ register u32 val asm("r5") = (u8)((a6 >> 1) - (GetStringWidth(win, text) >> 1));
+ left += (val >> 3);
+ InitWindow(win, text, tileDataStartOffset, left, top);
+ EraseAtCursor(win);
+ sub_80048D8(win, val & 7, 0);
+ return sub_8002F44(win);
+}
+
+u8 sub_8004E24(struct Window *win)
+{
+ return win->paletteNum;
+}
+
+void sub_8004E28(struct Window *win, u8 *foreground, u8 *background, u8 *shadow)
+{
+ *foreground = win->foregroundColor;
+ *background = win->backgroundColor;
+ *shadow = win->shadowColor;
+}
+
+void sub_8004E3C(struct WindowConfig *winConfig, u8 *tileData, const u8 *text)
+{
+ sTempWindow.config = winConfig;
+ InitWindow(&sTempWindow, text, 0, 0, 0);
+ sTempWindow.tileData = tileData;
+ sub_8002F44(&sTempWindow);
+}
+
+u8 GetStringWidthGivenWindowConfig(struct WindowConfig *winConfig, const u8 *s)
+{
+ sTempWindow.config = winConfig;
+ InitWindow(&sTempWindow, s, 0, 0, 0);
+ return GetStringWidth(&sTempWindow, s);
+}
+
+void ConvertInternationalString(u8 *s, u8 language)
+{
+ if (language == LANGUAGE_JAPANESE)
+ {
+ u8 i;
+
+ StripExtCtrlCodes(s);
+ i = StringLength(s);
+ s[i++] = 0xFC;
+ s[i++] = 22;
+ s[i++] = 0xFF;
+
+ i--;
+
+ while (i != (u8)-1)
+ {
+ s[i + 2] = s[i];
+ i--;
+ }
+
+ s[0] = 0xFC;
+ s[1] = 21;
+ }
+}
+
+void StripExtCtrlCodes(u8 *str)
+{
+ u16 srcIndex = 0;
+ u16 destIndex = 0;
+ while (str[srcIndex] != 0xFF)
+ {
+ if (str[srcIndex] == 0xFC)
+ {
+ srcIndex++;
+ srcIndex += GetExtCtrlCodeLength(str[srcIndex]);
+ }
+ else
+ {
+ str[destIndex++] = str[srcIndex++];
+ }
+ }
+ str[destIndex] = 0xFF;
+}
+
+static const u8 *SkipExtCtrlCode(const u8 *s)
+{
+ while (*s == 0xFC)
+ {
+ 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 == 0xFF)
+ retVal = 1;
+ }
+
+ if (*str1 == 0xFF)
+ return retVal;
+
+ str1++;
+ str2++;
+ }
+
+ retVal = 1;
+
+ if (*str1 == 0xFF)
+ retVal = -1;
+
+ return retVal;
+}
+
+u8 sub_8004FD0(struct Window *win, u8 *dest, const u8 *src, u16 tileDataStartOffset, u8 left, u16 top, u8 width, u32 a8)
+{
+ u8 newlineCount = 0;
+ u8 extCtrlCodeLength;
+ u8 *start;
+ u32 endsWithoutNewline;
+
+ if (dest == NULL)
+ dest = gStringVar4;
+
+ start = dest;
+ endsWithoutNewline = FALSE;
+
+ while (*src != 0xFF)
+ {
+ switch (*src)
+ {
+ default:
+ *dest = *src;
+ dest++;
+ src++;
+ endsWithoutNewline = TRUE;
+ break;
+ case 0xFC:
+ extCtrlCodeLength = GetExtCtrlCodeLength(src[1]) + 1;
+ memcpy(dest, src, extCtrlCodeLength);
+ dest += extCtrlCodeLength;
+ src += extCtrlCodeLength;
+ break;
+ case 0xFE:
+ dest[0] = 0xFC;
+ dest[1] = 19;
+ dest[2] = width;
+ dest[3] = 0xFE;
+ dest += 4;
+ src++;
+ newlineCount++;
+ endsWithoutNewline = FALSE;
+ break;
+ }
+ }
+
+ dest[0] = 0xFC;
+ dest[1] = 19;
+ dest[2] = width;
+ dest[3] = 0xFF;
+
+ if (endsWithoutNewline)
+ newlineCount++;
+
+ sub_8002E4C(win, start, tileDataStartOffset, left, top, a8);
+ sub_8002F44(win);
+
+ return newlineCount;
+}
+
+static s32 DrawGlyphTile_UnshadowedFont(struct GlyphTileInfo *glyphTileInfo)
+{
+ struct GlyphBuffer *glyphBuffer = &sGlyphBuffer;
+ u32 colors[2];
+ u32 *buffer = glyphTileInfo->dest;
+ const u32 *masks = sGlyphMasks[glyphTileInfo->width][glyphTileInfo->startPixel];
+ u32 mask1 = masks[0] | masks[2];
+
+ glyphBuffer->pixelRows[0] = buffer[0] & mask1;
+ glyphBuffer->pixelRows[1] = buffer[1] & mask1;
+ glyphBuffer->pixelRows[2] = buffer[2] & mask1;
+ glyphBuffer->pixelRows[3] = buffer[3] & mask1;
+ glyphBuffer->pixelRows[4] = buffer[4] & mask1;
+ glyphBuffer->pixelRows[5] = buffer[5] & mask1;
+ glyphBuffer->pixelRows[6] = buffer[6] & mask1;
+ glyphBuffer->pixelRows[7] = buffer[7] & mask1;
+
+ if (glyphTileInfo->startPixel + glyphTileInfo->width > 8)
+ {
+ u32 mask2 = masks[1];
+ if (glyphTileInfo->textMode == 2)
+ {
+ glyphBuffer->pixelRows[8] = buffer[8] & mask2;
+ glyphBuffer->pixelRows[9] = buffer[9] & mask2;
+ glyphBuffer->pixelRows[10] = buffer[10] & mask2;
+ glyphBuffer->pixelRows[11] = buffer[11] & mask2;
+ glyphBuffer->pixelRows[12] = buffer[12] & mask2;
+ glyphBuffer->pixelRows[13] = buffer[13] & mask2;
+ glyphBuffer->pixelRows[14] = buffer[14] & mask2;
+ glyphBuffer->pixelRows[15] = buffer[15] & mask2;
+ }
+ else
+ {
+ glyphBuffer->pixelRows[8] = buffer[16] & mask2;
+ glyphBuffer->pixelRows[9] = buffer[17] & mask2;
+ glyphBuffer->pixelRows[10] = buffer[18] & mask2;
+ glyphBuffer->pixelRows[11] = buffer[19] & mask2;
+ glyphBuffer->pixelRows[12] = buffer[20] & mask2;
+ glyphBuffer->pixelRows[13] = buffer[21] & mask2;
+ glyphBuffer->pixelRows[14] = buffer[22] & mask2;
+ glyphBuffer->pixelRows[15] = buffer[23] & mask2;
+ }
+ }
+
+ colors[0] = glyphTileInfo->colors[0];
+ colors[1] = glyphTileInfo->colors[15];
+
+ sShiftGlyphTileUnshadowedFuncs[glyphTileInfo->width](glyphBuffer, glyphTileInfo->src, colors, glyphTileInfo->startPixel);
+
+ buffer[0] = glyphBuffer->pixelRows[0];
+ buffer[1] = glyphBuffer->pixelRows[1];
+ buffer[2] = glyphBuffer->pixelRows[2];
+ buffer[3] = glyphBuffer->pixelRows[3];
+ buffer[4] = glyphBuffer->pixelRows[4];
+ buffer[5] = glyphBuffer->pixelRows[5];
+ buffer[6] = glyphBuffer->pixelRows[6];
+ buffer[7] = glyphBuffer->pixelRows[7];
+
+ if (glyphTileInfo->startPixel + glyphTileInfo->width > 8)
+ {
+ if (glyphTileInfo->textMode != 2)
+ buffer += 8;
+ buffer[8] = glyphBuffer->pixelRows[8];
+ buffer[9] = glyphBuffer->pixelRows[9];
+ buffer[10] = glyphBuffer->pixelRows[10];
+ buffer[11] = glyphBuffer->pixelRows[11];
+ buffer[12] = glyphBuffer->pixelRows[12];
+ buffer[13] = glyphBuffer->pixelRows[13];
+ buffer[14] = glyphBuffer->pixelRows[14];
+ buffer[15] = glyphBuffer->pixelRows[15];
+ }
+
+ return (glyphTileInfo->startPixel + glyphTileInfo->width) / 8;
+}
+
+static void ShiftGlyphTile_UnshadowedFont_Width0(struct GlyphBuffer *glyphBuffer, u8 *src, u32 *a3, u8 startPixel)
+{
+}
+
+static void ShiftGlyphTile_UnshadowedFont_Width1(struct GlyphBuffer *glyphBuffer, u8 *src, u32 *colors, u8 startPixel)
+{
+ const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
+ u8 i;
+ for (i = 0; i < 8; i++)
+ {
+ u32 val = colors[src[i] >> 7];
+ u32 *dest = &glyphBuffer->pixelRows[i];
+ dest[0] |= val << shiftAmount->left;
+ dest[8] |= val >> shiftAmount->right;
+ }
+}
+
+static void ShiftGlyphTile_UnshadowedFont_Width2(struct GlyphBuffer *glyphBuffer, u8 *src, u32 *colors, u8 startPixel)
+{
+ const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
+ u8 i;
+ for (i = 0; i < 8; i++)
+ {
+ u32 val = (colors[(src[i] >> 7) & 1] << 0)
+ | (colors[(src[i] >> 6) & 1] << 4);
+ u32 *dest = &glyphBuffer->pixelRows[i];
+ dest[0] |= val << shiftAmount->left;
+ dest[8] |= val >> shiftAmount->right;
+ }
+}
+
+static void ShiftGlyphTile_UnshadowedFont_Width3(struct GlyphBuffer *glyphBuffer, u8 *src, u32 *colors, u8 startPixel)
+{
+ const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
+ u8 i;
+ for (i = 0; i < 8; i++)
+ {
+ // XXX: why 4?
+ u32 val = (colors[(src[i] >> 7) & 1] << 0)
+ | (colors[(src[i] >> 6) & 1] << 4)
+ | (colors[(src[i] >> 5) & 1] << 8)
+ | (colors[(src[i] >> 4) & 1] << 12);
+ u32 *dest = &glyphBuffer->pixelRows[i];
+ dest[0] |= val << shiftAmount->left;
+ dest[8] |= val >> shiftAmount->right;
+ }
+}
+
+static void ShiftGlyphTile_UnshadowedFont_Width4(struct GlyphBuffer *glyphBuffer, u8 *src, u32 *colors, u8 startPixel)
+{
+ const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
+ u8 i;
+ for (i = 0; i < 8; i++)
+ {
+ u32 val = (colors[(src[i] >> 7) & 1] << 0)
+ | (colors[(src[i] >> 6) & 1] << 4)
+ | (colors[(src[i] >> 5) & 1] << 8)
+ | (colors[(src[i] >> 4) & 1] << 12);
+ u32 *dest = &glyphBuffer->pixelRows[i];
+ dest[0] |= val << shiftAmount->left;
+ dest[8] |= val >> shiftAmount->right;
+ }
+}
+
+static void ShiftGlyphTile_UnshadowedFont_Width5(struct GlyphBuffer *glyphBuffer, u8 *src, u32 *colors, u8 startPixel)
+{
+ const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
+ u8 i;
+ for (i = 0; i < 8; i++)
+ {
+ u32 val = (colors[(src[i] >> 7) & 1] << 0)
+ | (colors[(src[i] >> 6) & 1] << 4)
+ | (colors[(src[i] >> 5) & 1] << 8)
+ | (colors[(src[i] >> 4) & 1] << 12)
+ | (colors[(src[i] >> 3) & 1] << 16);
+ u32 *dest = &glyphBuffer->pixelRows[i];
+ dest[0] |= val << shiftAmount->left;
+ dest[8] |= val >> shiftAmount->right;
+ }
+}
+
+static void ShiftGlyphTile_UnshadowedFont_Width6(struct GlyphBuffer *glyphBuffer, u8 *src, u32 *colors, u8 startPixel)
+{
+ const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
+ u8 i;
+ for (i = 0; i < 8; i++)
+ {
+ u32 val = (colors[(src[i] >> 7) & 1] << 0)
+ | (colors[(src[i] >> 6) & 1] << 4)
+ | (colors[(src[i] >> 5) & 1] << 8)
+ | (colors[(src[i] >> 4) & 1] << 12)
+ | (colors[(src[i] >> 3) & 1] << 16)
+ | (colors[(src[i] >> 2) & 1] << 20);
+ u32 *dest = &glyphBuffer->pixelRows[i];
+ dest[0] |= val << shiftAmount->left;
+ dest[8] |= val >> shiftAmount->right;
+ }
+}
+
+static void ShiftGlyphTile_UnshadowedFont_Width7(struct GlyphBuffer *glyphBuffer, u8 *src, u32 *colors, u8 startPixel)
+{
+ const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
+ u8 i;
+ for (i = 0; i < 8; i++)
+ {
+ u32 val = (colors[(src[i] >> 7) & 1] << 0)
+ | (colors[(src[i] >> 6) & 1] << 4)
+ | (colors[(src[i] >> 5) & 1] << 8)
+ | (colors[(src[i] >> 4) & 1] << 12)
+ | (colors[(src[i] >> 3) & 1] << 16)
+ | (colors[(src[i] >> 2) & 1] << 20)
+ | (colors[(src[i] >> 1) & 1] << 24);
+ u32 *dest = &glyphBuffer->pixelRows[i];
+ dest[0] |= val << shiftAmount->left;
+ dest[8] |= val >> shiftAmount->right;
+ }
+}
+
+static void ShiftGlyphTile_UnshadowedFont_Width8(struct GlyphBuffer *glyphBuffer, u8 *src, u32 *colors, u8 startPixel)
+{
+ const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
+ u8 i;
+ for (i = 0; i < 8; i++)
+ {
+ u32 val = (colors[(src[i] >> 7) & 1] << 0)
+ | (colors[(src[i] >> 6) & 1] << 4)
+ | (colors[(src[i] >> 5) & 1] << 8)
+ | (colors[(src[i] >> 4) & 1] << 12)
+ | (colors[(src[i] >> 3) & 1] << 16)
+ | (colors[(src[i] >> 2) & 1] << 20)
+ | (colors[(src[i] >> 1) & 1] << 24)
+ | (colors[(src[i] >> 0) & 1] << 28);
+ u32 *dest = &glyphBuffer->pixelRows[i];
+ dest[0] |= val << shiftAmount->left;
+ dest[8] |= val >> shiftAmount->right;
+ }
+}
+
+static s32 DrawGlyphTile_ShadowedFont(struct GlyphTileInfo *glyphTileInfo)
+{
+ struct GlyphBuffer *glyphBuffer = &sGlyphBuffer;
+ u32 *buffer = glyphTileInfo->dest;
+ const u32 *masks = sGlyphMasks[glyphTileInfo->width][glyphTileInfo->startPixel];
+ u32 mask1 = masks[0] | masks[2];
+
+ glyphBuffer->pixelRows[0] = buffer[0] & mask1;
+ glyphBuffer->pixelRows[1] = buffer[1] & mask1;
+ glyphBuffer->pixelRows[2] = buffer[2] & mask1;
+ glyphBuffer->pixelRows[3] = buffer[3] & mask1;
+ glyphBuffer->pixelRows[4] = buffer[4] & mask1;
+ glyphBuffer->pixelRows[5] = buffer[5] & mask1;
+ glyphBuffer->pixelRows[6] = buffer[6] & mask1;
+ glyphBuffer->pixelRows[7] = buffer[7] & mask1;
+
+ if (glyphTileInfo->startPixel + glyphTileInfo->width > 8)
+ {
+ u32 mask2 = masks[1];
+ if (glyphTileInfo->textMode == 2)
+ {
+ glyphBuffer->pixelRows[8] = buffer[8] & mask2;
+ glyphBuffer->pixelRows[9] = buffer[9] & mask2;
+ glyphBuffer->pixelRows[10] = buffer[10] & mask2;
+ glyphBuffer->pixelRows[11] = buffer[11] & mask2;
+ glyphBuffer->pixelRows[12] = buffer[12] & mask2;
+ glyphBuffer->pixelRows[13] = buffer[13] & mask2;
+ glyphBuffer->pixelRows[14] = buffer[14] & mask2;
+ glyphBuffer->pixelRows[15] = buffer[15] & mask2;
+ }
+ else
+ {
+ glyphBuffer->pixelRows[8] = buffer[16] & mask2;
+ glyphBuffer->pixelRows[9] = buffer[17] & mask2;
+ glyphBuffer->pixelRows[10] = buffer[18] & mask2;
+ glyphBuffer->pixelRows[11] = buffer[19] & mask2;
+ glyphBuffer->pixelRows[12] = buffer[20] & mask2;
+ glyphBuffer->pixelRows[13] = buffer[21] & mask2;
+ glyphBuffer->pixelRows[14] = buffer[22] & mask2;
+ glyphBuffer->pixelRows[15] = buffer[23] & mask2;
+ }
+ }
+
+ sShiftGlyphTileShadowedFuncs[glyphTileInfo->width](glyphBuffer, (u32 *)glyphTileInfo->src, glyphTileInfo->colors, glyphTileInfo->startPixel);
+
+ buffer[0] = glyphBuffer->pixelRows[0];
+ buffer[1] = glyphBuffer->pixelRows[1];
+ buffer[2] = glyphBuffer->pixelRows[2];
+ buffer[3] = glyphBuffer->pixelRows[3];
+ buffer[4] = glyphBuffer->pixelRows[4];
+ buffer[5] = glyphBuffer->pixelRows[5];
+ buffer[6] = glyphBuffer->pixelRows[6];
+ buffer[7] = glyphBuffer->pixelRows[7];
+
+ if (glyphTileInfo->startPixel + glyphTileInfo->width > 8)
+ {
+ if (glyphTileInfo->textMode != 2)
+ buffer += 8;
+ buffer[8] = glyphBuffer->pixelRows[8];
+ buffer[9] = glyphBuffer->pixelRows[9];
+ buffer[10] = glyphBuffer->pixelRows[10];
+ buffer[11] = glyphBuffer->pixelRows[11];
+ buffer[12] = glyphBuffer->pixelRows[12];
+ buffer[13] = glyphBuffer->pixelRows[13];
+ buffer[14] = glyphBuffer->pixelRows[14];
+ buffer[15] = glyphBuffer->pixelRows[15];
+ }
+
+ return (glyphTileInfo->startPixel + glyphTileInfo->width) / 8;
+}
+
+static void ShiftGlyphTile_ShadowedFont_Width0(struct GlyphBuffer *glyphBuffer, u32 *src, u32 *colors, u8 startPixel)
+{
+}
+
+static void ShiftGlyphTile_ShadowedFont_Width1(struct GlyphBuffer *glyphBuffer, u32 *src, u32 *colors, u8 startPixel)
+{
+ const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
+ u8 i;
+ for (i = 0; i < 8; i++)
+ {
+ u32 val = colors[src[i] & 0xF];
+ u32 *dest = &glyphBuffer->pixelRows[i];
+ dest[0] |= val << shiftAmount->left;
+ dest[8] |= val >> shiftAmount->right;
+ }
+}
+
+static void ShiftGlyphTile_ShadowedFont_Width2(struct GlyphBuffer *glyphBuffer, u32 *src, u32 *colors, u8 startPixel)
+{
+ const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
+ u8 i;
+ for (i = 0; i < 8; i++)
+ {
+ u32 val = (colors[(src[i] >> 0) & 0xF] << 0)
+ | (colors[(src[i] >> 4) & 0xF] << 4);
+ u32 *dest = &glyphBuffer->pixelRows[i];
+ dest[0] |= val << shiftAmount->left;
+ dest[8] |= val >> shiftAmount->right;
+ }
+}
+
+static void ShiftGlyphTile_ShadowedFont_Width3(struct GlyphBuffer *glyphBuffer, u32 *src, u32 *colors, u8 startPixel)
+{
+ const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
+ u8 i;
+ for (i = 0; i < 8; i++)
+ {
+ u32 val = (colors[(src[i] >> 0) & 0xF] << 0)
+ | (colors[(src[i] >> 4) & 0xF] << 4)
+ | (colors[(src[i] >> 8) & 0xF] << 8);
+ u32 *dest = &glyphBuffer->pixelRows[i];
+ dest[0] |= val << shiftAmount->left;
+ dest[8] |= val >> shiftAmount->right;
+ }
+}
+
+static void ShiftGlyphTile_ShadowedFont_Width4(struct GlyphBuffer *glyphBuffer, u32 *src, u32 *colors, u8 startPixel)
+{
+ const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
+ u8 i;
+ for (i = 0; i < 8; i++)
+ {
+ u32 val = (colors[(src[i] >> 0) & 0xF] << 0)
+ | (colors[(src[i] >> 4) & 0xF] << 4)
+ | (colors[(src[i] >> 8) & 0xF] << 8)
+ | (colors[(src[i] >> 12) & 0xF] << 12);
+ u32 *dest = &glyphBuffer->pixelRows[i];
+ dest[0] |= val << shiftAmount->left;
+ dest[8] |= val >> shiftAmount->right;
+ }
+}
+
+#define SHIFT_GLYPH_WIDTH5_STEP(i) \
+val = (colors[(src[i] >> 0) & 0xF] << 0) \
+ | (colors[(src[i] >> 4) & 0xF] << 4) \
+ | (colors[(src[i] >> 8) & 0xF] << 8) \
+ | (colors[(src[i] >> 12) & 0xF] << 12) \
+ | (colors[(src[i] >> 16) & 0xF] << 16); \
+glyphBuffer->pixelRows[i] |= val << shiftAmount->left; \
+glyphBuffer->pixelRows[i + 8] |= val >> shiftAmount->right; \
+
+static void ShiftGlyphTile_ShadowedFont_Width5(struct GlyphBuffer *glyphBuffer, u32 *src, u32 *colors, u8 startPixel)
+{
+ const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
+ u32 val;
+ SHIFT_GLYPH_WIDTH5_STEP(0)
+ SHIFT_GLYPH_WIDTH5_STEP(1)
+ SHIFT_GLYPH_WIDTH5_STEP(2)
+ SHIFT_GLYPH_WIDTH5_STEP(3)
+ SHIFT_GLYPH_WIDTH5_STEP(4)
+ SHIFT_GLYPH_WIDTH5_STEP(5)
+ SHIFT_GLYPH_WIDTH5_STEP(6)
+ SHIFT_GLYPH_WIDTH5_STEP(7)
+}
+
+#define SHIFT_GLYPH_WIDTH6_STEP(i) \
+val = (colors[(src[i] >> 0) & 0xF] << 0) \
+ | (colors[(src[i] >> 4) & 0xF] << 4) \
+ | (colors[(src[i] >> 8) & 0xF] << 8) \
+ | (colors[(src[i] >> 12) & 0xF] << 12) \
+ | (colors[(src[i] >> 16) & 0xF] << 16) \
+ | (colors[(src[i] >> 20) & 0xF] << 20); \
+glyphBuffer->pixelRows[i] |= val << shiftAmount->left; \
+glyphBuffer->pixelRows[i + 8] |= val >> shiftAmount->right; \
+
+static void ShiftGlyphTile_ShadowedFont_Width6(struct GlyphBuffer *glyphBuffer, u32 *src, u32 *colors, u8 startPixel)
+{
+ const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
+ u32 val;
+ SHIFT_GLYPH_WIDTH6_STEP(0)
+ SHIFT_GLYPH_WIDTH6_STEP(1)
+ SHIFT_GLYPH_WIDTH6_STEP(2)
+ SHIFT_GLYPH_WIDTH6_STEP(3)
+ SHIFT_GLYPH_WIDTH6_STEP(4)
+ SHIFT_GLYPH_WIDTH6_STEP(5)
+ SHIFT_GLYPH_WIDTH6_STEP(6)
+ SHIFT_GLYPH_WIDTH6_STEP(7)
+}
+
+#define SHIFT_GLYPH_WIDTH7_STEP(i) \
+val = (colors[(src[i] >> 0) & 0xF] << 0) \
+ | (colors[(src[i] >> 4) & 0xF] << 4) \
+ | (colors[(src[i] >> 8) & 0xF] << 8) \
+ | (colors[(src[i] >> 12) & 0xF] << 12) \
+ | (colors[(src[i] >> 16) & 0xF] << 16) \
+ | (colors[(src[i] >> 20) & 0xF] << 20) \
+ | (colors[(src[i] >> 24) & 0xF] << 24); \
+glyphBuffer->pixelRows[i] |= val << shiftAmount->left; \
+glyphBuffer->pixelRows[i + 8] |= val >> shiftAmount->right; \
+
+static void ShiftGlyphTile_ShadowedFont_Width7(struct GlyphBuffer *glyphBuffer, u32 *src, u32 *colors, u8 startPixel)
+{
+ const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
+ u32 val;
+ SHIFT_GLYPH_WIDTH7_STEP(0)
+ SHIFT_GLYPH_WIDTH7_STEP(1)
+ SHIFT_GLYPH_WIDTH7_STEP(2)
+ SHIFT_GLYPH_WIDTH7_STEP(3)
+ SHIFT_GLYPH_WIDTH7_STEP(4)
+ SHIFT_GLYPH_WIDTH7_STEP(5)
+ SHIFT_GLYPH_WIDTH7_STEP(6)
+ SHIFT_GLYPH_WIDTH7_STEP(7)
+}
+
+#define SHIFT_GLYPH_WIDTH8_STEP(i) \
+val = (colors[(src[i] >> 0) & 0xF] << 0) \
+ | (colors[(src[i] >> 4) & 0xF] << 4) \
+ | (colors[(src[i] >> 8) & 0xF] << 8) \
+ | (colors[(src[i] >> 12) & 0xF] << 12) \
+ | (colors[(src[i] >> 16) & 0xF] << 16) \
+ | (colors[(src[i] >> 20) & 0xF] << 20) \
+ | (colors[(src[i] >> 24) & 0xF] << 24) \
+ | (colors[(src[i] >> 28) ] << 28); \
+glyphBuffer->pixelRows[i] |= val << shiftAmount->left; \
+glyphBuffer->pixelRows[i + 8] |= val >> shiftAmount->right; \
+
+static void ShiftGlyphTile_ShadowedFont_Width8(struct GlyphBuffer *glyphBuffer, u32 *src, u32 *colors, u8 startPixel)
+{
+ const struct ShiftAmount *shiftAmount = &sGlyphShiftAmounts[startPixel];
+ u32 val;
+ SHIFT_GLYPH_WIDTH8_STEP(0)
+ SHIFT_GLYPH_WIDTH8_STEP(1)
+ SHIFT_GLYPH_WIDTH8_STEP(2)
+ SHIFT_GLYPH_WIDTH8_STEP(3)
+ SHIFT_GLYPH_WIDTH8_STEP(4)
+ SHIFT_GLYPH_WIDTH8_STEP(5)
+ SHIFT_GLYPH_WIDTH8_STEP(6)
+ SHIFT_GLYPH_WIDTH8_STEP(7)
+}
+
+static s32 DrawGlyphTiles(struct Window *win, u32 glyph, u32 glyphWidth)
+{
+ struct GlyphTileInfo glyphTileInfo;
+ u8 *upperTile;
+ u8 *lowerTile;
+ s32 retVal = 0;
+
+ GetGlyphTilePointers(win->fontNum, win->language, glyph, &upperTile, &lowerTile);
+ glyphTileInfo.textMode = win->textMode;
+ glyphTileInfo.startPixel = (win->left + win->cursorX) & 7;
+ glyphTileInfo.width = glyphWidth;
+ glyphTileInfo.src = upperTile;
+ glyphTileInfo.dest = (u32 *)(win->tileData + 32 * GetCursorTileNum(win, 0, 0));
+ glyphTileInfo.colors = sGlyphBuffer.colors;
+
+ switch (win->fontNum)
+ {
+ case 0:
+ case 1:
+ case 2:
+ case 6:
+ DrawGlyphTile_UnshadowedFont(&glyphTileInfo);
+ glyphTileInfo.src = lowerTile;
+ glyphTileInfo.dest = (u32 *)(win->tileData + 32 * GetCursorTileNum(win, 0, 1));
+ retVal = DrawGlyphTile_UnshadowedFont(&glyphTileInfo);
+ break;
+ case 3:
+ case 4:
+ case 5:
+ DrawGlyphTile_ShadowedFont(&glyphTileInfo);
+ glyphTileInfo.src = lowerTile;
+ glyphTileInfo.dest = (u32 *)(win->tileData + 32 * GetCursorTileNum(win, 0, 1));
+ retVal = DrawGlyphTile_ShadowedFont(&glyphTileInfo);
+ break;
+ }
+
+ return retVal;
+}
+
+static void UpdateTilemap(struct Window *win, u32 tilesWidth)
+{
+ u16 *tilemap = GetCursorTilemapPointer(win);
+ if (tilesWidth && tilemap)
+ {
+ u16 paletteNum = (win->paletteNum << 12);
+ u16 upperLeftTileNum = GetCursorTileNum(win, 0, 0);
+ u16 lowerLeftTileNum = GetCursorTileNum(win, 0, 1);
+ tilemap[0] = upperLeftTileNum | paletteNum;
+ tilemap[32] = lowerLeftTileNum | paletteNum;
+ if (tilesWidth == 2)
+ {
+ u16 upperRightTileNum = GetCursorTileNum(win, 1, 0);
+ u16 lowerRightTileNum = GetCursorTileNum(win, 1, 1);
+ tilemap[1] = upperRightTileNum | paletteNum;
+ tilemap[33] = lowerRightTileNum | paletteNum;
+ }
+ }
+}
+
+static u16 GetCursorTileNum(struct Window *win, u32 xOffset, u32 yOffset)
+{
+ u16 index;
+
+ if (win->textMode == 2)
+ index = win->tileDataStartOffset
+ + win->tileDataOffset
+ + (((win->top + win->cursorY) >> 3) + yOffset) * win->width
+ + (((win->left + win->cursorX) >> 3) + xOffset);
+ else
+ index = win->tileDataStartOffset + win->tileDataOffset + 2 * xOffset + yOffset;
+
+ return index;
+}
diff --git a/src/engine/text_window.c b/src/engine/text_window.c
new file mode 100644
index 000000000..9a88789d1
--- /dev/null
+++ b/src/engine/text_window.c
@@ -0,0 +1,184 @@
+#include "global.h"
+#include "text_window.h"
+#include "main.h"
+#include "palette.h"
+#include "text.h"
+
+#define STD_MSG_BOX_LEFT 0
+#define STD_MSG_BOX_TOP 14
+#define STD_MSG_BOX_WIDTH 26
+#define STD_MSG_BOX_HEIGHT 4
+
+static void LoadTextWindowTiles(u8, void *);
+static void LoadTextWindowPalette(u8, u8);
+static void DrawTextWindowInternal(u16 *dest, u16 baseTileNum, u8 left, u8 top, u8 right, u8 bottom);
+static u16 GetMessageBoxTilemapEntry(u16 tilemapEntry, u8 x, u8 y, u8 width, u8 height);
+static void DrawMessageBox(struct Window *win, u8 left, u8 top, u8 width, u8 height);
+
+static u16 sTextWindowBaseTileNum;
+static u16 sMessageBoxBaseTileNum;
+
+extern const struct FrameGraphics gUnknown_083761F0[20];
+
+extern const u16 gMessageBoxTilemap[5][7];
+extern const u8 gMessageBox_Gfx[];
+
+u16 SetTextWindowBaseTileNum(u16 baseTileNum)
+{
+ sTextWindowBaseTileNum = baseTileNum;
+ return baseTileNum + 9;
+}
+
+void LoadTextWindowGraphics(struct Window *win)
+{
+ u8 *tileData = win->config->tileData + TILE_SIZE_4BPP * sTextWindowBaseTileNum;
+ LoadTextWindowTiles(gSaveBlock2.optionsWindowFrameType, tileData);
+ LoadTextWindowPalette(gSaveBlock2.optionsWindowFrameType, 0xE);
+}
+
+void LoadTextWindowGraphics_OverridePalSlot(struct Window *win, u8 palSlot)
+{
+ u8 *tileData = win->config->tileData + TILE_SIZE_4BPP * sTextWindowBaseTileNum;
+ LoadTextWindowTiles(gSaveBlock2.optionsWindowFrameType, tileData);
+ LoadTextWindowPalette(gSaveBlock2.optionsWindowFrameType, palSlot);
+}
+
+void LoadTextWindowGraphics_OverrideFrameType(struct Window *win, u8 frameType)
+{
+ u8 *tileData = win->config->tileData + TILE_SIZE_4BPP * sTextWindowBaseTileNum;
+ LoadTextWindowTiles(frameType, tileData);
+ LoadTextWindowPalette(frameType, 0xE);
+}
+
+void DrawTextWindow(struct Window *win, u8 left, u8 top, u8 right, u8 bottom)
+{
+ DrawTextWindowInternal(win->config->tilemap, sTextWindowBaseTileNum, left, top, right, bottom);
+}
+
+const struct FrameGraphics *GetTextWindowFrameGraphics(u8 frameType)
+{
+ if (frameType > 19)
+ return &gUnknown_083761F0[0];
+ else
+ return &gUnknown_083761F0[frameType];
+}
+
+static void LoadTextWindowTiles(u8 frameType, void *dest)
+{
+ const struct FrameGraphics *frameGraphics = GetTextWindowFrameGraphics(frameType);
+ CpuFastCopy(frameGraphics->tiles, dest, 9 * TILE_SIZE_4BPP);
+}
+
+static void LoadTextWindowPalette(u8 frameType, u8 palSlot)
+{
+ const struct FrameGraphics *frameGraphics = GetTextWindowFrameGraphics(frameType);
+ LoadPalette(frameGraphics->palette, 16 * palSlot, 0x20);
+}
+
+static void DrawTextWindowInternal(u16 *dest, u16 baseTileNum, u8 left, u8 top, u8 right, u8 bottom)
+{
+ u8 x, y;
+ u8 startX, endX;
+ u8 startY, endY;
+
+ startX = (left < right) ? left : right;
+ endX = (right > left) ? right : left;
+
+ startY = (top < bottom) ? top : bottom;
+ endY = (bottom > top) ? bottom : top;
+
+ dest[32 * startY + startX] = baseTileNum | 0xE000;
+
+ for (x = startX + 1; x < endX; x++)
+ dest[32 * startY + x] = (baseTileNum + 1) | 0xE000;
+
+ dest[32 * startY + endX] = (baseTileNum + 2) | 0xE000;
+
+ for (y = startY + 1; y < endY; y++)
+ {
+ dest[32 * y + startX] = (baseTileNum + 3) | 0xE000;
+
+ for (x = startX + 1; x < endX; x++)
+ dest[32 * y + x] = (baseTileNum + 4) | 0xE000;
+
+ dest[32 * y + endX] = (baseTileNum + 5) | 0xE000;
+ }
+
+ dest[32 * endY + startX] = (baseTileNum + 6) | 0xE000;
+
+ for (x = startX + 1; x < endX; x++)
+ dest[32 * endY + x] = (baseTileNum + 7) | 0xE000;
+
+ dest[32 * endY + endX] = (baseTileNum + 8) | 0xE000;
+}
+
+u16 SetMessageBoxBaseTileNum(u16 baseTileNum)
+{
+ sMessageBoxBaseTileNum = baseTileNum;
+ return baseTileNum + 14;
+}
+
+void unref_sub_80651DC(struct Window *win, u8 *text)
+{
+ sub_8002EB0(win, text, sMessageBoxBaseTileNum + 14, 2, 15);
+}
+
+void DisplayMessageBox(struct Window *win)
+{
+ LoadMessageBoxTiles(win);
+ DrawStandardMessageBox(win);
+}
+
+static u16 GetMessageBoxTilemapEntry(u16 baseTilemapEntry, u8 x, u8 y, u8 width, u8 height)
+{
+ u16 tilemapEntry = 9;
+
+ if (y >= height)
+ y = y - height + 3;
+ else if (y > 1)
+ y = 2;
+
+ if (x >= width + 2)
+ x = x - (width + 2) + 4;
+ else if (x > 2)
+ x = 3;
+
+ if (x <= 6 && y <= 4)
+ tilemapEntry = gMessageBoxTilemap[y][x];
+
+ tilemapEntry += baseTilemapEntry;
+
+ return tilemapEntry;
+}
+
+static void DrawMessageBox(struct Window *win, u8 left, u8 top, u8 width, u8 height)
+{
+ u8 i, j;
+ u16 tilemapEntry = (win->paletteNum << 12) | sMessageBoxBaseTileNum;
+ u16 *tilemap = win->config->tilemap;
+
+ for (i = 0; i < height + 2; i++)
+ for (j = 0; j < width + 6; j++)
+ tilemap[(left + j) + 32 * (top + i)] = (win->paletteNum << 12) | GetMessageBoxTilemapEntry(tilemapEntry, j, i, width, height);
+}
+
+void DrawStandardMessageBox(struct Window *win)
+{
+ DrawMessageBox(win, STD_MSG_BOX_LEFT, STD_MSG_BOX_TOP, STD_MSG_BOX_WIDTH, STD_MSG_BOX_HEIGHT);
+}
+
+void LoadMessageBoxTiles(struct Window *win)
+{
+ u8 *tileData = win->config->tileData;
+ CpuFastCopy(gMessageBox_Gfx, tileData + 32 * sMessageBoxBaseTileNum, 14 * TILE_SIZE_4BPP);
+}
+
+void ClearStandardMessageBox(struct Window *win)
+{
+ u8 i;
+ u16 *tilemap = win->config->tilemap + (STD_MSG_BOX_TOP * 32);
+ u16 tilemapEntry = win->paletteNum << 12;
+
+ for (i = 0; i < ((STD_MSG_BOX_HEIGHT + 2) * 32); i++)
+ tilemap[i] = tilemapEntry;
+}
diff --git a/src/engine/tileset_anim.c b/src/engine/tileset_anim.c
new file mode 100644
index 000000000..34685381d
--- /dev/null
+++ b/src/engine/tileset_anim.c
@@ -0,0 +1,630 @@
+#include "global.h"
+#include "tileset_anim.h"
+
+extern u8 *gTilesetAnimTable_General_0[];
+extern u8 *gTilesetAnimTable_General_1[];
+extern u8 *gTilesetAnimTable_General_2[];
+extern u8 *gTilesetAnimTable_General_3[];
+extern u8 *gTilesetAnimTable_General_4[];
+extern u8 *gTilesetAnimTable_Lavaridge[];
+extern u8 *gTilesetAnimTable_Pacifidlog_0[];
+extern u8 *gTilesetAnimTable_Underwater[];
+extern u8 *gTilesetAnimTable_Pacifidlog_1[];
+extern u8 *gUnknown_0837BAE4[];
+extern u8 *gUnknown_0837BB04[];
+extern u8 *gTilesetAnimTable_Mauville_0A[];
+extern u8 *gTilesetAnimTable_Mauville_1A[];
+extern u8 *gTilesetAnimTable_Mauville_0B[];
+extern u8 *gTilesetAnimTable_Mauville_1B[];
+extern u8 *gUnknown_0837BFA4[];
+extern u8 *gTilesetAnimTable_Rustboro_0[];
+extern u8 *gTilesetAnimTable_Rustboro_1[];
+extern u8 *gTilesetAnimTable_Cave[];
+extern u8 *gUnknown_0837C93C[];
+extern u8 *gTilesetAnimTable_EverGrande[];
+extern u8 *gTilesetAnimTable_Building[];
+extern u8 *gTilesetAnimTable_SootopolisGym_0[];
+extern u8 *gTilesetAnimTable_SootopolisGym_1[];
+extern u8 *gTilesetAnimTable_EliteFour_0[];
+extern u8 *gTilesetAnimTable_EliteFour_1[];
+extern u8 *gTilesetAnimTable_MauvilleGym[];
+extern u8 *gTilesetAnimTable_BikeShop[];
+
+struct Dma
+{
+ u8 *src;
+ u8 *dest;
+ u16 size;
+};
+
+EWRAM_DATA static struct Dma gTilesetAnimDmas[20] = {0};
+
+static u8 gNumTilesetAnimDmas;
+static u16 gTileset1AnimFrame;
+static u16 gTileset1AnimLength;
+static u16 gTileset2AnimFrame;
+static u16 gTileset2AnimLength;
+static void (*gTileset1AnimCallback)(u16);
+static void (*gTileset2AnimCallback)(u16);
+
+static void StartTileset1Animation(void);
+static void StartTileset2Animation(void);
+
+static void sub_8073014(u16);
+static void sub_8073058(u16);
+
+static void sub_8073070(u16);
+static void sub_8073098(u16);
+static void sub_80730C0(u16);
+static void sub_80730E8(u16);
+static void sub_807361C(u16);
+
+static void sub_8073424(u16);
+static void sub_80734A0(u16);
+static void sub_8073514(u16);
+static void sub_8073540(u16);
+static void sub_80735B4(u16);
+static void sub_80735E4(u16);
+static void sub_80738A8(u16);
+static void sub_8073600(u16);
+static void sub_80738C0(u16);
+static void sub_8073890(u16);
+static void sub_80738EC(u16);
+
+static void sub_80737A4(u16, u8);
+static void sub_80737E0(u16);
+static void sub_8073704(u16, u8);
+static void sub_8073644(u8);
+static void sub_8073808(u16);
+static void sub_8073830(u16, u8);
+static void sub_807368C(u8);
+static void sub_80736DC(u8);
+static void sub_80736B4(u8);
+static void sub_8073868(u16);
+
+static void sub_8073904(u16);
+static void sub_80739C4(u16);
+static void sub_807392C(u16);
+static void sub_807399C(u16);
+static void sub_8073974(u16);
+static void sub_80739EC(u16);
+
+static void ClearTilesetAnimDmas(void)
+{
+ gNumTilesetAnimDmas = 0;
+ CpuFill32(0, &gTilesetAnimDmas, sizeof(gTilesetAnimDmas));
+}
+
+static void QueueTilesetAnimDma(u8 *src, u8 *dest, u16 size)
+{
+ if (gNumTilesetAnimDmas < 20)
+ {
+ gTilesetAnimDmas[gNumTilesetAnimDmas].src = src;
+ gTilesetAnimDmas[gNumTilesetAnimDmas].dest = dest;
+ gTilesetAnimDmas[gNumTilesetAnimDmas].size = size;
+ gNumTilesetAnimDmas++;
+ }
+}
+
+void sub_8072E74(void)
+{
+ int i;
+ for (i = 0; i < gNumTilesetAnimDmas; i++)
+ {
+ DmaCopy16(3,
+ gTilesetAnimDmas[i].src,
+ gTilesetAnimDmas[i].dest,
+ gTilesetAnimDmas[i].size);
+ }
+ gNumTilesetAnimDmas = 0;
+}
+
+void cur_mapheader_run_tileset_funcs_after_some_cpuset(void)
+{
+ ClearTilesetAnimDmas();
+ StartTileset1Animation();
+ StartTileset2Animation();
+}
+
+void sub_8072ED0(void)
+{
+ StartTileset2Animation();
+}
+
+void sub_8072EDC(void)
+{
+ ClearTilesetAnimDmas();
+ if (++gTileset1AnimFrame >= gTileset1AnimLength)
+ {
+ gTileset1AnimFrame = 0;
+ }
+ if (++gTileset2AnimFrame >= gTileset2AnimLength)
+ {
+ gTileset2AnimFrame = 0;
+ }
+ if (gTileset1AnimCallback)
+ {
+ gTileset1AnimCallback(gTileset1AnimFrame);
+ }
+ if (gTileset2AnimCallback)
+ {
+ gTileset2AnimCallback(gTileset2AnimFrame);
+ }
+}
+
+static void StartTileset1Animation(void)
+{
+ gTileset1AnimFrame = 0;
+ gTileset1AnimLength = 0;
+ gTileset1AnimCallback = 0;
+ if (gMapHeader.mapData->primaryTileset)
+ {
+ if (gMapHeader.mapData->primaryTileset->callback)
+ {
+ gMapHeader.mapData->primaryTileset->callback();
+ }
+ }
+}
+
+static void StartTileset2Animation(void)
+{
+ gTileset2AnimFrame = 0;
+ gTileset2AnimLength = 0;
+ gTileset2AnimCallback = 0;
+ if (gMapHeader.mapData->secondaryTileset)
+ {
+ if (gMapHeader.mapData->secondaryTileset->callback)
+ {
+ gMapHeader.mapData->secondaryTileset->callback();
+ }
+ }
+}
+
+void TilesetCB_General(void)
+{
+ gTileset1AnimFrame = 0;
+ gTileset1AnimLength = 0x100;
+ gTileset1AnimCallback = sub_8073014;
+}
+
+void TilesetCB_Building(void)
+{
+ gTileset1AnimFrame = 0;
+ gTileset1AnimLength = 0x100;
+ gTileset1AnimCallback = sub_8073058;
+}
+
+static void sub_8073014(u16 a1)
+{
+ int v1;
+ v1 = a1 % 0x10;
+ if (v1 == 0) sub_8073070(a1 / 0x10);
+ if (v1 == 1) sub_8073098(a1 / 0x10);
+ if (v1 == 2) sub_80730C0(a1 / 0x10);
+ if (v1 == 3) sub_80730E8(a1 / 0x10);
+ if (v1 == 4) sub_807361C(a1 / 0x10);
+}
+
+static void sub_8073058(u16 a1)
+{
+ if (a1 % 8 == 0)
+ {
+ sub_8073904(a1 / 8);
+ }
+}
+
+static void sub_8073070(u16 a1)
+{
+ int v1;
+ v1 = a1 % 4;
+ QueueTilesetAnimDma(gTilesetAnimTable_General_0[v1], (u8 *)(BG_VRAM + 0x3f80), 0x80);
+}
+
+static void sub_8073098(u16 a1)
+{
+ u8 v1;
+ v1 = a1 % 8;
+ QueueTilesetAnimDma(gTilesetAnimTable_General_1[v1], (u8 *)(BG_VRAM + 0x3600), 0x3c0);
+}
+
+static void sub_80730C0(u16 a1)
+{
+ int v1;
+ v1 = a1 % 8;
+ QueueTilesetAnimDma(gTilesetAnimTable_General_2[v1], (u8 *)(BG_VRAM + 0x3a00), 0x140);
+}
+
+static void sub_80730E8(u16 a1)
+{
+ int v1;
+ v1 = a1 % 4;
+ QueueTilesetAnimDma(gTilesetAnimTable_General_3[v1], (u8 *)(BG_VRAM + 0x3e00), 0xc0);
+}
+
+void TilesetCB_Petalburg(void)
+{
+ gTileset2AnimFrame = 0;
+ gTileset2AnimLength = gTileset1AnimLength;
+ gTileset2AnimCallback = 0;
+}
+
+void TilesetCB_Rustboro(void)
+{
+ gTileset2AnimFrame = 0;
+ gTileset2AnimLength = gTileset1AnimLength;
+ gTileset2AnimCallback = sub_8073424;
+}
+
+void TilesetCB_Dewford(void)
+{
+ gTileset2AnimFrame = 0;
+ gTileset2AnimLength = gTileset1AnimLength;
+ gTileset2AnimCallback = 0;
+}
+
+void TilesetCB_Slateport(void)
+{
+ gTileset2AnimFrame = 0;
+ gTileset2AnimLength = gTileset1AnimLength;
+ gTileset2AnimCallback = 0;
+}
+
+void TilesetCB_Mauville(void)
+{
+ gTileset2AnimFrame = gTileset1AnimFrame;
+ gTileset2AnimLength = gTileset1AnimLength;
+ gTileset2AnimCallback = sub_80734A0;
+}
+
+void TilesetCB_Lavaridge(void)
+{
+ gTileset2AnimFrame = 0;
+ gTileset2AnimLength = gTileset1AnimLength;
+ gTileset2AnimCallback = sub_8073514;
+}
+
+void TilesetCB_Fallarbor(void)
+{
+ gTileset2AnimFrame = 0;
+ gTileset2AnimLength = gTileset1AnimLength;
+ gTileset2AnimCallback = 0;
+}
+
+void TilesetCB_Fortree(void)
+{
+ gTileset2AnimFrame = 0;
+ gTileset2AnimLength = gTileset1AnimLength;
+ gTileset2AnimCallback = 0;
+}
+
+void TilesetCB_Lilycove(void)
+{
+ gTileset2AnimFrame = 0;
+ gTileset2AnimLength = gTileset1AnimLength;
+ gTileset2AnimCallback = 0;
+}
+
+void TilesetCB_Mossdeep(void)
+{
+ gTileset2AnimFrame = 0;
+ gTileset2AnimLength = gTileset1AnimLength;
+ gTileset2AnimCallback = 0;
+}
+
+void TilesetCB_EverGrande(void)
+{
+ gTileset2AnimFrame = 0;
+ gTileset2AnimLength = gTileset1AnimLength;
+ gTileset2AnimCallback = sub_8073540;
+}
+
+void TilesetCB_Pacifidlog(void)
+{
+ gTileset2AnimFrame = gTileset1AnimFrame;
+ gTileset2AnimLength = gTileset1AnimLength;
+ gTileset2AnimCallback = sub_80735B4;
+}
+
+void TilesetCB_Sootopolis(void)
+{
+ gTileset2AnimFrame = 0;
+ gTileset2AnimLength = gTileset1AnimLength;
+ gTileset2AnimCallback = 0;
+}
+
+void TilesetCB_Underwater(void)
+{
+ gTileset2AnimFrame = 0;
+ gTileset2AnimLength = 0x80;
+ gTileset2AnimCallback = sub_80735E4;
+}
+
+void TilesetCB_SootopolisGym(void)
+{
+ gTileset2AnimFrame = 0;
+ gTileset2AnimLength = 0xf0;
+ gTileset2AnimCallback = sub_80738A8;
+}
+
+void TilesetCB_Cave(void)
+{
+ gTileset2AnimFrame = 0;
+ gTileset2AnimLength = gTileset1AnimLength;
+ gTileset2AnimCallback = sub_8073600;
+}
+
+void TilesetCB_EliteFour(void)
+{
+ gTileset2AnimFrame = 0;
+ gTileset2AnimLength = 0x80;
+ gTileset2AnimCallback = sub_80738C0;
+}
+
+void TilesetCB_MauvilleGym(void)
+{
+ gTileset2AnimFrame = 0;
+ gTileset2AnimLength = gTileset1AnimLength;
+ gTileset2AnimCallback = sub_8073890;
+}
+
+void TilesetCB_BikeShop(void)
+{
+ gTileset2AnimFrame = 0;
+ gTileset2AnimLength = gTileset1AnimLength;
+ gTileset2AnimCallback = sub_80738EC;
+}
+
+static void sub_8073424(u16 a1)
+{
+ int v1;
+ v1 = a1 % 8;
+ if (v1 == 0)
+ {
+ sub_80737A4(a1 / 8, 0);
+ sub_80737E0(a1 / 8);
+ }
+ if (v1 == 1) sub_80737A4(a1 / 8, 1);
+ if (v1 == 2) sub_80737A4(a1 / 8, 2);
+ if (v1 == 3) sub_80737A4(a1 / 8, 3);
+ if (v1 == 4) sub_80737A4(a1 / 8, 4);
+ if (v1 == 5) sub_80737A4(a1 / 8, 5);
+ if (v1 == 6) sub_80737A4(a1 / 8, 6);
+ if (v1 == 7) sub_80737A4(a1 / 8, 7);
+}
+
+static void sub_80734A0(u16 a1)
+{
+ int v1;
+ v1 = a1 % 8;
+ if (v1 == 0) sub_8073704(a1 / 8, 0);
+ if (v1 == 1) sub_8073704(a1 / 8, 1);
+ if (v1 == 2) sub_8073704(a1 / 8, 2);
+ if (v1 == 3) sub_8073704(a1 / 8, 3);
+ if (v1 == 4) sub_8073704(a1 / 8, 4);
+ if (v1 == 5) sub_8073704(a1 / 8, 5);
+ if (v1 == 6) sub_8073704(a1 / 8, 6);
+ if (v1 == 7) sub_8073704(a1 / 8, 7);
+}
+
+static void sub_8073514(u16 a1)
+{
+ int v1;
+ v1 = a1 % 0x10;
+ if (v1 == 0) sub_8073644(a1 / 0x10);
+ if (v1 == 1) sub_8073808(a1 / 0x10);
+}
+
+static void sub_8073540(u16 a1)
+{
+ int v1;
+ v1 = a1 % 8;
+ if (v1 == 0) sub_8073830(a1 / 8, 0);
+ if (v1 == 1) sub_8073830(a1 / 8, 1);
+ if (v1 == 2) sub_8073830(a1 / 8, 2);
+ if (v1 == 3) sub_8073830(a1 / 8, 3);
+ if (v1 == 4) sub_8073830(a1 / 8, 4);
+ if (v1 == 5) sub_8073830(a1 / 8, 5);
+ if (v1 == 6) sub_8073830(a1 / 8, 6);
+ if (v1 == 7) sub_8073830(a1 / 8, 7);
+}
+
+static void sub_80735B4(u16 a1)
+{
+ int v1;
+ v1 = a1 % 0x10;
+ if (v1 == 0) sub_807368C(a1 / 0x10);
+ if (v1 == 1) sub_80736DC(a1 / 0x10);
+}
+
+static void sub_80735E4(u16 a1)
+{
+ int v1;
+ v1 = a1 % 0x10;
+ if (v1 == 0) sub_80736B4(a1 / 0x10);
+}
+
+static void sub_8073600(u16 a1)
+{
+ int v1;
+ v1 = a1 % 0x10;
+ if (v1 == 1) sub_8073868(a1 / 0x10);
+}
+
+static void sub_807361C(u16 a1)
+{
+ int v1;
+ v1 = a1 % 4;
+ QueueTilesetAnimDma(gTilesetAnimTable_General_4[v1], (u8 *)(BG_VRAM + 0x3c00), 0x140);
+}
+
+static void sub_8073644(u8 a1)
+{
+ u8 v1;
+ v1 = a1 % 4;
+ QueueTilesetAnimDma(gTilesetAnimTable_Lavaridge[v1], (u8 *)(BG_VRAM + 0x6400), 0x80);
+ v1 = (a1 + 2) % 4;
+ QueueTilesetAnimDma(gTilesetAnimTable_Lavaridge[v1], (u8 *)(BG_VRAM + 0x6480), 0x80);
+}
+
+static void sub_807368C(u8 a1)
+{
+ int v1;
+ v1 = a1 % 4;
+ QueueTilesetAnimDma(gTilesetAnimTable_Pacifidlog_0[v1], (u8 *)(BG_VRAM + 0x7a00), 0x3c0);
+}
+
+static void sub_80736B4(u8 a1)
+{
+ int v1;
+ v1 = a1 % 4;
+ QueueTilesetAnimDma(gTilesetAnimTable_Underwater[v1], (u8 *)(BG_VRAM + 0x7e00), 0x80);
+}
+
+static void sub_80736DC(u8 a1)
+{
+ int v1;
+ v1 = a1 % 8;
+ QueueTilesetAnimDma(gTilesetAnimTable_Pacifidlog_1[v1], (u8 *)(BG_VRAM + 0x7e00), 0x100);
+}
+
+static void sub_8073704(u16 a1, u8 a2)
+{
+ int v1;
+ a1 -= a2;
+ if (a1 < 12)
+ {
+ v1 = a1 % 12;
+ QueueTilesetAnimDma(gTilesetAnimTable_Mauville_0A[v1], gUnknown_0837BAE4[a2], 0x80);
+ QueueTilesetAnimDma(gTilesetAnimTable_Mauville_1A[v1], gUnknown_0837BB04[a2], 0x80);
+ }
+ else
+ {
+ v1 = a1 % 4;
+ QueueTilesetAnimDma(gTilesetAnimTable_Mauville_0B[v1], gUnknown_0837BAE4[a2], 0x80);
+ QueueTilesetAnimDma(gTilesetAnimTable_Mauville_1B[v1], gUnknown_0837BB04[a2], 0x80);
+ }
+}
+
+static void sub_80737A4(u16 a1, u8 a2)
+{
+ int v1;
+ a1 -= a2;
+
+ v1 = a1 % 8;
+ if (gTilesetAnimTable_Rustboro_0[v1])
+ {
+ QueueTilesetAnimDma(gTilesetAnimTable_Rustboro_0[v1], gUnknown_0837BFA4[a2], 0x80);
+ }
+}
+
+static void sub_80737E0(u16 a1)
+{
+ int v1;
+ v1 = a1 % 2;
+ QueueTilesetAnimDma(gTilesetAnimTable_Rustboro_1[v1], (u8 *)(BG_VRAM + 0x7800), 0x80);
+}
+
+static void sub_8073808(u16 a1)
+{
+ int v1;
+ v1 = a1 % 4;
+ QueueTilesetAnimDma(gTilesetAnimTable_Cave[v1], (u8 *)(BG_VRAM + 0x5400), 0x80);
+}
+
+static void sub_8073830(u16 a1, u8 a2)
+{
+ int v1;
+ a1 -= a2;
+ v1 = a1 % 8;
+ QueueTilesetAnimDma(gTilesetAnimTable_EverGrande[v1], gUnknown_0837C93C[a2], 0x80);
+}
+
+static void sub_8073868(u16 a1)
+{
+ int v1;
+ v1 = a1 % 4;
+ QueueTilesetAnimDma(gTilesetAnimTable_Cave[v1], (u8 *)(BG_VRAM + 0x7400), 0x80);
+}
+
+static void sub_8073890(u16 a1)
+{
+ int v1;
+ v1 = a1 % 2;
+ if (!v1)
+ {
+ sub_80739C4(a1 / 2);
+ }
+}
+
+static void sub_80738A8(u16 a1)
+{
+ int v1;
+ v1 = a1 % 8;
+ if (!v1)
+ {
+ sub_807392C(a1 / 8);
+ }
+}
+
+static void sub_80738C0(u16 a1)
+{
+ if (a1 % 0x40 == 0)
+ {
+ sub_807399C(a1 / 0x40);
+ }
+ if (a1 % 8 == 1)
+ {
+ sub_8073974(a1 / 8);
+ }
+}
+
+static void sub_80738EC(u16 a1)
+{
+ if (a1 % 4 == 0)
+ {
+ sub_80739EC(a1 / 4);
+ }
+}
+
+static void sub_8073904(u16 a1)
+{
+ int v1;
+ v1 = a1 % 2;
+ QueueTilesetAnimDma(gTilesetAnimTable_Building[v1], (u8 *)(BG_VRAM + 0x3e00), 0x80);
+}
+
+static void sub_807392C(u16 a1)
+{
+ int v1;
+ v1 = a1 % 3;
+ QueueTilesetAnimDma(gTilesetAnimTable_SootopolisGym_0[v1], (u8 *)(BG_VRAM + 0x7e00), 0x180);
+ QueueTilesetAnimDma(gTilesetAnimTable_SootopolisGym_1[v1], (u8 *)(BG_VRAM + 0x7a00), 0x280);
+}
+
+static void sub_8073974(u16 a1)
+{
+ int v1;
+ v1 = a1 % 4;
+ QueueTilesetAnimDma(gTilesetAnimTable_EliteFour_0[v1], (u8 *)(BG_VRAM + 0x7f00), 0x20);
+}
+
+static void sub_807399C(u16 a1)
+{
+ int v1;
+ v1 = a1 % 2;
+ QueueTilesetAnimDma(gTilesetAnimTable_EliteFour_1[v1], (u8 *)(BG_VRAM + 0x7c00), 0x80);
+}
+
+static void sub_80739C4(u16 a1)
+{
+ int v1;
+ v1 = a1 % 2;
+ QueueTilesetAnimDma(gTilesetAnimTable_MauvilleGym[v1], (u8 *)(BG_VRAM + 0x5200), 0x200);
+}
+
+static void sub_80739EC(u16 a1)
+{
+ int v1;
+ v1 = a1 % 2;
+ QueueTilesetAnimDma(gTilesetAnimTable_BikeShop[v1], (u8 *)(BG_VRAM + 0x7e00), 0x120);
+}
diff --git a/src/engine/time_events.c b/src/engine/time_events.c
new file mode 100644
index 000000000..accb03db8
--- /dev/null
+++ b/src/engine/time_events.c
@@ -0,0 +1,118 @@
+#include "global.h"
+#include "time_events.h"
+#include "event_data.h"
+#include "field_weather.h"
+#include "pokemon.h"
+#include "rng.h"
+#include "overworld.h"
+#include "rtc.h"
+#include "script.h"
+#include "task.h"
+
+static u32 GetMirageRnd(void)
+{
+ u32 hi = VarGet(VAR_MIRAGE_RND_H);
+ u32 lo = VarGet(VAR_MIRAGE_RND_L);
+ return (hi << 16) | lo;
+}
+
+static void SetMirageRnd(u32 rnd)
+{
+ VarSet(VAR_MIRAGE_RND_H, rnd >> 16);
+ VarSet(VAR_MIRAGE_RND_L, rnd);
+}
+
+// unused
+void InitMirageRnd(void)
+{
+ SetMirageRnd((Random() << 16) | Random());
+}
+
+void UpdateMirageRnd(u16 days)
+{
+ s32 rnd = GetMirageRnd();
+ while (days)
+ {
+ rnd = 1103515245 * rnd + 12345;
+ days--;
+ }
+ SetMirageRnd(rnd);
+}
+
+bool8 IsMirageIslandPresent(void)
+{
+ u16 rnd = GetMirageRnd() >> 16;
+ int i;
+
+ for (i = 0; i < PARTY_SIZE; i++)
+ if (GetMonData(&gPlayerParty[i], MON_DATA_SPECIES) && (GetMonData(&gPlayerParty[i], MON_DATA_PERSONALITY) & 0xFFFF) == rnd)
+ return TRUE;
+
+ return FALSE;
+}
+
+void UpdateShoalTideFlag(void)
+{
+ static const u8 tide[] =
+ {
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 1,
+ };
+
+ if (is_map_type_1_2_3_5_or_6(get_map_type_from_warp0()))
+ {
+ RtcCalcLocalTime();
+ if (tide[gLocalTime.hours])
+ FlagSet(SYS_SHOAL_TIDE);
+ else
+ FlagClear(SYS_SHOAL_TIDE);
+ }
+}
+
+static void Task_WaitWeather(u8 taskId)
+{
+ if (sub_807DDFC())
+ {
+ EnableBothScriptContexts();
+ DestroyTask(taskId);
+ }
+}
+
+void WaitWeather(void)
+{
+ CreateTask(Task_WaitWeather, 80);
+}
+
+void InitBirchState(void)
+{
+ *(u16 *)GetVarPointer(VAR_BIRCH_STATE) = 0;
+}
+
+void UpdateBirchState(u16 days)
+{
+ u16 *state = GetVarPointer(VAR_BIRCH_STATE);
+ *state += days;
+ *state %= 7;
+}
diff --git a/src/engine/trade.c b/src/engine/trade.c
new file mode 100644
index 000000000..0816fe555
--- /dev/null
+++ b/src/engine/trade.c
@@ -0,0 +1,89 @@
+#include "global.h"
+#include "name_string_util.h"
+#include "string_util.h"
+#include "text.h"
+
+struct InGameTrade {
+ /*0x00*/ u8 name[11];
+ /*0x0C*/ u16 species;
+ /*0x0E*/ u8 ivs[6];
+ /*0x14*/ bool8 secondAbility;
+ /*0x18*/ u32 otId;
+ /*0x1C*/ u8 stats[5];
+ /*0x24*/ u32 personality;
+ /*0x28*/ u16 heldItem;
+ /*0x2A*/ u8 mailNum;
+ /*0x2B*/ u8 otName[11];
+ /*0x36*/ u8 otGender;
+ /*0x37*/ u8 sheen;
+ /*0x38*/ u16 playerSpecies;
+};
+
+struct UnkStructC {
+ /*0x00*/ u16 words[9];
+ /*0x10*/ u8 string[8];
+ /*0x1A*/ u8 otId[4];
+ /*0x1E*/ u16 species;
+ /*0x20*/ u16 heldItem;
+};
+
+struct UnkStructD {
+ /*0x00*/ u8 pad00[0x10];
+ /*0x10*/ u8 var10;
+ /*0x11*/ u8 pad11[1];
+ /*0x12*/ u16 var12[1];
+};
+
+extern const struct InGameTrade gIngameTrades[];
+extern const u16 gIngameTradeMail[][10];
+
+IWRAM_DATA u8 gUnknown_03000508[8];
+
+void sub_804A96C(struct UnkStructD *arg0, u8 left, u8 top, u16 *tilemap, u8 width, u8 height, u16 sp8) {
+ int y, x;
+
+ for (y = 0; y < height; y++)
+ {
+
+ for (x = 0; x < width; x++)
+ {
+ arg0->var12[(top * 32 + left) + y * 32 + x] = tilemap[width * y + x] | sp8;
+ }
+ }
+
+#if ENGLISH
+ arg0->var10 = 1;
+#endif
+}
+
+#if GERMAN
+void sub_804A96C_alt(struct UnkStructD *arg0, u8 left, u8 top, u16 *tilemap, u8 width, u8 height, u16 sp8) {
+ sub_804A96C(arg0, left, top, tilemap, width, height, sp8);
+
+ arg0->var10 = 1;
+}
+#endif
+
+asm(".section .text.sub_804DAD4");
+
+void sub_804DAD4(struct UnkStructC *arg0, struct InGameTrade *trade) {
+ s32 i;
+
+ for (i = 0; i < 9; i++)
+ {
+ arg0->words[i] = gIngameTradeMail[trade->mailNum][i];
+ }
+
+ StringCopy(arg0->string, trade->otName);
+
+#if GERMAN
+ PadNameString(arg0->string, CHAR_SPACE);
+#endif
+
+ arg0->otId[0] = trade->otId >> 24;
+ arg0->otId[1] = trade->otId >> 16;
+ arg0->otId[2] = trade->otId >> 8;
+ arg0->otId[3] = trade->otId;
+ arg0->species = trade->species;
+ arg0->heldItem = trade->heldItem;
+}
diff --git a/src/engine/trainer_card.c b/src/engine/trainer_card.c
new file mode 100644
index 000000000..bcb62ede6
--- /dev/null
+++ b/src/engine/trainer_card.c
@@ -0,0 +1,1569 @@
+#include "global.h"
+#include "trainer_card.h"
+#include "easy_chat.h"
+#include "event_data.h"
+#include "field_effect.h"
+#include "graphics.h"
+#include "link.h"
+#include "main.h"
+#include "menu.h"
+#include "money.h"
+#include "palette.h"
+#include "pokedex.h"
+#include "overworld.h"
+#include "script_pokemon_80C4.h"
+#include "songs.h"
+#include "sound.h"
+#include "sprite.h"
+#include "string_util.h"
+#include "strings2.h"
+#include "task.h"
+#include "unknown_task.h"
+#include "util.h"
+
+typedef void (*Callback)(void);
+
+struct Struct2000000
+{
+ /*0x00*/ u8 var_0;
+ /*0x01*/ bool8 var_1;
+ /*0x02*/ u8 var_2;
+ /*0x03*/ bool8 var_3;
+ /*0x04*/ u8 var_4;
+ /*0x05*/ u8 var_5;
+ /*0x06*/ u8 var_6;
+ /*0x07*/ bool8 var_7;
+ /*0x08*/ bool8 var_8;
+ /*0x09*/ bool8 var_9;
+ /*0x0A*/ bool8 var_a;
+ /*0x0B*/ bool8 var_b;
+ /*0x0C*/ bool8 var_c;
+ /*0x0D*/ bool8 var_d;
+ /*0x0E*/ u8 var_e[8];
+ /*0x16*/ u8 filler_16[10];
+ /*0x20*/ u8 var_20[4][0x10];
+ /*0x60*/ Callback *var_60;
+ /*0x64*/ struct TrainerCard var_64;
+ /*0x9C*/ u8 language; // 0x9C
+};
+
+extern u8 ewram[];
+#define ewram0 (*(struct Struct2000000 *)(ewram))
+
+extern struct LinkPlayerMapObject gLinkPlayerMapObjects[];
+
+EWRAM_DATA struct TrainerCard gTrainerCards[4] = {0};
+
+struct UnknownStruct1
+{
+ u16 filler0[0x3C0];
+ u16 unk780[160];
+};
+extern struct UnknownStruct1 gUnknown_03004DE0;
+
+extern const u8 gBadgesTiles[];
+extern const u16 gUnknown_083B5F0C[];
+extern const u16 gBadgesPalette[];
+extern const u16 gUnknown_083B5F4C[];
+extern const u16 gUnknown_083B5F6C[];
+extern const u16 gUnknown_083B5F8C[][4];
+
+const u8 gBadgesTiles[] = INCBIN_U8("graphics/trainer_card/badges.4bpp");
+// XXX: what is this?
+u8 *const ewram_ = ewram;
+
+bool8 sub_8093864(struct Task *);
+bool8 sub_80938A8(struct Task *);
+bool8 sub_80938CC(struct Task *);
+bool8 sub_8093918(struct Task *);
+bool8 sub_8093938(struct Task *);
+bool8 sub_8093954(struct Task *);
+bool8 sub_8093980(struct Task *);
+
+bool8 (*const gUnknown_083B5EBC[])(struct Task *) =
+{
+ sub_8093864,
+ sub_80938A8,
+ sub_80938CC,
+ sub_8093918,
+ sub_8093938,
+ sub_8093954,
+ sub_8093980,
+};
+
+bool8 sub_8093AA0(struct Task *);
+bool8 sub_8093AF0(struct Task *);
+bool8 sub_8093C0C(struct Task *);
+bool8 sub_8093C38(struct Task *);
+bool8 sub_8093D50(struct Task *);
+
+bool8 (*const gUnknown_083B5ED8[])(struct Task *) =
+{
+ sub_8093AA0,
+ sub_8093AF0,
+ sub_8093C0C,
+ sub_8093C38,
+ sub_8093D50,
+};
+
+// FIXME: Other signature than on save_menu_util.h
+void FormatPlayTime(u8 *playtime, u16 hours, u16 minutes, s16 colon);
+u16 GetPokedexSeenCount(void);
+
+enum
+{
+ TD_0,
+ TD_1,
+ TD_CALLBACK,
+};
+
+static void sub_8093174(void);
+static void sub_809323C(void);
+static void sub_8093254(void);
+static void sub_80932AC(Callback callBack);
+static void sub_80932E4(u8 arg1, Callback callBack);
+void sub_8093324(void);
+static void nullsub_60(u8);
+static u32 sav12_xor_get_clamped_above(u8 index, u32 maxVal);
+static u8 sub_80934F4(struct TrainerCard *);
+static void sub_8093534(void);
+static void sub_8093550(void);
+static void sub_8093598(void);
+static void sub_80935EC(void);
+static void sub_8093610(void);
+static void sub_8093688(void);
+void sub_80936D4(void);
+static void sub_80937A4(void);
+static void sub_80937BC(void);
+static void sub_80937D8(void);
+static void sub_80937F0(void);
+static void nullsub_15(void);
+static void sub_8093800(void);
+static void sub_809380C();
+static void sub_809382C(u8 taskId);
+static void sub_80939A4(void);
+static void sub_80939C0(void);
+static void sub_80939DC(u8 taskId);
+static void sub_8093A28(void);
+static u8 sub_8093A48(void);
+static void sub_8093A68(u8 taskId);
+void sub_8093D7C(void);
+static void sub_8093DAC(void);
+static void sub_8093DC8(void);
+static void sub_8093DEC(void);
+static void sub_8093E04(void);
+static void sub_8093E28(void);
+void sub_8093EA0(void);
+static void sub_8093EF8(void);
+static void sub_8093F14(void);
+static void sub_8093F48(void);
+static void sub_8093F64(void);
+static void sub_8093F80(void);
+static void sub_8093FD0(void);
+static void sub_8094038(void);
+static void sub_80940E4(void);
+static void sub_8094110(void);
+static void sub_8094140(void);
+static void sub_8094188(void);
+static void TrainerCard_Front_PrintTrainerID(void);
+static void TrainerCard_Front_PrintMoney(void);
+static void TrainerCard_Front_PrintPokedexCount(void);
+static void TrainerCard_Front_PrintPlayTime(u8 *arg1, s16 colon);
+static void sub_809429C(void);
+static void TrainerCard_Back_PrintName(void);
+static void TrainerCard_Back_PrintHallOfFameTime_Label(void);
+static void TrainerCard_Back_PrintHallOfFameTime(void);
+static void TrainerCard_Back_PrintLinkBattlesLabel(void);
+static void TrainerCard_Back_PrintLinkBattles(void);
+static void TrainerCard_Back_PrintBattleTower_Label(void);
+static void TrainerCard_Back_PrintBattleTower(void);
+static void TrainerCard_Back_PrintLinkContests_Label(void);
+static void TrainerCard_Back_PrintLinkContests(void);
+static void TrainerCard_Back_PrintLinkPokeblocks_Label(void);
+static void TrainerCard_Back_PrintLinkPokeblocks(void);
+static void TrainerCard_Back_PrintPokemonTrades_Label(void);
+static void TrainerCard_Back_PrintPokemonTrades(void);
+void unref_sub_8094588(u16 left, u16 top);
+
+void sub_8093110(Callback arg1)
+{
+ sub_80932AC(arg1);
+ SetMainCallback2(sub_8093174);
+ ewram0.language = GAME_LANGUAGE;
+}
+
+void sub_8093130(u8 playerIndex, Callback arg2)
+{
+ sub_80932E4(playerIndex, arg2);
+ SetMainCallback2(sub_8093174);
+ ewram0.language = gLinkPlayers[gLinkPlayerMapObjects[playerIndex].linkPlayerId].language;
+}
+
+static void sub_8093174(void)
+{
+ switch (gMain.state)
+ {
+ case 0:
+ sub_8093534();
+ sub_8093688();
+ gMain.state++;
+ break;
+ case 1:
+ sub_8093598();
+ gMain.state++;
+ break;
+ case 2:
+ sub_80935EC();
+ gMain.state++;
+ break;
+ case 3:
+ sub_8093610();
+ sub_80937A4();
+ gMain.state++;
+ break;
+ case 4:
+ sub_80937BC();
+ gMain.state++;
+ case 5:
+ if (MultistepInitMenuWindowContinue())
+ gMain.state++;
+ break;
+ case 6:
+ sub_80937F0();
+ gMain.state++;
+ break;
+ case 7:
+ sub_80937D8();
+ gMain.state++;
+ break;
+ case 8:
+ nullsub_15();
+ sub_8093800();
+ sub_8093550();
+ SetMainCallback2(sub_809323C);
+ break;
+ }
+}
+
+static void sub_809323C(void)
+{
+ RunTasks();
+ AnimateSprites();
+ BuildOamBuffer();
+ UpdatePaletteFade();
+}
+
+static void sub_8093254(void)
+{
+ LoadOam();
+ ProcessSpriteCopyRequests();
+ TransferPlttBuffer();
+ ewram0.var_6++;
+ if (ewram0.var_6 >= 60)
+ {
+ ewram0.var_6 = 0;
+ ewram0.var_5 ^= 1;
+ }
+ if (ewram0.var_4)
+ DmaCopy16(3, gUnknown_03004DE0.filler0, gUnknown_03004DE0.unk780, sizeof(gUnknown_03004DE0.unk780));
+}
+
+static void sub_80932AC(Callback callBack)
+{
+ u8 taskId = CreateTask(nullsub_60, 0xFF);
+ struct Task *task = &gTasks[taskId];
+ task->data[TD_0] = FALSE;
+ StoreWordInTwoHalfwords(&task->data[TD_CALLBACK], (u32)callBack);
+}
+
+static void sub_80932E4(u8 arg1, Callback callBack)
+{
+ u8 taskId = CreateTask(nullsub_60, 0xFF);
+
+ struct Task *task = &gTasks[taskId];
+ task->data[TD_0] = TRUE;
+ task->data[TD_1] = arg1;
+ StoreWordInTwoHalfwords(&task->data[TD_CALLBACK], (u32)callBack);
+}
+
+void sub_8093324(void)
+{
+ u8 taskId = FindTaskIdByFunc(nullsub_60);
+ struct Task *task = &gTasks[taskId];
+ ewram0.var_1 = task->data[TD_0];
+
+ LoadWordFromTwoHalfwords((u16 *)&task->data[TD_CALLBACK], (u32 *)&ewram0.var_60);
+
+ if (ewram0.var_1)
+ {
+ struct TrainerCard(*trainerCards)[4] = &gTrainerCards;
+ s16 var = task->data[TD_1];
+ struct TrainerCard *dest = &(*trainerCards)[var];
+ memcpy(&ewram0.var_64, dest, sizeof(struct TrainerCard));
+ }
+ else
+ {
+ sub_8093390(&ewram0.var_64);
+ }
+}
+
+static void nullsub_60(u8 taskid)
+{
+}
+
+void sub_8093390(struct TrainerCard *arg1)
+{
+ u32 playTime;
+ bool32 enteredHallOfFame;
+ bool8 r4;
+ u8 i;
+
+ arg1->gender = gSaveBlock2.playerGender;
+ arg1->playTimeHours = gSaveBlock2.playTimeHours;
+ arg1->playTimeMinutes = gSaveBlock2.playTimeMinutes;
+
+ playTime = GetGameStat(GAME_STAT_FIRST_HOF_PLAY_TIME);
+ enteredHallOfFame = GetGameStat(GAME_STAT_ENTERED_HOF);
+ if (!enteredHallOfFame)
+ {
+ playTime = 0;
+ }
+ arg1->firstHallOfFameA = playTime >> 16;
+ arg1->firstHallOfFameB = (playTime >> 8) & 0xFF;
+ arg1->firstHallOfFameC = playTime & 0xFF;
+
+ arg1->hasPokedex = FlagGet(SYS_POKEDEX_GET);
+ arg1->var_3 = sub_8090FC0();
+ arg1->pokedexSeen = GetPokedexSeenCount();
+
+ arg1->trainerId = (gSaveBlock2.playerTrainerId[1] << 8) | gSaveBlock2.playerTrainerId[0];
+
+ // Link Cable Battles
+ arg1->linkBattleWins = sav12_xor_get_clamped_above(GAME_STAT_LINK_BATTLE_WINS, 9999);
+ arg1->linkBattleLosses = sav12_xor_get_clamped_above(GAME_STAT_LINK_BATTLE_LOSSES, 9999);
+
+ // Contests w/ Friends
+ arg1->contestsWithFriends = sav12_xor_get_clamped_above(GAME_STAT_WON_LINK_CONTEST, 999);
+
+ // Pokéblocks w/ Friends
+ arg1->pokeblocksWithFriends = sav12_xor_get_clamped_above(GAME_STAT_POKEBLOCKS_WITH_FRIENDS, 0xFFFF);
+
+ // Pokémon Trades
+ arg1->pokemonTrades = sav12_xor_get_clamped_above(GAME_STAT_POKEMON_TRADES, 0xFFFF);
+
+ // Battle tower?
+ arg1->battleTowerWins = gSaveBlock2.filler_A8.var_4C8;
+ arg1->battleTowerLosses = gSaveBlock2.filler_A8.var_4CA;
+ if (arg1->battleTowerWins > 9999)
+ {
+ arg1->battleTowerWins = 9999;
+ }
+ if (arg1->battleTowerLosses > 9999)
+ {
+ arg1->battleTowerLosses = 9999;
+ }
+
+ r4 = FALSE;
+ if (sub_80C4D50() > 4)
+ {
+ r4 = TRUE;
+ }
+ arg1->var_4 = r4;
+
+ arg1->money = gSaveBlock1.money;
+
+ for (i = 0; i < 4; i++)
+ {
+ arg1->var_28[i] = gSaveBlock1.unk2B1C[i];
+ }
+
+ for (i = 0; i < 8; i++)
+ {
+ arg1->playerName[i] = gSaveBlock2.playerName[i];
+ }
+
+ arg1->stars = sub_80934F4(arg1);
+}
+
+u8 sub_80934C4(u8 id)
+{
+ return gTrainerCards[id].stars;
+}
+
+static u32 sav12_xor_get_clamped_above(u8 index, u32 maxVal)
+{
+ u32 value = GetGameStat(index);
+
+ if (value > maxVal)
+ {
+ value = maxVal;
+ }
+
+ return value;
+}
+
+static u8 sub_80934F4(struct TrainerCard *trainerCard)
+{
+ u8 value = 0;
+
+ if (trainerCard->firstHallOfFameA != 0 || trainerCard->firstHallOfFameB != 0 || trainerCard->firstHallOfFameC != 0)
+ {
+ value++;
+ }
+
+ if (trainerCard->var_3)
+ {
+ value++;
+ }
+
+ if (trainerCard->battleTowerLosses > 49)
+ {
+ value++;
+ }
+
+ if (trainerCard->var_4)
+ {
+ value++;
+ }
+
+ return value;
+}
+
+static void sub_8093534(void)
+{
+ SetVBlankCallback(NULL);
+ SetHBlankCallback(NULL);
+ REG_DISPCNT = 0;
+}
+
+static void sub_8093550(void)
+{
+ u16 backup;
+
+ SetVBlankCallback(sub_8093254);
+
+ backup = REG_IME;
+ REG_IME = 0;
+ REG_IE |= INTR_FLAG_VBLANK | INTR_FLAG_HBLANK;
+ REG_IME = backup;
+
+ REG_DISPSTAT |= DISPSTAT_VBLANK_INTR | DISPSTAT_HBLANK_INTR;
+ REG_DISPCNT = DISPCNT_MODE_0 | DISPCNT_OBJ_1D_MAP | DISPCNT_BG_ALL_ON | DISPCNT_OBJ_ON;
+}
+
+void sub_8093598(void)
+{
+ u8 *addr = (void *)VRAM;
+ u32 size = 0x10000;
+
+ while (1)
+ {
+ DmaFill16(3, 0, addr, 0x1000);
+ addr += 0x1000;
+ size -= 0x1000;
+ if (size <= 0x1000)
+ {
+ DmaFill16(3, 0, addr, size);
+ break;
+ }
+ }
+}
+
+void sub_80935EC(void)
+{
+ void *addr = (void *)OAM;
+
+ DmaFill16(3, 0, addr, 0x400);
+}
+
+void sub_8093610(void)
+{
+ REG_BG0CNT = 0;
+ REG_BG1CNT = 0;
+ REG_BG2CNT = 0;
+ REG_BG3CNT = 0;
+ REG_BG0HOFS = 0;
+ REG_BG0VOFS = 0;
+ REG_BG1HOFS = 0;
+ REG_BG1VOFS = 0;
+ REG_BG2HOFS = 0;
+ REG_BG2VOFS = 0;
+ REG_BG3HOFS = 0;
+ REG_BG3VOFS = 0;
+
+ REG_BG0CNT = BGCNT_PRIORITY(0) | BGCNT_CHARBASE(2) | BGCNT_SCREENBASE(30) | BGCNT_16COLOR | BGCNT_TXT256x256;
+ REG_BG1CNT = BGCNT_PRIORITY(1) | BGCNT_CHARBASE(0) | BGCNT_SCREENBASE(8) | BGCNT_16COLOR | BGCNT_TXT256x256;
+ REG_BG2CNT = BGCNT_PRIORITY(2) | BGCNT_CHARBASE(0) | BGCNT_SCREENBASE(9) | BGCNT_16COLOR | BGCNT_TXT256x256;
+ REG_BG3CNT = BGCNT_PRIORITY(3) | BGCNT_CHARBASE(0) | BGCNT_SCREENBASE(10) | BGCNT_16COLOR | BGCNT_TXT256x256;
+}
+
+static void sub_8093688(void)
+{
+ u8 i;
+
+ sub_8093324();
+ ewram0.var_0 = 0;
+ ewram0.var_3 = 0;
+ ewram0.var_4 = FALSE;
+ ewram0.var_2 = ewram0.var_64.stars;
+ ewram0.var_5 = 0;
+ ewram0.var_6 = 0;
+ for (i = 0; i < 4; i++)
+ EasyChat_GetWordText(ewram0.var_20[i], ewram0.var_64.var_28[i]);
+ sub_80936D4();
+}
+
+void sub_80936D4(void)
+{
+ ewram0.var_7 = 0;
+ ewram0.var_8 = 0;
+ ewram0.var_9 = 0;
+ ewram0.var_a = 0;
+ ewram0.var_b = 0;
+ ewram0.var_c = 0;
+ ewram0.var_d = 0;
+ memset(ewram0.var_e, 0, sizeof(ewram0.var_e));
+
+ if (ewram0.var_64.hasPokedex)
+ ewram0.var_7++;
+
+ if (ewram0.var_64.firstHallOfFameA != 0
+ || ewram0.var_64.firstHallOfFameB != 0
+ || ewram0.var_64.firstHallOfFameC != 0)
+ ewram0.var_8++;
+
+ if (ewram0.var_64.linkBattleWins != 0 || ewram0.var_64.linkBattleLosses != 0)
+ ewram0.var_9++;
+
+ if (ewram0.var_64.battleTowerWins != 0 || ewram0.var_64.battleTowerLosses != 0)
+ ewram0.var_a++;
+
+ if (ewram0.var_64.contestsWithFriends != 0)
+ ewram0.var_b++;
+
+ if (ewram0.var_64.pokeblocksWithFriends != 0)
+ ewram0.var_c++;
+
+ if (ewram0.var_64.pokemonTrades != 0)
+ ewram0.var_d++;
+
+ if (!ewram0.var_1)
+ {
+ u32 badgeFlag;
+ int i = 0;
+
+ badgeFlag = BADGE01_GET;
+ while (1)
+ {
+ if (FlagGet(badgeFlag))
+ ewram0.var_e[i]++;
+ badgeFlag++;
+ i++;
+ if (badgeFlag > BADGE08_GET)
+ {
+ break;
+ }
+ }
+ }
+}
+
+void sub_80937A4()
+{
+ ResetPaletteFade();
+ ResetSpriteData();
+ FreeAllSpritePalettes();
+ ResetTasks();
+}
+
+void sub_80937BC()
+{
+ SetUpWindowConfig(&WindowConfig_TrainerCard_Back_Values);
+ MultistepInitMenuWindowBegin(&WindowConfig_TrainerCard_Back_Values);
+}
+
+static void sub_80937D8()
+{
+ sub_8093E04();
+ sub_8093E28();
+ sub_8093F64();
+ sub_8093DAC();
+}
+
+static void sub_80937F0()
+{
+ sub_8093EF8();
+}
+
+static void nullsub_15(void)
+{
+}
+
+static void sub_8093800()
+{
+ sub_809380C();
+}
+
+static void sub_809380C()
+{
+ u8 taskId;
+
+ taskId = CreateTask(sub_809382C, 0);
+ sub_809382C(taskId);
+}
+
+static void sub_809382C(u8 taskId)
+{
+ while (gUnknown_083B5EBC[ewram0.var_0](&gTasks[taskId]) != 0)
+ ;
+}
+
+bool8 sub_8093864(struct Task *task)
+{
+ ewram0.var_5 = gSaveBlock2.playTimeSeconds & 1;
+ ewram0.var_6 = gSaveBlock2.playTimeVBlanks;
+ sub_80939A4();
+ BeginNormalPaletteFade(0xFFFFFFFF, 0, 16, 0, 0);
+ ewram0.var_0++;
+ return FALSE;
+}
+
+bool8 sub_80938A8(struct Task *task)
+{
+ if (!gPaletteFade.active)
+ ewram0.var_0++;
+ return FALSE;
+}
+
+bool8 sub_80938CC(struct Task *task)
+{
+ if (gMain.newKeys & B_BUTTON)
+ {
+ ewram0.var_0 = 5;
+ return TRUE;
+ }
+ else if (gMain.newKeys & A_BUTTON)
+ {
+ if (ewram0.var_3 != 0)
+ {
+ ewram0.var_0 = 5;
+ }
+ else
+ {
+ ewram0.var_3 ^= 1;
+ ewram0.var_0 = 3;
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+bool8 sub_8093918(struct Task *task)
+{
+ sub_8093A28();
+ PlaySE(SE_CARD);
+ ewram0.var_0++;
+ return FALSE;
+}
+
+bool8 sub_8093938(struct Task *task)
+{
+ if (sub_8093A48())
+ ewram0.var_0 = 2;
+ return FALSE;
+}
+
+bool8 sub_8093954(struct Task *task)
+{
+ sub_80939C0();
+ BeginNormalPaletteFade(0xFFFFFFFF, 0, 0, 16, 0);
+ ewram0.var_0++;
+ return FALSE;
+}
+
+bool8 sub_8093980(struct Task *task)
+{
+ if (!gPaletteFade.active)
+ SetMainCallback2((MainCallback)ewram0.var_60);
+ return FALSE;
+}
+
+static void sub_80939A4(void)
+{
+ CreateTask(sub_80939DC, 0);
+ BasicInitMenuWindow(&WindowConfig_TrainerCard_Back_Values);
+}
+
+static void sub_80939C0(void)
+{
+ u8 taskId = FindTaskIdByFunc(sub_80939DC);
+
+ if (taskId != 0xFF)
+ DestroyTask(taskId);
+}
+
+static void sub_80939DC(u8 taskId)
+{
+ u8 buffer[32];
+ struct Task *task = &gTasks[taskId];
+
+ if (ewram0.var_5 != task->data[TD_1])
+ {
+ task->data[TD_1] = ewram0.var_5;
+ task->data[TD_0] ^= TRUE;
+ }
+ TrainerCard_Front_PrintPlayTime(buffer, task->data[TD_0]);
+ MenuPrint(buffer, 10, 12);
+}
+
+static void sub_8093A28(void)
+{
+ u8 taskId;
+
+ taskId = CreateTask(sub_8093A68, 0);
+ sub_8093A68(taskId);
+}
+
+static u8 sub_8093A48(void)
+{
+ if (FindTaskIdByFunc(sub_8093A68) == 0xFF)
+ return TRUE;
+ else
+ return FALSE;
+}
+
+static void sub_8093A68(u8 taskId)
+{
+ while (gUnknown_083B5ED8[gTasks[taskId].data[0]](&gTasks[taskId]) != 0)
+ ;
+}
+
+bool8 sub_8093AA0(struct Task *task)
+{
+ s32 i;
+
+ ewram0.var_4 = FALSE;
+ dp12_8087EA4();
+ for (i = 0; i < ARRAY_COUNT(gUnknown_03004DE0.unk780); i++)
+ gUnknown_03004DE0.unk780[i] = -4;
+ SetHBlankCallback(sub_8093D7C);
+ ewram0.var_4 = TRUE;
+ task->data[0]++;
+ return FALSE;
+}
+
+/*
+bool8 sub_8093AF0(struct Task *task)
+{
+ u32 r7;
+ u16 r9;
+ u32 r6;
+ u32 r5;
+ u32 r4;
+ u32 r10;
+ u32 sp0;
+ s16 i;
+
+ ewram0.var_4 = 0;
+ task->data[1] += 3;
+ if (task->data[1] > 79)
+ task->data[1] = 79;
+
+ r7 = task->data[1];
+ r9 = 160 - r7;
+ r4 = r9 - r7;
+ r6 = -r7 << 16;
+ r5 = (160 << 16) / r4;
+ r5 -= 1 << 16;
+ r10 = r5 * r4 + r6;
+ sp0 = r5 / r4;
+ r5 *= 2;
+
+ for (i = 0; i < r7; i++)
+ {
+ gUnknown_03004DE0.filler0[i] = -4 - (u32)i;
+ }
+ //_08093B74
+ for (; i < r9; i++)
+ {
+ u16 var = r6 >> 16;
+ r6 += r5;
+ r5 -= sp0;
+ gUnknown_03004DE0.filler0[i] = -4 + var;
+ }
+ for (; i < 160; i++)
+ gUnknown_03004DE0.filler0[i] = -4 + (u16)(r10 >> 16);
+ ewram0.var_4 = 1;
+ if (task->data[1] > 0x4A)
+ task->data[0]++;
+ return FALSE;
+}
+*/
+
+__attribute__((naked))
+bool8 sub_8093AF0(struct Task *task)
+{
+ asm(".syntax unified\n\
+ push {r4-r7,lr}\n\
+ mov r7, r10\n\
+ mov r6, r9\n\
+ mov r5, r8\n\
+ push {r5-r7}\n\
+ sub sp, 0x4\n\
+ mov r8, r0\n\
+ ldr r1, _08093BFC @ =0x02000000\n\
+ movs r0, 0\n\
+ strb r0, [r1, 0x4]\n\
+ mov r2, r8\n\
+ ldrh r0, [r2, 0xA]\n\
+ adds r0, 0x3\n\
+ strh r0, [r2, 0xA]\n\
+ lsls r0, 16\n\
+ asrs r0, 16\n\
+ cmp r0, 0x4F\n\
+ ble _08093B18\n\
+ movs r0, 0x4F\n\
+ strh r0, [r2, 0xA]\n\
+_08093B18:\n\
+ mov r4, r8\n\
+ movs r0, 0xA\n\
+ ldrsh r7, [r4, r0]\n\
+ movs r0, 0xA0\n\
+ subs r0, r7\n\
+ mov r9, r0\n\
+ subs r4, r0, r7\n\
+ negs r0, r7\n\
+ lsls r6, r0, 16\n\
+ movs r0, 0xA0\n\
+ lsls r0, 16\n\
+ adds r1, r4, 0\n\
+ bl __udivsi3\n\
+ adds r5, r0, 0\n\
+ ldr r1, _08093C00 @ =0xffff0000\n\
+ adds r5, r1\n\
+ adds r0, r5, 0\n\
+ muls r0, r4\n\
+ adds r0, r6\n\
+ mov r10, r0\n\
+ adds r0, r5, 0\n\
+ adds r1, r4, 0\n\
+ bl __udivsi3\n\
+ str r0, [sp]\n\
+ lsls r5, 1\n\
+ movs r3, 0\n\
+ cmp r3, r7\n\
+ bcs _08093B74\n\
+ ldr r2, _08093C04 @ =gUnknown_03004DE0\n\
+ mov r12, r2\n\
+ ldr r0, _08093C08 @ =0x0000fffc\n\
+ adds r4, r0, 0\n\
+_08093B5C:\n\
+ lsls r0, r3, 16\n\
+ asrs r0, 16\n\
+ lsls r1, r0, 1\n\
+ add r1, r12\n\
+ subs r2, r4, r0\n\
+ strh r2, [r1]\n\
+ adds r0, 0x1\n\
+ lsls r0, 16\n\
+ lsrs r3, r0, 16\n\
+ asrs r0, 16\n\
+ cmp r0, r7\n\
+ bcc _08093B5C\n\
+_08093B74:\n\
+ lsls r2, r3, 16\n\
+ mov r1, r9\n\
+ lsls r0, r1, 16\n\
+ asrs r1, r0, 16\n\
+ mov r4, r10\n\
+ lsrs r7, r4, 16\n\
+ cmp r2, r0\n\
+ bge _08093BAE\n\
+ ldr r0, _08093C04 @ =gUnknown_03004DE0\n\
+ mov r9, r0\n\
+ ldr r4, _08093C08 @ =0x0000fffc\n\
+ mov r12, r4\n\
+ adds r4, r1, 0\n\
+_08093B8E:\n\
+ lsrs r1, r6, 16\n\
+ adds r6, r5\n\
+ ldr r0, [sp]\n\
+ subs r5, r0\n\
+ asrs r2, 16\n\
+ lsls r0, r2, 1\n\
+ add r0, r9\n\
+ add r1, r12\n\
+ strh r1, [r0]\n\
+ adds r2, 0x1\n\
+ lsls r2, 16\n\
+ lsrs r3, r2, 16\n\
+ lsls r2, r3, 16\n\
+ asrs r0, r2, 16\n\
+ cmp r0, r4\n\
+ blt _08093B8E\n\
+_08093BAE:\n\
+ adds r1, r7, 0\n\
+ lsls r0, r3, 16\n\
+ asrs r0, 16\n\
+ cmp r0, 0x9F\n\
+ bgt _08093BD4\n\
+ ldr r4, _08093C04 @ =gUnknown_03004DE0\n\
+ ldr r0, _08093C08 @ =0x0000fffc\n\
+ adds r2, r1, r0\n\
+_08093BBE:\n\
+ lsls r1, r3, 16\n\
+ asrs r1, 16\n\
+ lsls r0, r1, 1\n\
+ adds r0, r4\n\
+ strh r2, [r0]\n\
+ adds r1, 0x1\n\
+ lsls r1, 16\n\
+ lsrs r3, r1, 16\n\
+ asrs r1, 16\n\
+ cmp r1, 0x9F\n\
+ ble _08093BBE\n\
+_08093BD4:\n\
+ movs r0, 0x1\n\
+ ldr r1, _08093BFC @ =0x02000000\n\
+ strb r0, [r1, 0x4]\n\
+ mov r2, r8\n\
+ movs r4, 0xA\n\
+ ldrsh r0, [r2, r4]\n\
+ cmp r0, 0x4A\n\
+ ble _08093BEA\n\
+ ldrh r0, [r2, 0x8]\n\
+ adds r0, 0x1\n\
+ strh r0, [r2, 0x8]\n\
+_08093BEA:\n\
+ movs r0, 0\n\
+ add sp, 0x4\n\
+ pop {r3-r5}\n\
+ mov r8, r3\n\
+ mov r9, r4\n\
+ mov r10, r5\n\
+ pop {r4-r7}\n\
+ pop {r1}\n\
+ bx r1\n\
+ .align 2, 0\n\
+_08093BFC: .4byte 0x02000000\n\
+_08093C00: .4byte 0xffff0000\n\
+_08093C04: .4byte gUnknown_03004DE0\n\
+_08093C08: .4byte 0x0000fffc\n\
+ .syntax divided\n");
+}
+
+bool8 sub_8093C0C(struct Task *task)
+{
+ sub_80939C0();
+ sub_8093DAC();
+ if (!ewram0.var_3)
+ sub_80939A4();
+ task->data[0]++;
+ return TRUE;
+}
+
+__attribute__((naked))
+bool8 sub_8093C38(struct Task *task)
+{
+ asm(".syntax unified\n\
+ push {r4-r7,lr}\n\
+ mov r7, r10\n\
+ mov r6, r9\n\
+ mov r5, r8\n\
+ push {r5-r7}\n\
+ sub sp, 0x4\n\
+ mov r8, r0\n\
+ ldr r1, _08093D40 @ =0x02000000\n\
+ movs r2, 0\n\
+ strb r2, [r1, 0x4]\n\
+ ldrh r0, [r0, 0xA]\n\
+ subs r0, 0x3\n\
+ mov r3, r8\n\
+ strh r0, [r3, 0xA]\n\
+ lsls r0, 16\n\
+ cmp r0, 0\n\
+ bgt _08093C5C\n\
+ strh r2, [r3, 0xA]\n\
+_08093C5C:\n\
+ mov r4, r8\n\
+ movs r0, 0xA\n\
+ ldrsh r7, [r4, r0]\n\
+ movs r0, 0xA0\n\
+ subs r0, r7\n\
+ mov r9, r0\n\
+ subs r4, r0, r7\n\
+ negs r0, r7\n\
+ lsls r6, r0, 16\n\
+ movs r0, 0xA0\n\
+ lsls r0, 16\n\
+ adds r1, r4, 0\n\
+ bl __udivsi3\n\
+ adds r5, r0, 0\n\
+ ldr r1, _08093D44 @ =0xffff0000\n\
+ adds r5, r1\n\
+ adds r0, r5, 0\n\
+ muls r0, r4\n\
+ adds r0, r6\n\
+ mov r10, r0\n\
+ adds r0, r5, 0\n\
+ adds r1, r4, 0\n\
+ bl __udivsi3\n\
+ str r0, [sp]\n\
+ lsrs r5, 1\n\
+ movs r3, 0\n\
+ cmp r3, r7\n\
+ bcs _08093CB8\n\
+ ldr r2, _08093D48 @ =gUnknown_03004DE0\n\
+ mov r12, r2\n\
+ ldr r0, _08093D4C @ =0x0000fffc\n\
+ adds r4, r0, 0\n\
+_08093CA0:\n\
+ lsls r0, r3, 16\n\
+ asrs r0, 16\n\
+ lsls r1, r0, 1\n\
+ add r1, r12\n\
+ subs r2, r4, r0\n\
+ strh r2, [r1]\n\
+ adds r0, 0x1\n\
+ lsls r0, 16\n\
+ lsrs r3, r0, 16\n\
+ asrs r0, 16\n\
+ cmp r0, r7\n\
+ bcc _08093CA0\n\
+_08093CB8:\n\
+ lsls r2, r3, 16\n\
+ mov r1, r9\n\
+ lsls r0, r1, 16\n\
+ asrs r1, r0, 16\n\
+ mov r4, r10\n\
+ lsrs r7, r4, 16\n\
+ cmp r2, r0\n\
+ bge _08093CF2\n\
+ ldr r0, _08093D48 @ =gUnknown_03004DE0\n\
+ mov r9, r0\n\
+ ldr r3, _08093D4C @ =0x0000fffc\n\
+ mov r12, r3\n\
+ adds r4, r1, 0\n\
+_08093CD2:\n\
+ lsrs r1, r6, 16\n\
+ adds r6, r5\n\
+ ldr r0, [sp]\n\
+ adds r5, r0\n\
+ asrs r2, 16\n\
+ lsls r0, r2, 1\n\
+ add r0, r9\n\
+ add r1, r12\n\
+ strh r1, [r0]\n\
+ adds r2, 0x1\n\
+ lsls r2, 16\n\
+ lsrs r3, r2, 16\n\
+ lsls r2, r3, 16\n\
+ asrs r0, r2, 16\n\
+ cmp r0, r4\n\
+ blt _08093CD2\n\
+_08093CF2:\n\
+ adds r1, r7, 0\n\
+ lsls r0, r3, 16\n\
+ asrs r0, 16\n\
+ cmp r0, 0x9F\n\
+ bgt _08093D18\n\
+ ldr r4, _08093D48 @ =gUnknown_03004DE0\n\
+ ldr r0, _08093D4C @ =0x0000fffc\n\
+ adds r2, r1, r0\n\
+_08093D02:\n\
+ lsls r1, r3, 16\n\
+ asrs r1, 16\n\
+ lsls r0, r1, 1\n\
+ adds r0, r4\n\
+ strh r2, [r0]\n\
+ adds r1, 0x1\n\
+ lsls r1, 16\n\
+ lsrs r3, r1, 16\n\
+ asrs r1, 16\n\
+ cmp r1, 0x9F\n\
+ ble _08093D02\n\
+_08093D18:\n\
+ movs r0, 0x1\n\
+ ldr r1, _08093D40 @ =0x02000000\n\
+ strb r0, [r1, 0x4]\n\
+ mov r2, r8\n\
+ movs r3, 0xA\n\
+ ldrsh r0, [r2, r3]\n\
+ cmp r0, 0\n\
+ bgt _08093D2E\n\
+ ldrh r0, [r2, 0x8]\n\
+ adds r0, 0x1\n\
+ strh r0, [r2, 0x8]\n\
+_08093D2E:\n\
+ movs r0, 0\n\
+ add sp, 0x4\n\
+ pop {r3-r5}\n\
+ mov r8, r3\n\
+ mov r9, r4\n\
+ mov r10, r5\n\
+ pop {r4-r7}\n\
+ pop {r1}\n\
+ bx r1\n\
+ .align 2, 0\n\
+_08093D40: .4byte 0x02000000\n\
+_08093D44: .4byte 0xffff0000\n\
+_08093D48: .4byte gUnknown_03004DE0\n\
+_08093D4C: .4byte 0x0000fffc\n\
+ .syntax divided\n");
+}
+
+bool8 sub_8093D50(struct Task *task)
+{
+ u8 taskId;
+
+ ewram0.var_4 = FALSE;
+ SetHBlankCallback(NULL);
+ sub_8093E04();
+ taskId = FindTaskIdByFunc(sub_8093A68);
+ DestroyTask(taskId);
+ return FALSE;
+}
+
+void sub_8093D7C(void)
+{
+ u16 bgVOffset = gUnknown_03004DE0.unk780[REG_VCOUNT & 0xFF];
+
+ REG_BG0VOFS = bgVOffset;
+ REG_BG1VOFS = bgVOffset;
+ REG_BG2VOFS = bgVOffset;
+}
+
+static void sub_8093DAC(void)
+{
+ if (ewram0.var_3)
+ sub_8093DEC();
+ else
+ sub_8093DC8();
+}
+
+static void sub_8093DC8(void)
+{
+ MenuZeroFillScreen();
+ sub_80940E4();
+ sub_8093F14();
+ sub_8093F80();
+ sub_8093FD0();
+ sub_8094038();
+ sub_8094140();
+}
+
+static void sub_8093DEC(void)
+{
+ MenuZeroFillScreen();
+ sub_80940E4();
+ sub_8093F48();
+ sub_8094188();
+}
+
+static void sub_8093E04(void)
+{
+ REG_BG0VOFS = -4;
+ REG_BG1HOFS = 0;
+ REG_BG1VOFS = -4;
+ REG_BG2HOFS = 0;
+ REG_BG2VOFS = -4;
+}
+
+static void sub_8093E28(void)
+{
+ const u8 *src;
+ u8 *dst;
+ u32 size;
+
+ sub_8093EA0();
+ LoadPalette(gUnknown_083B5F6C, 0xE0, 32);
+ src = gMenuTrainerCard_Gfx;
+ dst = (void *)VRAM;
+ size = 0x1480;
+ while (1)
+ {
+ DmaCopy16(3, src, dst, 0x1000);
+ src += 0x1000;
+ dst += 0x1000;
+ size -= 0x1000;
+ if (size <= 0x1000)
+ {
+ DmaCopy16(3, src, dst, size);
+ break;
+ }
+ }
+ {
+ const void *src = gBadgesTiles;
+ void *dst = (void *)(VRAM + 0x1480);
+
+ DmaCopy16(3, src, dst, 0x400);
+ }
+}
+
+extern const u16 *const gUnknown_083B5EF8[];
+
+void sub_8093EA0(void)
+{
+ LoadPalette(gUnknown_083B5EF8[ewram0.var_2], 0, 48 * 2);
+ LoadPalette(gBadgesPalette, 48, 16 * 2);
+ LoadPalette(gUnknown_083B5F4C, 64, 16 * 2);
+ if (ewram0.var_64.gender != MALE)
+ LoadPalette(gUnknown_083B5F0C, 16, 16 * 2);
+}
+
+static void sub_8093EF8(void)
+{
+ LoadTrainerGfx_TrainerCard(ewram0.var_64.gender, 80, (void *)(VRAM + 0x1880));
+}
+
+static void sub_8093F14(void)
+{
+ const void *arr[] = {gUnknown_08E8CAC0, gUnknown_08E8D4C0};
+
+ CpuFastSet(arr[ewram0.var_1], (void *)(VRAM + 0x4800), 0x140);
+}
+
+// I don't really know where to put the data. It's in such a weird order.
+
+const u8 gUnknown_083B5EF4[] = _(" : ");
+
+const u16 *const gUnknown_083B5EF8[] =
+{
+ gMenuTrainerCard0Star_Pal,
+ gMenuTrainerCard1Star_Pal,
+ gMenuTrainerCard2Star_Pal,
+ gMenuTrainerCard3Star_Pal,
+ gMenuTrainerCard4Star_Pal,
+};
+
+const u16 gUnknown_083B5F0C[] = INCBIN_U16("graphics/trainer_card/83B5F0C.gbapal");
+const u16 gBadgesPalette[] = INCBIN_U16("graphics/trainer_card/badges.gbapal");
+const u16 gUnknown_083B5F4C[] = INCBIN_U16("graphics/trainer_card/83B5F4C.gbapal");
+const u16 gUnknown_083B5F6C[] = INCBIN_U16("graphics/trainer_card/83B5F6C.gbapal");
+const u16 gUnknown_083B5F8C[][4] = INCBIN_U16("graphics/trainer_card/83B5F8C_map.bin");
+
+static void sub_8093F48(void)
+{
+ CpuFastSet(gUnknown_08E8CFC0, (void *)(VRAM + 0x4800), 320);
+}
+
+static void sub_8093F64(void)
+{
+ CpuFastSet(gUnknown_08E8D9C0, (void *)(VRAM + 0x5000), 320);
+}
+
+static void sub_8093F80(void)
+{
+ u16 r5 = 0xC4;
+ u16 *ptr = (u16 *)(VRAM + 0x4000);
+ s16 i;
+ s16 j;
+
+ for (i = 5; i < 13; i++)
+ {
+ for (j = 19; j < 27; j++, r5++)
+ ptr[i * 32 + j] = r5 | 0x5000;
+ }
+}
+
+static void sub_8093FD0(void)
+{
+ u16 *ptr = (u16 *)(VRAM + 0x4000);
+ s16 i = 15;
+ s16 var = 15 + ewram0.var_2;
+
+ while (i < var)
+ {
+ ptr[6 * 32 + i] = 0x408F;
+ i++;
+ }
+ while (i < 0x13)
+ {
+ ptr[6 * 32 + i] = 0;
+ i++;
+ }
+}
+
+static void sub_8094038(void)
+{
+ if (ewram0.var_1 == 0)
+ {
+ u16 *ptr = (u16 *)(VRAM + 0x4000);
+ s16 i;
+ s16 r2;
+
+ for (i = 0, r2 = 4; i < 8; i++, r2 += 3)
+ {
+ if (ewram0.var_e[i] != 0)
+ {
+ ptr[15 * 32 + r2 + 0] = gUnknown_083B5F8C[i][0] | 0x3000;
+ ptr[15 * 32 + r2 + 1] = gUnknown_083B5F8C[i][1] | 0x3000;
+ ptr[16 * 32 + r2 + 0] = gUnknown_083B5F8C[i][2] | 0x3000;
+ ptr[16 * 32 + r2 + 1] = gUnknown_083B5F8C[i][3] | 0x3000;
+ }
+ }
+ }
+}
+
+static void sub_80940E4(void)
+{
+ s16 i;
+ u16 *ptr;
+
+ for (i = 0, ptr = (u16 *)(VRAM + 0x4000); i < 0x400; i++, ptr++)
+ *ptr = 0;
+}
+
+static void sub_8094110(void)
+{
+ u16 *ptr = (u16 *)(VRAM + 0x4800);
+ u16 i;
+
+ for (i = 3; i < 17; i++)
+ {
+ ptr[10 * 32 + i] = 1;
+ ptr[11 * 32 + i] = 1;
+ }
+}
+
+static void sub_8094140(void)
+{
+ u8 *buffer;
+
+ BasicInitMenuWindow(&WindowConfig_TrainerCard_Back_Values);
+
+ buffer = gStringVar1;
+ StringCopy(buffer, ewram0.var_64.playerName);
+ ConvertInternationalString(buffer, ewram0.language);
+ MenuPrint(buffer, 7, 5);
+
+ TrainerCard_Front_PrintTrainerID();
+ TrainerCard_Front_PrintMoney();
+ TrainerCard_Front_PrintPokedexCount();
+ sub_809429C();
+}
+
+static void sub_8094188(void)
+{
+ BasicInitMenuWindow(&WindowConfig_TrainerCard_Back_Values);
+ TrainerCard_Back_PrintName();
+ TrainerCard_Back_PrintHallOfFameTime_Label();
+ TrainerCard_Back_PrintLinkBattlesLabel();
+ TrainerCard_Back_PrintBattleTower_Label();
+ TrainerCard_Back_PrintLinkContests_Label();
+ TrainerCard_Back_PrintLinkPokeblocks_Label();
+ TrainerCard_Back_PrintPokemonTrades_Label();
+
+ BasicInitMenuWindow(&WindowConfig_TrainerCard_Back_Labels);
+ TrainerCard_Back_PrintHallOfFameTime();
+ TrainerCard_Back_PrintLinkBattles();
+ TrainerCard_Back_PrintBattleTower();
+ TrainerCard_Back_PrintLinkContests();
+ TrainerCard_Back_PrintLinkPokeblocks();
+ TrainerCard_Back_PrintPokemonTrades();
+}
+
+static void TrainerCard_Front_PrintTrainerID(void)
+{
+ u8 buffer[8];
+
+ ConvertIntToDecimalStringN(buffer, ewram0.var_64.trainerId, STR_CONV_MODE_LEADING_ZEROS, 5);
+ MenuPrint(buffer, 20, 2);
+}
+
+static void TrainerCard_Front_PrintMoney(void)
+{
+ sub_80B7AEC(ewram0.var_64.money, 16, 8);
+}
+
+static void TrainerCard_Front_PrintPokedexCount(void)
+{
+ u8 buffer[16];
+
+ if (ewram0.var_7 == FALSE)
+ {
+ sub_8094110();
+ }
+ else
+ {
+ ConvertIntToDecimalStringN(buffer, ewram0.var_64.pokedexSeen, STR_CONV_MODE_LEFT_ALIGN, 3);
+ MenuPrint_RightAligned(buffer, 16, 10);
+ }
+}
+
+static void TrainerCard_Front_PrintPlayTime(u8 *arg1, s16 colon)
+{
+ u8 buffer[16];
+ u16 playTimeHours;
+ u16 playTimeMinutes;
+
+ playTimeHours = gSaveBlock2.playTimeHours;
+ playTimeMinutes = gSaveBlock2.playTimeMinutes;
+ if (ewram0.var_1 != 0)
+ {
+ playTimeHours = ewram0.var_64.playTimeHours;
+ playTimeMinutes = ewram0.var_64.playTimeMinutes;
+ }
+ FormatPlayTime(buffer, playTimeHours, playTimeMinutes, colon);
+ sub_8072C74(arg1, buffer, 48, 1);
+}
+
+static void sub_809429C(void)
+{
+ u8 *str;
+
+ if (ewram0.var_1 != 0)
+ {
+ str = gStringVar1;
+ str = StringCopy(str, ewram0.var_20[0]);
+ str[0] = 00;
+ str++;
+ str = StringCopy(str, ewram0.var_20[1]);
+ MenuPrint(gStringVar1, 2, 14);
+
+ str = gStringVar1;
+ str = StringCopy(str, ewram0.var_20[2]);
+ str[0] = 00;
+ str++;
+ str = StringCopy(str, ewram0.var_20[3]);
+ MenuPrint(gStringVar1, 2, 16);
+ }
+}
+
+static void TrainerCard_Back_PrintName(void)
+{
+ u8 *str;
+
+ str = gStringVar1;
+ StringCopy(str, ewram0.var_64.playerName);
+ ConvertInternationalString(str, ewram0.language);
+
+#if ENGLISH
+ StringAppend(str, gOtherText_TrainersTrainerCard);
+#elif GERMAN
+ de_sub_8073174(str, gOtherText_TrainersTrainerCard);
+#endif
+
+ MenuPrint_RightAligned(gStringVar1, 28, 2);
+}
+
+static void TrainerCard_Back_PrintHallOfFameTime_Label(void)
+{
+ if (ewram0.var_8 != 0)
+ MenuPrint(gOtherText_FirstHOF, 3, 5);
+}
+
+static void TrainerCard_Back_PrintHallOfFameTime(void)
+{
+ u8 *str;
+
+ if (ewram0.var_8 != 0)
+ {
+ str = gStringVar1;
+ str = ConvertIntToDecimalStringN(str, ewram0.var_64.firstHallOfFameA, STR_CONV_MODE_RIGHT_ALIGN, 3);
+ str = StringCopy(str, gUnknown_083B5EF4);
+ str = ConvertIntToDecimalStringN(str, ewram0.var_64.firstHallOfFameB, STR_CONV_MODE_LEADING_ZEROS, 2);
+ str = StringCopy(str, gUnknown_083B5EF4);
+ str = ConvertIntToDecimalStringN(str, ewram0.var_64.firstHallOfFameC, STR_CONV_MODE_LEADING_ZEROS, 2);
+ MenuPrint_RightAligned(gStringVar1, 28, 5);
+ }
+}
+
+static void TrainerCard_Back_PrintLinkBattlesLabel(void)
+{
+ if (ewram0.var_9 != 0)
+ MenuPrint(gOtherText_LinkCableBattles, 3, 7);
+}
+
+static void TrainerCard_Back_PrintLinkBattles(void)
+{
+ u8 buffer[16];
+
+ if (ewram0.var_9 != 0)
+ {
+ ConvertIntToDecimalString(buffer, ewram0.var_64.linkBattleWins);
+ MenuPrint_RightAligned(buffer, 22, 7);
+
+ ConvertIntToDecimalString(buffer, ewram0.var_64.linkBattleLosses);
+ MenuPrint_RightAligned(buffer, 28, 7);
+ }
+}
+
+static void TrainerCard_Back_PrintBattleTower_Label(void)
+{
+ if (ewram0.var_a != 0)
+ MenuPrint(gOtherText_BattleTowerWinRecord, 3, 15);
+}
+
+static void TrainerCard_Back_PrintBattleTower(void)
+{
+ u8 buffer[16];
+
+ if (ewram0.var_a != 0)
+ {
+ sub_8072C44(buffer, ewram0.var_64.battleTowerWins, 24, 1);
+ MenuPrint_PixelCoords(buffer, 112, 120, 0);
+
+ sub_8072C44(buffer, ewram0.var_64.battleTowerLosses, 24, 1);
+ MenuPrint_PixelCoords(buffer, 149, 120, 0);
+ }
+}
+
+static void TrainerCard_Back_PrintLinkContests_Label(void)
+{
+ if (ewram0.var_b != 0)
+ MenuPrint(gOtherText_ContestRecord, 3, 13);
+}
+
+static void TrainerCard_Back_PrintLinkContests(void)
+{
+ u8 buffer[8];
+
+ if (ewram0.var_b != 0)
+ {
+ ConvertIntToDecimalStringN(buffer, ewram0.var_64.contestsWithFriends, STR_CONV_MODE_RIGHT_ALIGN, 3);
+ MenuPrint_RightAligned(buffer, 28, 13);
+ }
+}
+
+static void TrainerCard_Back_PrintLinkPokeblocks_Label(void)
+{
+ if (ewram0.var_c != 0)
+ MenuPrint(gOtherText_MixingRecord, 3, 11);
+}
+
+static void TrainerCard_Back_PrintLinkPokeblocks(void)
+{
+ u8 buffer[8];
+
+ if (ewram0.var_c != 0)
+ {
+ ConvertIntToDecimalStringN(buffer, ewram0.var_64.pokeblocksWithFriends, STR_CONV_MODE_RIGHT_ALIGN, 5);
+ MenuPrint_RightAligned(buffer, 28, 11);
+ }
+}
+
+static void TrainerCard_Back_PrintPokemonTrades_Label(void)
+{
+ if (ewram0.var_d != 0)
+ MenuPrint(gOtherText_TradeRecord, 3, 9);
+}
+
+static void TrainerCard_Back_PrintPokemonTrades(void)
+{
+ u8 buffer[8];
+
+ if (ewram0.var_d != 0)
+ {
+ ConvertIntToDecimalStringN(buffer, ewram0.var_64.pokemonTrades, STR_CONV_MODE_RIGHT_ALIGN, 5);
+ MenuPrint_RightAligned(buffer, 28, 9);
+ }
+}
+
+void unref_sub_8094588(u16 left, u16 top)
+{
+ const u8 *text = gOtherText_Boy;
+
+ if (gSaveBlock2.playerGender == FEMALE)
+ text = gOtherText_Girl;
+ MenuPrint(text, left, top);
+}
diff --git a/src/engine/trig.c b/src/engine/trig.c
new file mode 100644
index 000000000..e16a69e63
--- /dev/null
+++ b/src/engine/trig.c
@@ -0,0 +1,549 @@
+#include "global.h"
+#include "trig.h"
+
+// Converts a number to Q8.8 fixed-point format
+#define Q_8_8(n) ((s16)((n) * 256))
+
+// Converts a number to Q4.12 fixed-point format
+#define Q_4_12(n) ((s16)((n) * 4096))
+
+// Values of sin(x*(π/128)) as Q8.8 fixed-point numbers from x = 0 to x = 319
+const s16 gSineTable[] =
+{
+ Q_8_8(0), // sin(0*(π/128))
+ Q_8_8(0.0234375), // sin(1*(π/128))
+ Q_8_8(0.046875), // sin(2*(π/128))
+ Q_8_8(0.0703125), // sin(3*(π/128))
+ Q_8_8(0.09765625), // sin(4*(π/128))
+ Q_8_8(0.12109375), // sin(5*(π/128))
+ Q_8_8(0.14453125), // sin(6*(π/128))
+ Q_8_8(0.16796875), // sin(7*(π/128))
+ Q_8_8(0.19140625), // sin(8*(π/128))
+ Q_8_8(0.21875), // sin(9*(π/128))
+ Q_8_8(0.2421875), // sin(10*(π/128))
+ Q_8_8(0.265625), // sin(11*(π/128))
+ Q_8_8(0.2890625), // sin(12*(π/128))
+ Q_8_8(0.3125), // sin(13*(π/128))
+ Q_8_8(0.3359375), // sin(14*(π/128))
+ Q_8_8(0.359375), // sin(15*(π/128))
+ Q_8_8(0.37890625), // sin(16*(π/128))
+ Q_8_8(0.40234375), // sin(17*(π/128))
+ Q_8_8(0.42578125), // sin(18*(π/128))
+ Q_8_8(0.44921875), // sin(19*(π/128))
+ Q_8_8(0.46875), // sin(20*(π/128))
+ Q_8_8(0.4921875), // sin(21*(π/128))
+ Q_8_8(0.51171875), // sin(22*(π/128))
+ Q_8_8(0.53125), // sin(23*(π/128))
+ Q_8_8(0.5546875), // sin(24*(π/128))
+ Q_8_8(0.57421875), // sin(25*(π/128))
+ Q_8_8(0.59375), // sin(26*(π/128))
+ Q_8_8(0.61328125), // sin(27*(π/128))
+ Q_8_8(0.6328125), // sin(28*(π/128))
+ Q_8_8(0.65234375), // sin(29*(π/128))
+ Q_8_8(0.66796875), // sin(30*(π/128))
+ Q_8_8(0.6875), // sin(31*(π/128))
+ Q_8_8(0.70703125), // sin(32*(π/128))
+ Q_8_8(0.72265625), // sin(33*(π/128))
+ Q_8_8(0.73828125), // sin(34*(π/128))
+ Q_8_8(0.75390625), // sin(35*(π/128))
+ Q_8_8(0.76953125), // sin(36*(π/128))
+ Q_8_8(0.78515625), // sin(37*(π/128))
+ Q_8_8(0.80078125), // sin(38*(π/128))
+ Q_8_8(0.81640625), // sin(39*(π/128))
+ Q_8_8(0.828125), // sin(40*(π/128))
+ Q_8_8(0.84375), // sin(41*(π/128))
+ Q_8_8(0.85546875), // sin(42*(π/128))
+ Q_8_8(0.8671875), // sin(43*(π/128))
+ Q_8_8(0.87890625), // sin(44*(π/128))
+ Q_8_8(0.890625), // sin(45*(π/128))
+ Q_8_8(0.90234375), // sin(46*(π/128))
+ Q_8_8(0.9140625), // sin(47*(π/128))
+ Q_8_8(0.921875), // sin(48*(π/128))
+ Q_8_8(0.9296875), // sin(49*(π/128))
+ Q_8_8(0.94140625), // sin(50*(π/128))
+ Q_8_8(0.94921875), // sin(51*(π/128))
+ Q_8_8(0.953125), // sin(52*(π/128))
+ Q_8_8(0.9609375), // sin(53*(π/128))
+ Q_8_8(0.96875), // sin(54*(π/128))
+ Q_8_8(0.97265625), // sin(55*(π/128))
+ Q_8_8(0.98046875), // sin(56*(π/128))
+ Q_8_8(0.984375), // sin(57*(π/128))
+ Q_8_8(0.98828125), // sin(58*(π/128))
+ Q_8_8(0.9921875), // sin(59*(π/128))
+ Q_8_8(0.9921875), // sin(60*(π/128))
+ Q_8_8(0.99609375), // sin(61*(π/128))
+ Q_8_8(0.99609375), // sin(62*(π/128))
+ Q_8_8(0.99609375), // sin(63*(π/128))
+ Q_8_8(1), // sin(64*(π/128))
+ Q_8_8(0.99609375), // sin(65*(π/128))
+ Q_8_8(0.99609375), // sin(66*(π/128))
+ Q_8_8(0.99609375), // sin(67*(π/128))
+ Q_8_8(0.9921875), // sin(68*(π/128))
+ Q_8_8(0.9921875), // sin(69*(π/128))
+ Q_8_8(0.98828125), // sin(70*(π/128))
+ Q_8_8(0.984375), // sin(71*(π/128))
+ Q_8_8(0.98046875), // sin(72*(π/128))
+ Q_8_8(0.97265625), // sin(73*(π/128))
+ Q_8_8(0.96875), // sin(74*(π/128))
+ Q_8_8(0.9609375), // sin(75*(π/128))
+ Q_8_8(0.953125), // sin(76*(π/128))
+ Q_8_8(0.94921875), // sin(77*(π/128))
+ Q_8_8(0.94140625), // sin(78*(π/128))
+ Q_8_8(0.9296875), // sin(79*(π/128))
+ Q_8_8(0.921875), // sin(80*(π/128))
+ Q_8_8(0.9140625), // sin(81*(π/128))
+ Q_8_8(0.90234375), // sin(82*(π/128))
+ Q_8_8(0.890625), // sin(83*(π/128))
+ Q_8_8(0.87890625), // sin(84*(π/128))
+ Q_8_8(0.8671875), // sin(85*(π/128))
+ Q_8_8(0.85546875), // sin(86*(π/128))
+ Q_8_8(0.84375), // sin(87*(π/128))
+ Q_8_8(0.828125), // sin(88*(π/128))
+ Q_8_8(0.81640625), // sin(89*(π/128))
+ Q_8_8(0.80078125), // sin(90*(π/128))
+ Q_8_8(0.78515625), // sin(91*(π/128))
+ Q_8_8(0.76953125), // sin(92*(π/128))
+ Q_8_8(0.75390625), // sin(93*(π/128))
+ Q_8_8(0.73828125), // sin(94*(π/128))
+ Q_8_8(0.72265625), // sin(95*(π/128))
+ Q_8_8(0.70703125), // sin(96*(π/128))
+ Q_8_8(0.6875), // sin(97*(π/128))
+ Q_8_8(0.66796875), // sin(98*(π/128))
+ Q_8_8(0.65234375), // sin(99*(π/128))
+ Q_8_8(0.6328125), // sin(100*(π/128))
+ Q_8_8(0.61328125), // sin(101*(π/128))
+ Q_8_8(0.59375), // sin(102*(π/128))
+ Q_8_8(0.57421875), // sin(103*(π/128))
+ Q_8_8(0.5546875), // sin(104*(π/128))
+ Q_8_8(0.53125), // sin(105*(π/128))
+ Q_8_8(0.51171875), // sin(106*(π/128))
+ Q_8_8(0.4921875), // sin(107*(π/128))
+ Q_8_8(0.46875), // sin(108*(π/128))
+ Q_8_8(0.44921875), // sin(109*(π/128))
+ Q_8_8(0.42578125), // sin(110*(π/128))
+ Q_8_8(0.40234375), // sin(111*(π/128))
+ Q_8_8(0.37890625), // sin(112*(π/128))
+ Q_8_8(0.359375), // sin(113*(π/128))
+ Q_8_8(0.3359375), // sin(114*(π/128))
+ Q_8_8(0.3125), // sin(115*(π/128))
+ Q_8_8(0.2890625), // sin(116*(π/128))
+ Q_8_8(0.265625), // sin(117*(π/128))
+ Q_8_8(0.2421875), // sin(118*(π/128))
+ Q_8_8(0.21875), // sin(119*(π/128))
+ Q_8_8(0.19140625), // sin(120*(π/128))
+ Q_8_8(0.16796875), // sin(121*(π/128))
+ Q_8_8(0.14453125), // sin(122*(π/128))
+ Q_8_8(0.12109375), // sin(123*(π/128))
+ Q_8_8(0.09765625), // sin(124*(π/128))
+ Q_8_8(0.0703125), // sin(125*(π/128))
+ Q_8_8(0.046875), // sin(126*(π/128))
+ Q_8_8(0.0234375), // sin(127*(π/128))
+ Q_8_8(0), // sin(128*(π/128))
+ Q_8_8(-0.0234375), // sin(129*(π/128))
+ Q_8_8(-0.046875), // sin(130*(π/128))
+ Q_8_8(-0.0703125), // sin(131*(π/128))
+ Q_8_8(-0.09765625), // sin(132*(π/128))
+ Q_8_8(-0.12109375), // sin(133*(π/128))
+ Q_8_8(-0.14453125), // sin(134*(π/128))
+ Q_8_8(-0.16796875), // sin(135*(π/128))
+ Q_8_8(-0.19140625), // sin(136*(π/128))
+ Q_8_8(-0.21875), // sin(137*(π/128))
+ Q_8_8(-0.2421875), // sin(138*(π/128))
+ Q_8_8(-0.265625), // sin(139*(π/128))
+ Q_8_8(-0.2890625), // sin(140*(π/128))
+ Q_8_8(-0.3125), // sin(141*(π/128))
+ Q_8_8(-0.3359375), // sin(142*(π/128))
+ Q_8_8(-0.359375), // sin(143*(π/128))
+ Q_8_8(-0.37890625), // sin(144*(π/128))
+ Q_8_8(-0.40234375), // sin(145*(π/128))
+ Q_8_8(-0.42578125), // sin(146*(π/128))
+ Q_8_8(-0.44921875), // sin(147*(π/128))
+ Q_8_8(-0.46875), // sin(148*(π/128))
+ Q_8_8(-0.4921875), // sin(149*(π/128))
+ Q_8_8(-0.51171875), // sin(150*(π/128))
+ Q_8_8(-0.53125), // sin(151*(π/128))
+ Q_8_8(-0.5546875), // sin(152*(π/128))
+ Q_8_8(-0.57421875), // sin(153*(π/128))
+ Q_8_8(-0.59375), // sin(154*(π/128))
+ Q_8_8(-0.61328125), // sin(155*(π/128))
+ Q_8_8(-0.6328125), // sin(156*(π/128))
+ Q_8_8(-0.65234375), // sin(157*(π/128))
+ Q_8_8(-0.66796875), // sin(158*(π/128))
+ Q_8_8(-0.6875), // sin(159*(π/128))
+ Q_8_8(-0.70703125), // sin(160*(π/128))
+ Q_8_8(-0.72265625), // sin(161*(π/128))
+ Q_8_8(-0.73828125), // sin(162*(π/128))
+ Q_8_8(-0.75390625), // sin(163*(π/128))
+ Q_8_8(-0.76953125), // sin(164*(π/128))
+ Q_8_8(-0.78515625), // sin(165*(π/128))
+ Q_8_8(-0.80078125), // sin(166*(π/128))
+ Q_8_8(-0.81640625), // sin(167*(π/128))
+ Q_8_8(-0.828125), // sin(168*(π/128))
+ Q_8_8(-0.84375), // sin(169*(π/128))
+ Q_8_8(-0.85546875), // sin(170*(π/128))
+ Q_8_8(-0.8671875), // sin(171*(π/128))
+ Q_8_8(-0.87890625), // sin(172*(π/128))
+ Q_8_8(-0.890625), // sin(173*(π/128))
+ Q_8_8(-0.90234375), // sin(174*(π/128))
+ Q_8_8(-0.9140625), // sin(175*(π/128))
+ Q_8_8(-0.921875), // sin(176*(π/128))
+ Q_8_8(-0.9296875), // sin(177*(π/128))
+ Q_8_8(-0.94140625), // sin(178*(π/128))
+ Q_8_8(-0.94921875), // sin(179*(π/128))
+ Q_8_8(-0.953125), // sin(180*(π/128))
+ Q_8_8(-0.9609375), // sin(181*(π/128))
+ Q_8_8(-0.96875), // sin(182*(π/128))
+ Q_8_8(-0.97265625), // sin(183*(π/128))
+ Q_8_8(-0.98046875), // sin(184*(π/128))
+ Q_8_8(-0.984375), // sin(185*(π/128))
+ Q_8_8(-0.98828125), // sin(186*(π/128))
+ Q_8_8(-0.9921875), // sin(187*(π/128))
+ Q_8_8(-0.9921875), // sin(188*(π/128))
+ Q_8_8(-0.99609375), // sin(189*(π/128))
+ Q_8_8(-0.99609375), // sin(190*(π/128))
+ Q_8_8(-0.99609375), // sin(191*(π/128))
+ Q_8_8(-1), // sin(192*(π/128))
+ Q_8_8(-0.99609375), // sin(193*(π/128))
+ Q_8_8(-0.99609375), // sin(194*(π/128))
+ Q_8_8(-0.99609375), // sin(195*(π/128))
+ Q_8_8(-0.9921875), // sin(196*(π/128))
+ Q_8_8(-0.9921875), // sin(197*(π/128))
+ Q_8_8(-0.98828125), // sin(198*(π/128))
+ Q_8_8(-0.984375), // sin(199*(π/128))
+ Q_8_8(-0.98046875), // sin(200*(π/128))
+ Q_8_8(-0.97265625), // sin(201*(π/128))
+ Q_8_8(-0.96875), // sin(202*(π/128))
+ Q_8_8(-0.9609375), // sin(203*(π/128))
+ Q_8_8(-0.953125), // sin(204*(π/128))
+ Q_8_8(-0.94921875), // sin(205*(π/128))
+ Q_8_8(-0.94140625), // sin(206*(π/128))
+ Q_8_8(-0.9296875), // sin(207*(π/128))
+ Q_8_8(-0.921875), // sin(208*(π/128))
+ Q_8_8(-0.9140625), // sin(209*(π/128))
+ Q_8_8(-0.90234375), // sin(210*(π/128))
+ Q_8_8(-0.890625), // sin(211*(π/128))
+ Q_8_8(-0.87890625), // sin(212*(π/128))
+ Q_8_8(-0.8671875), // sin(213*(π/128))
+ Q_8_8(-0.85546875), // sin(214*(π/128))
+ Q_8_8(-0.84375), // sin(215*(π/128))
+ Q_8_8(-0.828125), // sin(216*(π/128))
+ Q_8_8(-0.81640625), // sin(217*(π/128))
+ Q_8_8(-0.80078125), // sin(218*(π/128))
+ Q_8_8(-0.78515625), // sin(219*(π/128))
+ Q_8_8(-0.76953125), // sin(220*(π/128))
+ Q_8_8(-0.75390625), // sin(221*(π/128))
+ Q_8_8(-0.73828125), // sin(222*(π/128))
+ Q_8_8(-0.72265625), // sin(223*(π/128))
+ Q_8_8(-0.70703125), // sin(224*(π/128))
+ Q_8_8(-0.6875), // sin(225*(π/128))
+ Q_8_8(-0.66796875), // sin(226*(π/128))
+ Q_8_8(-0.65234375), // sin(227*(π/128))
+ Q_8_8(-0.6328125), // sin(228*(π/128))
+ Q_8_8(-0.61328125), // sin(229*(π/128))
+ Q_8_8(-0.59375), // sin(230*(π/128))
+ Q_8_8(-0.57421875), // sin(231*(π/128))
+ Q_8_8(-0.5546875), // sin(232*(π/128))
+ Q_8_8(-0.53125), // sin(233*(π/128))
+ Q_8_8(-0.51171875), // sin(234*(π/128))
+ Q_8_8(-0.4921875), // sin(235*(π/128))
+ Q_8_8(-0.46875), // sin(236*(π/128))
+ Q_8_8(-0.44921875), // sin(237*(π/128))
+ Q_8_8(-0.42578125), // sin(238*(π/128))
+ Q_8_8(-0.40234375), // sin(239*(π/128))
+ Q_8_8(-0.37890625), // sin(240*(π/128))
+ Q_8_8(-0.359375), // sin(241*(π/128))
+ Q_8_8(-0.3359375), // sin(242*(π/128))
+ Q_8_8(-0.3125), // sin(243*(π/128))
+ Q_8_8(-0.2890625), // sin(244*(π/128))
+ Q_8_8(-0.265625), // sin(245*(π/128))
+ Q_8_8(-0.2421875), // sin(246*(π/128))
+ Q_8_8(-0.21875), // sin(247*(π/128))
+ Q_8_8(-0.19140625), // sin(248*(π/128))
+ Q_8_8(-0.16796875), // sin(249*(π/128))
+ Q_8_8(-0.14453125), // sin(250*(π/128))
+ Q_8_8(-0.12109375), // sin(251*(π/128))
+ Q_8_8(-0.09765625), // sin(252*(π/128))
+ Q_8_8(-0.0703125), // sin(253*(π/128))
+ Q_8_8(-0.046875), // sin(254*(π/128))
+ Q_8_8(-0.0234375), // sin(255*(π/128))
+ Q_8_8(0), // sin(256*(π/128))
+ Q_8_8(0.0234375), // sin(257*(π/128))
+ Q_8_8(0.046875), // sin(258*(π/128))
+ Q_8_8(0.0703125), // sin(259*(π/128))
+ Q_8_8(0.09765625), // sin(260*(π/128))
+ Q_8_8(0.12109375), // sin(261*(π/128))
+ Q_8_8(0.14453125), // sin(262*(π/128))
+ Q_8_8(0.16796875), // sin(263*(π/128))
+ Q_8_8(0.19140625), // sin(264*(π/128))
+ Q_8_8(0.21875), // sin(265*(π/128))
+ Q_8_8(0.2421875), // sin(266*(π/128))
+ Q_8_8(0.265625), // sin(267*(π/128))
+ Q_8_8(0.2890625), // sin(268*(π/128))
+ Q_8_8(0.3125), // sin(269*(π/128))
+ Q_8_8(0.3359375), // sin(270*(π/128))
+ Q_8_8(0.359375), // sin(271*(π/128))
+ Q_8_8(0.37890625), // sin(272*(π/128))
+ Q_8_8(0.40234375), // sin(273*(π/128))
+ Q_8_8(0.42578125), // sin(274*(π/128))
+ Q_8_8(0.44921875), // sin(275*(π/128))
+ Q_8_8(0.46875), // sin(276*(π/128))
+ Q_8_8(0.4921875), // sin(277*(π/128))
+ Q_8_8(0.51171875), // sin(278*(π/128))
+ Q_8_8(0.53125), // sin(279*(π/128))
+ Q_8_8(0.5546875), // sin(280*(π/128))
+ Q_8_8(0.57421875), // sin(281*(π/128))
+ Q_8_8(0.59375), // sin(282*(π/128))
+ Q_8_8(0.61328125), // sin(283*(π/128))
+ Q_8_8(0.6328125), // sin(284*(π/128))
+ Q_8_8(0.65234375), // sin(285*(π/128))
+ Q_8_8(0.66796875), // sin(286*(π/128))
+ Q_8_8(0.6875), // sin(287*(π/128))
+ Q_8_8(0.70703125), // sin(288*(π/128))
+ Q_8_8(0.72265625), // sin(289*(π/128))
+ Q_8_8(0.73828125), // sin(290*(π/128))
+ Q_8_8(0.75390625), // sin(291*(π/128))
+ Q_8_8(0.76953125), // sin(292*(π/128))
+ Q_8_8(0.78515625), // sin(293*(π/128))
+ Q_8_8(0.80078125), // sin(294*(π/128))
+ Q_8_8(0.81640625), // sin(295*(π/128))
+ Q_8_8(0.828125), // sin(296*(π/128))
+ Q_8_8(0.84375), // sin(297*(π/128))
+ Q_8_8(0.85546875), // sin(298*(π/128))
+ Q_8_8(0.8671875), // sin(299*(π/128))
+ Q_8_8(0.87890625), // sin(300*(π/128))
+ Q_8_8(0.890625), // sin(301*(π/128))
+ Q_8_8(0.90234375), // sin(302*(π/128))
+ Q_8_8(0.9140625), // sin(303*(π/128))
+ Q_8_8(0.921875), // sin(304*(π/128))
+ Q_8_8(0.9296875), // sin(305*(π/128))
+ Q_8_8(0.94140625), // sin(306*(π/128))
+ Q_8_8(0.94921875), // sin(307*(π/128))
+ Q_8_8(0.953125), // sin(308*(π/128))
+ Q_8_8(0.9609375), // sin(309*(π/128))
+ Q_8_8(0.96875), // sin(310*(π/128))
+ Q_8_8(0.97265625), // sin(311*(π/128))
+ Q_8_8(0.98046875), // sin(312*(π/128))
+ Q_8_8(0.984375), // sin(313*(π/128))
+ Q_8_8(0.98828125), // sin(314*(π/128))
+ Q_8_8(0.9921875), // sin(315*(π/128))
+ Q_8_8(0.9921875), // sin(316*(π/128))
+ Q_8_8(0.99609375), // sin(317*(π/128))
+ Q_8_8(0.99609375), // sin(318*(π/128))
+ Q_8_8(0.99609375), // sin(319*(π/128))
+};
+
+// values of sin(x) as Q4.12 fixed-point numbers from x = 0° to x = 179°
+const s16 gSineDegreeTable[] =
+{
+ Q_4_12(0), // sin(0°)
+ Q_4_12(0.017333984375), // sin(1°)
+ Q_4_12(0.034912109375), // sin(2°)
+ Q_4_12(0.05224609375), // sin(3°)
+ Q_4_12(0.06982421875), // sin(4°)
+ Q_4_12(0.087158203125), // sin(5°)
+ Q_4_12(0.1044921875), // sin(6°)
+ Q_4_12(0.121826171875), // sin(7°)
+ Q_4_12(0.13916015625), // sin(8°)
+ Q_4_12(0.156494140625), // sin(9°)
+ Q_4_12(0.173583984375), // sin(10°)
+ Q_4_12(0.19091796875), // sin(11°)
+ Q_4_12(0.2080078125), // sin(12°)
+ Q_4_12(0.224853515625), // sin(13°)
+ Q_4_12(0.241943359375), // sin(14°)
+ Q_4_12(0.2587890625), // sin(15°)
+ Q_4_12(0.275634765625), // sin(16°)
+ Q_4_12(0.29248046875), // sin(17°)
+ Q_4_12(0.30908203125), // sin(18°)
+ Q_4_12(0.32568359375), // sin(19°)
+ Q_4_12(0.342041015625), // sin(20°)
+ Q_4_12(0.3583984375), // sin(21°)
+ Q_4_12(0.37451171875), // sin(22°)
+ Q_4_12(0.390625), // sin(23°)
+ Q_4_12(0.40673828125), // sin(24°)
+ Q_4_12(0.422607421875), // sin(25°)
+ Q_4_12(0.4384765625), // sin(26°)
+ Q_4_12(0.4541015625), // sin(27°)
+ Q_4_12(0.469482421875), // sin(28°)
+ Q_4_12(0.48486328125), // sin(29°)
+ Q_4_12(0.5), // sin(30°)
+ Q_4_12(0.51513671875), // sin(31°)
+ Q_4_12(0.530029296875), // sin(32°)
+ Q_4_12(0.544677734375), // sin(33°)
+ Q_4_12(0.55908203125), // sin(34°)
+ Q_4_12(0.573486328125), // sin(35°)
+ Q_4_12(0.587890625), // sin(36°)
+ Q_4_12(0.601806640625), // sin(37°)
+ Q_4_12(0.61572265625), // sin(38°)
+ Q_4_12(0.62939453125), // sin(39°)
+ Q_4_12(0.642822265625), // sin(40°)
+ Q_4_12(0.656005859375), // sin(41°)
+ Q_4_12(0.669189453125), // sin(42°)
+ Q_4_12(0.681884765625), // sin(43°)
+ Q_4_12(0.694580078125), // sin(44°)
+ Q_4_12(0.70703125), // sin(45°)
+ Q_4_12(0.71923828125), // sin(46°)
+ Q_4_12(0.7314453125), // sin(47°)
+ Q_4_12(0.7431640625), // sin(48°)
+ Q_4_12(0.754638671875), // sin(49°)
+ Q_4_12(0.76611328125), // sin(50°)
+ Q_4_12(0.777099609375), // sin(51°)
+ Q_4_12(0.7880859375), // sin(52°)
+ Q_4_12(0.798583984375), // sin(53°)
+ Q_4_12(0.80908203125), // sin(54°)
+ Q_4_12(0.819091796875), // sin(55°)
+ Q_4_12(0.8291015625), // sin(56°)
+ Q_4_12(0.838623046875), // sin(57°)
+ Q_4_12(0.84814453125), // sin(58°)
+ Q_4_12(0.857177734375), // sin(59°)
+ Q_4_12(0.865966796875), // sin(60°)
+ Q_4_12(0.87451171875), // sin(61°)
+ Q_4_12(0.883056640625), // sin(62°)
+ Q_4_12(0.89111328125), // sin(63°)
+ Q_4_12(0.898681640625), // sin(64°)
+ Q_4_12(0.90625), // sin(65°)
+ Q_4_12(0.91357421875), // sin(66°)
+ Q_4_12(0.92041015625), // sin(67°)
+ Q_4_12(0.92724609375), // sin(68°)
+ Q_4_12(0.93359375), // sin(69°)
+ Q_4_12(0.939697265625), // sin(70°)
+ Q_4_12(0.945556640625), // sin(71°)
+ Q_4_12(0.951171875), // sin(72°)
+ Q_4_12(0.956298828125), // sin(73°)
+ Q_4_12(0.961181640625), // sin(74°)
+ Q_4_12(0.9658203125), // sin(75°)
+ Q_4_12(0.97021484375), // sin(76°)
+ Q_4_12(0.974365234375), // sin(77°)
+ Q_4_12(0.97802734375), // sin(78°)
+ Q_4_12(0.981689453125), // sin(79°)
+ Q_4_12(0.98486328125), // sin(80°)
+ Q_4_12(0.98779296875), // sin(81°)
+ Q_4_12(0.990234375), // sin(82°)
+ Q_4_12(0.992431640625), // sin(83°)
+ Q_4_12(0.994384765625), // sin(84°)
+ Q_4_12(0.99609375), // sin(85°)
+ Q_4_12(0.99755859375), // sin(86°)
+ Q_4_12(0.99853515625), // sin(87°)
+ Q_4_12(0.999267578125), // sin(88°)
+ Q_4_12(0.999755859375), // sin(89°)
+ Q_4_12(1), // sin(90°)
+ Q_4_12(0.999755859375), // sin(91°)
+ Q_4_12(0.999267578125), // sin(92°)
+ Q_4_12(0.99853515625), // sin(93°)
+ Q_4_12(0.99755859375), // sin(94°)
+ Q_4_12(0.99609375), // sin(95°)
+ Q_4_12(0.994384765625), // sin(96°)
+ Q_4_12(0.992431640625), // sin(97°)
+ Q_4_12(0.990234375), // sin(98°)
+ Q_4_12(0.98779296875), // sin(99°)
+ Q_4_12(0.98486328125), // sin(100°)
+ Q_4_12(0.981689453125), // sin(101°)
+ Q_4_12(0.97802734375), // sin(102°)
+ Q_4_12(0.974365234375), // sin(103°)
+ Q_4_12(0.97021484375), // sin(104°)
+ Q_4_12(0.9658203125), // sin(105°)
+ Q_4_12(0.961181640625), // sin(106°)
+ Q_4_12(0.956298828125), // sin(107°)
+ Q_4_12(0.951171875), // sin(108°)
+ Q_4_12(0.945556640625), // sin(109°)
+ Q_4_12(0.939697265625), // sin(110°)
+ Q_4_12(0.93359375), // sin(111°)
+ Q_4_12(0.92724609375), // sin(112°)
+ Q_4_12(0.92041015625), // sin(113°)
+ Q_4_12(0.91357421875), // sin(114°)
+ Q_4_12(0.90625), // sin(115°)
+ Q_4_12(0.898681640625), // sin(116°)
+ Q_4_12(0.89111328125), // sin(117°)
+ Q_4_12(0.883056640625), // sin(118°)
+ Q_4_12(0.87451171875), // sin(119°)
+ Q_4_12(0.865966796875), // sin(120°)
+ Q_4_12(0.857177734375), // sin(121°)
+ Q_4_12(0.84814453125), // sin(122°)
+ Q_4_12(0.838623046875), // sin(123°)
+ Q_4_12(0.8291015625), // sin(124°)
+ Q_4_12(0.819091796875), // sin(125°)
+ Q_4_12(0.80908203125), // sin(126°)
+ Q_4_12(0.798583984375), // sin(127°)
+ Q_4_12(0.7880859375), // sin(128°)
+ Q_4_12(0.777099609375), // sin(129°)
+ Q_4_12(0.76611328125), // sin(130°)
+ Q_4_12(0.754638671875), // sin(131°)
+ Q_4_12(0.7431640625), // sin(132°)
+ Q_4_12(0.7314453125), // sin(133°)
+ Q_4_12(0.71923828125), // sin(134°)
+ Q_4_12(0.70703125), // sin(135°)
+ Q_4_12(0.694580078125), // sin(136°)
+ Q_4_12(0.681884765625), // sin(137°)
+ Q_4_12(0.669189453125), // sin(138°)
+ Q_4_12(0.656005859375), // sin(139°)
+ Q_4_12(0.642822265625), // sin(140°)
+ Q_4_12(0.62939453125), // sin(141°)
+ Q_4_12(0.61572265625), // sin(142°)
+ Q_4_12(0.601806640625), // sin(143°)
+ Q_4_12(0.587890625), // sin(144°)
+ Q_4_12(0.573486328125), // sin(145°)
+ Q_4_12(0.55908203125), // sin(146°)
+ Q_4_12(0.544677734375), // sin(147°)
+ Q_4_12(0.530029296875), // sin(148°)
+ Q_4_12(0.51513671875), // sin(149°)
+ Q_4_12(0.5), // sin(150°)
+ Q_4_12(0.48486328125), // sin(151°)
+ Q_4_12(0.469482421875), // sin(152°)
+ Q_4_12(0.4541015625), // sin(153°)
+ Q_4_12(0.4384765625), // sin(154°)
+ Q_4_12(0.422607421875), // sin(155°)
+ Q_4_12(0.40673828125), // sin(156°)
+ Q_4_12(0.390625), // sin(157°)
+ Q_4_12(0.37451171875), // sin(158°)
+ Q_4_12(0.3583984375), // sin(159°)
+ Q_4_12(0.342041015625), // sin(160°)
+ Q_4_12(0.32568359375), // sin(161°)
+ Q_4_12(0.30908203125), // sin(162°)
+ Q_4_12(0.29248046875), // sin(163°)
+ Q_4_12(0.275634765625), // sin(164°)
+ Q_4_12(0.2587890625), // sin(165°)
+ Q_4_12(0.241943359375), // sin(166°)
+ Q_4_12(0.224853515625), // sin(167°)
+ Q_4_12(0.2080078125), // sin(168°)
+ Q_4_12(0.19091796875), // sin(169°)
+ Q_4_12(0.173583984375), // sin(170°)
+ Q_4_12(0.156494140625), // sin(171°)
+ Q_4_12(0.13916015625), // sin(172°)
+ Q_4_12(0.121826171875), // sin(173°)
+ Q_4_12(0.1044921875), // sin(174°)
+ Q_4_12(0.087158203125), // sin(175°)
+ Q_4_12(0.06982421875), // sin(176°)
+ Q_4_12(0.05224609375), // sin(177°)
+ Q_4_12(0.034912109375), // sin(178°)
+ Q_4_12(0.017333984375), // sin(179°)
+};
+
+// amplitude * sin(index*(π/128))
+s16 Sin(s16 index, s16 amplitude)
+{
+ return (amplitude * gSineTable[index]) >> 8;
+}
+
+// amplitude * cos(index*(π/128))
+s16 Cos(s16 index, s16 amplitude)
+{
+ return (amplitude * gSineTable[index + 64]) >> 8;
+}
+
+// angle in degrees
+s16 Sin2(u16 angle)
+{
+ s32 angleMod = angle % 180;
+ s32 negate = ((angle / 180) & 1);
+ s16 value = gSineDegreeTable[angleMod];
+
+ if (negate)
+ return -value;
+ else
+ return value;
+}
+
+// angle in degrees
+s16 Cos2(u16 angle)
+{
+ return Sin2(angle + 90);
+}
diff --git a/src/engine/util.c b/src/engine/util.c
new file mode 100644
index 000000000..582b9f806
--- /dev/null
+++ b/src/engine/util.c
@@ -0,0 +1,525 @@
+#include "global.h"
+#include "util.h"
+
+const u32 gBitTable[] =
+{
+ 1 << 0,
+ 1 << 1,
+ 1 << 2,
+ 1 << 3,
+ 1 << 4,
+ 1 << 5,
+ 1 << 6,
+ 1 << 7,
+ 1 << 8,
+ 1 << 9,
+ 1 << 10,
+ 1 << 11,
+ 1 << 12,
+ 1 << 13,
+ 1 << 14,
+ 1 << 15,
+ 1 << 16,
+ 1 << 17,
+ 1 << 18,
+ 1 << 19,
+ 1 << 20,
+ 1 << 21,
+ 1 << 22,
+ 1 << 23,
+ 1 << 24,
+ 1 << 25,
+ 1 << 26,
+ 1 << 27,
+ 1 << 28,
+ 1 << 29,
+ 1 << 30,
+ 1 << 31,
+};
+
+static const struct SpriteTemplate gInvisibleSpriteTemplate =
+{
+ .tileTag = 0,
+ .paletteTag = 0,
+ .oam = &gDummyOamData,
+ .anims = gDummySpriteAnimTable,
+ .images = NULL,
+ .affineAnims = gDummySpriteAffineAnimTable,
+ .callback = SpriteCallbackDummy,
+};
+
+static const u8 gSpriteDimensions[3][4][2] =
+{
+ // square
+ {
+ {1, 1},
+ {2, 2},
+ {4, 4},
+ {8, 8},
+ },
+
+ // horizontal rectangle
+ {
+ {2, 1},
+ {4, 1},
+ {4, 2},
+ {8, 4},
+ },
+
+ // vertical rectangle
+ {
+ {1, 2},
+ {1, 4},
+ {2, 4},
+ {4, 8},
+ },
+};
+
+static const u16 gCrc16Table[] =
+{
+ 0x0000, 0x1189, 0x2312, 0x329B, 0x4624, 0x57AD, 0x6536, 0x74BF,
+ 0x8C48, 0x9DC1, 0xAF5A, 0xBED3, 0xCA6C, 0xDBE5, 0xE97E, 0xF8F7,
+ 0x1081, 0x0108, 0x3393, 0x221A, 0x56A5, 0x472C, 0x75B7, 0x643E,
+ 0x9CC9, 0x8D40, 0xBFDB, 0xAE52, 0xDAED, 0xCB64, 0xF9FF, 0xE876,
+ 0x2102, 0x308B, 0x0210, 0x1399, 0x6726, 0x76AF, 0x4434, 0x55BD,
+ 0xAD4A, 0xBCC3, 0x8E58, 0x9FD1, 0xEB6E, 0xFAE7, 0xC87C, 0xD9F5,
+ 0x3183, 0x200A, 0x1291, 0x0318, 0x77A7, 0x662E, 0x54B5, 0x453C,
+ 0xBDCB, 0xAC42, 0x9ED9, 0x8F50, 0xFBEF, 0xEA66, 0xD8FD, 0xC974,
+ 0x4204, 0x538D, 0x6116, 0x709F, 0x0420, 0x15A9, 0x2732, 0x36BB,
+ 0xCE4C, 0xDFC5, 0xED5E, 0xFCD7, 0x8868, 0x99E1, 0xAB7A, 0xBAF3,
+ 0x5285, 0x430C, 0x7197, 0x601E, 0x14A1, 0x0528, 0x37B3, 0x263A,
+ 0xDECD, 0xCF44, 0xFDDF, 0xEC56, 0x98E9, 0x8960, 0xBBFB, 0xAA72,
+ 0x6306, 0x728F, 0x4014, 0x519D, 0x2522, 0x34AB, 0x0630, 0x17B9,
+ 0xEF4E, 0xFEC7, 0xCC5C, 0xDDD5, 0xA96A, 0xB8E3, 0x8A78, 0x9BF1,
+ 0x7387, 0x620E, 0x5095, 0x411C, 0x35A3, 0x242A, 0x16B1, 0x0738,
+ 0xFFCF, 0xEE46, 0xDCDD, 0xCD54, 0xB9EB, 0xA862, 0x9AF9, 0x8B70,
+ 0x8408, 0x9581, 0xA71A, 0xB693, 0xC22C, 0xD3A5, 0xE13E, 0xF0B7,
+ 0x0840, 0x19C9, 0x2B52, 0x3ADB, 0x4E64, 0x5FED, 0x6D76, 0x7CFF,
+ 0x9489, 0x8500, 0xB79B, 0xA612, 0xD2AD, 0xC324, 0xF1BF, 0xE036,
+ 0x18C1, 0x0948, 0x3BD3, 0x2A5A, 0x5EE5, 0x4F6C, 0x7DF7, 0x6C7E,
+ 0xA50A, 0xB483, 0x8618, 0x9791, 0xE32E, 0xF2A7, 0xC03C, 0xD1B5,
+ 0x2942, 0x38CB, 0x0A50, 0x1BD9, 0x6F66, 0x7EEF, 0x4C74, 0x5DFD,
+ 0xB58B, 0xA402, 0x9699, 0x8710, 0xF3AF, 0xE226, 0xD0BD, 0xC134,
+ 0x39C3, 0x284A, 0x1AD1, 0x0B58, 0x7FE7, 0x6E6E, 0x5CF5, 0x4D7C,
+ 0xC60C, 0xD785, 0xE51E, 0xF497, 0x8028, 0x91A1, 0xA33A, 0xB2B3,
+ 0x4A44, 0x5BCD, 0x6956, 0x78DF, 0x0C60, 0x1DE9, 0x2F72, 0x3EFB,
+ 0xD68D, 0xC704, 0xF59F, 0xE416, 0x90A9, 0x8120, 0xB3BB, 0xA232,
+ 0x5AC5, 0x4B4C, 0x79D7, 0x685E, 0x1CE1, 0x0D68, 0x3FF3, 0x2E7A,
+ 0xE70E, 0xF687, 0xC41C, 0xD595, 0xA12A, 0xB0A3, 0x8238, 0x93B1,
+ 0x6B46, 0x7ACF, 0x4854, 0x59DD, 0x2D62, 0x3CEB, 0x0E70, 0x1FF9,
+ 0xF78F, 0xE606, 0xD49D, 0xC514, 0xB1AB, 0xA022, 0x92B9, 0x8330,
+ 0x7BC7, 0x6A4E, 0x58D5, 0x495C, 0x3DE3, 0x2C6A, 0x1EF1, 0x0F78,
+};
+
+const u8 gMiscBlank_Gfx[] = INCBIN_U8("graphics/interface/blank.4bpp");
+
+
+u8 CreateInvisibleSpriteWithCallback(void (*callback)(struct Sprite *))
+{
+ u8 sprite = CreateSprite(&gInvisibleSpriteTemplate, 248, 168, 14);
+ gSprites[sprite].invisible = TRUE;
+ gSprites[sprite].callback = callback;
+ return sprite;
+}
+
+void StoreWordInTwoHalfwords(u16 *h, u32 w)
+{
+ h[0] = (u16)(w);
+ h[1] = (u16)(w >> 16);
+}
+
+void LoadWordFromTwoHalfwords(u16 *h, u32 *w)
+{
+ *w = h[0] | (s16)h[1] << 16;
+}
+
+void SetBgAffineStruct(struct BgAffineSrcData *src, u32 texX, u32 texY, s16 scrX, s16 scrY, s16 sx, s16 sy, u16 alpha)
+{
+ src->texX = texX;
+ src->texY = texY;
+ src->scrX = scrX;
+ src->scrY = scrY;
+ src->sx = sx;
+ src->sy = sy;
+ src->alpha = alpha;
+}
+
+void DoBgAffineSet(struct BgAffineDstData *dest, u32 texX, u32 texY, s16 scrX, s16 scrY, s16 sx, s16 sy, u16 alpha)
+{
+ struct BgAffineSrcData src;
+
+ SetBgAffineStruct(&src, texX, texY, scrX, scrY, sx, sy, alpha);
+ BgAffineSet(&src, dest, 1);
+}
+
+#ifdef NONMATCHING
+
+// Functionally equivalent.
+// Only the two yflip loops don't match.
+void CopySpriteTiles(u8 shape, u8 size, u8 *tiles, u16 *tilemap, u8 *output)
+{
+ u8 x, y;
+ s8 i, j;
+ u8 xflip[32];
+ u8 h = gSpriteDimensions[shape][size][1];
+ u8 w = gSpriteDimensions[shape][size][0];
+
+ for (y = 0; y < h; y++)
+ {
+ int filler = 32 - w;
+
+ for (x = 0; x < w; x++)
+ {
+ int tile = (*tilemap & 0x3ff) * 32;
+ int attr = *tilemap & 0xc00;
+
+ if (attr == 0)
+ {
+ void *src = tiles + tile;
+ void *dest = output;
+ int length = 32;
+ DmaCopy32(3, src, dest, length);
+ }
+ else if (attr == 0x800) // yflip
+ {
+ for (i = 0; i < 8; i++)
+ {
+ void *src = tiles;
+ void *dest = output;
+ int length = 4;
+ // this is likely wrong, but makes it closer to matching
+ src += tile + (7 - i) * 4;
+ dest += i * 4;
+ DmaCopy32(3, src, dest, length);
+ }
+ }
+ else // xflip
+ {
+ for (i = 0; i < 8; i++)
+ {
+ for (j = 0; j < 4; j++)
+ {
+ u8 i2 = i * 4;
+ xflip[i2 + (3-j)] = (tiles[tile + i2 + j] & 0xf) << 4;
+ xflip[i2 + (3-j)] |= tiles[tile + i2 + j] >> 4;
+ }
+ }
+ if (*tilemap & 0x800) // yflip
+ {
+ for (i = 0; i < 8; i++)
+ {
+ void *src = xflip + (7-i) * 4;
+ void *dest = output + i*4;
+ int length = 4;
+ DmaCopy32(3, src, dest, length);
+ }
+ }
+ else
+ {
+ void *src = xflip;
+ void *dest = output;
+ int length = 32;
+ DmaCopy32(3, src, dest, length);
+ }
+ }
+ tilemap++;
+ output += 32;
+ }
+ tilemap += filler;
+ }
+}
+
+#else
+
+__attribute__((naked)) void CopySpriteTiles(u8 shape, u8 size, u8 *tiles, u16 *tilemap, u8 *output)
+{
+ asm("\n"
+ " .syntax unified\n"
+ " push {r4-r7,lr}\n"
+ " mov r7, r10\n"
+ " mov r6, r9\n"
+ " mov r5, r8\n"
+ " push {r5-r7}\n"
+ " sub sp, 0x3C\n"
+ " str r2, [sp, 0x20]\n"
+ " adds r4, r3, 0\n"
+ " ldr r7, [sp, 0x5C]\n"
+ " lsls r0, 24\n"
+ " lsls r1, 24\n"
+ " ldr r2, _08041008 @ =gSpriteDimensions\n"
+ " lsrs r1, 23\n"
+ " lsrs r0, 21\n"
+ " adds r1, r0\n"
+ " adds r0, r2, 0x1\n"
+ " adds r0, r1, r0\n"
+ " ldrb r0, [r0]\n"
+ " str r0, [sp, 0x24]\n"
+ " adds r1, r2\n"
+ " ldrb r1, [r1]\n"
+ " str r1, [sp, 0x28]\n"
+ " movs r1, 0\n"
+ " cmp r1, r0\n"
+ " bcc _08040FB4\n"
+ " b _08041136\n"
+ "_08040FB4:\n"
+ " movs r0, 0x20\n"
+ " ldr r2, [sp, 0x28]\n"
+ " subs r0, r2\n"
+ " lsls r0, 1\n"
+ " str r0, [sp, 0x2C]\n"
+ "_08040FBE:\n"
+ " movs r2, 0\n"
+ " adds r1, 0x1\n"
+ " str r1, [sp, 0x34]\n"
+ " ldr r3, [sp, 0x28]\n"
+ " cmp r2, r3\n"
+ " bcc _08040FCC\n"
+ " b _08041124\n"
+ "_08040FCC:\n"
+ " ldr r0, _0804100C @ =0x040000d4\n"
+ " mov r8, r0\n"
+ "_08040FD0:\n"
+ " ldrh r1, [r4]\n"
+ " ldr r0, _08041010 @ =0x000003ff\n"
+ " ands r0, r1\n"
+ " lsls r0, 5\n"
+ " mov r12, r0\n"
+ " movs r0, 0xC0\n"
+ " lsls r0, 4\n"
+ " ands r0, r1\n"
+ " mov r3, sp\n"
+ " strh r1, [r3, 0x38]\n"
+ " cmp r0, 0\n"
+ " bne _08041018\n"
+ " ldr r0, [sp, 0x20]\n"
+ " add r0, r12\n"
+ " mov r1, r8\n"
+ " str r0, [r1]\n"
+ " str r7, [r1, 0x4]\n"
+ " ldr r3, _08041014 @ =0x84000008\n"
+ " str r3, [r1, 0x8]\n"
+ " ldr r0, [r1, 0x8]\n"
+ " adds r4, 0x2\n"
+ " str r4, [sp, 0x30]\n"
+ " adds r7, 0x20\n"
+ " mov r10, r7\n"
+ " adds r2, 0x1\n"
+ " mov r9, r2\n"
+ " b _08041112\n"
+ " .align 2, 0\n"
+ "_08041008: .4byte gSpriteDimensions\n"
+ "_0804100C: .4byte 0x040000d4\n"
+ "_08041010: .4byte 0x000003ff\n"
+ "_08041014: .4byte 0x84000008\n"
+ "_08041018:\n"
+ " movs r1, 0x80\n"
+ " lsls r1, 4\n"
+ " cmp r0, r1\n"
+ " bne _08041068\n"
+ " movs r3, 0\n"
+ " adds r4, 0x2\n"
+ " str r4, [sp, 0x30]\n"
+ " movs r0, 0x20\n"
+ " adds r0, r7\n"
+ " mov r10, r0\n"
+ " adds r2, 0x1\n"
+ " mov r9, r2\n"
+ " ldr r4, _08041060 @ =0x040000d4\n"
+ " ldr r6, _08041064 @ =0x84000001\n"
+ " movs r5, 0x7\n"
+ "_08041036:\n"
+ " lsls r2, r3, 24\n"
+ " asrs r2, 24\n"
+ " subs r0, r5, r2\n"
+ " lsls r0, 2\n"
+ " add r0, r12\n"
+ " ldr r1, [sp, 0x20]\n"
+ " adds r0, r1, r0\n"
+ " lsls r1, r2, 2\n"
+ " adds r1, r7, r1\n"
+ " str r0, [r4]\n"
+ " str r1, [r4, 0x4]\n"
+ " str r6, [r4, 0x8]\n"
+ " ldr r0, [r4, 0x8]\n"
+ " adds r2, 0x1\n"
+ " lsls r2, 24\n"
+ " lsrs r3, r2, 24\n"
+ " asrs r2, 24\n"
+ " cmp r2, 0x7\n"
+ " ble _08041036\n"
+ " b _08041112\n"
+ " .align 2, 0\n"
+ "_08041060: .4byte 0x040000d4\n"
+ "_08041064: .4byte 0x84000001\n"
+ "_08041068:\n"
+ " movs r3, 0\n"
+ " adds r4, 0x2\n"
+ " str r4, [sp, 0x30]\n"
+ " movs r0, 0x20\n"
+ " adds r0, r7\n"
+ " mov r10, r0\n"
+ " adds r2, 0x1\n"
+ " mov r9, r2\n"
+ "_08041078:\n"
+ " movs r2, 0\n"
+ " lsls r4, r3, 24\n"
+ " lsls r0, r4, 2\n"
+ " lsrs r0, 24\n"
+ " adds r6, r0, 0x3\n"
+ " mov r1, r12\n"
+ " adds r5, r1, r0\n"
+ "_08041086:\n"
+ " lsls r1, r2, 24\n"
+ " asrs r1, 24\n"
+ " subs r0, r6, r1\n"
+ " mov r2, sp\n"
+ " adds r3, r2, r0\n"
+ " adds r0, r5, r1\n"
+ " ldr r2, [sp, 0x20]\n"
+ " adds r0, r2, r0\n"
+ " ldrb r2, [r0]\n"
+ " movs r0, 0xF\n"
+ " ands r0, r2\n"
+ " lsls r0, 4\n"
+ " lsrs r2, 4\n"
+ " orrs r0, r2\n"
+ " strb r0, [r3]\n"
+ " adds r1, 0x1\n"
+ " lsls r1, 24\n"
+ " lsrs r2, r1, 24\n"
+ " asrs r1, 24\n"
+ " cmp r1, 0x3\n"
+ " ble _08041086\n"
+ " movs r3, 0x80\n"
+ " lsls r3, 17\n"
+ " adds r0, r4, r3\n"
+ " lsrs r3, r0, 24\n"
+ " asrs r0, 24\n"
+ " cmp r0, 0x7\n"
+ " ble _08041078\n"
+ " movs r0, 0x80\n"
+ " lsls r0, 4\n"
+ " mov r1, sp\n"
+ " ldrh r1, [r1, 0x38]\n"
+ " ands r0, r1\n"
+ " cmp r0, 0\n"
+ " beq _08041104\n"
+ " movs r3, 0\n"
+ " ldr r4, _080410FC @ =0x040000d4\n"
+ " ldr r6, _08041100 @ =0x84000001\n"
+ " movs r5, 0x7\n"
+ "_080410D4:\n"
+ " lsls r1, r3, 24\n"
+ " asrs r1, 24\n"
+ " subs r0, r5, r1\n"
+ " lsls r0, 2\n"
+ " mov r3, sp\n"
+ " adds r2, r3, r0\n"
+ " lsls r0, r1, 2\n"
+ " adds r0, r7, r0\n"
+ " str r2, [r4]\n"
+ " str r0, [r4, 0x4]\n"
+ " str r6, [r4, 0x8]\n"
+ " ldr r0, [r4, 0x8]\n"
+ " adds r1, 0x1\n"
+ " lsls r1, 24\n"
+ " lsrs r3, r1, 24\n"
+ " asrs r1, 24\n"
+ " cmp r1, 0x7\n"
+ " ble _080410D4\n"
+ " b _08041112\n"
+ " .align 2, 0\n"
+ "_080410FC: .4byte 0x040000d4\n"
+ "_08041100: .4byte 0x84000001\n"
+ "_08041104:\n"
+ " mov r0, sp\n"
+ " mov r1, r8\n"
+ " str r0, [r1]\n"
+ " str r7, [r1, 0x4]\n"
+ " ldr r2, _08041148 @ =0x84000008\n"
+ " str r2, [r1, 0x8]\n"
+ " ldr r0, [r1, 0x8]\n"
+ "_08041112:\n"
+ " ldr r4, [sp, 0x30]\n"
+ " mov r7, r10\n"
+ " mov r3, r9\n"
+ " lsls r0, r3, 24\n"
+ " lsrs r2, r0, 24\n"
+ " ldr r0, [sp, 0x28]\n"
+ " cmp r2, r0\n"
+ " bcs _08041124\n"
+ " b _08040FD0\n"
+ "_08041124:\n"
+ " ldr r1, [sp, 0x2C]\n"
+ " adds r4, r1\n"
+ " ldr r2, [sp, 0x34]\n"
+ " lsls r0, r2, 24\n"
+ " lsrs r1, r0, 24\n"
+ " ldr r3, [sp, 0x24]\n"
+ " cmp r1, r3\n"
+ " bcs _08041136\n"
+ " b _08040FBE\n"
+ "_08041136:\n"
+ " add sp, 0x3C\n"
+ " pop {r3-r5}\n"
+ " mov r8, r3\n"
+ " mov r9, r4\n"
+ " mov r10, r5\n"
+ " pop {r4-r7}\n"
+ " pop {r0}\n"
+ " bx r0\n"
+ " .align 2, 0\n"
+ "_08041148: .4byte 0x84000008\n"
+ " .syntax divided\n"
+ );
+}
+
+#endif
+
+int CountTrailingZeroBits(u32 value)
+{
+ u8 i;
+
+ for (i = 0; i < 32; i++)
+ {
+ if ((value & 1) == 0)
+ value >>= 1;
+ else
+ return i;
+ }
+ return 0;
+}
+
+u16 CalcCRC16(u8 *data, int length)
+{
+ u16 i, j;
+ u16 crc = 0x1121;
+
+ for (i = 0; i < length; i++)
+ {
+ crc ^= data[i];
+ for (j = 0; j < 8; j++)
+ {
+ if (crc & 1)
+ crc = (crc >> 1) ^ 0x8408;
+ else
+ crc >>= 1;
+ }
+ }
+ return ~crc;
+}
+
+u16 CalcCRC16WithTable(u8 *data, int length)
+{
+ u16 i;
+ u16 crc = 0x1121;
+ u8 byte;
+
+ for (i = 0; i < length; i++)
+ {
+ byte = crc >> 8;
+ crc ^= data[i];
+ crc = byte ^ gCrc16Table[(u8)crc];
+ }
+ return ~crc;
+}