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:
commit
d1442e3747
1382 changed files with 170725 additions and 0 deletions
636
Native/Decal/DecalManager.cpp
Normal file
636
Native/Decal/DecalManager.cpp
Normal file
|
|
@ -0,0 +1,636 @@
|
|||
// 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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue