openDecal/Native/Decal/DecalManager.cpp
erik d1442e3747 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>
2026-02-08 18:27:56 +01:00

636 lines
No EOL
15 KiB
C++

// DecalManager.cpp : Implementation of cDecal
#include "stdafx.h"
#include "Decal.h"
#include "DecalManager.h"
#include "DecalEnum.h"
#include "PluginSite.h"
/////////////////////////////////////////////////////////////////////////////
// cDecal
HRESULT cDecal::convertToken( std::string &strToken, std::string &strOut )
{
USES_CONVERSION;
struct cPathRegistry
{
LPCTSTR szToken,
szKey,
szValue;
};
static cPathRegistry _paths[] = {
{ _T( "ac" ), _T( "SOFTWARE\\Microsoft\\Microsoft Games\\Asheron's Call\\1.00" ), _T( "Portal Dat" ) },
{ _T( "decal" ), _T( "SOFTWARE\\Decal\\Agent" ), _T( "AgentPath" ) } },
*_end_paths = _paths + sizeof( _paths ) / sizeof( cPathRegistry );
// There are two types of tokens, ones that contain a colon (object relative)
// and the other kind, which just gets looked up in the registry
int nColon = strToken.find_first_of( _T( ':' ) );
if( nColon == std::string::npos )
{
// Take the whole token string and look it up
for( cPathRegistry *pPath = _paths; pPath != _end_paths; ++ pPath )
{
if( strToken.compare( pPath->szToken ) != 0 )
continue;
// Found the path now look it up in the registry
DWORD dwCount = MAX_PATH;
TCHAR szBuffer[ MAX_PATH ];
RegKey key;
key.Open( HKEY_LOCAL_MACHINE, pPath->szKey );
key.QueryStringValue (pPath->szValue, szBuffer, &dwCount);
strOut += T2A( szBuffer );
return S_OK;
}
// Token not found
_ASSERT( FALSE );
return E_INVALIDARG;
}
// Attempt to convert the CLSID
LPOLESTR strCLSID = A2OLE( strToken.substr( nColon + 1 ).c_str() );
HRESULT hRes;
CLSID clsid;
if( strCLSID[ 0 ] == OLESTR( '{' ) )
hRes = ::CLSIDFromString( strCLSID, &clsid );
else
hRes = ::CLSIDFromProgID( strCLSID, &clsid );
if( FAILED( hRes ) )
{
_ASSERT( FALSE );
return E_INVALIDARG;
}
CComPtr< IDecalEnum > pEnum;
if( get_Configuration( _bstr_t( strToken.substr( 0, nColon ).c_str() ), clsid, &pEnum ) != S_OK )
{
_ASSERT( FALSE );
return E_INVALIDARG;
}
CComBSTR strResourcePath;
if( FAILED( pEnum->get_ResourcePath( &strResourcePath ) ) )
{
_ASSERT( FALSE );
return E_INVALIDARG;
}
strOut += OLE2A( strResourcePath );
return S_OK;
}
STDMETHODIMP cDecal::InitGraphics( IUnknown *pDirectDraw, IUnknown *pD3DDevice )
{
m_pD = static_cast< IDirectDraw4 * >( pDirectDraw );
m_pD3D = static_cast< IDirect3DDevice3 * >( pD3DDevice );
for( cServiceList::iterator i = m_services.begin(); i != m_services.end(); ++ i )
{
if( !( i->m_dwCaps & eServiceRender ) )
continue;
CComPtr< IDecalRender > pRender;
HRESULT hRes = i->m_p->QueryInterface( &pRender );
_ASSERTE( SUCCEEDED( hRes ) );
pRender->ChangeDirectX();
}
return S_OK;
}
STDMETHODIMP cDecal::get_DirectDraw(REFIID iid, VOID * * ppvItf)
{
return m_pD->QueryInterface( iid, ppvItf );
}
STDMETHODIMP cDecal::get_D3DDevice(REFIID iid, VOID * * ppvItf)
{
return m_pD3D->QueryInterface( iid, ppvItf );
}
STDMETHODIMP cDecal::get_HWND(LONG * pVal)
{
if (pVal == NULL)
return E_POINTER;
*pVal = reinterpret_cast< long >( m_hWnd );
return S_OK;
}
STDMETHODIMP cDecal::put_HWND( LONG newVal )
{
HWND newWnd = reinterpret_cast< HWND >( newVal );
if( newWnd != m_hWnd )
{
m_hWnd = newWnd;
for( cServiceList::iterator i = m_services.begin(); i != m_services.end(); ++ i )
{
if( !( i->m_dwCaps & eServiceRender ) )
continue;
CComPtr< IDecalRender > pRender;
HRESULT hRes = i->m_p->QueryInterface( &pRender );
_ASSERTE( SUCCEEDED( hRes ) );
pRender->ChangeHWND();
}
}
return S_OK;
}
STDMETHODIMP cDecal::get_Focus(VARIANT_BOOL * pVal)
{
if (pVal == NULL)
return E_POINTER;
*pVal = m_bFocus;
return S_OK;
}
STDMETHODIMP cDecal::put_Focus( VARIANT_BOOL newVal )
{
m_bFocus = newVal;
return S_OK;
}
STDMETHODIMP cDecal::get_ScreenSize(long *pWidth, long *pHeight)
{
if (pWidth == NULL || pHeight == NULL)
return E_POINTER;
RECT rc;
::GetClientRect( m_hWnd, &rc );
*pWidth = rc.right - rc.left;
*pHeight = rc.bottom - rc.top;
return S_OK;
}
STDMETHODIMP cDecal::MapPath(BSTR pPath, BSTR * pMapped)
{
USES_CONVERSION;
if (pMapped == NULL)
{
_ASSERT( FALSE );
return E_POINTER;
}
std::string str( OLE2A( pPath ) ),
strOut;
// If the base implementation, iterate over the %% pairs and make substitutions
// where appropriate
int nIterate = 0;
for( ;; )
{
int nStart = str.find_first_of( _T( '%' ), nIterate );
if( nStart == std::string::npos )
{
strOut += str.substr( nIterate );
break;
}
int nEnd = str.find_first_of( _T( '%' ), nStart + 1 );
HRESULT hRes = convertToken( str.substr( nStart + 1, nEnd - ( nStart + 1 ) ), strOut );
if( FAILED( hRes ) )
return hRes;
if( nEnd == std::string::npos )
{
// Unterminated '%'
_ASSERT( FALSE );
return E_INVALIDARG;
}
nIterate = nEnd + 1;
if( nIterate == str.length() )
{
// The string ended in a token, break now
break;
}
}
// We're done - convert the string
*pMapped = A2BSTR( strOut.c_str() );
return S_OK;
}
STDMETHODIMP cDecal::StartPlugins()
{
if( !m_bServicesStarted )
{
HRESULT hRes = StartServices();
if( FAILED( hRes ) )
return hRes;
}
if( m_bPluginsStarted )
return S_FALSE;
// Walk through the list of services and send the plugin start notification
{
for( cServiceList::iterator i = m_services.begin(); i != m_services.end(); ++ i )
i->m_p->BeforePlugins();
}
CComPtr< IDecalEnum > pEnumPlugins;
get_Configuration( _bstr_t( "Plugins" ), GUID_NULL, &pEnumPlugins );
while( pEnumPlugins->Next() == S_OK )
{
VARIANT_BOOL bEnabled;
pEnumPlugins->get_Enabled( &bEnabled );
if( !bEnabled )
continue;
cPlugin plugin;
pEnumPlugins->get_ComClass( &plugin.m_clsid );
// Attempt to make an instance of the plugin
CComPtr< IPlugin2 > pPlugin;
HRESULT hRes = pEnumPlugins->CreateInstance( __uuidof( IPlugin2 ), reinterpret_cast< void ** >( &pPlugin ) );
if( FAILED( hRes ) )
{
_ASSERT( FALSE );
continue;
}
CComObject< cPluginSite > *pSite;
CComObject< cPluginSite >::CreateInstance( &pSite );
CComPtr< IPluginSite2 > pSiteRef = pSite;
pSite->m_pPlugin = pPlugin;
pSite->m_pDecal = this;
hRes = pSite->m_pPlugin->Initialize( pSiteRef );
if( FAILED( hRes ) )
{
_ASSERT( FALSE );
continue;
}
plugin.m_pSite = pSite;
m_plugins.push_back( plugin );
// NOTE: If the plugin has not stored the IPluginSite2 interface pointer, it
// will immediately terminate
}
m_bPluginsStarted = true;
return S_OK;
}
STDMETHODIMP cDecal::StopPlugins()
{
if( !m_bPluginsStarted )
return S_FALSE;
IPluginSite2 **pSites = reinterpret_cast< IPluginSite2 ** > ( _alloca ( sizeof ( IPluginSite2 * ) * m_plugins.size () ) );
{
IPluginSite2 **i2 = pSites;
for( cPluginList::iterator i = m_plugins.begin(); i != m_plugins.end(); ++ i, ++ i2 )
*i2 = i->m_pSite;
}
{
IPluginSite2 **end_sites = pSites + m_plugins.size ();
for ( IPluginSite2 **i = pSites; i != end_sites; ++ i )
( *i )->Unload ();
}
_ASSERTE ( m_plugins.empty () );
m_bPluginsStarted = false;
// Walk through the list of services and send the plugin start notification
{
for( cServiceList::iterator i = m_services.begin(); i != m_services.end(); ++ i )
i->m_p->AfterPlugins();
}
return S_OK;
}
STDMETHODIMP cDecal::get_Object(BSTR strPath, REFIID iid, LPVOID *pVal)
{
if( pVal == NULL )
{
_ASSERT( FALSE );
return E_POINTER;
}
USES_CONVERSION;
std::string str = OLE2A( strPath );
// OK, strPath is formated as such, {plugin|service}\{{clsid|progid}[\text]
int nOffset = str.find_first_of( _T( '\\' ) );
if( nOffset == std::string::npos )
{
_ASSERT( FALSE );
return E_INVALIDARG;
}
// Find the next backslash
int nclsid = str.find_first_of( _T( '\\' ), nOffset + 1 );
// Attempt to convert it to a CLSID
std::string strCLSID = str.substr( nOffset + 1, nclsid - ( nOffset + 1 ) );
CLSID clsid;
HRESULT hRes;
if( strCLSID[ 0 ] == _T( '{' ) )
hRes = ::CLSIDFromString( A2OLE( strCLSID.c_str() ), &clsid );
else
hRes = ::CLSIDFromProgID( A2OLE( strCLSID.c_str() ), &clsid );
if( FAILED( hRes ) )
{
// The substring could not be converted to a proper CLSID
_ASSERT( FALSE );
return E_INVALIDARG;
}
std::string collection = str.substr( 0, nOffset );
CComPtr< IUnknown > pObject;
if( collection.compare( "plugins" ) == 0 )
{
for( cPluginList::iterator i = m_plugins.begin(); i != m_plugins.end(); ++ i )
{
if( i->m_clsid == clsid )
{
pObject = i->m_pSite->m_pPlugin;
break;
}
}
}
else if( collection.compare( "services" ) == 0 )
{
for( cServiceList::iterator i = m_services.begin(); i != m_services.end(); ++ i )
{
if( i->m_clsid == clsid )
{
pObject = i->m_p;
break;
}
}
}
else
{
// Invalid prefix
_ASSERT( FALSE );
return E_INVALIDARG;
}
if( pObject == NULL )
{
// Could not find a match for that clsid
_ASSERT( FALSE );
return E_INVALIDARG;
}
while( nclsid != std::string::npos )
{
CComPtr< IDecalDirectory > pDir;
HRESULT hRes = pObject->QueryInterface( &pDir );
if( FAILED( hRes ) )
{
_ASSERT( FALSE );
return E_INVALIDARG;
}
int nEndclsid = str.find_first_of( _T( '\\' ), nclsid + 1 );
pObject.Release();
hRes = pDir->Lookup( _bstr_t( str.substr( nclsid + 1, nEndclsid - ( nclsid + 1 ) ).c_str() ), &pObject );
if( FAILED( hRes ) )
return hRes;
nclsid = nEndclsid;
}
return pObject->QueryInterface( iid, pVal );
}
struct cItfCaps
{
const IID *iid;
DWORD m_dwMask;
};
STDMETHODIMP cDecal::StartServices()
{
static cItfCaps _service_caps[] = {
{ &IID_IDecalRender, eServiceRender } },
*_end_service_caps = _service_caps + sizeof( _service_caps ) / sizeof( cItfCaps );
if( m_bServicesStarted )
// They've already been started
return S_FALSE;
m_pHooks.CoCreateInstance( _bstr_t( "Decal.ACHooks" ), NULL, CLSCTX_INPROC_SERVER );
m_pHooks->SetDecal( reinterpret_cast< IUnknown * >( this ) );
CComPtr< IDecalEnum > pEnumServices;
get_Configuration( _bstr_t( "Services" ), GUID_NULL, &pEnumServices );
while( pEnumServices->Next() == S_OK )
{
VARIANT_BOOL bEnabled;
HRESULT hRes = pEnumServices->get_Enabled ( &bEnabled );
_ASSERTE ( SUCCEEDED ( hRes ) );
if ( !bEnabled )
continue;
cService service;
pEnumServices->get_ComClass( &service.m_clsid );
hRes = pEnumServices->CreateInstance( __uuidof( IDecalService ), reinterpret_cast< void ** >( &service.m_p ) );
if( FAILED( hRes ) )
{
_ASSERT( FALSE );
// Unlike plugins, all services must initialize correctly
return hRes;
}
hRes = service.m_p->Initialize( this );
if( FAILED( hRes ) )
{
_ASSERT( FALSE );
// The service could not initialize for some reason
return hRes;
}
service.m_dwCaps = 0;
CComPtr< IUnknown > pUnkCaps;
for( cItfCaps *i_caps = _service_caps; i_caps != _end_service_caps; ++ i_caps )
{
HRESULT hRes = service.m_p->QueryInterface( *i_caps->iid, reinterpret_cast< void ** >( &pUnkCaps ) );
if( SUCCEEDED( hRes ) )
{
service.m_dwCaps |= i_caps->m_dwMask;
pUnkCaps.Release();
}
}
m_services.push_back( service );
}
m_bServicesStarted = true;
return S_OK;
}
STDMETHODIMP cDecal::get_Configuration(BSTR strType, REFCLSID clsidAdvance, IDecalEnum **pVal)
{
if (pVal == NULL)
{
_ASSERT( FALSE );
return E_POINTER;
}
CComObject< cDecalEnum > *pEnum;
HRESULT hRes = CComObject< cDecalEnum >::CreateInstance( &pEnum );
if( FAILED( hRes ) )
return hRes;
CComPtr< IUnknown > pUnk = pEnum;
if( !pEnum->Initialize( this, strType ) )
return E_FAIL;
if( clsidAdvance != GUID_NULL )
{
HRESULT hRes = pEnum->Advance( clsidAdvance );
if ( FAILED ( hRes ) )
return hRes;
}
else
{
HRESULT hRes = pEnum->Begin();
if ( FAILED ( hRes ) )
return hRes;
}
return pUnk->QueryInterface( pVal );
}
STDMETHODIMP cDecal::StopServices()
{
if( m_bPluginsStarted )
StopPlugins( );
if( !m_bServicesStarted )
return S_FALSE;
try
{
while( !m_services.empty() )
{
cServiceList::iterator i = ( m_services.end() - 1 );
i->m_p->Terminate();
m_services.erase( i );
}
}
catch( ... )
{
}
if( m_pHooks.p )
m_pHooks.Release( );
return S_OK;
}
STDMETHODIMP cDecal::get_Plugin(REFCLSID clsid, REFIID iid, LPVOID *pVal)
{
for( cPluginList::iterator i = m_plugins.begin(); i != m_plugins.end(); ++ i )
{
if( i->m_clsid == clsid )
return i->m_pSite->m_pPlugin->QueryInterface( iid, pVal );
}
return E_INVALIDARG;
}
STDMETHODIMP cDecal::get_Service(REFCLSID clsid, REFIID iid, LPVOID *pVal)
{
for( cServiceList::iterator i = m_services.begin(); i != m_services.end(); ++ i )
{
if( i->m_clsid == clsid )
return i->m_p->QueryInterface( iid, pVal );
}
return E_INVALIDARG;
}
STDMETHODIMP cDecal::Render2D()
{
for( cServiceList::iterator i = m_services.begin(); i != m_services.end(); ++ i )
{
if( !( i->m_dwCaps & eServiceRender ) )
continue;
CComPtr< IDecalRender > pRender;
HRESULT hRes = i->m_p->QueryInterface( &pRender );
_ASSERTE( SUCCEEDED( hRes ) );
pRender->Render2D();
}
return S_OK;
}
STDMETHODIMP cDecal::Render3D()
{
for( cServiceList::iterator i = m_services.begin(); i != m_services.end(); ++ i )
{
if( !( i->m_dwCaps & eServiceRender ) )
continue;
CComPtr< IDecalRender > pRender;
HRESULT hRes = i->m_p->QueryInterface( &pRender );
_ASSERTE( SUCCEEDED( hRes ) );
pRender->Render3D();
}
return S_OK;
}
STDMETHODIMP cDecal::get_Hooks(IACHooks** pVal)
{
if( m_pHooks == NULL )
return E_FAIL;
m_pHooks->QueryInterface( pVal );
return S_OK;
}