/* DirectPlay & DirectPlayLobby messaging implementation * * Copyright 2000,2001 - Peter Hunnisett * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA * * NOTES * o Messaging interface required for both DirectPlay and DirectPlayLobby. */ #include #include #include "windef.h" #include "winbase.h" #include "wingdi.h" #include "winuser.h" #include "winerror.h" #include "dplayx_messages.h" #include "dplay_global.h" #include "dplayx_global.h" #include "name_server.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(dplay); typedef struct tagMSGTHREADINFO { HANDLE hStart; HANDLE hDeath; HANDLE hSettingRead; HANDLE hNotifyEvent; } MSGTHREADINFO, *LPMSGTHREADINFO; static DWORD CALLBACK DPL_MSG_ThreadMain( LPVOID lpContext ); static void *DP_MSG_ExpectReply( IDirectPlayImpl *This, DPSP_SENDDATA *data, DWORD dwWaitTime, WORD wReplyCommandId, void **lplpReplyMsg, DWORD *lpdwMsgBodySize ); /* Create the message reception thread to allow the application to receive * asynchronous message reception */ DWORD CreateLobbyMessageReceptionThread( HANDLE hNotifyEvent, HANDLE hStart, HANDLE hDeath, HANDLE hConnRead ) { DWORD dwMsgThreadId; LPMSGTHREADINFO lpThreadInfo; HANDLE hThread; lpThreadInfo = malloc( sizeof( *lpThreadInfo ) ); if( lpThreadInfo == NULL ) { return 0; } /* The notify event may or may not exist. Depends if async comm or not */ if( hNotifyEvent && !DuplicateHandle( GetCurrentProcess(), hNotifyEvent, GetCurrentProcess(), &lpThreadInfo->hNotifyEvent, 0, FALSE, DUPLICATE_SAME_ACCESS ) ) { ERR( "Unable to duplicate event handle\n" ); goto error; } /* These 3 handles don't need to be duplicated because we don't keep a * reference to them where they're created. They're created specifically * for the message thread */ lpThreadInfo->hStart = hStart; lpThreadInfo->hDeath = hDeath; lpThreadInfo->hSettingRead = hConnRead; hThread = CreateThread( NULL, /* Security attribs */ 0, /* Stack */ DPL_MSG_ThreadMain, /* Msg reception function */ lpThreadInfo, /* Msg reception func parameter */ 0, /* Flags */ &dwMsgThreadId /* Updated with thread id */ ); if ( hThread == NULL ) { ERR( "Unable to create msg thread\n" ); goto error; } CloseHandle(hThread); return dwMsgThreadId; error: free( lpThreadInfo ); return 0; } static DWORD CALLBACK DPL_MSG_ThreadMain( LPVOID lpContext ) { LPMSGTHREADINFO lpThreadInfo = lpContext; DWORD dwWaitResult; TRACE( "Msg thread created. Waiting on app startup\n" ); /* Wait to ensure that the lobby application is started w/ 1 min timeout */ dwWaitResult = WaitForSingleObject( lpThreadInfo->hStart, 10000 /* 10 sec */ ); if( dwWaitResult == WAIT_TIMEOUT ) { FIXME( "Should signal app/wait creation failure (0x%08lx)\n", dwWaitResult ); goto end_of_thread; } /* Close this handle as it's not needed anymore */ CloseHandle( lpThreadInfo->hStart ); lpThreadInfo->hStart = 0; /* Wait until the lobby knows what it is */ dwWaitResult = WaitForSingleObject( lpThreadInfo->hSettingRead, INFINITE ); if( dwWaitResult == WAIT_TIMEOUT ) { ERR( "App Read connection setting timeout fail (0x%08lx)\n", dwWaitResult ); } /* Close this handle as it's not needed anymore */ CloseHandle( lpThreadInfo->hSettingRead ); lpThreadInfo->hSettingRead = 0; TRACE( "App created && initialized starting main message reception loop\n" ); for ( ;; ) { MSG lobbyMsg; GetMessageW( &lobbyMsg, 0, 0, 0 ); } end_of_thread: TRACE( "Msg thread exiting!\n" ); free( lpThreadInfo ); return 0; } /* DP messaging stuff */ static HANDLE DP_MSG_BuildAndLinkReplyStruct( IDirectPlayImpl *This, DP_MSG_REPLY_STRUCT_LIST *lpReplyStructList, WORD wReplyCommandId ) { lpReplyStructList->replyExpected.hReceipt = CreateEventW( NULL, FALSE, FALSE, NULL ); lpReplyStructList->replyExpected.wExpectedReply = wReplyCommandId; lpReplyStructList->replyExpected.lpReplyMsg = NULL; lpReplyStructList->replyExpected.dwMsgBodySize = 0; /* Insert into the message queue while locked */ EnterCriticalSection( &This->lock ); DPQ_INSERT( This->dp2->repliesExpected, lpReplyStructList, repliesExpected ); LeaveCriticalSection( &This->lock ); return lpReplyStructList->replyExpected.hReceipt; } static LPVOID DP_MSG_CleanReplyStruct( LPDP_MSG_REPLY_STRUCT_LIST lpReplyStructList, LPVOID* lplpReplyMsg, LPDWORD lpdwMsgBodySize ) { CloseHandle( lpReplyStructList->replyExpected.hReceipt ); *lplpReplyMsg = lpReplyStructList->replyExpected.lpReplyMsg; *lpdwMsgBodySize = lpReplyStructList->replyExpected.dwMsgBodySize; return lpReplyStructList->replyExpected.lpReplyMsg; } HRESULT DP_MSG_SendRequestPlayerId( IDirectPlayImpl *This, DWORD dwFlags, DPID *lpdpidAllocatedId ) { LPVOID lpMsg; LPDPMSG_REQUESTNEWPLAYERID lpMsgBody; DWORD dwMsgSize; HRESULT hr = DP_OK; dwMsgSize = This->dp2->spData.dwSPHeaderSize + sizeof( *lpMsgBody ); lpMsg = calloc( 1, dwMsgSize ); lpMsgBody = (LPDPMSG_REQUESTNEWPLAYERID)( (BYTE*)lpMsg + This->dp2->spData.dwSPHeaderSize ); /* Compose dplay message envelope */ lpMsgBody->envelope.dwMagic = DPMSGMAGIC_DPLAYMSG; lpMsgBody->envelope.wCommandId = DPMSGCMD_REQUESTNEWPLAYERID; lpMsgBody->envelope.wVersion = DPMSGVER_DP6; /* Compose the body of the message */ lpMsgBody->dwFlags = dwFlags; /* Send the message */ { DPSP_SENDDATA data; data.dwFlags = DPSEND_GUARANTEED; data.idPlayerTo = 0; /* Name server */ data.idPlayerFrom = 0; /* Sending from DP */ data.lpMessage = lpMsg; data.dwMessageSize = dwMsgSize; data.bSystemMessage = TRUE; /* Allow reply to be sent */ data.lpISP = This->dp2->spData.lpISP; TRACE( "Asking for player id w/ dwFlags 0x%08lx\n", lpMsgBody->dwFlags ); DP_MSG_ExpectReply( This, &data, DPMSG_DEFAULT_WAIT_TIME, DPMSGCMD_NEWPLAYERIDREPLY, &lpMsg, &dwMsgSize ); } /* Need to examine the data and extract the new player id */ if( SUCCEEDED(hr) ) { LPCDPMSG_NEWPLAYERIDREPLY lpcReply; lpcReply = lpMsg; *lpdpidAllocatedId = lpcReply->dpidNewPlayerId; TRACE( "Received reply for id = 0x%08lx\n", lpcReply->dpidNewPlayerId ); /* FIXME: I think that the rest of the message has something to do * with remote data for the player that perhaps I need to setup. * However, with the information that is passed, all that it could * be used for is a standardized initialization value, which I'm * guessing we can do without. Unless the message content is the same * for several different messages? */ free( lpMsg ); } return hr; } HRESULT DP_MSG_ForwardPlayerCreation( IDirectPlayImpl *This, DPID dpidServer ) { LPVOID lpMsg; LPDPMSG_FORWARDADDPLAYER lpMsgBody; DWORD dwMsgSize; HRESULT hr = DP_OK; dwMsgSize = This->dp2->spData.dwSPHeaderSize + sizeof( *lpMsgBody ); lpMsg = calloc( 1, dwMsgSize ); lpMsgBody = (LPDPMSG_FORWARDADDPLAYER)( (BYTE*)lpMsg + This->dp2->spData.dwSPHeaderSize ); /* Compose dplay message envelope */ lpMsgBody->envelope.dwMagic = DPMSGMAGIC_DPLAYMSG; lpMsgBody->envelope.wCommandId = DPMSGCMD_FORWARDADDPLAYER; lpMsgBody->envelope.wVersion = DPMSGVER_DP6; #if 0 { LPBYTE lpPData; DWORD dwDataSize; /* SP Player remote data needs to be propagated at some point - is this the point? */ IDirectPlaySP_GetSPPlayerData( This->dp2->spData.lpISP, 0, &lpPData, &dwDataSize, DPSET_REMOTE ); ERR( "Player Data size is 0x%08lx\n" "[%02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x]\n" "[%02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x]\n", dwDataSize, lpPData[0], lpPData[1], lpPData[2], lpPData[3], lpPData[4], lpPData[5], lpPData[6], lpPData[7], lpPData[8], lpPData[9], lpPData[10], lpPData[11], lpPData[12], lpPData[13], lpPData[14], lpPData[15], lpPData[16], lpPData[17], lpPData[18], lpPData[19], lpPData[20], lpPData[21], lpPData[22], lpPData[23], lpPData[24], lpPData[25], lpPData[26], lpPData[27], lpPData[28], lpPData[29], lpPData[30], lpPData[31] ); DebugBreak(); } #endif /* Compose body of message */ lpMsgBody->dpidAppServer = dpidServer; lpMsgBody->unknown2[0] = 0x0; lpMsgBody->unknown2[1] = 0x1c; lpMsgBody->unknown2[2] = 0x6c; lpMsgBody->unknown2[3] = 0x50; lpMsgBody->unknown2[4] = 0x9; lpMsgBody->dpidAppServer2 = dpidServer; lpMsgBody->unknown3[0] = 0x0; lpMsgBody->unknown3[1] = 0x0; lpMsgBody->unknown3[2] = 0x20; lpMsgBody->unknown3[3] = 0x0; lpMsgBody->unknown3[4] = 0x0; lpMsgBody->dpidAppServer3 = dpidServer; lpMsgBody->unknown4[0] = 0x30; lpMsgBody->unknown4[1] = 0xb; lpMsgBody->unknown4[2] = 0x0; lpMsgBody->unknown4[3] = NS_GetNsMagic( This->dp2->lpNameServerData ) - 0x02000000; TRACE( "Setting first magic to 0x%08lx\n", lpMsgBody->unknown4[3] ); lpMsgBody->unknown4[4] = 0x0; lpMsgBody->unknown4[5] = 0x0; lpMsgBody->unknown4[6] = 0x0; lpMsgBody->unknown4[7] = NS_GetNsMagic( This->dp2->lpNameServerData ); TRACE( "Setting second magic to 0x%08lx\n", lpMsgBody->unknown4[7] ); lpMsgBody->unknown4[8] = 0x0; lpMsgBody->unknown4[9] = 0x0; lpMsgBody->unknown4[10] = 0x0; lpMsgBody->unknown4[11] = 0x0; lpMsgBody->unknown5[0] = 0x0; lpMsgBody->unknown5[1] = 0x0; /* Send the message */ { DPSP_SENDDATA data; data.dwFlags = DPSEND_GUARANTEED; data.idPlayerTo = 0; /* Name server */ data.idPlayerFrom = dpidServer; /* Sending from session server */ data.lpMessage = lpMsg; data.dwMessageSize = dwMsgSize; data.bSystemMessage = TRUE; /* Allow reply to be sent */ data.lpISP = This->dp2->spData.lpISP; TRACE( "Sending forward player request with 0x%08lx\n", dpidServer ); lpMsg = DP_MSG_ExpectReply( This, &data, DPMSG_WAIT_60_SECS, DPMSGCMD_GETNAMETABLEREPLY, &lpMsg, &dwMsgSize ); } /* Need to examine the data and extract the new player id */ if( lpMsg != NULL ) { FIXME( "Name Table reply received: stub\n" ); } return hr; } /* Queue up a structure indicating that we want a reply of type wReplyCommandId. DPlay does * not seem to offer any way of uniquely differentiating between replies of the same type * relative to the request sent. There is an implicit assumption that there will be no * ordering issues on sends and receives from the opposite machine. No wonder MS is not * a networking company. */ static void *DP_MSG_ExpectReply( IDirectPlayImpl *This, DPSP_SENDDATA *lpData, DWORD dwWaitTime, WORD wReplyCommandId, void **lplpReplyMsg, DWORD *lpdwMsgBodySize ) { HRESULT hr; HANDLE hMsgReceipt; DP_MSG_REPLY_STRUCT_LIST replyStructList; DWORD dwWaitReturn; /* Setup for receipt */ hMsgReceipt = DP_MSG_BuildAndLinkReplyStruct( This, &replyStructList, wReplyCommandId ); TRACE( "Sending msg and expecting cmd %u in reply within %lu ticks\n", wReplyCommandId, dwWaitTime ); hr = (*This->dp2->spData.lpCB->Send)( lpData ); if( FAILED(hr) ) { ERR( "Send failed: %s\n", DPLAYX_HresultToString( hr ) ); return NULL; } /* The reply message will trigger the hMsgReceipt event effectively switching * control back to this thread. See DP_MSG_ReplyReceived. */ dwWaitReturn = WaitForSingleObject( hMsgReceipt, dwWaitTime ); if( dwWaitReturn != WAIT_OBJECT_0 ) { ERR( "Wait failed 0x%08lx\n", dwWaitReturn ); return NULL; } /* Clean Up */ return DP_MSG_CleanReplyStruct( &replyStructList, lplpReplyMsg, lpdwMsgBodySize ); } /* Determine if there is a matching request for this incoming message and then copy * all important data. It is quite silly to have to copy the message, but the documents * indicate that a copy is taken. Silly really. */ void DP_MSG_ReplyReceived( IDirectPlayImpl *This, WORD wCommandId, const void *lpcMsgBody, DWORD dwMsgBodySize ) { LPDP_MSG_REPLY_STRUCT_LIST lpReplyList; #if 0 if( wCommandId == DPMSGCMD_FORWARDADDPLAYER ) { DebugBreak(); } #endif /* Find, and immediately remove (to avoid double triggering), the appropriate entry. Call locked to * avoid problems. */ EnterCriticalSection( &This->lock ); DPQ_REMOVE_ENTRY( This->dp2->repliesExpected, repliesExpected, replyExpected.wExpectedReply, ==, wCommandId, lpReplyList ); LeaveCriticalSection( &This->lock ); if( lpReplyList != NULL ) { lpReplyList->replyExpected.dwMsgBodySize = dwMsgBodySize; lpReplyList->replyExpected.lpReplyMsg = malloc( dwMsgBodySize ); CopyMemory( lpReplyList->replyExpected.lpReplyMsg, lpcMsgBody, dwMsgBodySize ); /* Signal the thread which sent the message that it has a reply */ SetEvent( lpReplyList->replyExpected.hReceipt ); } else { ERR( "No receipt event set - only expecting in reply mode\n" ); DebugBreak(); } } void DP_MSG_ToSelf( IDirectPlayImpl *This, DPID dpidSelf ) { LPVOID lpMsg; LPDPMSG_SENDENVELOPE lpMsgBody; DWORD dwMsgSize; dwMsgSize = This->dp2->spData.dwSPHeaderSize + sizeof( *lpMsgBody ); lpMsg = calloc( 1, dwMsgSize ); lpMsgBody = (LPDPMSG_SENDENVELOPE)( (BYTE*)lpMsg + This->dp2->spData.dwSPHeaderSize ); /* Compose dplay message envelope */ lpMsgBody->dwMagic = DPMSGMAGIC_DPLAYMSG; lpMsgBody->wCommandId = DPMSGCMD_JUSTENVELOPE; lpMsgBody->wVersion = DPMSGVER_DP6; /* Send the message to ourselves */ { DPSP_SENDDATA data; data.dwFlags = 0; data.idPlayerTo = dpidSelf; /* Sending to session server */ data.idPlayerFrom = 0; /* Sending from session server */ data.lpMessage = lpMsg; data.dwMessageSize = dwMsgSize; data.bSystemMessage = TRUE; /* Allow reply to be sent */ data.lpISP = This->dp2->spData.lpISP; lpMsg = DP_MSG_ExpectReply( This, &data, DPMSG_WAIT_5_SECS, DPMSGCMD_JUSTENVELOPE, &lpMsg, &dwMsgSize ); } } void DP_MSG_ErrorReceived( IDirectPlayImpl *This, WORD wCommandId, const void *lpMsgBody, DWORD dwMsgBodySize ) { LPCDPMSG_FORWARDADDPLAYERNACK lpcErrorMsg; lpcErrorMsg = lpMsgBody; ERR( "Received error message %u. Error is %s\n", wCommandId, DPLAYX_HresultToString( lpcErrorMsg->errorCode) ); DebugBreak(); }