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>
636 lines
No EOL
15 KiB
C++
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;
|
|
} |