#include "global.h" #include "cable_club.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 "random.h" #include "save.h" #include "constants/songs.h" #include "sound.h" #include "sprite.h" #include "string_util.h" #include "strings2.h" #include "task.h" #include "text.h" #include "ewram.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 u16 gBattleTypeFlags; extern u16 word_3004858; extern void Blender_SetBankBerryData(u8 bank, u16 itemID); static void InitLinkTestBG(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 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); 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]; #if DEBUG u8 gUnknown_Debug_30030E0; #endif 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[] = _("テストな"); const u8 sMagic[] = "GameFreak inc."; 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); Text_LoadWindowTemplate(&gMenuTextWindowTemplate); InitMenuWindow((struct WindowTemplate *)&gMenuTextWindowTemplate); 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.id = 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(gSharedMem + 0x4000, 0x2004); if (gMain.newKeys & L_BUTTON) BeginNormalPaletteFade(0xFFFFFFFF, 0, 16, 0, RGB(2, 0, 0)); if (gMain.newKeys & START_BUTTON) SetSuppressLinkErrorMessage(TRUE); if (gMain.newKeys & R_BUTTON) Save_WriteData(SAVE_LINK); if (gMain.newKeys & SELECT_BUTTON) SetCloseLinkCallback(); 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 *)gSharedMem; 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); #if DEBUG debug_sub_808B838(i); #endif } } 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] = gSpecialVar_ItemId; 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 IsLinkTaskFinished(void) { return gLinkCallback == NULL; } 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 = BG_SCREEN_ADDR(gLinkTestBGInfo.screenBaseBlock); tilemap[(32 * y) + x] = (gLinkTestBGInfo.paletteNum << 12) | (tileNum + 1); } 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++; } } #if DEBUG EWRAM_DATA uintptr_t debugCharacterBase = 0; EWRAM_DATA uintptr_t unk_20238C8 = 0; EWRAM_DATA u16 *debugTileMap = NULL; EWRAM_DATA u32 unk_20238D0 = 0; void debug_sub_8008218(u16 *buffer, u32 arg1, u16 *arg2, u32 arg3) { CpuSet(sLinkTestDigitTiles, buffer, 272); debugCharacterBase = (uintptr_t)buffer; unk_20238C8 = (uintptr_t)arg1; debugTileMap = arg2; unk_20238D0 = arg3; } void debug_sub_8008264(u32 value, int left, int top, int d, int e) { s32 i; u32 buffer[8]; if (unk_20238D0 == e) { u32 *ptr; u16 *tilemapDest; if (d > 8) d = 8; ptr = buffer; for (i = 0; i < d; i++) { *ptr++ = value & 0xF; value >>= 4; } tilemapDest = (u16 *)debugTileMap + top * 0x20 + left; ptr = buffer + d - 1; for (i = 0; i < d; i++) { *tilemapDest = (debugCharacterBase - unk_20238C8) / 32 + *ptr + 1; ptr--; tilemapDest++; } } } #endif 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 SetCloseLinkCallback(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); Text_LoadWindowTemplate(&gWindowTemplate_81E7198); InitMenuWindow((struct WindowTemplate *)&gWindowTemplate_81E7198); Menu_EraseScreen(); 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); } void CB2_PrintErrorMessage(void) { u8 array[32] __attribute__((unused)); // unused u8 array2[32] __attribute__((unused)); // unused switch (gMain.state) { case 0: Menu_PrintTextPixelCoords(gMultiText_LinkError, 20, 56, 1); #if DEBUG StringCopy(array, sColorCodes); ConvertIntToHexStringN(array2, sErrorLinkStatus, STR_CONV_MODE_LEADING_ZEROS, 8); StringAppend(array, array2); StringAppend(array, sEmptyString); ConvertIntToHexStringN(array2, sErrorLastSendQueueCount, STR_CONV_MODE_LEADING_ZEROS, 2); StringAppend(array, array2); StringAppend(array, sEmptyString); ConvertIntToHexStringN(array2, sErrorLastRecvQueueCount, STR_CONV_MODE_LEADING_ZEROS, 2); StringAppend(array, array2); Menu_PrintText(array, 2, 15); #endif 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; }