/*---------------------------------------------------------------------------* Project: RevolutionDWC Demos File: ./match_serverclient/src/main.c Copyright 2005-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. *---------------------------------------------------------------------------*/ /** * * * Brief comment: This is a sample of server-client matchmaking. * * Performs server-client matchmaking over the internet. * When matchmaking completes, communication is used to exchange positions between oneself and other parties. * One's own position can be moved by pressing Up/Down/Left/Right on the +Control Pad. * * Version: 1.4.12 Revised the way in which Cancel is handled when it is passed to a callback function * Version: 1.4.10: Removed login processing timeouts. */ #include "../../common/include/common.h" //--------------------------------------------------------- #define USE_RECV_TIMEOUT // Use DWC_SetRecvTimeoutTime #ifdef USE_RECV_TIMEOUT #define RECV_TIMEOUT_TIME (1000*30) #define KEEPALIVE_INTERVAL (RECV_TIMEOUT_TIME/5) #endif //#define USE_RELIABLE // Perform reliable communications // Constant Declarations //--------------------------------------------------------- /// Enumerator that indicates the connection type typedef enum{ SCCONNECT_NULL, SCCONNECT_SERVER, SCCONNECT_CLIENT, SCCONNECT_GROUPID }SCCONNECT; /// Enumerator that indicates status typedef enum{ STATE_INIT, STATE_START, STATE_UPDATE_FRIENDS, STATE_ONLINE, STATE_SETTING, STATE_MATCHING, STATE_SERVER, STATE_CLIENT, STATE_COMMUNICATING, STATE_ERROR }MATCHINGTESTSTATE; /// Enumerator that indicates the present suspension state typedef enum{ SUSPEND_NONE, // not suspended SUSPEND_YES, // waiting in a suspended state SUSPEND_NO // waiting in an unsuspended state }SUSPEND_STATE; #define MAX_PLAYERS 4 // Number of connected players including yourself #define SIZE_SEND_BUFFER 512 // Size of send buffer #define SIZE_RECV_BUFFER 4 * 1024 // Size of receive buffer #define TIMEOUT (60 * 300) // Timeout duration (frames) #define USER_TYPE_MALE 1 // Type used to attempt matchmaking #define USER_TYPE_FEMALE 2 // Type used to attempt matchmaking #define USER_MAX_MALE 2 // Maximum player count for the attempt type (male) #define USER_MAX_FEMALE 2 // Maximum player count for the attempt type (female) #define APP_CONNECTION_KEEPALIVE_TIME 30000 // Variable Declarations //--------------------------------------------------------- static SCCONNECT s_connect; // Connection type static MATCHINGTESTSTATE s_state; // Status static SUSPEND_STATE suspend_state; // Suspension state static int s_counter; // Timeout counter static int s_currentPlayerNum; // The number of people currently connected. static int s_serverIndex; // Index of the server to connect to. static u32 s_groupID; // Group ID static u8 force_stop = 0; static BOOL useAttemptCallback = FALSE; // Flag used to attempt matchmaking static u8 male = USER_TYPE_MALE; // Parameters used to attempt matchmaking (male: 1, female: 2) static DWCTopologyType s_topologyType = DWC_TOPOLOGY_TYPE_HYBRID; // Network topology to use // Position data structure exchanged through communications. static struct Position { s32 xpos; s32 ypos; } s_position[MAX_PLAYERS]; // Position data saved locally. static u8 s_send_buf[ SIZE_SEND_BUFFER ]; // Send buffer static u8 s_recv_buf[ MAX_PLAYERS-1 ][ SIZE_RECV_BUFFER ];// Receive buffer /// Structure that contains the player information static struct tagPlayerInfo { /// User data DWCUserData userdata; /// Friend data DWCFriendData friendlist[FRIEND_LIST_LEN]; } s_playerinfo ATTRIBUTE_ALIGN(32); // Application-specific data sample to pass to the communication callback typedef struct tagCommunicationData { s32 count; } CommunicationData; CommunicationData s_commData; // Function Prototype //--------------------------------------------------------- static BOOL LoadFromNAND( void ); static void DispFriendList( void ); static void loginCallback(DWCError error, int profileID, void * param); static void newClientCallback(int index, void* param ); static void matchedSCCallback(DWCError error, BOOL cancel, BOOL self, BOOL isServer, int index, void* param ); static void closeCallback(DWCError error, BOOL isLocal, BOOL isServer, u8 aid, int index, void* param); static void sendCallback( int size, u8 aid, void* param ); static void recvCallback( u8 aid, u8* buffer, int size, void* param ); static void suspendCallback(DWCSuspendResult result, BOOL suspend, void* data); static BOOL connectAttemptCallback( u8* data, void* param ); /** * Load data from NAND * * Loads the user data and the friend roster from NAND. * * Return value: TRUE: There is user data in NAND flash memory. * Return value: FALSE: There is no user data in NAND flash memory. */ BOOL LoadFromNAND( void ) { // Load from NAND // DWCDemoPrintf("Loading from NAND...\n"); DWCDemoLoadNAND( "playerinfo", 0, (u8*)&s_playerinfo, sizeof( s_playerinfo ) ); // Check if user data is valid // if ( DWC_CheckUserData( &s_playerinfo.userdata ) ) { // It was valid, so output the user data and return TRUE DWC_ReportUserData( &s_playerinfo.userdata ); return TRUE; } // If valid user data had not been saved // // This demo cannot do anything if there are no friends // DWCDemoPrintf( "No valid userdata found.\n" ); return FALSE; } /** * Display the friend roster. */ void DispFriendList( void ) { int i; DWCDemoPrintf( "\nFriend list\n"); for ( i = 0; i < FRIEND_LIST_LEN; i++ ) { if ( !DWC_IsValidFriendData( &s_playerinfo.friendlist[i] ) ) continue; DWCDemoPrintf( "Index: %d (%s) => ", i, DWCDemoGetFriendStatusString(&s_playerinfo.friendlist[i]) ); DWC_ReportFriendData( &s_playerinfo.userdata, &s_playerinfo.friendlist[i] ); } DWCDemoPrintf( "\n"); } /** * This is the callback invoked after data transmission. */ static void sendCallback( int size, u8 aid, void* param ) { CommunicationData* commData = (CommunicationData*)param; commData->count++; // Add if data was sent DWCDemoPrintf( "Send Callback aid:%d, size:%d mycount:%d\n", aid, size, commData->count ); } /** * This is the callback invoked at data reception. */ static void recvCallback( u8 aid, u8* buffer, int size, void* param ) { // // Save the received data. // // Eight bytes of data were sent in; storing a 4-byte X coordinate and a 4-byte Y coordinate. // if ( size == 8 ) { CommunicationData* commData = (CommunicationData*)param; commData->count--; // Subtract if data was received s_position[ aid ].xpos = (s32)DWCi_LEtoHl(((u32*)buffer)[0]); s_position[ aid ].ypos = (s32)DWCi_LEtoHl(((u32*)buffer)[1]); DWCDemoPrintf( "[aid:%d] x:% 8d y:% 8d mycount:%d\n", aid, s_position[ aid ].xpos, s_position[ aid ].ypos, commData->count); } else { DWCDemoPrintf( "invalid recv data size\n" ); } } /** * The callback function invoked at disconnection. */ static void closeCallback(DWCError error, BOOL isLocal, BOOL isServer, u8 aid, int index, void* param) { (void)isLocal; (void)isServer; (void)param; // reduce the number --s_currentPlayerNum; DWCDemoPrintf( "Closed Callback err:%d, aid:%d, idx:%d\n", error, aid, index ); } // Friend roster synchronization completion callback static void updateServersCallback(DWCError error, BOOL isChanged, void* param) { (void)param; if (error == DWC_ERROR_NONE) { // Successful synchronous processing of friend roster. // The friend roster must be saved if it has been modified if (isChanged) { // Save to NAND DWCDemoSaveNAND("playerinfo", 0, (u8*)&s_playerinfo, sizeof(s_playerinfo)); } s_state = STATE_ONLINE; } else { DWCDemoPrintf("Update friend data Failed\n"); s_state = STATE_ERROR; } } // Friend roster delete callback static void deleteFriendCallback(int deletedIndex, int srcIndex, void* param) { (void)param; DWC_Printf(DWC_REPORTFLAG_TEST, "friend[%d] was deleted (equal friend[%d]).\n", deletedIndex, srcIndex); // Save to NAND DWCDemoSaveNAND("playerinfo", 0, (u8*)&s_playerinfo, sizeof(s_playerinfo)); } /** * The callback function called during login */ static void loginCallback(DWCError error, int profileID, void * param) { (void)param; DWCDemoPrintf( "Login callback : %d\n", error ); if ( error == DWC_ERROR_NONE ) { // When the login is successful // DWCDemoPrintf( "Login Success. ProfileID=%d\n", profileID ); // Specify the callback function. DWC_SetConnectionClosedCallback( closeCallback, NULL ); DWC_SetUserSendCallback( sendCallback, &s_commData ); DWC_SetUserRecvCallback( recvCallback, &s_commData ); // Synchronize the local friend roster with the server friend roster if (!DWC_UpdateServersAsync( NULL, updateServersCallback, NULL, NULL, NULL, deleteFriendCallback, NULL)) { // Failed to start synchronization DWCDemoPrintf( "DWC_UpdateServersAsync() call Failed\n" ); s_state = STATE_ERROR; return; } s_state = STATE_UPDATE_FRIENDS; } else { // When the login failed // DWCDemoPrintf( "Login Failed\n" ); s_state = STATE_ERROR; } } /** * The callback function invoked whenever a client connection is started. */ static void newClientCallback(int index, void* param ) { (void)param; DWCDemoPrintf( "New Client is connecting(idx %d)...\n", index); } /** * The callback function invoked whenever a client connection is completed. */ static void matchedSCCallback(DWCError error, BOOL cancel, BOOL self, BOOL isServer, int index, void* param ) { (void)param; DWCDemoPrintf( "Match err:%d, cancel:%d, self:%d isServer:%d index:%d \n", error, cancel, self, isServer, index ); if (cancel == TRUE) { if (self == TRUE && isServer == FALSE) { s_state = STATE_ONLINE; } DWCDemoPrintf( "canceled\n" ); return; } if ( error == DWC_ERROR_NONE ) { s_currentPlayerNum++; DWCDemoPrintf( "%d players connected.\n", s_currentPlayerNum ); /* if(s_currentPlayerNum == MAX_PLAYERS)*/ { u8* pAidList; int num = DWC_GetAIDList(&pAidList); int i, j; for (i = 0, j = 0; i < num; i++) { if (pAidList[i] == DWC_GetMyAID()) { j++; continue; } // Set a receive buffer for AIDs other than the local host's DWC_SetRecvBuffer( pAidList[i], &s_recv_buf[i-j], SIZE_RECV_BUFFER ); #ifdef USE_RECV_TIMEOUT DWC_SetRecvTimeoutTime(pAidList[i], RECV_TIMEOUT_TIME); #endif } s_state = STATE_COMMUNICATING; } // Use this timing to get the group ID s_groupID = DWC_GetGroupID(); } else if (error == DWC_ERROR_SC_CONNECT_BLOCK) { // When local host was fumbled because of a difference in connection speed int errorCode; DWCErrorType errorType; DWC_GetLastErrorEx(&errorCode, &errorType); DWCDemoPrintf("DWC_GetLastErrorEx ErrorCode:%d ErrorType:%d\n", errorCode, (int)errorType); DWCDemoPrintf( "DWC_ERROR_SC_CONNECT_BLOCK\n" ); DWC_ClearError(); s_state = STATE_ONLINE; } else if ( error == DWC_ERROR_NOT_FRIEND_SERVER ) { DWCDemoPrintf( "Server not found\n" ); s_state = STATE_ERROR; } else { DWCDemoPrintf( "Match Error\n" ); s_state = STATE_ERROR; } } // Callback to suspend matchmaking static void stopSCCallback(void* param) { (void)param; DWCDemoPrintf( "stopSCCallback called.\n"); } // Callback invoked during an NN check static BOOL connectAttemptCallback( u8* data, void* param ) { // Check with the condition that there are no more than 2 males and 2 females int numAid; u8* aidList; u8 userData[DWC_CONNECTION_USERDATA_LEN]; int i; int maleCount = 0; // Number of males int femaleCount = 0; // Number of females (void)param; numAid = DWC_GetAIDList(&aidList); for (i = 0; i < numAid; i++) { DWC_GetConnectionUserData(aidList[i], userData); if (userData[0] == USER_TYPE_MALE) maleCount++; else if (userData[0] == USER_TYPE_FEMALE) femaleCount++; } if (data[0] == USER_TYPE_MALE && maleCount < USER_MAX_MALE) return TRUE; else if (data[0] == USER_TYPE_FEMALE && femaleCount < USER_MAX_FEMALE) return TRUE; else return FALSE; } // Callback invoked when suspending is complete static void suspendCallback(DWCSuspendResult result, BOOL suspend, void* data) { (void)data; if (result == DWC_SUSPEND_SUCCESS) { DWCDemoPrintf(" suspend successfly. suspend is (%s).\n", suspend ? "TRUE" : "FALSE"); } else { DWCDemoPrintf(" suspend error! result is (%d). suspend is (%s).\n", result, suspend ? "TRUE" : "FALSE"); } suspend_state = SUSPEND_NONE; } #ifdef USE_RECV_TIMEOUT static void userRecvTimeoutCallback(u8 aid, void* param) { CommunicationData* commData = (CommunicationData*)param; DWCDemoPrintf("aid (%d) is timeout. mycount is %d\n", aid, commData->count); } #endif /** * Main */ void DWCDemoMain() { int i; u32 padlast; // The previous pressed state u32 pad = 0; // The current pressed state u32 padtrig; // Keys whose state changed from the last time u32 padpressed; // Newly-pressed keys u32 padreleased; // Newly-released keys u8 userData[DWC_CONNECTION_USERDATA_LEN]; MATCHINGTESTSTATE lastState; // State in the previous cycle. BOOL firstTime = TRUE; // When the state has changed. // Initializes the position data. //------------------------------ for ( i=0; i= STATE_MATCHING ) { if ( force_stop ) { if ( padpressed & DWCDEMO_KEY_Z ) { DWCDemoPrintf("force stop OFF.\n"); force_stop = 0; } else continue; } else { if ( padpressed & DWCDEMO_KEY_Z ) { DWCDemoPrintf("force stop ON.\n"); force_stop = 1; } } } DWC_ProcessFriendsMatch(); if (DWC_GetLastErrorEx(NULL, NULL) != DWC_ERROR_NONE) goto error; firstTime = lastState != s_state; lastState = s_state; switch ( s_state ) { // Processing login // case STATE_START: // Wait until loginCallback() is invoked. break; // updating the friend roster case STATE_UPDATE_FRIENDS: break; // Choosing of the connection type in progress // case STATE_ONLINE: { if ( firstTime) { s_connect = SCCONNECT_NULL; DWCDemoPrintf("\n"); DWCDemoPrintf("--------------------------------\n"); DWCDemoPrintf("- Server Client Match Demo -\n"); DWCDemoPrintf("--------------------------------\n"); DWCDemoPrintf("A: Set up server\n"); DWCDemoPrintf("Z: Set up server(male)\n"); DWCDemoPrintf("B: Connect to server\n"); DWCDemoPrintf("X: Connect to server from groupID\n"); DWCDemoPrintf(" groupID is %d\n", s_groupID); //DWCDemoPrintf("U/D:Change debug latency level\n"); DWCDemoPrintf("LEFT: Start with attempt callback(male)\n"); DWCDemoPrintf("RIGHT: Start with attempt callback(female)\n"); DWCDemoPrintf("START: Set Hybrid mode\n"); DWCDemoPrintf("Y: Set Star mode\n"); DWCDemoPrintf("UP: Set Fullmesh mode\n"); DWCDemoPrintf("DOWN: exit demo\n"); DWCDemoPrintf("\n"); suspend_state = SUSPEND_NONE; } if ( padpressed & DWCDEMO_KEY_A ) { useAttemptCallback = FALSE; s_connect = SCCONNECT_SERVER; } if ( padpressed & DWCDEMO_KEY_Z ) { DWCDemoPrintf("Z: Set up server(male)\n"); s_connect = SCCONNECT_SERVER; useAttemptCallback = TRUE; male = USER_TYPE_MALE; } if ( padpressed & DWCDEMO_KEY_B ) { useAttemptCallback = FALSE; s_connect = SCCONNECT_CLIENT; } if ( padpressed & DWCDEMO_KEY_X ) { // Uses the previous state for group ID connection s_connect = SCCONNECT_GROUPID; } if ( padpressed & DWCDEMO_KEY_START ) { DWCDemoPrintf("DWC_TOPOLOGY_TYPE_HYBRID set.\n"); s_topologyType = DWC_TOPOLOGY_TYPE_HYBRID; firstTime = TRUE; break; } else if ( padpressed & DWCDEMO_KEY_Y ) { DWCDemoPrintf("DWC_TOPOLOGY_TYPE_STAR set.\n"); s_topologyType = DWC_TOPOLOGY_TYPE_STAR; firstTime = TRUE; break; } else if ( padpressed & DWCDEMO_KEY_UP ) { DWCDemoPrintf("DWC_TOPOLOGY_TYPE_FULLMESH set.\n"); s_topologyType = DWC_TOPOLOGY_TYPE_FULLMESH; firstTime = TRUE; break; } else if ( padpressed & DWCDEMO_KEY_LEFT ) { DWCDemoPrintf("Start with attempt callback(male)\n"); useAttemptCallback = TRUE; s_state = STATE_MATCHING; firstTime = TRUE; s_connect = SCCONNECT_CLIENT; male = USER_TYPE_MALE; } else if ( padpressed & DWCDEMO_KEY_RIGHT ) { DWCDemoPrintf("Start with attempt callback(female)\n"); useAttemptCallback = TRUE; s_state = STATE_MATCHING; firstTime = TRUE; s_connect = SCCONNECT_CLIENT; male = USER_TYPE_FEMALE; } else if ( padpressed & DWCDEMO_KEY_DOWN ) { // Quit goto exit; } if ( s_connect != SCCONNECT_NULL) s_state = STATE_SETTING; break; } // Configuring // case STATE_SETTING: { switch ( s_connect ) { case SCCONNECT_SERVER: s_state = STATE_MATCHING; break; // Specify the Friend Roster index of the server to connect to. case SCCONNECT_CLIENT: if ( firstTime ) { DispFriendList(); DWCDemoPrintf("Arrow: Select index\n"); DWCDemoPrintf("A: OK\n"); DWCDemoPrintf("B: Cancel\n"); } if ( padpressed & DWCDEMO_KEY_UP ) s_serverIndex++; if ( padpressed & DWCDEMO_KEY_DOWN ) s_serverIndex--; if ( s_serverIndex == FRIEND_LIST_LEN) s_serverIndex = FRIEND_LIST_LEN-1; if ( s_serverIndex < 0) s_serverIndex = 0; if ( firstTime || padpressed & (DWCDEMO_KEY_A | DWCDEMO_KEY_UP | DWCDEMO_KEY_DOWN)) { DWCDemoPrintf("Specify server's friend list index: %d(%u:%s)\n", s_serverIndex, s_playerinfo.friendlist[s_serverIndex].gs_profile_id.id, DWCDemoGetFriendStatusString(&s_playerinfo.friendlist[s_serverIndex])); } else if (padpressed & DWCDEMO_KEY_B) { s_state = STATE_ONLINE; firstTime = TRUE; } break; // Either select the current group ID or input a new one case SCCONNECT_GROUPID: { if ( s_groupID ) { // Display it if it exists DWCDemoPrintf("A: %d\n", s_groupID); s_state = STATE_MATCHING; } else { // Return to the menu for the moment DWCDemoPrintf("GroupID is None.\n", s_groupID); s_state = STATE_ONLINE; s_connect = SCCONNECT_NULL; } } break; } if ( padpressed & DWCDEMO_KEY_A) { s_state = STATE_MATCHING; break; } break; } // Matchmaking in progress // case STATE_MATCHING: { if ( firstTime ) { // Set values depending on the UserType userData[0] = male; // unused, but for testing userData[1] = 5; userData[2] = 120; userData[3] = 254; switch ( s_connect ) { case SCCONNECT_SERVER: DWCDemoPrintf("Waiting for clients...\n"); // Become a server. DWC_SetupGameServer( s_topologyType, // Connection network structure (u8)MAX_PLAYERS, // The number of people to matchmake. matchedSCCallback, // NULL, // newClientCallback, // NULL, useAttemptCallback ? connectAttemptCallback : NULL, userData, NULL ); // s_state = STATE_COMMUNICATING; firstTime = TRUE; break; case SCCONNECT_CLIENT: DWCDemoPrintf("Connecting to server...\n"); // Connect to the server. DWC_ConnectToGameServerAsync( s_topologyType, // Connection network structure s_serverIndex, // Friend Roster index for the connection target server. matchedSCCallback, // NULL, // newClientCallback, // NULL, useAttemptCallback ? connectAttemptCallback : NULL, userData, NULL ); // break; case SCCONNECT_GROUPID: DWCDemoPrintf("Connecting to server from groupID...\n"); // Connect to the server through a group ID DWC_ConnectToGameServerByGroupID( s_topologyType, // Connection network structure s_groupID, matchedSCCallback, NULL, newClientCallback, NULL, useAttemptCallback ? connectAttemptCallback : NULL, userData, NULL); break; } } if (padpressed & DWCDEMO_KEY_B) { if (DWC_CancelMatch() == TRUE) { DWCDemoPrintf("DWC_CancelMatch() called.\n"); s_state = STATE_ONLINE; firstTime = TRUE; } else { DWCDemoPrintf("cannot call DWC_CancelMatch() now.\n"); } break; } break; } // Communicating // case STATE_COMMUNICATING: { struct Position sendPos; u8 me; if ( firstTime ) { DWCDemoPrintf("Push Arrow to send position.\n"); DWCDemoPrintf("Push [B] to call DWC_CloseAllConnectionsHard().\n"); } // Get one's own AID. me = DWC_GetMyAID(); if ( pad & DWCDEMO_KEY_UP ) s_position[ me ].ypos--; if ( pad & DWCDEMO_KEY_DOWN ) s_position[ me ].ypos++; if ( pad & DWCDEMO_KEY_LEFT ) s_position[ me ].xpos--; if ( pad & DWCDEMO_KEY_RIGHT ) s_position[ me ].xpos++; // Send a new position to the communication partner when there is input. #ifdef USE_RECV_TIMEOUT { // Forcibly send data at intervals shorter than the receive timeout interval. static int lastKeepAliveSent = 0; int now = (int)DWCi_Np_TicksToMilliSeconds(DWCi_Np_GetTick()); if (lastKeepAliveSent == 0 || (now - lastKeepAliveSent) > KEEPALIVE_INTERVAL) { pad |= DWCDEMO_KEY_UP; lastKeepAliveSent = now; } } #endif if ( pad & ( DWCDEMO_KEY_UP | DWCDEMO_KEY_DOWN | DWCDEMO_KEY_LEFT | DWCDEMO_KEY_RIGHT ) ) { sendPos.xpos = (s32)DWCi_HtoLEl((u32)s_position[ me ].xpos); sendPos.ypos = (s32)DWCi_HtoLEl((u32)s_position[ me ].ypos); // Send one's own position to all other parties. #ifdef USE_RELIABLE DWC_SendReliableBitmap( DWC_GetAIDBitmap(), (const void*)&sendPos, sizeof( s32 ) * 2 ); #else DWC_SendUnreliableBitmap( DWC_GetAIDBitmap(), (const void*)&sendPos, sizeof( s32 ) * 2 ); #endif } // Disconnect communications and return to ONLINE if the cancel button is pressed. // if ( pad & DWCDEMO_KEY_B ) { DWC_CloseAllConnectionsHard(); s_state = STATE_ONLINE; s_connect = SCCONNECT_NULL; } // Suspend matchmaking with Y if ( pad & DWCDEMO_KEY_Y ) { // Temporarily suspend DWC_RequestSuspendMatchAsync(TRUE, suspendCallback, NULL); suspend_state = SUSPEND_YES; } else if ( pad & DWCDEMO_KEY_X ) { // Begin accepting again DWC_RequestSuspendMatchAsync(FALSE, suspendCallback, NULL); suspend_state = SUSPEND_NO; } } break; // Error occurred // case STATE_ERROR: goto error; } // Display information on the bottom-most line if (DWC_GetServerAID() != DWC_INVALID_AID) DWCDemoPrintfToFixedRow("GetServerAID=%d,IsServerMyself=%s", DWC_GetServerAID(), DWC_IsServerMyself() ? "Y" : "N"); else DWCDemoPrintfToFixedRow("Svr:unknown."); #ifdef _WIN32 // Display information in the title bar DWCViewInfoToTitle(); #endif DWCDemoUpdate(); } exit: // Quit DWC_ShutdownFriendsMatch(); return; error: // Error processing { int errorCode; DWCErrorType errorType; if ( DWC_GetLastErrorEx( &errorCode, &errorType ) != DWC_ERROR_NONE ) { DWCDemoPrintf( "DWC_GetLastErrorEx ErrorCode:%d ErrorType:%d\n", errorCode, (int)errorType ); // Clear the errors. DWC_ClearError(); } } // Quit DWC_ShutdownFriendsMatch(); return; }