ReactOS Fundraising Campaign 2012
 
€ 4,410 / € 30,000

Information | Donate

Home | Info | Community | Development | myReactOS | Contact Us

  1. Home
  2. Community
  3. Development
  4. myReactOS
  5. Fundraiser 2012

  1. Main Page
  2. Alphabetical List
  3. Data Structures
  4. Directories
  5. File List
  6. Data Fields
  7. Globals
  8. Related Pages

ReactOS Development > Doxygen

dplayx_messages.c
Go to the documentation of this file.
00001 /* DirectPlay & DirectPlayLobby messaging implementation
00002  *
00003  * Copyright 2000,2001 - Peter Hunnisett
00004  *
00005  * This library is free software; you can redistribute it and/or
00006  * modify it under the terms of the GNU Lesser General Public
00007  * License as published by the Free Software Foundation; either
00008  * version 2.1 of the License, or (at your option) any later version.
00009  *
00010  * This library is distributed in the hope that it will be useful,
00011  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00012  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00013  * Lesser General Public License for more details.
00014  *
00015  * You should have received a copy of the GNU Lesser General Public
00016  * License along with this library; if not, write to the Free Software
00017  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00018  *
00019  * NOTES
00020  *  o Messaging interface required for both DirectPlay and DirectPlayLobby.
00021  */
00022 
00023 #include <stdarg.h>
00024 #include <string.h>
00025 #include "windef.h"
00026 #include "winbase.h"
00027 #include "wingdi.h"
00028 #include "winuser.h"
00029 #include "winerror.h"
00030 
00031 #include "dplayx_messages.h"
00032 #include "dplay_global.h"
00033 #include "dplayx_global.h"
00034 #include "name_server.h"
00035 #include "wine/debug.h"
00036 
00037 WINE_DEFAULT_DEBUG_CHANNEL(dplay);
00038 
00039 typedef struct tagMSGTHREADINFO
00040 {
00041   HANDLE hStart;
00042   HANDLE hDeath;
00043   HANDLE hSettingRead;
00044   HANDLE hNotifyEvent;
00045 } MSGTHREADINFO, *LPMSGTHREADINFO;
00046 
00047 static DWORD CALLBACK DPL_MSG_ThreadMain( LPVOID lpContext );
00048 static LPVOID DP_MSG_ExpectReply( IDirectPlay2AImpl* This, LPDPSP_SENDDATA data,
00049                                   DWORD dwWaitTime, WORD wReplyCommandId,
00050                                   LPVOID* lplpReplyMsg, LPDWORD lpdwMsgBodySize );
00051 
00052 
00053 /* Create the message reception thread to allow the application to receive
00054  * asynchronous message reception
00055  */
00056 DWORD CreateLobbyMessageReceptionThread( HANDLE hNotifyEvent, HANDLE hStart,
00057                                          HANDLE hDeath, HANDLE hConnRead )
00058 {
00059   DWORD           dwMsgThreadId;
00060   LPMSGTHREADINFO lpThreadInfo;
00061   HANDLE          hThread;
00062 
00063   lpThreadInfo = HeapAlloc( GetProcessHeap(), 0, sizeof( *lpThreadInfo ) );
00064   if( lpThreadInfo == NULL )
00065   {
00066     return 0;
00067   }
00068 
00069   /* The notify event may or may not exist. Depends if async comm or not */
00070   if( hNotifyEvent &&
00071       !DuplicateHandle( GetCurrentProcess(), hNotifyEvent,
00072                         GetCurrentProcess(), &lpThreadInfo->hNotifyEvent,
00073                         0, FALSE, DUPLICATE_SAME_ACCESS ) )
00074   {
00075     ERR( "Unable to duplicate event handle\n" );
00076     goto error;
00077   }
00078 
00079   /* These 3 handles don't need to be duplicated because we don't keep a
00080    * reference to them where they're created. They're created specifically
00081    * for the message thread
00082    */
00083   lpThreadInfo->hStart       = hStart;
00084   lpThreadInfo->hDeath       = hDeath;
00085   lpThreadInfo->hSettingRead = hConnRead;
00086 
00087   hThread = CreateThread( NULL,                  /* Security attribs */
00088                           0,                     /* Stack */
00089                           DPL_MSG_ThreadMain,    /* Msg reception function */
00090                           lpThreadInfo,          /* Msg reception func parameter */
00091                           0,                     /* Flags */
00092                           &dwMsgThreadId         /* Updated with thread id */
00093                         );
00094   if ( hThread == NULL )
00095   {
00096     ERR( "Unable to create msg thread\n" );
00097     goto error;
00098   }
00099 
00100   CloseHandle(hThread);
00101 
00102   return dwMsgThreadId;
00103 
00104 error:
00105 
00106   HeapFree( GetProcessHeap(), 0, lpThreadInfo );
00107 
00108   return 0;
00109 }
00110 
00111 static DWORD CALLBACK DPL_MSG_ThreadMain( LPVOID lpContext )
00112 {
00113   LPMSGTHREADINFO lpThreadInfo = (LPMSGTHREADINFO)lpContext;
00114   DWORD dwWaitResult;
00115 
00116   TRACE( "Msg thread created. Waiting on app startup\n" );
00117 
00118   /* Wait to ensure that the lobby application is started w/ 1 min timeout */
00119   dwWaitResult = WaitForSingleObject( lpThreadInfo->hStart, 10000 /* 10 sec */ );
00120   if( dwWaitResult == WAIT_TIMEOUT )
00121   {
00122     FIXME( "Should signal app/wait creation failure (0x%08lx)\n", dwWaitResult );
00123     goto end_of_thread;
00124   }
00125 
00126   /* Close this handle as it's not needed anymore */
00127   CloseHandle( lpThreadInfo->hStart );
00128   lpThreadInfo->hStart = 0;
00129 
00130   /* Wait until the lobby knows what it is */
00131   dwWaitResult = WaitForSingleObject( lpThreadInfo->hSettingRead, INFINITE );
00132   if( dwWaitResult == WAIT_TIMEOUT )
00133   {
00134     ERR( "App Read connection setting timeout fail (0x%08lx)\n", dwWaitResult );
00135   }
00136 
00137   /* Close this handle as it's not needed anymore */
00138   CloseHandle( lpThreadInfo->hSettingRead );
00139   lpThreadInfo->hSettingRead = 0;
00140 
00141   TRACE( "App created && intialized starting main message reception loop\n" );
00142 
00143   for ( ;; )
00144   {
00145     MSG lobbyMsg;
00146     GetMessageW( &lobbyMsg, 0, 0, 0 );
00147   }
00148 
00149 end_of_thread:
00150   TRACE( "Msg thread exiting!\n" );
00151   HeapFree( GetProcessHeap(), 0, lpThreadInfo );
00152 
00153   return 0;
00154 }
00155 
00156 /* DP messageing stuff */
00157 static HANDLE DP_MSG_BuildAndLinkReplyStruct( IDirectPlay2Impl* This,
00158                                               LPDP_MSG_REPLY_STRUCT_LIST lpReplyStructList,
00159                                               WORD wReplyCommandId );
00160 static LPVOID DP_MSG_CleanReplyStruct( LPDP_MSG_REPLY_STRUCT_LIST lpReplyStructList,
00161                                        LPVOID* lplpReplyMsg, LPDWORD lpdwMsgBodySize );
00162 
00163 
00164 static
00165 HANDLE DP_MSG_BuildAndLinkReplyStruct( IDirectPlay2Impl* This,
00166                                        LPDP_MSG_REPLY_STRUCT_LIST lpReplyStructList, WORD wReplyCommandId )
00167 {
00168   lpReplyStructList->replyExpected.hReceipt       = CreateEventW( NULL, FALSE, FALSE, NULL );
00169   lpReplyStructList->replyExpected.wExpectedReply = wReplyCommandId;
00170   lpReplyStructList->replyExpected.lpReplyMsg     = NULL;
00171   lpReplyStructList->replyExpected.dwMsgBodySize  = 0;
00172 
00173   /* Insert into the message queue while locked */
00174   EnterCriticalSection( &This->unk->DP_lock );
00175     DPQ_INSERT( This->dp2->replysExpected, lpReplyStructList, replysExpected );
00176   LeaveCriticalSection( &This->unk->DP_lock );
00177 
00178   return lpReplyStructList->replyExpected.hReceipt;
00179 }
00180 
00181 static
00182 LPVOID DP_MSG_CleanReplyStruct( LPDP_MSG_REPLY_STRUCT_LIST lpReplyStructList,
00183                                 LPVOID* lplpReplyMsg, LPDWORD lpdwMsgBodySize  )
00184 {
00185   CloseHandle( lpReplyStructList->replyExpected.hReceipt );
00186 
00187   *lplpReplyMsg    = lpReplyStructList->replyExpected.lpReplyMsg;
00188   *lpdwMsgBodySize = lpReplyStructList->replyExpected.dwMsgBodySize;
00189 
00190   return lpReplyStructList->replyExpected.lpReplyMsg;
00191 }
00192 
00193 HRESULT DP_MSG_SendRequestPlayerId( IDirectPlay2AImpl* This, DWORD dwFlags,
00194                                     LPDPID lpdpidAllocatedId )
00195 {
00196   LPVOID                     lpMsg;
00197   LPDPMSG_REQUESTNEWPLAYERID lpMsgBody;
00198   DWORD                      dwMsgSize;
00199   HRESULT                    hr = DP_OK;
00200 
00201   dwMsgSize = This->dp2->spData.dwSPHeaderSize + sizeof( *lpMsgBody );
00202 
00203   lpMsg = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwMsgSize );
00204 
00205   lpMsgBody = (LPDPMSG_REQUESTNEWPLAYERID)( (BYTE*)lpMsg +
00206                                              This->dp2->spData.dwSPHeaderSize );
00207 
00208   /* Compose dplay message envelope */
00209   lpMsgBody->envelope.dwMagic    = DPMSGMAGIC_DPLAYMSG;
00210   lpMsgBody->envelope.wCommandId = DPMSGCMD_REQUESTNEWPLAYERID;
00211   lpMsgBody->envelope.wVersion   = DPMSGVER_DP6;
00212 
00213   /* Compose the body of the message */
00214   lpMsgBody->dwFlags = dwFlags;
00215 
00216   /* Send the message */
00217   {
00218     DPSP_SENDDATA data;
00219 
00220     data.dwFlags        = DPSEND_GUARANTEED;
00221     data.idPlayerTo     = 0; /* Name server */
00222     data.idPlayerFrom   = 0; /* Sending from DP */
00223     data.lpMessage      = lpMsg;
00224     data.dwMessageSize  = dwMsgSize;
00225     data.bSystemMessage = TRUE; /* Allow reply to be sent */
00226     data.lpISP          = This->dp2->spData.lpISP;
00227 
00228     TRACE( "Asking for player id w/ dwFlags 0x%08lx\n",
00229            lpMsgBody->dwFlags );
00230 
00231     DP_MSG_ExpectReply( This, &data, DPMSG_DEFAULT_WAIT_TIME, DPMSGCMD_NEWPLAYERIDREPLY,
00232                         &lpMsg, &dwMsgSize );
00233   }
00234 
00235   /* Need to examine the data and extract the new player id */
00236   if( !FAILED(hr) )
00237   {
00238     LPCDPMSG_NEWPLAYERIDREPLY lpcReply;
00239 
00240     lpcReply = (LPCDPMSG_NEWPLAYERIDREPLY)lpMsg;
00241 
00242     *lpdpidAllocatedId = lpcReply->dpidNewPlayerId;
00243 
00244     TRACE( "Received reply for id = 0x%08lx\n", lpcReply->dpidNewPlayerId );
00245 
00246     /* FIXME: I think that the rest of the message has something to do
00247      *        with remote data for the player that perhaps I need to setup.
00248      *        However, with the information that is passed, all that it could
00249      *        be used for is a standardized intialization value, which I'm
00250      *        guessing we can do without. Unless the message content is the same
00251      *        for several different messages?
00252      */
00253 
00254     HeapFree( GetProcessHeap(), 0, lpMsg );
00255   }
00256 
00257   return hr;
00258 }
00259 
00260 HRESULT DP_MSG_ForwardPlayerCreation( IDirectPlay2AImpl* This, DPID dpidServer )
00261 {
00262   LPVOID                   lpMsg;
00263   LPDPMSG_FORWARDADDPLAYER lpMsgBody;
00264   DWORD                    dwMsgSize;
00265   HRESULT                  hr = DP_OK;
00266 
00267   dwMsgSize = This->dp2->spData.dwSPHeaderSize + sizeof( *lpMsgBody );
00268 
00269   lpMsg = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwMsgSize );
00270 
00271   lpMsgBody = (LPDPMSG_FORWARDADDPLAYER)( (BYTE*)lpMsg +
00272                                           This->dp2->spData.dwSPHeaderSize );
00273 
00274   /* Compose dplay message envelope */
00275   lpMsgBody->envelope.dwMagic    = DPMSGMAGIC_DPLAYMSG;
00276   lpMsgBody->envelope.wCommandId = DPMSGCMD_FORWARDADDPLAYER;
00277   lpMsgBody->envelope.wVersion   = DPMSGVER_DP6;
00278 
00279 #if 0
00280   {
00281     LPBYTE lpPData;
00282     DWORD  dwDataSize;
00283 
00284     /* SP Player remote data needs to be propagated at some point - is this the point? */
00285     IDirectPlaySP_GetSPPlayerData( This->dp2->spData.lpISP, 0, (LPVOID*)&lpPData, &dwDataSize, DPSET_REMOTE );
00286 
00287     ERR( "Player Data size is 0x%08lx\n"
00288          "[%02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x]\n"
00289          "[%02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x %02x%02x]\n",
00290 
00291      dwDataSize,
00292          lpPData[0], lpPData[1], lpPData[2], lpPData[3], lpPData[4],
00293      lpPData[5], lpPData[6], lpPData[7], lpPData[8], lpPData[9],
00294          lpPData[10], lpPData[11], lpPData[12], lpPData[13], lpPData[14],
00295      lpPData[15], lpPData[16], lpPData[17], lpPData[18], lpPData[19],
00296          lpPData[20], lpPData[21], lpPData[22], lpPData[23], lpPData[24],
00297      lpPData[25], lpPData[26], lpPData[27], lpPData[28], lpPData[29],
00298          lpPData[30], lpPData[31]
00299         );
00300     DebugBreak();
00301   }
00302 #endif
00303 
00304   /* Compose body of message */
00305   lpMsgBody->dpidAppServer = dpidServer;
00306   lpMsgBody->unknown2[0] = 0x0;
00307   lpMsgBody->unknown2[1] = 0x1c;
00308   lpMsgBody->unknown2[2] = 0x6c;
00309   lpMsgBody->unknown2[3] = 0x50;
00310   lpMsgBody->unknown2[4] = 0x9;
00311 
00312   lpMsgBody->dpidAppServer2 = dpidServer;
00313   lpMsgBody->unknown3[0] = 0x0;
00314   lpMsgBody->unknown3[0] = 0x0;
00315   lpMsgBody->unknown3[0] = 0x20;
00316   lpMsgBody->unknown3[0] = 0x0;
00317   lpMsgBody->unknown3[0] = 0x0;
00318 
00319   lpMsgBody->dpidAppServer3 = dpidServer;
00320   lpMsgBody->unknown4[0] =  0x30;
00321   lpMsgBody->unknown4[1] =  0xb;
00322   lpMsgBody->unknown4[2] =  0x0;
00323 
00324   lpMsgBody->unknown4[3] =  NS_GetNsMagic( This->dp2->lpNameServerData ) -
00325                             0x02000000;
00326   TRACE( "Setting first magic to 0x%08lx\n", lpMsgBody->unknown4[3] );
00327 
00328   lpMsgBody->unknown4[4] =  0x0;
00329   lpMsgBody->unknown4[5] =  0x0;
00330   lpMsgBody->unknown4[6] =  0x0;
00331 
00332 #if 0
00333   lpMsgBody->unknown4[7] =  NS_GetOtherMagic( This->dp2->lpNameServerData )
00334 #else
00335   lpMsgBody->unknown4[7] =  NS_GetNsMagic( This->dp2->lpNameServerData );
00336 #endif
00337   TRACE( "Setting second magic to 0x%08lx\n", lpMsgBody->unknown4[7] );
00338 
00339   lpMsgBody->unknown4[8] =  0x0;
00340   lpMsgBody->unknown4[9] =  0x0;
00341   lpMsgBody->unknown4[10] = 0x0;
00342   lpMsgBody->unknown4[11] = 0x0;
00343 
00344   lpMsgBody->unknown5[0] = 0x0;
00345   lpMsgBody->unknown5[1] = 0x0;
00346 
00347   /* Send the message */
00348   {
00349     DPSP_SENDDATA data;
00350 
00351     data.dwFlags        = DPSEND_GUARANTEED;
00352     data.idPlayerTo     = 0; /* Name server */
00353     data.idPlayerFrom   = dpidServer; /* Sending from session server */
00354     data.lpMessage      = lpMsg;
00355     data.dwMessageSize  = dwMsgSize;
00356     data.bSystemMessage = TRUE; /* Allow reply to be sent */
00357     data.lpISP          = This->dp2->spData.lpISP;
00358 
00359     TRACE( "Sending forward player request with 0x%08lx\n", dpidServer );
00360 
00361     lpMsg = DP_MSG_ExpectReply( This, &data,
00362                                 DPMSG_WAIT_60_SECS,
00363                                 DPMSGCMD_GETNAMETABLEREPLY,
00364                                 &lpMsg, &dwMsgSize );
00365   }
00366 
00367   /* Need to examine the data and extract the new player id */
00368   if( lpMsg != NULL )
00369   {
00370     FIXME( "Name Table reply received: stub\n" );
00371   }
00372 
00373   return hr;
00374 }
00375 
00376 /* Queue up a structure indicating that we want a reply of type wReplyCommandId. DPlay does
00377  * not seem to offer any way of uniquely differentiating between replies of the same type
00378  * relative to the request sent. There is an implicit assumption that there will be no
00379  * ordering issues on sends and receives from the opposite machine. No wonder MS is not
00380  * a networking company.
00381  */
00382 static
00383 LPVOID DP_MSG_ExpectReply( IDirectPlay2AImpl* This, LPDPSP_SENDDATA lpData,
00384                            DWORD dwWaitTime, WORD wReplyCommandId,
00385                            LPVOID* lplpReplyMsg, LPDWORD lpdwMsgBodySize )
00386 {
00387   HRESULT                  hr;
00388   HANDLE                   hMsgReceipt;
00389   DP_MSG_REPLY_STRUCT_LIST replyStructList;
00390   DWORD                    dwWaitReturn;
00391 
00392   /* Setup for receipt */
00393   hMsgReceipt = DP_MSG_BuildAndLinkReplyStruct( This, &replyStructList,
00394                                                 wReplyCommandId );
00395 
00396   TRACE( "Sending msg and expecting cmd %u in reply within %lu ticks\n",
00397          wReplyCommandId, dwWaitTime );
00398   hr = (*This->dp2->spData.lpCB->Send)( lpData );
00399 
00400   if( FAILED(hr) )
00401   {
00402     ERR( "Send failed: %s\n", DPLAYX_HresultToString( hr ) );
00403     return NULL;
00404   }
00405 
00406   /* The reply message will trigger the hMsgReceipt event effectively switching
00407    * control back to this thread. See DP_MSG_ReplyReceived.
00408    */
00409   dwWaitReturn = WaitForSingleObject( hMsgReceipt, dwWaitTime );
00410   if( dwWaitReturn != WAIT_OBJECT_0 )
00411   {
00412     ERR( "Wait failed 0x%08lx\n", dwWaitReturn );
00413     return NULL;
00414   }
00415 
00416   /* Clean Up */
00417   return DP_MSG_CleanReplyStruct( &replyStructList, lplpReplyMsg, lpdwMsgBodySize );
00418 }
00419 
00420 /* Determine if there is a matching request for this incoming message and then copy
00421  * all important data. It is quite silly to have to copy the message, but the documents
00422  * indicate that a copy is taken. Silly really.
00423  */
00424 void DP_MSG_ReplyReceived( IDirectPlay2AImpl* This, WORD wCommandId,
00425                            LPCVOID lpcMsgBody, DWORD dwMsgBodySize )
00426 {
00427   LPDP_MSG_REPLY_STRUCT_LIST lpReplyList;
00428 
00429 #if 0
00430   if( wCommandId == DPMSGCMD_FORWARDADDPLAYER )
00431   {
00432     DebugBreak();
00433   }
00434 #endif
00435 
00436   /* Find, and immediately remove (to avoid double triggering), the appropriate entry. Call locked to
00437    * avoid problems.
00438    */
00439   EnterCriticalSection( &This->unk->DP_lock );
00440     DPQ_REMOVE_ENTRY( This->dp2->replysExpected, replysExpected, replyExpected.wExpectedReply,\
00441                      ==, wCommandId, lpReplyList );
00442   LeaveCriticalSection( &This->unk->DP_lock );
00443 
00444   if( lpReplyList != NULL )
00445   {
00446     lpReplyList->replyExpected.dwMsgBodySize = dwMsgBodySize;
00447     lpReplyList->replyExpected.lpReplyMsg = HeapAlloc( GetProcessHeap(),
00448                                                        HEAP_ZERO_MEMORY,
00449                                                        dwMsgBodySize );
00450     CopyMemory( lpReplyList->replyExpected.lpReplyMsg,
00451                 lpcMsgBody, dwMsgBodySize );
00452 
00453     /* Signal the thread which sent the message that it has a reply */
00454     SetEvent( lpReplyList->replyExpected.hReceipt );
00455   }
00456   else
00457   {
00458     ERR( "No receipt event set - only expecting in reply mode\n" );
00459     DebugBreak();
00460   }
00461 }
00462 
00463 void DP_MSG_ToSelf( IDirectPlay2AImpl* This, DPID dpidSelf )
00464 {
00465   LPVOID                   lpMsg;
00466   LPDPMSG_SENDENVELOPE     lpMsgBody;
00467   DWORD                    dwMsgSize;
00468 
00469   dwMsgSize = This->dp2->spData.dwSPHeaderSize + sizeof( *lpMsgBody );
00470 
00471   lpMsg = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, dwMsgSize );
00472 
00473   lpMsgBody = (LPDPMSG_SENDENVELOPE)( (BYTE*)lpMsg +
00474                                       This->dp2->spData.dwSPHeaderSize );
00475 
00476   /* Compose dplay message envelope */
00477   lpMsgBody->dwMagic    = DPMSGMAGIC_DPLAYMSG;
00478   lpMsgBody->wCommandId = DPMSGCMD_JUSTENVELOPE;
00479   lpMsgBody->wVersion   = DPMSGVER_DP6;
00480 
00481   /* Send the message to ourselves */
00482   {
00483     DPSP_SENDDATA data;
00484 
00485     data.dwFlags        = 0;
00486     data.idPlayerTo     = dpidSelf; /* Sending to session server */
00487     data.idPlayerFrom   = 0; /* Sending from session server */
00488     data.lpMessage      = lpMsg;
00489     data.dwMessageSize  = dwMsgSize;
00490     data.bSystemMessage = TRUE; /* Allow reply to be sent */
00491     data.lpISP          = This->dp2->spData.lpISP;
00492 
00493     lpMsg = DP_MSG_ExpectReply( This, &data,
00494                                 DPMSG_WAIT_5_SECS,
00495                                 DPMSGCMD_JUSTENVELOPE,
00496                                 &lpMsg, &dwMsgSize );
00497   }
00498 }
00499 
00500 void DP_MSG_ErrorReceived( IDirectPlay2AImpl* This, WORD wCommandId,
00501                            LPCVOID lpMsgBody, DWORD dwMsgBodySize )
00502 {
00503   LPCDPMSG_FORWARDADDPLAYERNACK lpcErrorMsg;
00504 
00505   lpcErrorMsg = (LPCDPMSG_FORWARDADDPLAYERNACK)lpMsgBody;
00506 
00507   ERR( "Received error message %u. Error is %s\n",
00508        wCommandId, DPLAYX_HresultToString( lpcErrorMsg->errorCode) );
00509   DebugBreak();
00510 }

Generated on Mon May 28 2012 04:21:07 for ReactOS by doxygen 1.7.6.1

ReactOS is a registered trademark or a trademark of ReactOS Foundation in the United States and other countries.