Started local name server framework.
This commit is contained in:
parent
b40a9b7f1d
commit
e2853822c5
|
@ -71,6 +71,8 @@ typedef struct tagDirectPlay2Data
|
|||
HANDLE hEnumSessionThread;
|
||||
|
||||
EnumSessionAsyncCallbackData enumSessionAsyncCallbackData;
|
||||
|
||||
LPVOID lpNameServerData; /* DPlay interface doesn't know type */
|
||||
} DirectPlay2Data;
|
||||
|
||||
typedef struct tagDirectPlay3Data
|
||||
|
@ -163,6 +165,11 @@ BOOL DP_CreateDirectPlay2( LPVOID lpDP )
|
|||
This->dp2->enumSessionAsyncCallbackData.lpContext = NULL;
|
||||
This->dp2->enumSessionAsyncCallbackData.dwTimeout = INFINITE;
|
||||
|
||||
if( !NS_InitializeSessionCache( &This->dp2->lpNameServerData ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
@ -175,6 +182,8 @@ BOOL DP_DestroyDirectPlay2( LPVOID lpDP )
|
|||
TerminateThread( This->dp2->hEnumSessionThread, 0 );
|
||||
CloseHandle( This->dp2->hEnumSessionThread );
|
||||
}
|
||||
|
||||
NS_DeleteSessionCache( This->dp2->lpNameServerData );
|
||||
|
||||
/* Delete the contents */
|
||||
HeapFree( GetProcessHeap(), 0, This->dp2 );
|
||||
|
@ -751,21 +760,30 @@ static HRESULT WINAPI DirectPlay2WImpl_EnumPlayers
|
|||
return DP_OK;
|
||||
}
|
||||
|
||||
/* This function is responsible for sending a request for all other known
|
||||
nameservers to send us what sessions they have registered locally
|
||||
*/
|
||||
void DP_SendSessionRequestBroadcast()
|
||||
{
|
||||
FIXME( ": stub\n" );
|
||||
}
|
||||
|
||||
/* This function should call the registered callback function that the user
|
||||
passed into EnumSessions
|
||||
passed into EnumSessions for each entry available.
|
||||
*/
|
||||
static void DP_InvokeEnumSessionCallbacksA( LPDPENUMSESSIONSCALLBACK2 lpEnumSessionsCallback2,
|
||||
LPVOID lpNSInfo,
|
||||
DWORD dwTimeout,
|
||||
LPVOID lpContext )
|
||||
{
|
||||
FIXME( ": stub\n" );
|
||||
LPDPSESSIONDESC2 lpSessionDesc;
|
||||
|
||||
FIXME( ": not checking for conditions\n" );
|
||||
|
||||
NS_ResetSessionEnumeration( lpNSInfo );
|
||||
|
||||
/* Enumerate all sessions */
|
||||
while( (lpSessionDesc = NS_WalkSessions( lpNSInfo ) ) != NULL )
|
||||
{
|
||||
TRACE( "EnumSessionsCallback2 invoked\n" );
|
||||
if( !lpEnumSessionsCallback2( lpSessionDesc, &dwTimeout, 0, lpContext ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static DWORD CALLBACK DP_EnumSessionsSpwanThreadA( LPVOID lpContext )
|
||||
|
@ -773,21 +791,34 @@ static DWORD CALLBACK DP_EnumSessionsSpwanThreadA( LPVOID lpContext )
|
|||
ICOM_THIS(IDirectPlay2Impl,lpContext);
|
||||
DWORD dwTimeout = This->dp2->enumSessionAsyncCallbackData.dwTimeout;
|
||||
|
||||
TRACE( "->(%p)->(0x%08lx)\n", This, dwTimeout );
|
||||
TRACE( "(%p)->(0x%08lx)\n", This, dwTimeout );
|
||||
|
||||
/* FIXME: Don't think this is exactly right. It'll do for now */
|
||||
for( ;; )
|
||||
{
|
||||
/* 2: Send the broadcast for session enumeration */
|
||||
DP_SendSessionRequestBroadcast( NULL, 0 ); /* Should pass lpsd */
|
||||
NS_SendSessionRequestBroadcast( This->dp2->lpNameServerData );
|
||||
|
||||
SleepEx( dwTimeout, FALSE );
|
||||
|
||||
DP_InvokeEnumSessionCallbacksA( This->dp2->enumSessionAsyncCallbackData.cb,
|
||||
This->dp2->lpNameServerData,
|
||||
dwTimeout,
|
||||
This->dp2->enumSessionAsyncCallbackData.lpContext );
|
||||
|
||||
/* All sessions have been enumerated. Invoke the callback function
|
||||
once more indicating a timeout has occured. This is the way
|
||||
that the application can indicate that it wishes to continue with the
|
||||
enumeration */
|
||||
if( !(This->dp2->enumSessionAsyncCallbackData.cb)( NULL, &dwTimeout, DPESC_TIMEDOUT, lpContext ) )
|
||||
{
|
||||
/* The application doesn't want us to continue - end this thread */
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static HRESULT WINAPI DirectPlay2AImpl_EnumSessions
|
||||
|
@ -800,6 +831,8 @@ static HRESULT WINAPI DirectPlay2AImpl_EnumSessions
|
|||
|
||||
if( dwTimeout == 0 )
|
||||
{
|
||||
/* Should actually be getting the dwTimeout value through
|
||||
IDirectPlay_GetCaps( This, ...) */
|
||||
FIXME( ": should provide a dependent dwTimeout\n" );
|
||||
dwTimeout = 5 * 1000; /* 5 seconds */
|
||||
}
|
||||
|
@ -833,10 +866,14 @@ static HRESULT WINAPI DirectPlay2AImpl_EnumSessions
|
|||
{
|
||||
DWORD dwThreadId;
|
||||
|
||||
/* Enumerate everything presently in the local session cache */
|
||||
DP_InvokeEnumSessionCallbacksA( lpEnumSessionsCallback2, This->dp2->lpNameServerData, dwTimeout, lpContext );
|
||||
|
||||
/* See if we've already created a thread to service this interface */
|
||||
if( This->dp2->hEnumSessionThread == INVALID_HANDLE_VALUE )
|
||||
{
|
||||
|
||||
/* FIXME: Should be adding a reference here - another thread now knows
|
||||
how to call this interface */
|
||||
This->dp2->enumSessionAsyncCallbackData.cb = lpEnumSessionsCallback2;
|
||||
This->dp2->enumSessionAsyncCallbackData.lpContext = lpContext;
|
||||
This->dp2->enumSessionAsyncCallbackData.dwTimeout = dwTimeout;
|
||||
|
@ -851,17 +888,15 @@ static HRESULT WINAPI DirectPlay2AImpl_EnumSessions
|
|||
&dwThreadId );
|
||||
}
|
||||
|
||||
DP_InvokeEnumSessionCallbacksA( lpEnumSessionsCallback2, lpContext );
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Send the broadcast for session enumeration */
|
||||
/* FIXME: How to handle the replies? Queue? */
|
||||
DP_SendSessionRequestBroadcast( lpsd, dwFlags );
|
||||
NS_SendSessionRequestBroadcast( This->dp2->lpNameServerData );
|
||||
|
||||
SleepEx( dwTimeout, FALSE );
|
||||
|
||||
DP_InvokeEnumSessionCallbacksA( lpEnumSessionsCallback2, lpContext );
|
||||
DP_InvokeEnumSessionCallbacksA( lpEnumSessionsCallback2, This->dp2->lpNameServerData, dwTimeout, lpContext );
|
||||
}
|
||||
|
||||
return DP_OK;
|
||||
|
@ -1061,7 +1096,7 @@ static HRESULT WINAPI DirectPlay2AImpl_Open
|
|||
|
||||
/* Rightoo - this computer is the host and the local computer needs to be
|
||||
the name server so that others can join this session */
|
||||
DPLAYX_NS_SetLocalComputerAsNameServer( lpsd );
|
||||
NS_SetLocalComputerAsNameServer( lpsd );
|
||||
|
||||
}
|
||||
|
||||
|
@ -1593,9 +1628,31 @@ static HRESULT WINAPI DirectPlay3AImpl_SecureOpen
|
|||
|
||||
FIXME("(%p)->(%p,0x%08lx,%p,%p): stub\n", This, lpsd, dwFlags, lpSecurity, lpCredentials );
|
||||
|
||||
if( This->dp2->bConnectionOpen )
|
||||
{
|
||||
TRACE( ": rejecting already open connection.\n" );
|
||||
return DPERR_ALREADYINITIALIZED;
|
||||
}
|
||||
|
||||
/* When we open we need to stop any EnumSession activity */
|
||||
IDirectPlayX_EnumSessions( iface, NULL, 0, NULL, NULL, DPENUMSESSIONS_STOPASYNC );
|
||||
|
||||
if( dwFlags & DPOPEN_CREATE )
|
||||
{
|
||||
dwFlags &= ~DPOPEN_CREATE;
|
||||
|
||||
/* Rightoo - this computer is the host and the local computer needs to be
|
||||
the name server so that others can join this session */
|
||||
NS_SetLocalComputerAsNameServer( lpsd );
|
||||
|
||||
}
|
||||
if( dwFlags )
|
||||
{
|
||||
ERR( ": ignored dwFlags 0x%08lx\n", dwFlags );
|
||||
}
|
||||
|
||||
return DP_OK;
|
||||
|
||||
}
|
||||
|
||||
static HRESULT WINAPI DirectPlay3WImpl_SecureOpen
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* dplayx.dll global data implementation.
|
||||
*
|
||||
* Copyright 1999 - Peter Hunnisett
|
||||
* Copyright 1999,2000 - Peter Hunnisett
|
||||
*
|
||||
* <presently under construction - contact hunnise@nortelnetworks.com>
|
||||
*
|
||||
|
@ -36,27 +36,34 @@ static HANDLE hDplayxSema;
|
|||
|
||||
|
||||
/* HACK for simple global data right now */
|
||||
enum { numSupportedLobbies = 32 };
|
||||
enum { numSupportedLobbies = 32, numSupportedSessions = 32 };
|
||||
typedef struct tagDirectPlayLobbyData
|
||||
{
|
||||
/* Just a DPLCONNECTION struct */
|
||||
DWORD dwConnFlags;
|
||||
DPSESSIONDESC2 sessionDesc;
|
||||
DPNAME playerName;
|
||||
GUID guidSP;
|
||||
LPVOID lpAddress;
|
||||
DWORD dwAddressSize;
|
||||
|
||||
/* Information for dplobby interfaces */
|
||||
DWORD dwAppID;
|
||||
HANDLE hReceiveEvent;
|
||||
DWORD dwAppLaunchedFromID;
|
||||
|
||||
} DirectPlayLobbyData, *lpDirectPlayLobbyData;
|
||||
|
||||
static DirectPlayLobbyData lobbyData[ numSupportedLobbies ];
|
||||
|
||||
/* Hack for now */
|
||||
static DPSESSIONDESC2 sessionData[ numSupportedSessions ];
|
||||
|
||||
/* Function prototypes */
|
||||
BOOL DPLAYX_IsAppIdLobbied( DWORD dwAppId, lpDirectPlayLobbyData* dplData );
|
||||
void DPLAYX_InitializeLobbyDataEntry( lpDirectPlayLobbyData lpData );
|
||||
|
||||
|
||||
BOOL DPLAYX_CopyIntoSessionDesc2A( LPDPSESSIONDESC2 lpSessionDest,
|
||||
LPCDPSESSIONDESC2 lpSessionSrc );
|
||||
|
||||
|
||||
|
||||
|
@ -87,6 +94,12 @@ void DPLAYX_ConstructData(void)
|
|||
DPLAYX_InitializeLobbyDataEntry( &lobbyData[ i ] );
|
||||
}
|
||||
|
||||
/* Set all sessions to be "empty" */
|
||||
for( i=0; i < numSupportedSessions; i++ )
|
||||
{
|
||||
sessionData[i].dwSize = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
|
@ -578,6 +591,89 @@ HRESULT DPLAYX_SetConnectionSettingsW
|
|||
return DP_OK;
|
||||
}
|
||||
|
||||
LPDPSESSIONDESC2 DPLAYX_CopyAndAllocateSessionDesc2A( LPCDPSESSIONDESC2 lpSessionSrc )
|
||||
{
|
||||
LPDPSESSIONDESC2 lpSessionDest = (LPDPSESSIONDESC2) HeapAlloc( GetProcessHeap(),
|
||||
HEAP_ZERO_MEMORY,
|
||||
sizeof( *lpSessionSrc ) );
|
||||
DPLAYX_CopyIntoSessionDesc2A( lpSessionDest, lpSessionSrc );
|
||||
|
||||
return lpSessionDest;
|
||||
}
|
||||
|
||||
/* Copy an ANSI session desc structure to the given buffer */
|
||||
BOOL DPLAYX_CopyIntoSessionDesc2A( LPDPSESSIONDESC2 lpSessionDest,
|
||||
LPCDPSESSIONDESC2 lpSessionSrc )
|
||||
{
|
||||
memcpy( lpSessionDest, lpSessionSrc, sizeof( *lpSessionSrc ) );
|
||||
|
||||
if( lpSessionSrc->sess.lpszSessionNameA )
|
||||
{
|
||||
lpSessionDest->sess.lpszSessionNameA =
|
||||
HEAP_strdupA( GetProcessHeap(), HEAP_ZERO_MEMORY, lpSessionSrc->sess.lpszSessionNameA );
|
||||
}
|
||||
if( lpSessionSrc->pass.lpszPasswordA )
|
||||
{
|
||||
lpSessionDest->pass.lpszPasswordA =
|
||||
HEAP_strdupA( GetProcessHeap(), HEAP_ZERO_MEMORY, lpSessionSrc->pass.lpszPasswordA );
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Start the index at 0. index will be updated to equal that which should
|
||||
be passed back into this function for the next element */
|
||||
LPDPSESSIONDESC2 DPLAYX_CopyAndAllocateLocalSession( UINT* index )
|
||||
{
|
||||
for( ; (*index) < numSupportedSessions; (*index)++ )
|
||||
{
|
||||
if( sessionData[(*index)].dwSize != 0 )
|
||||
{
|
||||
return DPLAYX_CopyAndAllocateSessionDesc2A( &sessionData[(*index)++] );
|
||||
}
|
||||
}
|
||||
|
||||
/* No more sessions */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Start the index at 0. index will be updated to equal that which should
|
||||
be passed back into this function for the next element */
|
||||
BOOL DPLAYX_CopyLocalSession( UINT* index, LPDPSESSIONDESC2 lpsd )
|
||||
{
|
||||
for( ; (*index) < numSupportedSessions; (*index)++ )
|
||||
{
|
||||
if( sessionData[(*index)].dwSize != 0 )
|
||||
{
|
||||
return DPLAYX_CopyIntoSessionDesc2A( lpsd, &sessionData[(*index)++] );
|
||||
}
|
||||
}
|
||||
|
||||
/* No more sessions */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void DPLAYX_SetLocalSession( LPCDPSESSIONDESC2 lpsd )
|
||||
{
|
||||
UINT i;
|
||||
|
||||
FIXME( ": stub\n" );
|
||||
|
||||
/* FIXME: Is this an error if it exists already? */
|
||||
|
||||
/* Crude/wrong implementation for now. Just always add to first empty spot */
|
||||
for( i=0; i < numSupportedSessions; i++ )
|
||||
{
|
||||
/* Is this one empty? */
|
||||
if( sessionData[i].dwSize == 0 )
|
||||
{
|
||||
DPLAYX_CopyIntoSessionDesc2A( &sessionData[i], lpsd );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* NOTE: This is potentially not thread safe. You are not guaranteed to end up
|
||||
with the correct string printed in the case where the HRESULT is not
|
||||
known. You'll just get the last hr passed in printed. This can change
|
||||
|
|
|
@ -16,6 +16,10 @@ HRESULT DPLAYX_SetConnectionSettingsW ( DWORD dwFlags, DWORD dwAppID, LPDPLCONNE
|
|||
BOOL DPLAYX_CreateLobbyApplication( DWORD dwAppID, HANDLE hReceiveEvent );
|
||||
BOOL DPLAYX_DestroyLobbyApplication( DWORD dwAppID );
|
||||
|
||||
LPDPSESSIONDESC2 DPLAYX_CopyAndAllocateLocalSession( UINT* index );
|
||||
BOOL DPLAYX_CopyLocalSession( UINT* index, LPDPSESSIONDESC2 lpsd );
|
||||
void DPLAYX_SetLocalSession( LPCDPSESSIONDESC2 lpsd );
|
||||
|
||||
/* Convert a DP or DPL HRESULT code into a string for human consumption */
|
||||
LPCSTR DPLAYX_HresultToString( HRESULT hr );
|
||||
|
||||
|
|
|
@ -8,15 +8,152 @@
|
|||
|
||||
/* NOTE: Methods with the NS_ prefix are name server methods */
|
||||
|
||||
|
||||
#include "winbase.h"
|
||||
#include "debugtools.h"
|
||||
|
||||
#include "dplayx_global.h"
|
||||
#include "name_server.h"
|
||||
|
||||
/* FIXME: Need to aquire the interface semaphore before didlling data */
|
||||
|
||||
DEFAULT_DEBUG_CHANNEL(dplay);
|
||||
|
||||
|
||||
void DPLAYX_NS_SetLocalComputerAsNameServer( LPDPSESSIONDESC2 lpsd )
|
||||
/* NS specific structures */
|
||||
typedef struct tagNSCacheData
|
||||
{
|
||||
FIXME( ": (%p) stub\n", lpsd );
|
||||
struct tagNSCacheData* next;
|
||||
|
||||
LPDPSESSIONDESC2 data;
|
||||
|
||||
} NSCacheData, *lpNSCacheData;
|
||||
|
||||
typedef struct tagNSCache
|
||||
{
|
||||
lpNSCacheData present; /* keep track of what is to be looked at */
|
||||
lpNSCacheData first;
|
||||
} NSCache, *lpNSCache;
|
||||
|
||||
/* Local Prototypes */
|
||||
static void NS_InvalidateSessionCache( lpNSCache lpCache );
|
||||
|
||||
|
||||
/* Name Server functions
|
||||
* ---------------------
|
||||
*/
|
||||
void NS_SetLocalComputerAsNameServer( LPCDPSESSIONDESC2 lpsd )
|
||||
{
|
||||
DPLAYX_SetLocalSession( lpsd );
|
||||
}
|
||||
|
||||
/* This function is responsible for sending a request for all other known
|
||||
nameservers to send us what sessions they have registered locally
|
||||
*/
|
||||
void NS_SendSessionRequestBroadcast( LPVOID lpNSInfo )
|
||||
{
|
||||
UINT index = 0;
|
||||
lpNSCache lpCache = (lpNSCache)lpNSInfo;
|
||||
LPDPSESSIONDESC2 lpTmp = NULL;
|
||||
|
||||
/* Invalidate the session cache for the interface */
|
||||
NS_InvalidateSessionCache( lpCache );
|
||||
|
||||
/* Add the local known sessions to the cache */
|
||||
if( ( lpTmp = DPLAYX_CopyAndAllocateLocalSession( &index ) ) != NULL )
|
||||
{
|
||||
lpCache->first = (lpNSCacheData)HeapAlloc( GetProcessHeap(),
|
||||
HEAP_ZERO_MEMORY,
|
||||
sizeof( *(lpCache->first) ) );
|
||||
lpCache->first->data = lpTmp;
|
||||
lpCache->first->next = NULL;
|
||||
lpCache->present = lpCache->first;
|
||||
|
||||
while( ( lpTmp = DPLAYX_CopyAndAllocateLocalSession( &index ) ) != NULL )
|
||||
{
|
||||
lpCache->present->next = (lpNSCacheData)HeapAlloc( GetProcessHeap(),
|
||||
HEAP_ZERO_MEMORY,
|
||||
sizeof( *(lpCache->present) ) );
|
||||
lpCache->present = lpCache->present->next;
|
||||
lpCache->present->data = lpTmp;
|
||||
lpCache->present->next = NULL;
|
||||
}
|
||||
|
||||
lpCache->present = lpCache->first;
|
||||
}
|
||||
|
||||
/* Send out requests for matching sessions to all other known computers */
|
||||
FIXME( ": no remote requests sent\n" );
|
||||
/* FIXME - how to handle responses to messages anyways? */
|
||||
}
|
||||
|
||||
/* Render all data in a session cache invalid */
|
||||
static void NS_InvalidateSessionCache( lpNSCache lpCache )
|
||||
{
|
||||
if( lpCache == NULL )
|
||||
{
|
||||
ERR( ": invalidate non existant cache\n" );
|
||||
return;
|
||||
}
|
||||
|
||||
/* Remove everything from the cache */
|
||||
while( lpCache->first )
|
||||
{
|
||||
lpCache->present = lpCache->first;
|
||||
lpCache->first = lpCache->first->next;
|
||||
HeapFree( GetProcessHeap(), 0, lpCache->present );
|
||||
}
|
||||
|
||||
/* NULL out the cache pointers */
|
||||
lpCache->present = NULL;
|
||||
lpCache->first = NULL;
|
||||
}
|
||||
|
||||
/* Create and initialize a session cache */
|
||||
BOOL NS_InitializeSessionCache( LPVOID* lplpNSInfo )
|
||||
{
|
||||
lpNSCache lpCache = (lpNSCache)HeapAlloc( GetProcessHeap(),
|
||||
HEAP_ZERO_MEMORY,
|
||||
sizeof( *lpCache ) );
|
||||
|
||||
*lplpNSInfo = lpCache;
|
||||
|
||||
if( lpCache == NULL )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
lpCache->first = lpCache->present = NULL;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Delete a session cache */
|
||||
void NS_DeleteSessionCache( LPVOID lpNSInfo )
|
||||
{
|
||||
NS_InvalidateSessionCache( (lpNSCache)lpNSInfo );
|
||||
}
|
||||
|
||||
/* Reinitialize the present pointer for this cache */
|
||||
void NS_ResetSessionEnumeration( LPVOID lpNSInfo )
|
||||
{
|
||||
|
||||
((lpNSCache)lpNSInfo)->present = ((lpNSCache)lpNSInfo)->first;
|
||||
}
|
||||
|
||||
LPDPSESSIONDESC2 NS_WalkSessions( LPVOID lpNSInfo )
|
||||
{
|
||||
LPDPSESSIONDESC2 lpSessionDesc;
|
||||
lpNSCache lpCache = (lpNSCache)lpNSInfo;
|
||||
|
||||
/* Test for end of the list */
|
||||
if( lpCache->present == NULL )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
lpSessionDesc = lpCache->present->data;
|
||||
|
||||
/* Advance tracking pointer */
|
||||
lpCache->present = lpCache->present->next;
|
||||
|
||||
return lpSessionDesc;
|
||||
}
|
||||
|
|
|
@ -4,6 +4,13 @@
|
|||
|
||||
#include "dplay.h"
|
||||
|
||||
void DPLAYX_NS_SetLocalComputerAsNameServer( LPDPSESSIONDESC2 lpsd );
|
||||
void NS_SetLocalComputerAsNameServer( LPCDPSESSIONDESC2 lpsd );
|
||||
void NS_SendSessionRequestBroadcast( LPVOID lpNSInfo );
|
||||
|
||||
BOOL NS_InitializeSessionCache( LPVOID* lplpNSInfo );
|
||||
void NS_DeleteSessionCache( LPVOID lpNSInfo );
|
||||
|
||||
void NS_ResetSessionEnumeration( LPVOID lpNSInfo );
|
||||
LPDPSESSIONDESC2 NS_WalkSessions( LPVOID lpNSInfo );
|
||||
|
||||
#endif /* __WINE_DPLAYX_NAMESERVER */
|
||||
|
|
Loading…
Reference in New Issue