1 /* DirectPlay & DirectPlayLobby messaging implementation
2  *
3  * Copyright 2000,2001 - Peter Hunnisett
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
18  *
19  * NOTES
20  *  o Messaging interface required for both DirectPlay and DirectPlayLobby.
21  */
22 
23 #include "dplayx_global.h"
24 
25 typedef struct tagMSGTHREADINFO
26 {
27   HANDLE hStart;
28   HANDLE hDeath;
29   HANDLE hSettingRead;
30   HANDLE hNotifyEvent;
31 } MSGTHREADINFO, *LPMSGTHREADINFO;
32 
33 static DWORD CALLBACK DPL_MSG_ThreadMain( LPVOID lpContext );
34 static void *DP_MSG_ExpectReply( IDirectPlayImpl *This, DPSP_SENDDATA *data, DWORD dwWaitTime,
35         WORD wReplyCommandId, void **lplpReplyMsg, DWORD *lpdwMsgBodySize );
36 
37 
38 /* Create the message reception thread to allow the application to receive
39  * asynchronous message reception
40  */
41 DWORD CreateLobbyMessageReceptionThread( HANDLE hNotifyEvent, HANDLE hStart,
42                                          HANDLE hDeath, HANDLE hConnRead )
43 {
44   DWORD           dwMsgThreadId;
45   LPMSGTHREADINFO lpThreadInfo;
46   HANDLE          hThread;
47 
48   lpThreadInfo = HeapAlloc( GetProcessHeap(), 0, sizeof( *lpThreadInfo ) );
49   if( lpThreadInfo == NULL )
50   {
51     return 0;
52   }
53 
54   /* The notify event may or may not exist. Depends if async comm or not */
55   if( hNotifyEvent &&
56       !DuplicateHandle( GetCurrentProcess(), hNotifyEvent,
57                         GetCurrentProcess(), &lpThreadInfo->hNotifyEvent,
58                         0, FALSE, DUPLICATE_SAME_ACCESS ) )
59   {
60     ERR( "Unable to duplicate event handle\n" );
61     goto error;
62   }
63 
64   /* These 3 handles don't need to be duplicated because we don't keep a
65    * reference to them where they're created. They're created specifically
66    * for the message thread
67    */
68   lpThreadInfo->hStart       = hStart;
69   lpThreadInfo->hDeath       = hDeath;
70   lpThreadInfo->hSettingRead = hConnRead;
71 
72   hThread = CreateThread( NULL,                  /* Security attribs */
73                           0,                     /* Stack */
74                           DPL_MSG_ThreadMain,    /* Msg reception function */
75                           lpThreadInfo,          /* Msg reception func parameter */
76                           0,                     /* Flags */
77                           &dwMsgThreadId         /* Updated with thread id */
78                         );
79   if ( hThread == NULL )
80   {
81     ERR( "Unable to create msg thread\n" );
82     goto error;
83   }
84 
85   CloseHandle(hThread);
86 
87   return dwMsgThreadId;
88 
89 error:
90 
91   HeapFree( GetProcessHeap(), 0, lpThreadInfo );
92 
93   return 0;
94 }
95 
96 static DWORD CALLBACK DPL_MSG_ThreadMain( LPVOID lpContext )
97 {
98   LPMSGTHREADINFO lpThreadInfo = lpContext;
99   DWORD dwWaitResult;
100 
101   TRACE( "Msg thread created. Waiting on app startup\n" );
102 
103   /* Wait to ensure that the lobby application is started w/ 1 min timeout */
104   dwWaitResult = WaitForSingleObject( lpThreadInfo->hStart, 10000 /* 10 sec */ );
105   if( dwWaitResult == WAIT_TIMEOUT )
106   {
107     FIXME( "Should signal app/wait creation failure (0x%08x)\n", dwWaitResult );
108     goto end_of_thread;
109   }
110 
111   /* Close this handle as it's not needed anymore */
112   CloseHandle( lpThreadInfo->hStart );
113   lpThreadInfo->hStart = 0;
114 
115   /* Wait until the lobby knows what it is */
116   dwWaitResult = WaitForSingleObject( lpThreadInfo->hSettingRead, INFINITE );
117   if( dwWaitResult == WAIT_TIMEOUT )
118   {
119     ERR( "App Read connection setting timeout fail (0x%08x)\n", dwWaitResult );
120   }
121 
122   /* Close this handle as it's not needed anymore */
123   CloseHandle( lpThreadInfo->hSettingRead );
124   lpThreadInfo->hSettingRead = 0;
125 
126   TRACE( "App created && initialized starting main message reception loop\n" );
127 
128   for ( ;; )
129   {
130     MSG lobbyMsg;
131     GetMessageW( &lobbyMsg, 0, 0, 0 );
132   }
133 
134 end_of_thread:
135   TRACE( "Msg thread exiting!\n" );
136   HeapFree( GetProcessHeap(), 0, lpThreadInfo );
137 
138   return 0;
139 }
140 
141 /* DP messaging stuff */
142 static HANDLE DP_MSG_BuildAndLinkReplyStruct( IDirectPlayImpl *This,
143         DP_MSG_REPLY_STRUCT_LIST *lpReplyStructList, WORD wReplyCommandId )
144 {
145   lpReplyStructList->replyExpected.hReceipt       = CreateEventW( NULL, FALSE, FALSE, NULL );
146   lpReplyStructList->replyExpected.wExpectedReply = wReplyCommandId;
147   lpReplyStructList->replyExpected.lpReplyMsg     = NULL;
148   lpReplyStructList->replyExpected.dwMsgBodySize  = 0;
149 
150   /* Insert into the message queue while locked */
151   EnterCriticalSection( &This->lock );
152     DPQ_INSERT( This->dp2->repliesExpected, lpReplyStructList, repliesExpected );
153   LeaveCriticalSection( &This->lock );
154 
155   return lpReplyStructList->replyExpected.hReceipt;
156 }
157 
158 static
159 LPVOID DP_MSG_CleanReplyStruct( LPDP_MSG_REPLY_STRUCT_LIST lpReplyStructList,
160                                 LPVOID* lplpReplyMsg, LPDWORD lpdwMsgBodySize  )
161 {
162   CloseHandle( lpReplyStructList->replyExpected.hReceipt );
163 
164   *lplpReplyMsg    = lpReplyStructList->replyExpected.lpReplyMsg;
165   *lpdwMsgBodySize = lpReplyStructList->replyExpected.dwMsgBodySize;
166 
167   return lpReplyStructList->replyExpected.lpReplyMsg;
168 }
169 
170 HRESULT DP_MSG_SendRequestPlayerId( IDirectPlayImpl *This, DWORD dwFlags, DPID *lpdpidAllocatedId )
171 {
172   LPVOID                     lpMsg;
173   LPDPMSG_REQUESTNEWPLAYERID lpMsgBody;
174   DWORD                      dwMsgSize;
175   HRESULT                    hr = DP_OK;
176 
177   dwMsgSize = This->dp2->spData.dwSPHeaderSize + sizeof( *lpMsgBody );
178 
179   lpMsg = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwMsgSize );
180 
181   lpMsgBody = (LPDPMSG_REQUESTNEWPLAYERID)( (BYTE*)lpMsg +
182                                              This->dp2->spData.dwSPHeaderSize );
183 
184   /* Compose dplay message envelope */
185   lpMsgBody->envelope.dwMagic    = DPMSGMAGIC_DPLAYMSG;
186   lpMsgBody->envelope.wCommandId = DPMSGCMD_REQUESTNEWPLAYERID;
187   lpMsgBody->envelope.wVersion   = DPMSGVER_DP6;
188 
189   /* Compose the body of the message */
190   lpMsgBody->dwFlags = dwFlags;
191 
192   /* Send the message */
193   {
194     DPSP_SENDDATA data;
195 
196     data.dwFlags        = DPSEND_GUARANTEED;
197     data.idPlayerTo     = 0; /* Name server */
198     data.idPlayerFrom   = 0; /* Sending from DP */
199     data.lpMessage      = lpMsg;
200     data.dwMessageSize  = dwMsgSize;
201     data.bSystemMessage = TRUE; /* Allow reply to be sent */
202     data.lpISP          = This->dp2->spData.lpISP;
203 
204     TRACE( "Asking for player id w/ dwFlags 0x%08x\n",
205            lpMsgBody->dwFlags );
206 
207     DP_MSG_ExpectReply( This, &data, DPMSG_DEFAULT_WAIT_TIME, DPMSGCMD_NEWPLAYERIDREPLY,
208                         &lpMsg, &dwMsgSize );
209   }
210 
211   /* Need to examine the data and extract the new player id */
212   if( SUCCEEDED(hr) )
213   {
214     LPCDPMSG_NEWPLAYERIDREPLY lpcReply;
215 
216     lpcReply = lpMsg;
217 
218     *lpdpidAllocatedId = lpcReply->dpidNewPlayerId;
219 
220     TRACE( "Received reply for id = 0x%08x\n", lpcReply->dpidNewPlayerId );
221 
222     /* FIXME: I think that the rest of the message has something to do
223      *        with remote data for the player that perhaps I need to setup.
224      *        However, with the information that is passed, all that it could
225      *        be used for is a standardized initialization value, which I'm
226      *        guessing we can do without. Unless the message content is the same
227      *        for several different messages?
228      */
229 
230     HeapFree( GetProcessHeap(), 0, lpMsg );
231   }
232 
233   return hr;
234 }
235 
236 HRESULT DP_MSG_ForwardPlayerCreation( IDirectPlayImpl *This, DPID dpidServer )
237 {
238   LPVOID                   lpMsg;
239   LPDPMSG_FORWARDADDPLAYER lpMsgBody;
240   DWORD                    dwMsgSize;
241   HRESULT                  hr = DP_OK;
242 
243   dwMsgSize = This->dp2->spData.dwSPHeaderSize + sizeof( *lpMsgBody );
244 
245   lpMsg = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwMsgSize );
246 
247   lpMsgBody = (LPDPMSG_FORWARDADDPLAYER)( (BYTE*)lpMsg +
248                                           This->dp2->spData.dwSPHeaderSize );
249 
250   /* Compose dplay message envelope */
251   lpMsgBody->envelope.dwMagic    = DPMSGMAGIC_DPLAYMSG;
252   lpMsgBody->envelope.wCommandId = DPMSGCMD_FORWARDADDPLAYER;
253   lpMsgBody->envelope.wVersion   = DPMSGVER_DP6;
254 
255 #if 0
256   {
257     LPBYTE lpPData;
258     DWORD  dwDataSize;
259 
260     /* SP Player remote data needs to be propagated at some point - is this the point? */
261     IDirectPlaySP_GetSPPlayerData( This->dp2->spData.lpISP, 0, &lpPData, &dwDataSize, DPSET_REMOTE );
262 
263     ERR( "Player Data size is 0x%08lx\n"
264          "[%02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x]\n"
265          "[%02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x]\n",
266 
267 	 dwDataSize,
268          lpPData[0], lpPData[1], lpPData[2], lpPData[3], lpPData[4],
269 	 lpPData[5], lpPData[6], lpPData[7], lpPData[8], lpPData[9],
270          lpPData[10], lpPData[11], lpPData[12], lpPData[13], lpPData[14],
271 	 lpPData[15], lpPData[16], lpPData[17], lpPData[18], lpPData[19],
272          lpPData[20], lpPData[21], lpPData[22], lpPData[23], lpPData[24],
273 	 lpPData[25], lpPData[26], lpPData[27], lpPData[28], lpPData[29],
274          lpPData[30], lpPData[31]
275         );
276     DebugBreak();
277   }
278 #endif
279 
280   /* Compose body of message */
281   lpMsgBody->dpidAppServer = dpidServer;
282   lpMsgBody->unknown2[0] = 0x0;
283   lpMsgBody->unknown2[1] = 0x1c;
284   lpMsgBody->unknown2[2] = 0x6c;
285   lpMsgBody->unknown2[3] = 0x50;
286   lpMsgBody->unknown2[4] = 0x9;
287 
288   lpMsgBody->dpidAppServer2 = dpidServer;
289   lpMsgBody->unknown3[0] = 0x0;
290   lpMsgBody->unknown3[1] = 0x0;
291   lpMsgBody->unknown3[2] = 0x20;
292   lpMsgBody->unknown3[3] = 0x0;
293   lpMsgBody->unknown3[4] = 0x0;
294 
295   lpMsgBody->dpidAppServer3 = dpidServer;
296   lpMsgBody->unknown4[0] =  0x30;
297   lpMsgBody->unknown4[1] =  0xb;
298   lpMsgBody->unknown4[2] =  0x0;
299 
300   lpMsgBody->unknown4[3] =  NS_GetNsMagic( This->dp2->lpNameServerData ) -
301                             0x02000000;
302   TRACE( "Setting first magic to 0x%08x\n", lpMsgBody->unknown4[3] );
303 
304   lpMsgBody->unknown4[4] =  0x0;
305   lpMsgBody->unknown4[5] =  0x0;
306   lpMsgBody->unknown4[6] =  0x0;
307 
308   lpMsgBody->unknown4[7] =  NS_GetNsMagic( This->dp2->lpNameServerData );
309   TRACE( "Setting second magic to 0x%08x\n", lpMsgBody->unknown4[7] );
310 
311   lpMsgBody->unknown4[8] =  0x0;
312   lpMsgBody->unknown4[9] =  0x0;
313   lpMsgBody->unknown4[10] = 0x0;
314   lpMsgBody->unknown4[11] = 0x0;
315 
316   lpMsgBody->unknown5[0] = 0x0;
317   lpMsgBody->unknown5[1] = 0x0;
318 
319   /* Send the message */
320   {
321     DPSP_SENDDATA data;
322 
323     data.dwFlags        = DPSEND_GUARANTEED;
324     data.idPlayerTo     = 0; /* Name server */
325     data.idPlayerFrom   = dpidServer; /* Sending from session server */
326     data.lpMessage      = lpMsg;
327     data.dwMessageSize  = dwMsgSize;
328     data.bSystemMessage = TRUE; /* Allow reply to be sent */
329     data.lpISP          = This->dp2->spData.lpISP;
330 
331     TRACE( "Sending forward player request with 0x%08x\n", dpidServer );
332 
333     lpMsg = DP_MSG_ExpectReply( This, &data,
334                                 DPMSG_WAIT_60_SECS,
335                                 DPMSGCMD_GETNAMETABLEREPLY,
336                                 &lpMsg, &dwMsgSize );
337   }
338 
339   /* Need to examine the data and extract the new player id */
340   if( lpMsg != NULL )
341   {
342     FIXME( "Name Table reply received: stub\n" );
343   }
344 
345   return hr;
346 }
347 
348 /* Queue up a structure indicating that we want a reply of type wReplyCommandId. DPlay does
349  * not seem to offer any way of uniquely differentiating between replies of the same type
350  * relative to the request sent. There is an implicit assumption that there will be no
351  * ordering issues on sends and receives from the opposite machine. No wonder MS is not
352  * a networking company.
353  */
354 static void *DP_MSG_ExpectReply( IDirectPlayImpl *This, DPSP_SENDDATA *lpData, DWORD dwWaitTime,
355         WORD wReplyCommandId, void **lplpReplyMsg, DWORD *lpdwMsgBodySize )
356 {
357   HRESULT                  hr;
358   HANDLE                   hMsgReceipt;
359   DP_MSG_REPLY_STRUCT_LIST replyStructList;
360   DWORD                    dwWaitReturn;
361 
362   /* Setup for receipt */
363   hMsgReceipt = DP_MSG_BuildAndLinkReplyStruct( This, &replyStructList,
364                                                 wReplyCommandId );
365 
366   TRACE( "Sending msg and expecting cmd %u in reply within %u ticks\n",
367          wReplyCommandId, dwWaitTime );
368   hr = (*This->dp2->spData.lpCB->Send)( lpData );
369 
370   if( FAILED(hr) )
371   {
372     ERR( "Send failed: %s\n", DPLAYX_HresultToString( hr ) );
373     return NULL;
374   }
375 
376   /* The reply message will trigger the hMsgReceipt event effectively switching
377    * control back to this thread. See DP_MSG_ReplyReceived.
378    */
379   dwWaitReturn = WaitForSingleObject( hMsgReceipt, dwWaitTime );
380   if( dwWaitReturn != WAIT_OBJECT_0 )
381   {
382     ERR( "Wait failed 0x%08x\n", dwWaitReturn );
383     return NULL;
384   }
385 
386   /* Clean Up */
387   return DP_MSG_CleanReplyStruct( &replyStructList, lplpReplyMsg, lpdwMsgBodySize );
388 }
389 
390 /* Determine if there is a matching request for this incoming message and then copy
391  * all important data. It is quite silly to have to copy the message, but the documents
392  * indicate that a copy is taken. Silly really.
393  */
394 void DP_MSG_ReplyReceived( IDirectPlayImpl *This, WORD wCommandId, const void *lpcMsgBody,
395         DWORD dwMsgBodySize )
396 {
397   LPDP_MSG_REPLY_STRUCT_LIST lpReplyList;
398 
399 #if 0
400   if( wCommandId == DPMSGCMD_FORWARDADDPLAYER )
401   {
402     DebugBreak();
403   }
404 #endif
405 
406   /* Find, and immediately remove (to avoid double triggering), the appropriate entry. Call locked to
407    * avoid problems.
408    */
409   EnterCriticalSection( &This->lock );
410     DPQ_REMOVE_ENTRY( This->dp2->repliesExpected, repliesExpected, replyExpected.wExpectedReply,
411                      ==, wCommandId, lpReplyList );
412   LeaveCriticalSection( &This->lock );
413 
414   if( lpReplyList != NULL )
415   {
416     lpReplyList->replyExpected.dwMsgBodySize = dwMsgBodySize;
417     lpReplyList->replyExpected.lpReplyMsg = HeapAlloc( GetProcessHeap(),
418                                                        HEAP_ZERO_MEMORY,
419                                                        dwMsgBodySize );
420     CopyMemory( lpReplyList->replyExpected.lpReplyMsg,
421                 lpcMsgBody, dwMsgBodySize );
422 
423     /* Signal the thread which sent the message that it has a reply */
424     SetEvent( lpReplyList->replyExpected.hReceipt );
425   }
426   else
427   {
428     ERR( "No receipt event set - only expecting in reply mode\n" );
429     DebugBreak();
430   }
431 }
432 
433 void DP_MSG_ToSelf( IDirectPlayImpl *This, DPID dpidSelf )
434 {
435   LPVOID                   lpMsg;
436   LPDPMSG_SENDENVELOPE     lpMsgBody;
437   DWORD                    dwMsgSize;
438 
439   dwMsgSize = This->dp2->spData.dwSPHeaderSize + sizeof( *lpMsgBody );
440 
441   lpMsg = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwMsgSize );
442 
443   lpMsgBody = (LPDPMSG_SENDENVELOPE)( (BYTE*)lpMsg +
444                                       This->dp2->spData.dwSPHeaderSize );
445 
446   /* Compose dplay message envelope */
447   lpMsgBody->dwMagic    = DPMSGMAGIC_DPLAYMSG;
448   lpMsgBody->wCommandId = DPMSGCMD_JUSTENVELOPE;
449   lpMsgBody->wVersion   = DPMSGVER_DP6;
450 
451   /* Send the message to ourselves */
452   {
453     DPSP_SENDDATA data;
454 
455     data.dwFlags        = 0;
456     data.idPlayerTo     = dpidSelf; /* Sending to session server */
457     data.idPlayerFrom   = 0; /* Sending from session server */
458     data.lpMessage      = lpMsg;
459     data.dwMessageSize  = dwMsgSize;
460     data.bSystemMessage = TRUE; /* Allow reply to be sent */
461     data.lpISP          = This->dp2->spData.lpISP;
462 
463     lpMsg = DP_MSG_ExpectReply( This, &data,
464                                 DPMSG_WAIT_5_SECS,
465                                 DPMSGCMD_JUSTENVELOPE,
466                                 &lpMsg, &dwMsgSize );
467   }
468 }
469 
470 void DP_MSG_ErrorReceived( IDirectPlayImpl *This, WORD wCommandId, const void *lpMsgBody,
471         DWORD dwMsgBodySize )
472 {
473   LPCDPMSG_FORWARDADDPLAYERNACK lpcErrorMsg;
474 
475   lpcErrorMsg = lpMsgBody;
476 
477   ERR( "Received error message %u. Error is %s\n",
478        wCommandId, DPLAYX_HresultToString( lpcErrorMsg->errorCode) );
479   DebugBreak();
480 }
481