openDecal/Native/DecalFilters/Prefilter.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

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 );
}