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>
438 lines
11 KiB
C++
438 lines
11 KiB
C++
// Prefilter.cpp : Implementation of cPrefilter
|
|
#include "stdafx.h"
|
|
#include "DecalFilters.h"
|
|
#include "Prefilter.h"
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// cPrefilter
|
|
|
|
class cFieldRule
|
|
: public cPrefilter::cFilterRule
|
|
{
|
|
public:
|
|
_variant_t m_strField;
|
|
bool m_bReset;
|
|
|
|
static _bstr_t _strField,
|
|
_strReset;
|
|
|
|
cFieldRule()
|
|
: m_bReset( false )
|
|
{
|
|
}
|
|
|
|
bool loadField( MSXML::IXMLDOMElementPtr &pElement )
|
|
{
|
|
m_strField = pElement->getAttribute( _strField );
|
|
|
|
_variant_t vReset = pElement->getAttribute( _strReset );
|
|
if( vReset.vt != VT_NULL )
|
|
m_bReset = vReset;
|
|
|
|
#ifdef _DEBUG
|
|
if( m_bReset )
|
|
_ASSERTE( m_strField.vt == VT_BSTR );
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
void get( _variant_t &vValue, IMessageIterator *pMsg )
|
|
{
|
|
if( m_bReset )
|
|
pMsg->Reset();
|
|
|
|
if( m_strField.vt == VT_BSTR )
|
|
pMsg->get_Next( m_strField, &vValue );
|
|
else
|
|
pMsg->get_Current( &vValue );
|
|
}
|
|
};
|
|
|
|
class cEndRule
|
|
: public cPrefilter::cFilterRule
|
|
{
|
|
public:
|
|
virtual bool test( IMessageIterator * )
|
|
{
|
|
return true;
|
|
}
|
|
};
|
|
|
|
_bstr_t cFieldRule::_strField( _T( "field" ) );
|
|
_bstr_t cFieldRule::_strReset( _T( "reset" ) );
|
|
|
|
class cTestRule
|
|
: public cFieldRule
|
|
{
|
|
public:
|
|
virtual bool test( IMessageIterator *pMsg )
|
|
{
|
|
_variant_t vValue;
|
|
|
|
get( vValue, pMsg );
|
|
|
|
return ( vValue.vt != VT_NULL );
|
|
}
|
|
};
|
|
|
|
class cEqualsRule
|
|
: public cFieldRule
|
|
{
|
|
public:
|
|
_variant_t m_vValue;
|
|
|
|
virtual bool test( IMessageIterator *pMsg )
|
|
{
|
|
_variant_t vValue;
|
|
|
|
get( vValue, pMsg );
|
|
|
|
if( vValue.vt == VT_NULL || m_vValue.vt == VT_NULL )
|
|
// Null values are never equal, use cTestRule
|
|
return false;
|
|
|
|
if( vValue.vt != m_vValue.vt )
|
|
{
|
|
// Attempt to convert them to the same type - this will typically happen
|
|
// the first time the rule is invoked. This will speed up comparisons
|
|
// in the future
|
|
HRESULT hRes = ::VariantChangeType( &m_vValue, &m_vValue, 0, vValue.vt );
|
|
if( FAILED( hRes ) )
|
|
{
|
|
_ASSERT( FALSE );
|
|
|
|
// Could not convert the value - clear it so this rule becomes, disabled
|
|
::VariantClear( &m_vValue );
|
|
m_vValue.vt = VT_NULL;
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Compare the values
|
|
return( ::VarCmp( &vValue, &m_vValue, LOCALE_USER_DEFAULT, 0 ) == VARCMP_EQ );
|
|
}
|
|
};
|
|
|
|
class cContainsRule
|
|
: public cFieldRule
|
|
{
|
|
public:
|
|
_bstr_t m_strValue;
|
|
|
|
virtual bool test( IMessageIterator *pMsg )
|
|
{
|
|
_variant_t vValue;
|
|
|
|
get( vValue, pMsg );
|
|
|
|
if( vValue.vt != VT_BSTR )
|
|
// Null values are never equal, use cTestRule
|
|
return false;
|
|
|
|
// Use the std::algorithm search
|
|
LPOLESTR szValueBegin = vValue.bstrVal,
|
|
szValueEnd = vValue.bstrVal + ::SysStringLen( vValue.bstrVal ),
|
|
szSearchBegin = m_strValue,
|
|
szSearchEnd = szSearchBegin + m_strValue.length();
|
|
|
|
LPOLESTR szFound = std::search( szValueBegin, szValueEnd, szSearchBegin, szSearchEnd );
|
|
return( szFound != szValueEnd );
|
|
}
|
|
};
|
|
|
|
class cMaskRule
|
|
: public cFieldRule
|
|
{
|
|
public:
|
|
long m_nValue;
|
|
|
|
virtual bool test( IMessageIterator *pMsg )
|
|
{
|
|
long nValue;
|
|
|
|
// Custom get
|
|
HRESULT hRes = pMsg->get_NextInt( m_strField.bstrVal, &nValue );
|
|
if( FAILED( hRes ) )
|
|
return false;
|
|
|
|
return !!( nValue & m_nValue );
|
|
}
|
|
};
|
|
|
|
bool cPrefilter::testRules( cFilterRule *pRule, IMessageIterator *pMsg )
|
|
{
|
|
if( !pRule->test( pMsg ) )
|
|
return false;
|
|
|
|
// If there is a fire ID, fire it now
|
|
if( pRule->m_nFire != cFilterRule::eFireChildren )
|
|
{
|
|
CComPtr< IMessage > pMsgObj;
|
|
pMsg->get_Message( &pMsgObj );
|
|
Fire_Event( pRule->m_nFire, pMsgObj );
|
|
return true;
|
|
}
|
|
|
|
// If it has no children ... carry on
|
|
if( pRule->m_childRules.empty() )
|
|
return true;
|
|
|
|
// Test our children
|
|
for( cFilterRule::cFilterRuleList::iterator i = pRule->m_childRules.begin(); i != pRule->m_childRules.end(); ++ i )
|
|
{
|
|
if( testRules( i->get(), pMsg ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
cPrefilter::cFilterRule *cPrefilter::loadRule( MSXML::IXMLDOMElementPtr &pElement )
|
|
{
|
|
USES_CONVERSION;
|
|
|
|
// Load the current rule, first figure out the type
|
|
static _bstr_t _strMessage( _T( "message" ) ),
|
|
_strFire( _T( "fire" ) ),
|
|
_strTest( _T( "test" ) ),
|
|
_strEqual( _T( "equal" ) ),
|
|
_strContain( _T( "contains" ) ),
|
|
_strMask( _T( "mask" ) ),
|
|
_strField( _T( "field" ) ),
|
|
_strNext( _T( "next" ) ),
|
|
_strValue( _T( "value" ) ),
|
|
_strEnd( _T( "end" ) );
|
|
|
|
try
|
|
{
|
|
_bstr_t strElement = pElement->tagName;
|
|
|
|
VSBridge::auto_ptr< cFilterRule > pFilter;
|
|
|
|
if( strElement == _strMessage ||
|
|
strElement == _strFire )
|
|
{
|
|
pFilter = VSBridge::auto_ptr< cFilterRule >( new cFilterRule );
|
|
}
|
|
else if( strElement == _strTest )
|
|
{
|
|
cTestRule *pRule = new cTestRule;
|
|
pFilter = VSBridge::auto_ptr< cFilterRule >( pRule );
|
|
|
|
if( !pRule->loadField( pElement ) )
|
|
return NULL;
|
|
}
|
|
else if( strElement == _strEqual )
|
|
{
|
|
cEqualsRule *pRule = new cEqualsRule;
|
|
pFilter = VSBridge::auto_ptr< cFilterRule >( pRule );
|
|
|
|
if( !pRule->loadField( pElement ) )
|
|
return NULL;
|
|
|
|
pRule->m_vValue = pElement->getAttribute( _strValue );
|
|
if( pRule->m_vValue.vt == VT_NULL )
|
|
{
|
|
// Missing required field
|
|
_ASSERT( FALSE );
|
|
return false;
|
|
}
|
|
}
|
|
else if( strElement == _strContain )
|
|
{
|
|
cContainsRule *pRule = new cContainsRule;
|
|
pFilter = VSBridge::auto_ptr< cFilterRule >( pRule );
|
|
|
|
if( !pRule->loadField( pElement ) )
|
|
return NULL;
|
|
|
|
_variant_t vValue = pElement->getAttribute( _strValue );
|
|
if( vValue.vt != VT_BSTR )
|
|
{
|
|
// Missing required field or incorrect type
|
|
_ASSERT( FALSE );
|
|
return false;
|
|
}
|
|
|
|
pRule->m_strValue = vValue;
|
|
}
|
|
else if( strElement == _strMask )
|
|
{
|
|
cMaskRule *pRule = new cMaskRule;
|
|
pFilter = VSBridge::auto_ptr< cFilterRule >( pRule );
|
|
|
|
if( !pRule->loadField( pElement ) )
|
|
return NULL;
|
|
|
|
_variant_t vValue = pElement->getAttribute( _strValue );
|
|
if( vValue.vt == VT_NULL )
|
|
{
|
|
// Missing required field
|
|
_ASSERT( FALSE );
|
|
return false;
|
|
}
|
|
|
|
// Attempt to convert the value from hex
|
|
if( ::_stscanf( OLE2T( vValue.bstrVal ), _T( "%X" ), &pRule->m_nValue ) != 1 )
|
|
{
|
|
_ASSERT( FALSE );
|
|
return NULL;
|
|
}
|
|
}
|
|
else if( strElement == _strEnd )
|
|
pFilter = VSBridge::auto_ptr< cFilterRule >( new cEndRule );
|
|
else
|
|
return NULL;
|
|
|
|
_variant_t vFire = pElement->getAttribute( _strFire );
|
|
|
|
// Convert the fire type, then carry on
|
|
if( vFire.vt != VT_NULL )
|
|
pFilter->m_nFire = vFire;
|
|
else
|
|
{
|
|
// Select all children - all element children
|
|
MSXML::IXMLDOMNodeListPtr pRules = pElement->selectNodes( _T( "*" ) );
|
|
for( MSXML::IXMLDOMElementPtr pChildRule = pRules->nextNode(); pChildRule; pChildRule = pRules->nextNode() )
|
|
{
|
|
VSBridge::auto_ptr< cFilterRule > pChildFilter( loadRule( pChildRule ) );
|
|
if( pChildFilter.get() == NULL )
|
|
continue;
|
|
|
|
// Load the children
|
|
pFilter->m_childRules.push_back( pChildFilter );
|
|
}
|
|
}
|
|
|
|
return pFilter.release();
|
|
}
|
|
catch( _com_error &e )
|
|
{
|
|
HRESULT hRes = e.Error();
|
|
_ASSERTE( FALSE );
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
STDMETHODIMP cPrefilter::Initialize(INetService *pService)
|
|
{
|
|
USES_CONVERSION;
|
|
|
|
CComPtr< IDecal > pDecal;
|
|
pService->get_Decal( &pDecal );
|
|
|
|
CComBSTR strMappedFile;
|
|
HRESULT hRes = pDecal->MapPath( m_strFile, &strMappedFile );
|
|
|
|
if( FAILED( hRes ) )
|
|
// Bad token in the filename
|
|
return hRes;
|
|
|
|
MSXML::IXMLDOMDocumentPtr pFilter;
|
|
hRes = ::CoCreateInstance( __uuidof( MSXML::DOMDocument ), NULL, CLSCTX_INPROC_SERVER,
|
|
__uuidof( MSXML::IXMLDOMDocument ), reinterpret_cast< void ** >( &pFilter ) );
|
|
|
|
if( FAILED( hRes ) )
|
|
return hRes;
|
|
|
|
bool bLoad = !!pFilter->load( strMappedFile.m_str );
|
|
|
|
if( !bLoad )
|
|
{
|
|
// The document failed to load, get the error info for posterity
|
|
MSXML::IXMLDOMParseErrorPtr pErr = pFilter->parseError;
|
|
|
|
long nCode = pErr->errorCode;
|
|
long nFilePos = pErr->filepos;
|
|
long nLine = pErr->line;
|
|
long nLinePos = pErr->linepos;
|
|
_bstr_t strReason = pErr->reason;
|
|
_bstr_t strText = pErr->srcText;
|
|
|
|
TCHAR szError[ 1024 ];
|
|
::_stprintf( szError, _T( "0x%08X (%i, %i): %s" ),
|
|
nCode, nLine, nLinePos, OLE2T( strReason ) );
|
|
|
|
/*
|
|
long nHWND;
|
|
m_pSite->get_HWND( &nHWND );
|
|
*/
|
|
|
|
::MessageBox( NULL, szError, _T( "XML Parse Error" ), MB_ICONERROR | MB_OK );
|
|
|
|
// Give the user a chance to break and look at this lovely info
|
|
_ASSERTE( FALSE );
|
|
|
|
return E_FAIL;
|
|
}
|
|
|
|
// Looking good - now we'll load el'schema
|
|
MSXML::IXMLDOMNodeListPtr pMessages = pFilter->selectNodes( _T( "/filter/message" ) );
|
|
for( MSXML::IXMLDOMElementPtr pMessage = pMessages->nextNode(); pMessage; pMessage = pMessages->nextNode() )
|
|
{
|
|
_variant_t vMessage = pMessage->getAttribute( _T( "type" ) ),
|
|
vFire = pMessage->getAttribute( _T( "fire" ) );
|
|
|
|
if( vMessage.vt != VT_BSTR )
|
|
{
|
|
_ASSERT( FALSE );
|
|
continue;
|
|
}
|
|
|
|
// Attempt to convert the message type
|
|
long nMessage;
|
|
if( ::_stscanf( OLE2T( vMessage.bstrVal ), _T( "%X" ), &nMessage ) != 1 )
|
|
{
|
|
// Invalid message ID format
|
|
_ASSERT( FALSE );
|
|
continue;
|
|
}
|
|
|
|
VSBridge::auto_ptr<cFilterRule> pMessageRule( loadRule( pMessage ) );
|
|
if( pMessageRule.get() == NULL )
|
|
continue;
|
|
|
|
m_messageRules.insert( cMessageRuleList::value_type( nMessage, pMessageRule ) );
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP cPrefilter::DispatchServer( IMessage2 *pMsg )
|
|
{
|
|
long nID;
|
|
pMsg->get_Type( &nID );
|
|
|
|
// Look for a type
|
|
cMessageRuleList::iterator i_msg = m_messageRules.find( nID );
|
|
if( i_msg == m_messageRules.end() )
|
|
return S_OK;
|
|
|
|
CComPtr< IMessageIterator > pMembers;
|
|
pMsg->get_Begin( &pMembers );
|
|
testRules( i_msg->second.get(), pMembers );
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP cPrefilter::CreateInstance( IDecalEnum *pInitData, REFIID iid, LPVOID *pObject )
|
|
{
|
|
CComVariant vFile;
|
|
HRESULT hRes = pInitData->get_Property( _bstr_t( "File" ), &vFile );
|
|
|
|
if( FAILED( hRes ) )
|
|
return hRes;
|
|
|
|
if( vFile.vt != VT_BSTR )
|
|
{
|
|
// The surrogate must know the file
|
|
_ASSERT( FALSE );
|
|
return E_FAIL;
|
|
}
|
|
|
|
m_strFile = vFile.bstrVal;
|
|
return static_cast< IDecalSurrogate * >( this )->QueryInterface( iid, pObject );
|
|
}
|