Initial commit: Complete open-source Decal rebuild

All 5 phases of the open-source Decal rebuild:

Phase 1: 14 decompiled .NET projects (Interop.*, Adapter, FileService, DecalUtil)
Phase 2: 10 native DLLs rewritten as C# COM servers with matching GUIDs
  - DecalDat, DHS, SpellFilter, DecalInput, DecalNet, DecalFilters
  - Decal.Core, DecalControls, DecalRender, D3DService
Phase 3: C++ shims for Inject.DLL (D3D9 hooking) and LauncherHook.DLL
Phase 4: DenAgent WinForms tray application
Phase 5: WiX installer and build script

25 C# projects building with 0 errors.
Native C++ projects require VS 2022 + Windows SDK (x86).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
erik 2026-02-08 18:27:56 +01:00
commit d1442e3747
1382 changed files with 170725 additions and 0 deletions

View file

@ -0,0 +1,460 @@
// NetService.cpp : Implementation of cNetService
#include "stdafx.h"
#include "DecalNet.h"
#include "NetService.h"
#include <time.h>
#include "Message.h"
#include <ApiHook.h>
#include "ProtocolHook.h"
#include "FilterAdapterV1.h"
#define _OUTGOING_PACKETS_
//#define _LOGGING
extern DWORD HookCall (DWORD dwCallAddress, DWORD dwReplacement);
extern void ReceiveBlob();
bool (__cdecl*LockMemoryPages)(void *);
bool g_bLockMemoryPages = false;
DWORD g_dwReceiveBlobReadyCall = 0;
DWORD g_dwReceiveBlobReadyProc = 0;
DWORD g_dwNetBlobObjectDataOffset = 0;
DWORD g_dwNetBlobObjectSizeOffset = 0;
/////////////////////////////////////////////////////////////////////////////
// cNetService
// Functions for hooking from wsock32.dll
int PASCAL recvfromF( SOCKET s, char FAR* buf, int len, int flags, struct sockaddr FAR* from, int FAR* fromlen );
int PASCAL sendtoF( SOCKET s, const char FAR* buf, int len, int flags, const struct sockaddr FAR* to, int tolen );
static cHookDescriptor _hooks[] = {
{ eByOrdinal, _T( "fsock32.dll" ), _T( "recvfrom" ), 17, reinterpret_cast< DWORD >( recvfromF ), 0 },
{ eByOrdinal, _T( "wsock32.dll" ), _T( "recvfrom" ), 17, reinterpret_cast< DWORD >( recvfromF ), 0 },
};
static cHookDescriptor _hooksOut[] = {
{ eByOrdinal, _T( "wsock32.dll" ), _T( "sendto" ), 20, reinterpret_cast< DWORD >( sendtoF ), 0 },
};
HRESULT cNetService::onInitialize()
{
if( g_pService == NULL )
{
g_pService = this;
m_pDecal->get_Hooks( &m_pHooks );
long lTemp = 0;
m_pHooks->QueryMemLoc( _bstr_t( "ReceiveBlobReadyCall" ), &lTemp );
g_dwReceiveBlobReadyCall = lTemp, lTemp = 0;
m_pHooks->QueryMemLoc( _bstr_t( "NetBlobObjectDataOffset" ), &lTemp );
g_dwNetBlobObjectDataOffset = lTemp, lTemp = 0;
m_pHooks->QueryMemLoc( _bstr_t( "NetBlobObjectSizeOffset" ), &lTemp );
g_dwNetBlobObjectSizeOffset = lTemp, lTemp = 0;
m_pHooks->QueryMemLoc( _bstr_t( "LockMemoryPages" ), &lTemp );
if( lTemp )
g_bLockMemoryPages = true;
LockMemoryPages = (bool(__cdecl*)(void*)) lTemp, lTemp = 0;
if( g_dwReceiveBlobReadyCall && g_dwNetBlobObjectDataOffset && g_dwNetBlobObjectSizeOffset )
g_dwReceiveBlobReadyProc = HookCall( g_dwReceiveBlobReadyCall, (DWORD) ReceiveBlob );
else
{
g_dwReceiveBlobReadyCall = 0; // just in case.
hookFunctions( _hooks, 2, true );
if( _hooks[ 0 ].m_pOldFunction != 0 )
g_fn_recvfrom = reinterpret_cast< fn_recvfrom >( _hooks[ 0 ].m_pOldFunction );
else if( _hooks[ 1 ].m_pOldFunction != 0 )
g_fn_recvfrom = reinterpret_cast< fn_recvfrom >( _hooks[ 1 ].m_pOldFunction );
else
_ASSERTE( false );
}
}
#ifdef _OUTGOING_PACKETS_
hookFunctions( _hooksOut, 1, true );
if( _hooksOut[ 0 ].m_pOldFunction != 0 )
g_fn_sendto = reinterpret_cast< fn_sendto >( _hooksOut[ 0 ].m_pOldFunction );
else
_ASSERTE( false );
#endif
m_stack.start( this );
// Start all of the network filters
{
CComPtr< IDecalEnum > pEnum;
HRESULT hRes = m_pDecal->get_Configuration( _bstr_t( _T( "NetworkFilters" ) ), GUID_NULL, &pEnum );
if( FAILED( hRes ) )
return hRes;
while( pEnum->Next() == S_OK )
{
// Unlike services, network filters are allowed to fail
VARIANT_BOOL bEnabled;
HRESULT hRes = pEnum->get_Enabled ( &bEnabled );
_ASSERTE ( SUCCEEDED ( hRes ) );
if ( !bEnabled )
continue;
cFilter f;
pEnum->get_ComClass( &f.m_clsid );
hRes = pEnum->CreateInstance( __uuidof( INetworkFilter2 ), reinterpret_cast< void ** >( &f.m_p ) );
if( FAILED( hRes ) )
{
// Heads up that a filter is failing to init
_ASSERT( FALSE );
continue;
}
hRes = f.m_p->Initialize( this );
if( SUCCEEDED( hRes ) )
{
f.m_ver = eVersion2;
m_filters.push_back( f );
}
}
}
// Prepare the message container
CComObject< cMessage > *pMessage;
CComObject< cMessage >::CreateInstance( &pMessage );
pMessage->AddRef();
pMessage->m_pService = this;
pMessage->init();
m_pMessage = pMessage;
return S_OK;
}
void cNetService::onTerminate()
{
m_pMessage->term();
// The release should destroy it
m_pMessage->Release();
m_pMessage = NULL;
// Kill all of the network filters in reverse order
while( !m_filters.empty() )
{
cFilterList::iterator i_end = ( -- m_filters.end() );
i_end->m_p->Terminate();
m_filters.erase( i_end );
}
m_filters.clear();
m_stack.stop();
if( g_pService == this )
{
#ifdef _OUTGOING_PACKETS_
hookFunctions( _hooksOut, 1, false );
#endif
if( g_dwReceiveBlobReadyCall )
HookCall( g_dwReceiveBlobReadyCall, g_dwReceiveBlobReadyProc );
else
hookFunctions( _hooks, 2, false );
g_pService = NULL;
}
m_pHooks.Release();
}
int PASCAL recvfromF( SOCKET s, char FAR* buf, int len, int flags, struct sockaddr FAR* from, int FAR* fromlen )
{
_ASSERTE( cNetService::g_pService != NULL );
int iRes = cNetService::g_fn_recvfrom( s, buf, len, flags, from, fromlen );
#ifdef _LOGGING_
if( iRes != SOCKET_ERROR && cNetService::g_pService != NULL )
{
char *harro = new char[256];
_snprintf(harro, 256, "recvfrom( %d, %08X, %d, %d, %08X, %d ) = %d", s, buf, len, flags, from, fromlen, iRes);
m_pHooks->ChatOut(_bstr_t (harro), 0);
delete[] harro;
}
#endif
if( iRes != SOCKET_ERROR && cNetService::g_pService != NULL )
cNetService::g_pService->m_stack.processPacket( static_cast< DWORD >( iRes ), reinterpret_cast< BYTE * >( buf ) );
return iRes;
}
int PASCAL sendtoF( SOCKET s, const char FAR* buf, int len, int flags, const struct sockaddr FAR* to, int tolen )
{
_ASSERTE( cNetService::g_pService != NULL );
#ifdef _LOGGING_
char *harro = new char[256];
char *buffer = new char[512];
sprintf(harro, "sendto( %d, %08X, %d, %d, %s:%d, %d )",
s, buf, len, flags,
inet_ntoa( ((sockaddr_in *)(to))->sin_addr ),
ntohs( ((sockaddr_in *)(to))->sin_port ),
tolen);
m_pHooks->ChatOut(_bstr_t (harro), 0);
delete[] buffer;
delete[] harro;
#endif
int iRes = cNetService::g_fn_sendto( s, buf, len, flags, to, tolen );
if( iRes != SOCKET_ERROR && cNetService::g_pService != NULL )
cNetService::g_pService->m_stack.processPacketOut( static_cast< DWORD >( len ), reinterpret_cast< const BYTE * >( buf ) );
return iRes;
}
cNetService *cNetService::g_pService = NULL;
cNetService::fn_recvfrom cNetService::g_fn_recvfrom = NULL;
cNetService::fn_sendto cNetService::g_fn_sendto = NULL;
void cNetService::onMessage( ACMessage &msg )
{
_ASSERTE( m_pMessage != NULL );
DWORD dwMessageCode = msg.getType();
if( dwMessageCode == 0xF7C7 )
m_pDecal->StartPlugins();
else if(( dwMessageCode == 0xF653 ) || ( dwMessageCode == 0xF659 ))
m_pDecal->StopPlugins();
m_pMessage->crackMessage( msg.getData (), msg.getSize () );
// if( g_bLockMemoryPages )
// if( !LockMemoryPages( reinterpret_cast< void * >(m_pMessage) ) )
// return;
// Dispatch the message to all of the network filters
for( cFilterList::iterator i = m_filters.begin(); i != m_filters.end(); ++ i )
i->m_p->DispatchServer( m_pMessage );
}
void cNetService::onMessageOut( ACMessage &msg )
{
_ASSERTE( m_pMessage != NULL );
DWORD dwMessageCode = msg.getType();
m_pMessage->crackMessage( msg.getData (), msg.getSize () );
// Dispatch the message to all of the network filters
for( cFilterList::iterator i = m_filters.begin(); i != m_filters.end(); ++ i )
i->m_p->DispatchClient( m_pMessage );
}
STDMETHODIMP cNetService::BeforePlugins()
{
USES_CONVERSION;
// Startup version 1 network filters
RegKey hkeyNF;
if ( hkeyNF.Open ( HKEY_LOCAL_MACHINE, _T( "Software\\Decal\\NetworkFilters" ), KEY_READ ) == ERROR_SUCCESS )
{
// Enum the values
DWORD dwSize = 64;
TCHAR szCLSID[ 64 ];
for ( int i = 0; ::RegEnumValue ( hkeyNF, i, szCLSID, &dwSize, NULL, NULL, NULL, NULL ) == ERROR_SUCCESS; ++ i )
{
// Reset the sizes
dwSize = 64;
// Try to convert the string
cFilter f;
HRESULT hRes = ::CLSIDFromString ( T2OLE ( szCLSID ), &f.m_clsid );
if ( FAILED ( hRes ) )
// This is not a CLSID
continue;
// Check for enablement
DWORD dwEnabled;
hkeyNF.QueryDWORDValue ( szCLSID, dwEnabled );
if ( !dwEnabled )
continue;
// We have a CLSID - try to create the object and the adapter
CComObject< cFilterAdapterV1 > *pAdapter;
hRes = CComObject< cFilterAdapterV1 >::CreateInstance ( &pAdapter );
if ( FAILED ( hRes ) )
{
_ASSERT ( FALSE );
continue;
}
f.m_p = pAdapter;
hRes = pAdapter->init ( f.m_clsid );
if( SUCCEEDED( hRes ) )
{
f.m_ver = eVersion1;
m_filters.push_back( f );
}
}
}
return S_OK;
}
STDMETHODIMP cNetService::AfterPlugins()
{
// Remove all version 1 network filters
for ( cFilterList::iterator i = m_filters.begin (); i != m_filters.end (); )
{
if ( i->m_ver == eVersion1 )
i = m_filters.erase ( i );
else
++ i;
}
return S_OK;
}
STDMETHODIMP cNetService::Lookup( BSTR strName, IUnknown **ppvItf )
{
// Attempt to convert the name to a GUID
CLSID clsid;
HRESULT hRes;
if( strName[ 0 ] == OLECHAR( '{' ) )
hRes = ::CLSIDFromString( strName, &clsid );
else
hRes = ::CLSIDFromProgID( strName, &clsid );
if( FAILED( hRes ) )
{
_ASSERT( FALSE );
return E_INVALIDARG;
}
// Walk through the list of filters to find our match
for( cFilterList::iterator i = m_filters.begin(); i != m_filters.end(); ++ i )
{
if( i->m_clsid == clsid )
return i->m_p->QueryInterface( IID_IUnknown, reinterpret_cast< void ** >( ppvItf ) );
}
// Could not find a network filter with that class ID
_ASSERT( FALSE );
return E_INVALIDARG;
}
STDMETHODIMP cNetService::get_Decal(IDecal **pVal)
{
return m_pDecal->QueryInterface( pVal );
}
STDMETHODIMP cNetService::get_Filter(REFCLSID clsid, REFIID iid, LPVOID *pVal)
{
for( cFilterList::iterator i = m_filters.begin(); i != m_filters.end(); ++ i )
{
if( i->m_clsid == clsid )
return i->m_p->QueryInterface( iid, pVal );
}
return E_INVALIDARG;
}
STDMETHODIMP cNetService::get_FilterVB(BSTR strProgID, LPDISPATCH *pVal)
{
_ASSERTE( strProgID != NULL );
_ASSERTE( pVal != NULL );
// Prepend a fully-qualified service id
_bstr_t strPath ( _T( "services\\DecalNet.NetService\\" ) );
strPath += strProgID;
return m_pDecal->get_Object ( strPath, IID_IDispatch, reinterpret_cast< LPVOID * > ( pVal ) );
}
STDMETHODIMP cNetService::get_Hooks(IACHooks **pVal)
{
return m_pHooks->QueryInterface( pVal );
}
void __stdcall OnReceiveBlob (DWORD *pStructure)
{
LPBYTE pPacket = (LPBYTE) (pStructure [g_dwNetBlobObjectDataOffset]);
DWORD dwSize = pStructure [g_dwNetBlobObjectSizeOffset];
if (cNetService::g_pService)
{
HookedMessage msg (pPacket, dwSize);
cNetService::g_pService->onMessage (msg);
}
}
void __declspec (naked) ReceiveBlob()
{
_asm
{
push ecx
push ecx
call OnReceiveBlob
pop ecx
jmp g_dwReceiveBlobReadyProc
}
}
// Returns the function which previously was being called, after replacing it with the new call.
DWORD HookCall (DWORD dwCallAddress, DWORD dwReplacement)
{
DWORD* pTemp = (DWORD *) (dwCallAddress + 1);
HANDLE hProcess = OpenProcess
(
PROCESS_VM_WRITE | PROCESS_VM_OPERATION,
FALSE,
GetCurrentProcessId ()
);
DWORD dwOriginal = 0;
if (hProcess)
{
dwOriginal = (*pTemp) + dwCallAddress + 5;
DWORD dwTemp = dwReplacement - (dwCallAddress + 5);
if (WriteProcessMemory
(
hProcess,
pTemp,
&dwTemp,
sizeof (DWORD),
NULL
))
{
}
else
{
dwOriginal = 0;
}
CloseHandle (hProcess);
}
return dwOriginal ;
}