/*---------------------------------------------------------------------------* Project: TwlSDK - WM - demos - dataShare-Model File: main.c Copyright 2007-2008 Nintendo. All rights reserved. These coded instructions, statements, and computer programs contain proprietary information of Nintendo of America Inc. and/or Nintendo Company Ltd., and are protected by Federal copyright law. They may not be disclosed to third parties or copied or duplicated in any form, in whole or in part, without the prior written consent of Nintendo. $Date:: 2009-08-28#$ $Rev: 11024 $ $Author: tominaga_masafumi $ *---------------------------------------------------------------------------*/ /* dataShare-Model Although this is not in game format, this sample program actively implements the features that are necessary (that seem necessary) for an actual game application. Specific features implemented include those listed below. 1) Lobby screen 2) Parent selection screen 3) Measures against process slow-down (synchronous) 4) Error display and return For normal controls, the +Control Pad makes a menu selection, the A Button confirms, and the B Button cancels. For additional debug controls, the L Button and R Button respectively increase and decrease the load count. */ #ifdef SDK_TWL #include #else #include #endif #include #include #include "main.h" #include "font.h" #include "print.h" #include "key.h" #include "graphics.h" #include "wh.h" #include "menu.h" #include "unicode.h" #include "ball.h" /* The GGID used in this demo */ #define WH_GGID SDK_MAKEGGID_SYSTEM(0x13) #define GRAPH_TOTAL_FRAME 60 typedef struct GraphData_ { u16 level; s16 data; } GraphData; static u16 sForcedChannel = 0; static u16 sTgid = 0; static int sSysMode = 0; static int sSysModeStep = 0; static s32 sFrame = 0; static u8 sSendBuf[512] ATTRIBUTE_ALIGN(32); static u8 sRecvBuf[512] ATTRIBUTE_ALIGN(32); static BOOL sRecvFlag[WM_NUM_MAX_CHILD + 1]; static OSOwnerInfo sMyInfo; static MyGameInfo sGameInfo; static WMKeySet sKeySet; static GraphData sGraphData[1 + WM_NUM_MAX_CHILD][GRAPH_TOTAL_FRAME]; static PRScreen sInfoScreen ATTRIBUTE_ALIGN(32); static PRScreen sDebugScreen ATTRIBUTE_ALIGN(32); static GXOamAttr oamBak[BALL_PLAYER_MAX]; static int sBeaconCount = 0; static int sNeedWait = 0; static BOOL sGraphEnabled = 0; static WMBssDesc sBssDesc[ITEM_NUM_MAX]; static Window sRoleMenuWindow; static Window sSelectChannelWindow; static Window sSelectParentWindow; static Window sLobbyWindow; static Window sErrorWindow; static Window sBusyWindow; static Window sOptionWindow; static Window sWaitWindow; extern const unsigned char wlicon_image[]; extern const unsigned short wlicon_palette[]; static void TraceWH(const char *fmt, ...) { va_list vlist; va_start(vlist, fmt); PR_VPrintString(&sDebugScreen, fmt, vlist); va_end(vlist); } static void printString(const char *fmt, ...) { va_list vlist; va_start(vlist, fmt); PR_VPrintString(&sInfoScreen, fmt, vlist); va_end(vlist); } BOOL isDataReceived(int index) { return sRecvFlag[index]; } ShareData *getRecvData(int index) { return (ShareData *) (&(sRecvBuf[index * sizeof(ShareData)])); } ShareData *getSendData(void) { return (ShareData *) sSendBuf; } PRScreen *getInfoScreen(void) { return &sInfoScreen; } void changeSysMode(int s) { if (sSysMode == s) { return; } OS_Printf("sysmode = %d\n", s); sSysMode = s; sSysModeStep = 0; } static void indicateCallback(void *arg) { WMIndCallback *cb; cb = (WMIndCallback *)arg; if (cb->state == WM_STATECODE_BEACON_RECV) { sBeaconCount = 2; } } static void scanCallback(WMBssDesc *bssdesc) { char buf[ITEM_LENGTH_MAX]; u16 nickName[10 + 1]; char sjisNickName[10 * 2 + 1]; u8 entryCount; int i; (void)STD_CopyLString(sjisNickName, "unknown", sizeof(sjisNickName)); entryCount = 0; // Check whether the userGameInfo is an expected value if (bssdesc->gameInfoLength != 0 && bssdesc->gameInfo.userGameInfoLength == sizeof(MyGameInfo)) { const MyGameInfo *pMyGameInfo = (MyGameInfo *) bssdesc->gameInfo.userGameInfo; u32 nickNameLength = pMyGameInfo->nickNameLength; // Do not trust data obtained via communications. Always check that the values are appropriate. if (nickNameLength <= 10) { MI_CpuCopy8(pMyGameInfo->nickName, nickName, nickNameLength * 2); nickName[nickNameLength] = 0; MI_CpuFill8(sjisNickName, 0, sizeof(sjisNickName)); ExUTF16_LEtoSJIS_BE((u8*)sjisNickName, (u16*)nickName, (u16)nickNameLength); entryCount = pMyGameInfo->entryCount; } } // Investigate the detected parents for (i = 0; i < sSelectParentWindow.itemnum; ++i) { if (WM_IsBssidEqual(sBssDesc[i].bssid, bssdesc->bssid)) { break; } } // Create the menu item for parent selection (void)OS_SNPrintf(buf, ITEM_LENGTH_MAX, "[%d]channel%d %s %d/%d", i + 1, bssdesc->channel, sjisNickName, entryCount, WH_CHILD_MAX); if (i < sSelectParentWindow.itemnum) { // Overwrite because it is already registered in the menu setItemToWindow(&sSelectParentWindow, buf, i); } else { // The information is stored and added to the menu because a new parent was detected sBssDesc[sSelectParentWindow.itemnum] = *bssdesc; addItemToWindow(&sSelectParentWindow, buf); WH_PrintBssDesc(bssdesc); } } static void gameinfoCallback(void *arg) { WMCallback *cb = (WMCallback *)arg; } static void updateGameInfo(BOOL isEntry) { ++sGameInfo.periodicalCount; sGameInfo.entryCount = (u8)(MATH_CountPopulation(WH_GetBitmap()) - 1); (void)WM_SetGameInfo(gameinfoCallback, (u16 *)&sGameInfo, sizeof(sGameInfo), WH_GGID, sTgid, (u8)(isEntry ? WM_ATTR_FLAG_ENTRY : 0)); } static void forceSpinWait(void) { // Process to create the process failure state at will using OS_SpinWait // static int waitcycle = 0; if (getKeyInfo()->cnt & PAD_BUTTON_L) { waitcycle += 4000; // OS_Printf("wait = %d\n", waitcycle); } else if (getKeyInfo()->cnt & PAD_BUTTON_R) { waitcycle -= 4000; if (waitcycle < 0) { waitcycle = 0; } // OS_Printf("wait = %d\n", waitcycle); } OS_SpinWait((u32)waitcycle); } static void ModeSelectRole(void) { static const char *menuitems[] = { "Start (Parent mode)", "Start (Child mode)", "Option", NULL }; if (sSysModeStep == 0) { sRoleMenuWindow.selected = 0; setupWindow(&sRoleMenuWindow, 16, 16, WIN_FLAG_SELECTABLE, 24, 24, 16); if (sRoleMenuWindow.itemnum == 0) { int i; for (i = 0; menuitems[i] != NULL; ++i) { addItemToWindow(&sRoleMenuWindow, menuitems[i]); } } openWindow(&sRoleMenuWindow); } if (sRoleMenuWindow.state == WIN_STATE_CLOSED) { if (sRoleMenuWindow.selected < 0) { openWindow(&sRoleMenuWindow); return; } switch (sRoleMenuWindow.selected) { case 0: // WH initialization if (!WH_Initialize()) { OS_Panic("WH_Initiailze failed."); } else { // Wait for initialization to complete while(WH_GetSystemState()==WH_SYSSTATE_BUSY){} } if (sForcedChannel == 0) { // Get the best channel based on the usage rate and makes a connection (void)WH_StartMeasureChannel(); } else { sTgid++; // Update userGameInfo while in the entry reception state updateGameInfo(TRUE); // Delete cached parent information MI_CpuClear8(sBssDesc, sizeof(sBssDesc)); // Start the connection using the manually-selected channel (void)WH_ParentConnect(WH_CONNECTMODE_DS_PARENT, sTgid, sForcedChannel); } InitBall(NULL, 13, 2, 4); changeSysMode(SYSMODE_LOBBY); break; case 1: // WH initialization if (!WH_Initialize()) { OS_Panic("WH_Initiailze failed."); } else { // Wait for initialization to complete while(WH_GetSystemState()==WH_SYSSTATE_BUSY){} } { // Search for a parent static const u8 ANY_PARENT[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; enum { ALL_CHANNEL = 0 }; initWindow(&sSelectParentWindow); setupWindow(&sSelectParentWindow, 16, 16, WIN_FLAG_SELECTABLE, 8 * 2, 8, 16); (void)WH_StartScan(scanCallback, ANY_PARENT, ALL_CHANNEL); InitBall(NULL, 13, 2, 4); changeSysMode(SYSMODE_SCAN_PARENT); } break; case 2: // To the option screen changeSysMode(SYSMODE_OPTION); break; default: break; } } } static void ModeOption(void) { // Option screen process if (sSysModeStep == 0) { initWindow(&sOptionWindow); setupWindow(&sOptionWindow, 16, 16, WIN_FLAG_SELECTABLE, 8, 8, 16); addItemToWindow(&sOptionWindow, "Select channel (parent)"); if (sGraphEnabled) { addItemToWindow(&sOptionWindow, "Disable beacon graph"); } else { addItemToWindow(&sOptionWindow, "Enable beacon graph"); } openWindow(&sOptionWindow); return; } if (sOptionWindow.state == WIN_STATE_CLOSED) { if (sOptionWindow.selected < 0) { // Cancelled changeSysMode(SYSMODE_SELECT_ROLE); return; } if (sOptionWindow.selected == 0) { // To the channel selection screen changeSysMode(SYSMODE_SELECT_CHANNEL); } else if (sOptionWindow.selected == 1) { sGraphEnabled = sGraphEnabled ? FALSE : TRUE; changeSysMode(SYSMODE_SELECT_ROLE); } } } static void ModeLobby(void) { // Lobby screen process u16 bmap; int i; updateGameInfo(TRUE); if (sSysModeStep == 0) { initWindow(&sLobbyWindow); setupWindow(&sLobbyWindow, 16, 16, WIN_FLAG_NONE, 8, 8, 16); for (i = 0; i < WH_CHILD_MAX; ++i) { addItemToWindow(&sLobbyWindow, ""); } addItemToWindow(&sLobbyWindow, ""); addItemToWindow(&sLobbyWindow, "Push A to start"); openWindow(&sLobbyWindow); return; } bmap = WH_GetBitmap(); for (i = 0; i < WH_CHILD_MAX; ++i) { if (bmap & (1 << i)) { (void)OS_SNPrintf(sLobbyWindow.item[i], ITEM_LENGTH_MAX, "[%02d] - entry", i); } else { (void)OS_SNPrintf(sLobbyWindow.item[i], ITEM_LENGTH_MAX, "[%02d] - waiting", i); } } if (sLobbyWindow.state == WIN_STATE_CLOSED) { getSendData()->command = SHARECMD_NONE; if (sLobbyWindow.selected < 0) { // WH shutdown WH_Finalize(); while(WH_GetSystemState()==WH_SYSSTATE_BUSY){} (void)WH_End(); while(WH_GetSystemState()==WH_SYSSTATE_BUSY){} changeSysMode(SYSMODE_SELECT_ROLE); return; } changeSysMode(SYSMODE_PARENT); } } static void ModeLobbyWait(void) { // Process during child device lobby standby // Wait until a parent sends a start signal if (sSysModeStep == 0) { initWindow(&sWaitWindow); setupWindow(&sWaitWindow, 32, 56, WIN_FLAG_NOCONTROL, 8, 8, 8); addItemToWindow(&sWaitWindow, "\\2Accepted."); addItemToWindow(&sWaitWindow, "\\2Waiting for parent..."); openWindow(&sWaitWindow); return; } if (getRecvData(0)->command == SHARECMD_EXITLOBBY) { closeWindow(&sWaitWindow); changeSysMode(SYSMODE_CHILD); } } static void ModeSelectChannel(void) { static u16 channelList[15]; // Auto select + maximum 14 channels // Channel selection screen if (sSysModeStep == 0) { setupWindow(&sSelectChannelWindow, 16, 16, WIN_FLAG_SELECTABLE, 16, 12, 16); if (sSelectChannelWindow.itemnum == 0) { u16 pattern; int i, j; for (i = 0; i < 14; i++) { channelList[i] = 0; } // WH initialization if (!WH_Initialize()) { OS_Panic("WH_Initiailze failed."); } else { // Wait for initialization to complete while(WH_GetSystemState()==WH_SYSSTATE_BUSY){} } pattern = WH_GetAllowedChannel(); // WH shutdown WH_Finalize(); while(WH_GetSystemState()==WH_SYSSTATE_BUSY){} (void)WH_End(); while(WH_GetSystemState()==WH_SYSSTATE_BUSY){} addItemToWindow(&sSelectChannelWindow, "Auto select"); for (i = 1, j = 1; i <= 14; ++i) { if (pattern & (1 << (i - 1))) { char buf[ITEM_LENGTH_MAX]; (void)OS_SNPrintf(buf, ITEM_LENGTH_MAX, "Channel %d", i); channelList[j] = (u16)i; ++j; addItemToWindow(&sSelectChannelWindow, buf); } } } openWindow(&sSelectChannelWindow); } if (sSelectChannelWindow.state == WIN_STATE_CLOSED) { if (sSelectChannelWindow.selected >= 0) { sForcedChannel = channelList[sSelectChannelWindow.selected]; } // Return to the role selection screen changeSysMode(SYSMODE_SELECT_ROLE); } } static void ModeSelectParent(void) { // Display parent on the list and select it. // In this state, WH is scanning a parent, and if a new parent is found during scanning, it is immediately added/reflected to the menu. // if (WH_GetSystemState() == WH_SYSSTATE_CONNECT_FAIL) { // Because the internal WM state is invalid if the WM_StartConnect function fails, WM_Reset must be called once to reset the state to IDLE // WH_Reset(); return; } if (sSysModeStep == 0) { openWindow(&sSelectParentWindow); } // Did the user close the parent search screen? if ((sSelectParentWindow.state == WIN_STATE_CLOSED)) { if (WH_GetSystemState() == WH_SYSSTATE_SCANNING) { // If the parent is currently scanning, scan is finished for a moment (void)WH_EndScan(); return; } if (WH_GetSystemState() == WH_SYSSTATE_IDLE) { if (sSelectParentWindow.selected < 0) { // WH shutdown WH_Finalize(); while(WH_GetSystemState()==WH_SYSSTATE_BUSY){} (void)WH_End(); while(WH_GetSystemState()==WH_SYSSTATE_BUSY){} changeSysMode(SYSMODE_SELECT_ROLE); return; } // When connecting to a parent, set the local host's nickname in the SSID user region and send a notification WH_SetSsid(sMyInfo.nickName, (u32)(sMyInfo.nickNameLength * 2)); // If not scanning, and if the user has selected a parent, data sharing starts (void)WH_ChildConnect(WH_CONNECTMODE_DS_CHILD, &(sBssDesc[sSelectParentWindow.selected])); changeSysMode(SYSMODE_LOBBYWAIT); } } } static void ModeError(void) { // Error state if (sSysModeStep == 0) { initWindow(&sErrorWindow); setupWindow(&sErrorWindow, 16, 16, WIN_FLAG_NONE, 8, 8, 16); addItemToWindow(&sErrorWindow, "\\1Error has occured!"); if (WH_GetLastError() == WM_ERRCODE_OVER_MAX_ENTRY) { addItemToWindow(&sErrorWindow, "\\4Rejected\n"); } if (WH_GetLastError() == WH_ERRCODE_DISCONNECTED) { addItemToWindow(&sErrorWindow, "\\4Disconnected by parent\n"); } if (WH_GetLastError() == WH_ERRCODE_PARENT_NOT_FOUND) { addItemToWindow(&sErrorWindow, "\\4Parent not found\n"); } if (WH_GetLastError() == WH_ERRCODE_LOST_PARENT) { addItemToWindow(&sErrorWindow, "\\4Lost parent\n"); } addItemToWindow(&sErrorWindow, ""); addItemToWindow(&sErrorWindow, "\\fPush A to reset"); closeAllWindow(); openWindow(&sErrorWindow); } if (sErrorWindow.state == WIN_STATE_CLOSED) { // WH shutdown WH_Finalize(); while(WH_GetSystemState()==WH_SYSSTATE_BUSY){} (void)WH_End(); while(WH_GetSystemState()==WH_SYSSTATE_BUSY){} getRecvData(0)->command = SHARECMD_NONE; changeSysMode(SYSMODE_SELECT_ROLE); } } static void printShareData(void) { s32 i; ShareData *sd; sd = getSendData(); printString("\\2Send: 0x%04x 0x%04x\n", sd->key, sd->count & 0xffff); printString("\\4Receive:\n"); for (i = 1; i < (WM_NUM_MAX_CHILD + 1); i++) { if (sRecvFlag[i]) { sd = getRecvData(i); printString("\\4Child%02d: 0x%04x 0x%04x\n", i, sd->key, sd->count & 0xffff); } else { printString("No child\n"); } } } static void ModeParent(void) { printString("\n \\fParent mode\n\n"); printShareData(); } static void ModeChild(void) { ShareData *sd; printString("\n \\fChild mode\n\n"); printShareData(); sd = (ShareData *) getRecvData(0); printString("\\4Parent: 0x%04x 0x%04x\n", sd->key, sd->count & 0xffff); } static void VBlankIntr(void) { /* Some other samples use StepDataSharing here (the VBlankIntr function). This sample also performed StepDataSharing here before. It is this way because WM_StepDataSharing (and WM_GetKeySet) have to be called before the start of MP communications in a frame for communications to be stable. The SDK prepares a VCount of 240 for child devices and 260 for parent devices with default MP communications, so the fewest problems are presented by setting a value immediately after the VBlank starts. (Although not implemented when this document was written, there are plans to allow the VCount for the start of MP communications to be set during initialization.) For 30-fps (and other) games, code is normally required to limit the number of StepDS calls to one every two frames. In that case, wait only a single frame (instead of two) when WM_ERRCODE_NO_DATASET is returned (if processing was lost). Otherwise, when there is one frame difference between the parent and child, it cannot be corrected. */ updateKeys(); OS_SetIrqCheckFlag(OS_IE_V_BLANK); } static void initAllocSystem(void) { void *tempLo; OSHeapHandle hh; tempLo = OS_InitAlloc(OS_ARENA_MAIN, OS_GetMainArenaLo(), OS_GetMainArenaHi(), 1); OS_SetArenaLo(OS_ARENA_MAIN, tempLo); hh = OS_CreateHeap(OS_ARENA_MAIN, OS_GetMainArenaLo(), OS_GetMainArenaHi()); if (hh < 0) { OS_Panic("ARM9: Fail to create heap...\n"); } hh = OS_SetCurrentHeap(OS_ARENA_MAIN, hh); } static void drawPowerGraph(void) { static const GXRgb linecolor[4] = { GX_RGB(15, 0, 0), // Dark red (dead) GX_RGB(31, 31, 0), // Yellow GX_RGB(0, 31, 0), // Green GX_RGB(20, 31, 20), // Light green }; int midx, ringidx; ringidx = (sFrame % GRAPH_TOTAL_FRAME); for (midx = 0; midx < WM_NUM_MAX_CHILD + 1; ++midx) { sGraphData[midx][ringidx].data = (s16)getRecvData(midx)->data; sGraphData[midx][ringidx].level = (u16)getRecvData(midx)->level; } G3_PolygonAttr(GX_LIGHTMASK_NONE, GX_POLYGONMODE_MODULATE, GX_CULL_NONE, 0, 31, GX_POLYGON_ATTR_MISC_DISP_1DOT); G3_TexImageParam(GX_TEXFMT_NONE, GX_TEXGEN_NONE, GX_TEXSIZE_S16, GX_TEXSIZE_T16, GX_TEXREPEAT_NONE, GX_TEXFLIP_NONE, GX_TEXPLTTCOLOR0_USE, 0); G3_MtxMode(GX_MTXMODE_POSITION_VECTOR); G3_PushMtx(); G3_Identity(); G3_Translate(0, 0, -FX16_ONE * 4); G3_Begin(GX_BEGIN_TRIANGLES); for (midx = 1; midx < WM_NUM_MAX_CHILD + 1; ++midx) { int basey, ys, ye, gi, x, level; basey = ((WM_NUM_MAX_CHILD / 2 - midx) * 9 + 6) * FX16_ONE / 64; for (gi = 0; gi < GRAPH_TOTAL_FRAME; ++gi) { int ri; ri = (ringidx - gi); if (ri < 0) { ri += GRAPH_TOTAL_FRAME; } ys = sGraphData[midx][ri].data; level = sGraphData[midx][ri].level; ++ri; if (ri >= GRAPH_TOTAL_FRAME) { ri -= GRAPH_TOTAL_FRAME; } ye = sGraphData[midx][ri].data; x = -(gi - GRAPH_TOTAL_FRAME / 2) * 3; x *= FX16_ONE / 64; ys = ys * FX16_ONE / 64 + basey; ye = ye * FX16_ONE / 64 + basey; G3_Color(linecolor[level]); G3_Vtx((fx16)x, (fx16)ys, 0); G3_Vtx((fx16)(x + FX16_ONE / 64 / 2), (fx16)(ys + FX16_ONE / 64), 0); G3_Vtx((fx16)(x + 3 * FX16_ONE / 64), (fx16)ye, 0); } } G3_End(); G3_PopMtx(1); } static void drawPowerIcon(void) { // GUIDELINE // Display the radio reception strength icon setupPseudo2DCamera(); G3_PolygonAttr(GX_LIGHTMASK_NONE, GX_POLYGONMODE_DECAL, GX_CULL_NONE, 0, 31, 0); G3_TexImageParam(GX_TEXFMT_PLTT16, GX_TEXGEN_TEXCOORD, GX_TEXSIZE_S16, GX_TEXSIZE_T16, GX_TEXREPEAT_NONE, GX_TEXFLIP_NONE, GX_TEXPLTTCOLOR0_USE, (u32)(0x2000 + WM_GetLinkLevel() * 16 * 16 / 2)); G3_TexPlttBase(0x2000, GX_TEXFMT_PLTT16); G3_MtxMode(GX_MTXMODE_POSITION_VECTOR); drawPseudo2DTexQuad(224, 160, 16, 16, 16, 16); } static void drawRadioIcon(void) { // GUIDELINE // Display the radio wave strength icon int i; G3_PolygonAttr(GX_LIGHTMASK_NONE, GX_POLYGONMODE_DECAL, GX_CULL_NONE, 0, 31, 0); G3_TexPlttBase(0x2000, GX_TEXFMT_PLTT16); G3_TexImageParam(GX_TEXFMT_PLTT16, GX_TEXGEN_TEXCOORD, GX_TEXSIZE_S16, GX_TEXSIZE_T16, GX_TEXREPEAT_NONE, GX_TEXFLIP_NONE, GX_TEXPLTTCOLOR0_USE, 0x2000 + 4 * 16 * 16 / 2); for (i = 0; i < 2; ++i) { drawPseudo2DTexQuad(16, 12 + i * 24 + ((i == sRoleMenuWindow.selected) ? (sFrame / 15 & 1) : 0), 16, 16, 16, 16); } } static void updateShareData(void) { if (WH_GetSystemState() == WH_SYSSTATE_DATASHARING) { if (WH_StepDS(sSendBuf)) { u16 i; for (i = 0; i < WH_CHILD_MAX + 1; ++i) { u8 *adr; ShareData *sd; adr = (u8 *)WH_GetSharedDataAdr(i); sd = (ShareData *) & (sRecvBuf[i * sizeof(ShareData)]); if (adr != NULL) { MI_CpuCopy8(adr, sd, sizeof(ShareData)); sRecvFlag[i] = TRUE; } else { sd->level = 0; sd->data = 0; sRecvFlag[i] = FALSE; } } for (; i < WM_NUM_MAX_CHILD + 1; ++i) { ShareData *sd; sd = (ShareData *) & (sRecvBuf[i * sizeof(ShareData)]); sd->level = 0; sd->data = 0; sRecvFlag[i] = FALSE; } sNeedWait = FALSE; } else { u16 i; for (i = 0; i < WM_NUM_MAX_CHILD + 1; ++i) { sRecvFlag[i] = FALSE; } sNeedWait = TRUE; } } else { u16 i; for (i = 0; i < WM_NUM_MAX_CHILD + 1; ++i) { sRecvFlag[i] = FALSE; } sNeedWait = FALSE; } } static void packSendData(void) { ShareData *senddata; if (sNeedWait) { return; } senddata = getSendData(); senddata->command = (sSysMode == SYSMODE_LOBBY) ? SHARECMD_NONE : SHARECMD_EXITLOBBY; senddata->level = (u16)WM_GetLinkLevel(); senddata->data = 0; senddata->key = getKeyInfo()->cnt; senddata->count = sFrame & 0xffff; if (sBeaconCount != 0) { senddata->data += sBeaconCount * senddata->level; if (sBeaconCount > 0) { sBeaconCount = -sBeaconCount + 1; } else { sBeaconCount = -sBeaconCount - 1; } } } static void mainLoop(void) { int retry = 0; enum { MAX_RETRY = 5 }; for (sFrame = 0; TRUE; sFrame++) { int whstate; int prevmode; whstate = WH_GetSystemState(); prevmode = sSysMode; switch (whstate) { case WH_SYSSTATE_CONNECT_FAIL: // Retry several times when a connection fails if (sSysMode == SYSMODE_LOBBYWAIT && retry < MAX_RETRY) { changeSysMode(SYSMODE_SELECT_PARENT); sSysModeStep = 1; // So that window does not open... retry++; break; } // When a retry fails, transition unchanged to the error state case WH_SYSSTATE_ERROR: // WH state has the priority if an error occurred changeSysMode(SYSMODE_ERROR); break; case WH_SYSSTATE_MEASURECHANNEL: { u16 channel = WH_GetMeasureChannel(); sTgid++; (void)WH_ParentConnect(WH_CONNECTMODE_DS_PARENT, sTgid, channel); } break; default: break; } PR_ClearScreen(&sInfoScreen); // Load test forceSpinWait(); switch (sSysMode) { case SYSMODE_SELECT_ROLE: // Role (parent or child) selection screen ModeSelectRole(); retry = 0; break; case SYSMODE_SELECT_CHANNEL: // Channel selection screen ModeSelectChannel(); break; case SYSMODE_LOBBY: // Lobby screen ModeLobby(); break; case SYSMODE_LOBBYWAIT: // Lobby standby screen ModeLobbyWait(); break; case SYSMODE_OPTION: // Option screen ModeOption(); break; case SYSMODE_SCAN_PARENT: case SYSMODE_SELECT_PARENT: // Parent selection screen ModeSelectParent(); break; case SYSMODE_ERROR: // Error report screen ModeError(); break; case SYSMODE_PARENT: // Parent mode screen ModeParent(); break; case SYSMODE_CHILD: // Child mode screen ModeChild(); break; default: break; } if ((whstate == WH_SYSSTATE_BUSY) || ((whstate == WH_SYSSTATE_SCANNING) && (sSelectParentWindow.itemnum == 0))) { sBusyWindow.state = WIN_STATE_OPENED; } else { sBusyWindow.state = WIN_STATE_CLOSED; } updateAllWindow(); drawAllWindow(); if( (sSysMode != SYSMODE_SELECT_ROLE) && (sSysMode != SYSMODE_OPTION) && (sSysMode != SYSMODE_SELECT_CHANNEL) ) { drawPowerIcon(); } if ((sSysMode == SYSMODE_PARENT) || (sSysMode == SYSMODE_CHILD)) { if (sGraphEnabled) { drawPowerGraph(); } } if ((sSysMode == SYSMODE_SELECT_ROLE) && (sRoleMenuWindow.state == WIN_STATE_OPENED)) { drawRadioIcon(); } G3_SwapBuffers(GX_SORTMODE_AUTO, GX_BUFFERMODE_W); if (!sNeedWait && ((sSysMode == SYSMODE_PARENT) || (sSysMode == SYSMODE_CHILD))) { // ... In an actual game, the update processing for characters and so on is performed here // int i; for (i = 0; i < BALL_PLAYER_MAX; ++i) { if (sRecvFlag[i]) { ShareData *sd = getRecvData(i); InputBallKey((int)i, (int)sd->key); } } // Update the balls' state using data that could be shared. // Note that if this is run in a frame during which WH_StepDS failed, synchronization between the parent and child game state will be lost. // UpdateBalls(WH_GetCurrentAid()); // Prepare the display data for (i = 0; i < BALL_PLAYER_MAX; ++i) { G2_SetOBJAttr(&oamBak[i], shared->ball[i].x, shared->ball[i].y, 0, GX_OAM_MODE_NORMAL, FALSE, GX_OAM_EFFECT_NONE, GX_OAM_SHAPE_8x8, GX_OAM_COLOR_16, shared->ball[i].chr, shared->ball[i].plt, 0); } } DC_FlushRange(sInfoScreen.screen, sizeof(u16) * PR_SCREEN_SIZE); /* I/O register is accessed using DMA operation, so cache wait is not needed */ // DC_WaitWriteBufferEmpty(); GX_LoadBG2Scr(sInfoScreen.screen, 0, sizeof(u16) * PR_SCREEN_SIZE); DC_FlushRange(sDebugScreen.screen, sizeof(u16) * PR_SCREEN_SIZE); /* I/O register is accessed using DMA operation, so cache wait is not needed */ // DC_WaitWriteBufferEmpty(); GXS_LoadBG2Scr(sDebugScreen.screen, 0, sizeof(u16) * PR_SCREEN_SIZE); DC_FlushRange(&oamBak, sizeof(oamBak)); if ((sSysMode == SYSMODE_PARENT) || (sSysMode == SYSMODE_CHILD)) { GX_LoadOAM(&oamBak, 0, sizeof(oamBak)); } else { // No display of entire OAM MI_CpuFillFast((void *)HW_OAM, 192, HW_OAM_SIZE); } OS_WaitVBlankIntr(); packSendData(); updateShareData(); if (prevmode == sSysMode) { ++sSysModeStep; } } } #ifdef SDK_TWL void TwlMain(void) #else void NitroMain(void) #endif { OS_Init(); FX_Init(); initGraphics(); GX_SetBankForBG(GX_VRAM_BG_256_AB); GX_SetBankForBGExtPltt(GX_VRAM_BGEXTPLTT_01_F); G2_SetBG2ControlText(GX_BG_SCRSIZE_TEXT_256x256, GX_BG_COLORMODE_16, GX_BG_SCRBASE_0x0000, GX_BG_CHARBASE_0x04000); GX_BeginLoadTex(); GX_LoadTex(wlicon_image, 0x2000, 16 * 16 * 5); GX_EndLoadTex(); GX_BeginLoadTexPltt(); GX_LoadTexPltt(wlicon_palette, 0x2000, 32); GX_EndLoadTexPltt(); GX_LoadBG2Char(d_CharData, 0, sizeof(d_CharData)); GX_LoadBGPltt(d_PaletteData, 0, sizeof(d_PaletteData)); GX_SetBankForSubBG(GX_VRAM_SUB_BG_128_C); GX_SetBankForSubBGExtPltt(GX_VRAM_SUB_BGEXTPLTT_0123_H); G2S_SetBG2ControlText(GX_BG_SCRSIZE_TEXT_256x256, GX_BG_COLORMODE_16, GX_BG_SCRBASE_0x0000, GX_BG_CHARBASE_0x04000); GXS_SetGraphicsMode(GX_BGMODE_0); GXS_SetVisiblePlane(GX_PLANEMASK_BG2); G2S_SetBG2Priority(0); G2S_BG2Mosaic(FALSE); GXS_LoadBG2Char(d_CharData, 0, sizeof(d_CharData)); GXS_LoadBGPltt(d_PaletteData, 0, sizeof(d_PaletteData)); GX_SetBankForOBJ(GX_VRAM_OBJ_16_G); GX_LoadOBJ(d_CharData, 0, sizeof(d_CharData)); GX_LoadOBJPltt(d_PaletteData, 0, sizeof(d_PaletteData)); GX_SetVisiblePlane(GX_GetVisiblePlane() | GX_PLANEMASK_OBJ); initAllocSystem(); OS_SetIrqFunction(OS_IE_V_BLANK, VBlankIntr); (void)OS_EnableIrqMask(OS_IE_V_BLANK); (void)GX_VBlankIntr(TRUE); (void)OS_EnableIrq(); (void)OS_EnableInterrupts(); sInfoScreen.scroll = FALSE; sDebugScreen.scroll = TRUE; // Load owner information OS_GetOwnerInfo(&sMyInfo); sGameInfo.nickNameLength = (u8)sMyInfo.nickNameLength; MI_CpuCopy8(sMyInfo.nickName, sGameInfo.nickName, MATH_MIN(sMyInfo.nickNameLength * 2, sizeof(sGameInfo.nickName))); // WH initialization settings WH_SetGgid(WH_GGID); WH_SetDebugOutput(TraceWH); // Set the callback specified in WM_SetIndCallback, which is called within WH WH_SetIndCallback(indicateCallback); GX_DispOn(); GXS_DispOn(); initWindow(&sRoleMenuWindow); initWindow(&sSelectChannelWindow); initWindow(&sSelectParentWindow); initWindow(&sLobbyWindow); initWindow(&sErrorWindow); initWindow(&sOptionWindow); initWindow(&sWaitWindow); initWindow(&sBusyWindow); setupWindow(&sBusyWindow, 64, 80, WIN_FLAG_NOCONTROL, 8, 8, 16); addItemToWindow(&sBusyWindow, "\\2Working..."); // Countermeasure for pressing A Button in IPL updateKeys(); // initCharacter(); changeSysMode(SYSMODE_SELECT_ROLE); mainLoop(); }