1 /* ******************************************************************** *
2 * *
3 * DPLAYEXT.C - DirectPlay multi-player code extensions. *
4 * *
5 * By: Garry Lancaster Version: 0.1 *
6 * *
7 * ******************************************************************** */
8
9 /* N O T E S ---------------------------------------------------------- */
10
11 /* Define USE_DB here to use my db.c debugging file. If you wish to use
12 * another debugging system you will have to redefine the ASSERT macro to
13 * use your debugging system or turn off assertions by defining NDEBUG.
14 * - Garry.
15 */
16
17 /* This code marks an experimental departure for me into Hungarian variable
18 * names. Enjoy! (or perhaps not.)
19 */
20
21 /* I N C L U D E S ---------------------------------------------------- */
22
23 /* OS includes. */
24 #include <windows.h>
25
26 /* Include for this file. */
27 #include "dplayext.h"
28
29 /* Custom includes. */
30 #define UseLocalAssert Yes
31 #include "ourasert.h"
32
33 /* C O N S T A N T S -------------------------------------------------- */
34
35 /* The maximum length of a message that can be received by the system, in
36 * bytes. DPEXT_MAX_MSG_SIZE bytes will be required to hold the message, so
37 * you can't just set this to a huge number.
38 */
39 #define DPEXT_MAX_MSG_SIZE 3072
40
41 /* This msg is not guaranteed. */
42 #define DPEXT_NOT_GUARANTEED 1
43
44 /* M A C R O S -------------------------------------------------------- */
45
46
47 /* Advance the guaranteed msg count. Wraps around to 1 when the maximum
48 * +ve value for a signed int is passed.
49 */
50 #define DPEXT_NEXT_GRNTD_MSG_COUNT() \
51 if( ++gnCurrGrntdMsgId < 1 ) gnCurrGrntdMsgId = 1
52
53 /* Generate the msg stamp for a reply to a msg with a specified
54 * guaranteed msg id.
55 */
56 #define DPEXT_TO_REPLY_STAMP( iSentStamp ) ( -( iSentStamp ) )
57
58 /* Identify the original msg stamp given a reply stamp. */
59 #define DPEXT_TO_ORIGINAL_STAMP( iReplyStamp ) \
60 DPEXT_GET_REPLY_STAMP( iReplyStamp )
61
62 /* T Y P E S ---------------------------------------------------------- */
63
64 struct DpExtGrntdMsgInfo
65 {
66 void **abufPending;
67 DWORD cBuffers;
68 DWORD iLastUsed;
69 };
70
71 /* G L O B A L S . . . ------------------------------------------------ */
72
73 /* ...with external scope. */
74
75 /* ...with internal (static) scope. */
76
77 /* Buffer used to store incoming messages. */
78 static unsigned char gbufDpExtRecv[ DPEXT_MAX_MSG_SIZE ];
79
80 /* Are we expected to add our own error checking? */
81 static BOOL gbDpExtDoErrChcks = FALSE;
82
83 /* Are we expected to implement guaranteed message sending? */
84 static BOOL gbDpExtDoGrntdMsgs = FALSE;
85
86 /* The current count of guaranteed msgs. Zero is reserved for msgs that
87 * aren't guaranteed (using DPEXT_NOT_GUARANTEED) and negative numbers are
88 * reserved for replies to guaranteed messages.
89 */
90 static int gnCurrGrntdMsgId = 1;
91
92 /* Storage for guaranteed msgs and replies to guaranteed msgs. */
93 static struct DpExtGrntdMsgInfo ggmiMsgs = { NULL, 0, 0 };
94 static struct DpExtGrntdMsgInfo ggmiReplies = { NULL, 0, 0 };
95
96 /* P R O T O S -------------------------------------------------------- */
97
98 /* Should all be static. */
99
100 /* Perform any required processing on a received msg. No processing is
101 * required if neither DpExt guaranteed msg system or the DpExt error
102 * checking system are on. Returns TRUE if the message was internal only,
103 * FALSE if it should be passed to the user.
104 */
105 static BOOL DpExtProcessRecvdMsg(BOOL bIsSystemMsg, LPVOID lpData,
106 DWORD dwDataSize);
107
108 /* Generates and returns a checksum for the supplied buffer. The algorithm
109 * used ignores the first 4 bytes of the buffer (this is where the checksum
110 * should eventually be stored) and is sensitive to the order of the bytes
111 * and long words stored as well as lost data, added data, duplicated data,
112 * and standard corruption.
113 */
114 static DWORD DpExtChecksumMsg(LPVOID lpData, DWORD dwDataSize);
115
116 /* Use a destroy msg to try and free up nodes in the guaranteed msg list
117 * that will never now be freed in the usual manner i.e. by a reply to or
118 * a re-send from the player.
119 */
120 static void DpExtUseDestroyMsg(LPDPMSG_DESTROYPLAYERORGROUP pmsgDestroy);
121
122 /* -------------------------------------------------------------------- *
123 * *
124 * F U N C T I O N S *
125 * *
126 * -------------------------------------------------------------------- */
127
128 /* -------------------------------------------------------------------- *
129 * I N T E R F A C E F N S - with external scope. *
130 * -------------------------------------------------------------------- */
131
132
133 /* Call this fn to initialise the DpExt module. cGrntdBufs sets the number
134 * of guaranteed message buffers to use and cBytesPerBuf is the number of
135 * bytes to allocate to each buffer, and thus the maximum length
136 * (including DpExt header) for a guaranteed msg. cGrntdBufs should be 0
137 * if you don't want DpExt's guaranteed msg sending system to be turned on.
138 * bErrChcks should be TRUE if you require DpExt's error checking system,
139 * otherwise FALSE.
140 */
DpExtInit(DWORD cGrntdBufs,DWORD cBytesPerBuf,BOOL bErrChcks)141 BOOL DpExtInit(DWORD cGrntdBufs, DWORD cBytesPerBuf,
142 BOOL bErrChcks)
143 {
144 return TRUE;
145 }
146
147
148 /* Un-initialises the DpExt module. */
DpExtUnInit(void)149 void DpExtUnInit(void)
150 {
151 }
152
153
154 /* This fn has the same parameters as the standard DirectPlay Send() fn.
155 * However, you *must* leave DPEXT_HEADER_SIZE bytes free at the start of
156 * your data buffer. The dwDataSize byte count must includes these bytes.
157 */
DpExtSend(LPDIRECTPLAY4 lpDP2A,DPID idFrom,DPID idTo,DWORD dwFlags,LPVOID lpData,DWORD dwDataSize)158 HRESULT DpExtSend
159 (
160 LPDIRECTPLAY4 lpDP2A, /* IN: Ptr to IDirectPlay3A (DBCS) interface. */
161 DPID idFrom, /* IN: ID of sending player (you) */
162 DPID idTo, /* IN: ID of destination player. */
163 DWORD dwFlags, /* IN: DirectPlay Flags. */
164 LPVOID lpData, /* IN: Ptr to start of message. */
165 DWORD dwDataSize /* IN: Byte count of message. */
166 )
167 {
168 HRESULT hrSend;
169
170 /* Assert input conditions that the DirectPlay call may not check. */
171 LOCALASSERT( lpDP2A );
172 LOCALASSERT( lpData );
173 LOCALASSERT( dwDataSize >= DPEXT_HEADER_SIZE );
174
175 /* Add header information. */
176 {
177 struct DpExtHeader *pmsghdr = (struct DpExtHeader *) lpData;
178
179 if( gbDpExtDoGrntdMsgs && ( DPSEND_GUARANTEED & dwFlags ) )
180 {
181 /* Add guaranteed msg stamp. */
182 pmsghdr->dwMsgStamp = gnCurrGrntdMsgId;
183 DPEXT_NEXT_GRNTD_MSG_COUNT();
184 }
185 else
186 {
187 pmsghdr->dwMsgStamp = DPEXT_NOT_GUARANTEED;
188 }
189 if( gbDpExtDoErrChcks )
190 {
191 pmsghdr->dwChecksum = DpExtChecksumMsg( lpData, dwDataSize );
192 }
193 }
194
195 hrSend = IDirectPlay3_Send( lpDP2A, idFrom, idTo, dwFlags, lpData,
196 dwDataSize );
197
198 return hrSend;
199 }
200
201
202 /* This fn has similar parameters to the standard DirectPlay Receive()
203 * fn. Be aware that the 2 id parameters, the lplpData parameter and the
204 * lpdwDataSize parameters are only valid as inputs if the
205 * appropriate flags are used in the dwFlags parameter. They are always
206 * valid as outputs.
207 *
208 * The main difference from the standard Receive() is that the LPVOID
209 * lpData field has changed to LPVOID *lplpData. Now instead of supplying
210 * the buffer for the message and having the fn fail if the buffer isn't
211 * big enough, the buffer is allocated for you and you get a pointer to it.
212 * I re-use the same buffer each time DpExtRecv() is called, so the pointer
213 * is only valid until the next time you call this fn.
214 *
215 * If you want to maintain the message longer than that you can (a) copy the
216 * message data to your own buffer or (b) more efficiently, pass a ptr to
217 * a ptr to your own allocated data buffer in lplpData, a ptr to its size
218 * in lpdwDataSize and set the flag DPEXT_USER_BUFFER in dwFlags - this is
219 * more like the original DirectPlay Receive() fn behaviour. In the case of
220 * (b), you must leave DPEXT_HEADER_SIZE bytes free at the start of your
221 * user buffer and include these bytes in *lpdwDataSize.
222 *
223 * All non-system messages received will use their first DPEXT_HEADER_SIZE
224 * bytes for header information. Your message proper begins after this
225 * header.
226 */
DpExtRecv(LPDIRECTPLAY4 lpDP2A,LPDPID lpidFrom,LPDPID lpidTo,DWORD dwFlags,LPVOID * lplpData,LPDWORD lpdwDataSize)227 HRESULT DpExtRecv
228 (
229 LPDIRECTPLAY4 lpDP2A, /* IN: Ptr to IDirectPlay3A (DBCS) interface. */
230 LPDPID lpidFrom, /* IN/OUT: Ptr to from player id. */
231 LPDPID lpidTo, /* IN/OUT: Ptr to to player id. */
232 DWORD dwFlags, /* IN: DirectPlay flags. */
233 LPVOID *lplpData, /* IN/OUT: Ptr to ptr to message data. */
234 LPDWORD lpdwDataSize /* IN/OUT: Ptr to byte count of message. */
235 )
236 {
237 HRESULT hrRecv;
238 BOOL bInternalOnly;
239 BOOL bIsSysMsg;
240
241 /* Assert input conditions that the DirectPlay call may not check. */
242 LOCALASSERT( lpDP2A );
243 LOCALASSERT( lpidFrom );
244 LOCALASSERT( lpidTo );
245 LOCALASSERT( lpdwDataSize );
246
247 /* Did the user want to use their own data buffer?
248 * N.B. Does this need to go in the loop?
249 */
250 if( !( DPEXT_USER_BUFFER & dwFlags ) )
251 {
252 /* No. Set parameters to write to internal buffer. */
253 *lplpData = gbufDpExtRecv;
254 *lpdwDataSize = DPEXT_MAX_MSG_SIZE;
255 }
256
257 do
258 {
259 bInternalOnly = FALSE; /* Default. */
260
261 hrRecv = IDirectPlay3_Receive( lpDP2A, lpidFrom, lpidTo, dwFlags,
262 *lplpData, lpdwDataSize );
263
264 /* DirectPlay bug work-around. *lpdwDataSize does not get filled in
265 * automatically for some system messages.
266 */
267 if( ( DPID_SYSMSG == *lpidFrom ) &&
268 ( DP_OK == hrRecv ) )
269 {
270 DPMSG_GENERIC msgGenSys = *( (LPDPMSG_GENERIC) *lplpData );
271
272 bIsSysMsg = TRUE;
273 switch( msgGenSys.dwType )
274 {
275 case DPSYS_ADDPLAYERTOGROUP:
276 case DPSYS_DELETEPLAYERFROMGROUP:
277 /* Didn't test for bug - possibly okay. */
278 *lpdwDataSize = sizeof( DPMSG_ADDPLAYERTOGROUP );
279 break;
280 case DPSYS_CREATEPLAYERORGROUP:
281 /* Not necessary - bug doesn't affect this message. */
282 break;
283 case DPSYS_DESTROYPLAYERORGROUP:
284 *lpdwDataSize = sizeof( DPMSG_DESTROYPLAYERORGROUP );
285 break;
286 case DPSYS_SETPLAYERORGROUPDATA:
287 /* Didn't test for bug - possibly okay. */
288 *lpdwDataSize = sizeof( DPMSG_SETPLAYERORGROUPDATA );
289 break;
290 case DPSYS_SETPLAYERORGROUPNAME:
291 /* Didn't test for bug - possibly okay. */
292 *lpdwDataSize = sizeof( DPMSG_SETPLAYERORGROUPNAME );
293 break;
294 case DPSYS_HOST:
295 case DPSYS_SESSIONLOST:
296 *lpdwDataSize = sizeof( DPMSG_GENERIC );
297 break;
298 }
299 }
300 else bIsSysMsg = FALSE;
301
302 /* Might we need to process this message? */
303 if( ( DP_OK == hrRecv ) &&
304 ( gbDpExtDoGrntdMsgs || gbDpExtDoErrChcks ) )
305 {
306 bInternalOnly = DpExtProcessRecvdMsg
307 (
308 bIsSysMsg,
309 *lplpData,
310 *lpdwDataSize
311 );
312 }
313 }
314 while( bInternalOnly );
315
316 return hrRecv;
317 }
318
319
320 /* -------------------------------------------------------------------- *
321 * S T A T I C F N S - with internal scope. *
322 * -------------------------------------------------------------------- */
323
324
325 /* Perform any required processing on a received msg. No processing is
326 * required if neither DpExt guaranteed msg system or the DpExt error
327 * checking system are on. Returns TRUE if the message was internal only,
328 * FALSE if it should be passed to the user.
329 */
DpExtProcessRecvdMsg(BOOL bIsSystemMsg,LPVOID lpData,DWORD dwDataSize)330 static BOOL DpExtProcessRecvdMsg(BOOL bIsSystemMsg, LPVOID lpData,
331 DWORD dwDataSize)
332 {
333 /* Is this a system message? */
334 if( bIsSystemMsg )
335 {
336 /* Yes. Do we need to intercept any system messages for our own
337 * nefarious purposes?
338 */
339 if( gbDpExtDoGrntdMsgs )
340 {
341 /* Yes, we need to intercept messages about deleted players or
342 * groups. Is this one?
343 */
344 DPMSG_GENERIC *pmsgGeneric = (DPMSG_GENERIC *) lpData;
345
346 if( DPSYS_DESTROYPLAYERORGROUP == pmsgGeneric->dwType )
347 {
348 DpExtUseDestroyMsg( (LPDPMSG_DESTROYPLAYERORGROUP)
349 pmsgGeneric );
350 }
351 }
352 }
353 else
354 {
355 /* No, this isn't a system message. Use DpExt header... */
356 struct DpExtHeader *pmsghdr = (struct DpExtHeader *) lpData;
357
358 LOCALASSERT( dwDataSize >= DPEXT_HEADER_SIZE );
359
360 /* ...Should we check this message for errors? */
361 if( gbDpExtDoErrChcks )
362 {
363
364 }
365
366 /* ...Was this message sent by the DpExt guaranteed message system? */
367 }
368
369 return FALSE;
370 }
371
372
373 /* Generates and returns a checksum for the supplied buffer. The algorithm
374 * used ignores the first 4 bytes of the buffer (this is where the checksum
375 * should eventually be stored) and is sensitive to the order of the bytes
376 * and long words stored as well as lost data, added data, duplicated data,
377 * and standard corruption.
378 */
DpExtChecksumMsg(LPVOID lpData,DWORD dwDataSize)379 static DWORD DpExtChecksumMsg(LPVOID lpData, DWORD dwDataSize)
380 {
381 return 0; /* No implementation yet. */
382 }
383
384
385 /* Use a destroy msg to try and free up nodes in the guaranteed msg list
386 * that will never now be freed in the usual manner i.e. by a reply to or
387 * a re-send from the player.
388 */
DpExtUseDestroyMsg(LPDPMSG_DESTROYPLAYERORGROUP pmsgDestroy)389 static void DpExtUseDestroyMsg(LPDPMSG_DESTROYPLAYERORGROUP pmsgDestroy)
390 {
391 /* No implementation yet. */
392 }