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:
erik 2026-02-08 18:27:56 +01:00
commit d1442e3747
1382 changed files with 170725 additions and 0 deletions

View file

@ -0,0 +1,530 @@
// InputBuffer.cpp : Implementation of cInputBuffer
#include "stdafx.h"
#include "Inject.h"
#include "InputBuffer.h"
#include "Manager.h"
/////////////////////////////////////////////////////////////////////////////
// cInputBuffer
struct cCharNames
{
LPCTSTR szName;
WORD m_nVKey;
};
static cCharNames _charnames[] = {
{ _T( "BACKSPACE" ), VK_BACK },
{ _T( "BS" ), VK_BACK },
{ _T( "BKSP" ), VK_BACK },
{ _T( "CAPSLOCK" ), VK_CAPITAL },
{ _T( "DELETE" ), VK_DELETE },
{ _T( "DEL" ), VK_DELETE },
{ _T( "DOWN" ), VK_DOWN },
{ _T( "END" ), VK_END },
{ _T( "ENTER" ), VK_RETURN },
{ _T( "ESC" ), VK_ESCAPE },
{ _T( "HELP" ), VK_HELP },
{ _T( "HOME" ), VK_HOME },
{ _T( "INS" ), VK_INSERT },
{ _T( "INSERT" ), VK_INSERT },
{ _T( "LEFT" ), VK_LEFT },
{ _T( "NUMLOCK" ), VK_NUMLOCK },
{ _T( "PGDN" ), VK_NEXT },
{ _T( "PGUP" ), VK_PRIOR },
{ _T( "PRTSC" ), VK_SNAPSHOT },
{ _T( "RIGHT" ), VK_RIGHT },
{ _T( "SCROLLLOCK" ), VK_SCROLL },
{ _T( "TAB" ), VK_TAB },
{ _T( "UP" ), VK_UP },
{ _T( "F1" ), VK_F1 },
{ _T( "F2" ), VK_F2 },
{ _T( "F3" ), VK_F3 },
{ _T( "F4" ), VK_F4 },
{ _T( "F5" ), VK_F5 },
{ _T( "F6" ), VK_F6 },
{ _T( "F7" ), VK_F7 },
{ _T( "F8" ), VK_F8 },
{ _T( "F9" ), VK_F9 },
{ _T( "F10" ), VK_F10 },
{ _T( "F11" ), VK_F11 },
{ _T( "F12" ), VK_F12 },
{ _T( "F13" ), VK_F13 },
{ _T( "F14" ), VK_F14 },
{ _T( "F15" ), VK_F15 },
{ _T( "F16" ), VK_F16 },
{ _T( "+" ), VK_ADD } },
*_end_charnames = _charnames + ( sizeof(_charnames ) / sizeof( cCharNames ) );
#define MASK_SHIFT 0x0100
#define MASK_CTRL 0x0200
#define MASK_ALT 0x0400
#define SHIFT_MASK 0x0700
#define VKEY_MASK 0x00FF
void pushVirtualKey( WORD vk, cInputVec &vec, bool bDown )
{
INPUT i;
::memset( &i, 0, sizeof( INPUT ) );
i.type = INPUT_KEYBOARD;
i.ki.wVk = vk;
i.ki.wScan = MapVirtualKey( vk, 0 );
i.ki.dwFlags = ( bDown ) ? 0 : KEYEVENTF_KEYUP;
vec.push_back( i );
}
void processKeyboardChars( BSTR strChars, cInputVec &vec )
{
USES_CONVERSION;
typedef std::stack< WORD > cMaskStack;
cMaskStack mask;
WORD wCurrentMask = 0;
std::string strParse = OLE2T( strChars );
for( std::string::iterator i = strParse.begin(); i != strParse.end(); )
{
switch( *i )
{
case _T( '+' ):
_ASSERTE( ( i + 2 ) < strParse.end() );
_ASSERTE( *( i + 1 ) == _T( '(' ) );
_ASSERTE( !( wCurrentMask & MASK_SHIFT ) );
wCurrentMask |= MASK_SHIFT;
mask.push( MASK_SHIFT );
// Send the keydown
pushVirtualKey( VK_SHIFT, vec, true );
i += 2;
break;
case _T( '^' ):
_ASSERTE( ( i + 2 ) < strParse.end() );
_ASSERTE( *( i + 1 ) == _T( '(' ) );
_ASSERTE( !( wCurrentMask & MASK_CTRL ) );
wCurrentMask |= MASK_CTRL;
mask.push( MASK_CTRL );
// Send the keydown
pushVirtualKey( VK_CONTROL, vec, true );
i += 2;
break;
case _T( '%' ):
_ASSERTE( ( i + 2 ) < strParse.end() );
_ASSERTE( *( i + 1 ) == _T( '(' ) );
_ASSERTE( !( wCurrentMask & MASK_ALT ) );
wCurrentMask |= MASK_ALT;
mask.push( MASK_ALT );
// Send the keydown
pushVirtualKey( VK_MENU, vec, true );
i += 2;
break;
case _T( ')' ):
_ASSERTE( mask.size() > 0 );
{
DWORD wMask = mask.top();
mask.pop();
switch( wMask )
{
case MASK_SHIFT:
pushVirtualKey( VK_SHIFT, vec, false );
break;
case MASK_CTRL:
pushVirtualKey( VK_CONTROL, vec, false );
break;
case MASK_ALT:
pushVirtualKey( VK_MENU, vec, false );
break;
default:
// Bad value in the stack
_ASSERTE( FALSE );
break;
}
wCurrentMask &= ~wMask;
}
++ i;
break;
case _T( '{' ):
// This is a special key
++ i;
{
for( std::string::iterator j = i; *j != _T( '}' ); ++ j )
{
_ASSERTE( j != strParse.end() );
}
std::string strSpecial( i, j );
for( cCharNames *i_name = _charnames; i_name != _end_charnames; ++ i_name )
{
if( strSpecial.compare( i_name->szName ) == 0 )
{
// We found the match, push and release the vkey
pushVirtualKey( i_name->m_nVKey, vec, true );
pushVirtualKey( i_name->m_nVKey, vec, false );
break;
}
}
// The special key was not found in the list
_ASSERTE( i_name != _end_charnames );
i = j + 1;
}
break;
default:
// Assume everything else is a text character but may require additional
// shifting
{
WORD wVkScan = VkKeyScan( *i ),
wAdditionalShift = ( wVkScan & SHIFT_MASK ) & ~wCurrentMask,
wVKey = wVkScan & VKEY_MASK;
if( wAdditionalShift & MASK_SHIFT )
pushVirtualKey( VK_SHIFT, vec, true );
if( wAdditionalShift & MASK_CTRL )
pushVirtualKey( VK_CONTROL, vec, true );
if( wAdditionalShift & MASK_ALT )
pushVirtualKey( VK_MENU, vec, true );
pushVirtualKey( wVKey, vec, true );
pushVirtualKey( wVKey, vec, false );
if( wAdditionalShift & MASK_ALT )
pushVirtualKey( VK_MENU, vec, false );
if( wAdditionalShift & MASK_CTRL )
pushVirtualKey( VK_CONTROL, vec, false );
if( wAdditionalShift & MASK_SHIFT )
pushVirtualKey( VK_SHIFT, vec, false );
}
++ i;
break;
}
}
// Make sure we released all shifts
_ASSERTE( wCurrentMask == 0 );
}
void pushMouseEvent( DWORD dwFlags, cInputVec &vec )
{
INPUT i;
::memset( &i, 0, sizeof( INPUT ) );
i.type = INPUT_MOUSE;
i.mi.dwFlags = dwFlags;
vec.push_back( i );
}
void processMouseInput( eMouseInput eType, cInputVec &vec )
{
DWORD dwError = ::GetLastError();
switch( eType )
{
case eMouseLeftDoubleClick:
pushMouseEvent( MOUSEEVENTF_LEFTDOWN, vec );
pushMouseEvent( MOUSEEVENTF_LEFTUP, vec );
case eMouseLeftClick:
pushMouseEvent( MOUSEEVENTF_LEFTDOWN, vec );
pushMouseEvent( MOUSEEVENTF_LEFTUP, vec );
break;
case eMouseRightDoubleClick:
pushMouseEvent( MOUSEEVENTF_RIGHTDOWN, vec );
pushMouseEvent( MOUSEEVENTF_RIGHTUP, vec );
case eMouseRightClick:
pushMouseEvent( MOUSEEVENTF_RIGHTDOWN, vec );
pushMouseEvent( MOUSEEVENTF_RIGHTUP, vec );
break;
default:
_ASSERTE( FALSE );
break;
}
}
HANDLE cInputBuffer::m_hThread = NULL;
HANDLE cInputBuffer::m_hInputWaiting = NULL;
HANDLE cInputBuffer::m_hTerm = NULL;
CRITICAL_SECTION cInputBuffer::m_csQueue;
cInputBuffer::cInputQueue cInputBuffer::m_queue;
DWORD WINAPI inputThreadProc( LPVOID pvParam );
void cInputBuffer::init()
{
::InitializeCriticalSection( &m_csQueue );
m_hTerm = ::CreateEvent( NULL, TRUE, FALSE, NULL );
m_hInputWaiting = ::CreateEvent( NULL, TRUE, FALSE, NULL );
DWORD dwThreadID;
m_hThread = ::CreateThread( NULL, 0, inputThreadProc, NULL, 0, &dwThreadID );
}
void cInputBuffer::term()
{
// Clear out the queue
::EnterCriticalSection( &m_csQueue );
while( m_queue.size() > 0 )
m_queue.pop_front();
::SetEvent( m_hTerm );
::LeaveCriticalSection( &m_csQueue );
::WaitForSingleObject( m_hThread, INFINITE );
::CloseHandle( m_hThread );
::CloseHandle( m_hInputWaiting );
::CloseHandle( m_hTerm );
::DeleteCriticalSection( &m_csQueue );
}
void cInputBuffer::postBuffer()
{
::EnterCriticalSection( &m_csQueue );
if( m_queue.empty() )
::SetEvent( m_hInputWaiting );
// Marshal our interface into a drop in in the queue
IStream *pStream;
::CoMarshalInterThreadInterfaceInStream( IID_IInputNotify, static_cast< IInputNotify * >( this ), &pStream );
m_queue.push_back( cInputQueue::value_type( this, pStream ) );
m_status = eInputWaiting;
::LeaveCriticalSection( &m_csQueue );
}
cInputVec *cInputBuffer::getLastInput()
{
if( m_actions.size() == 0 || m_actions.back().first != eActionInput )
// Add in a new buffer
m_actions.push_back( cActionList::value_type( eActionInput, VSBridge::auto_ptr< cAction >( new cInput ) ) );
// Return the last buffer
return &( static_cast< cInput * >( m_actions.back().second.get() )->m_input );
}
DWORD WINAPI inputThreadProc( LPVOID pvParam )
{
// This thread must join the mult-threaded apartment becasue it does
// not create a message loop
::CoInitializeEx( NULL, COINIT_MULTITHREADED );
BYTE prevKeyboard[ 256 ],
blankKeyboard[ 256 ];
::memset( blankKeyboard, 0, sizeof( blankKeyboard ) );
HANDLE hEvent[2] = { cInputBuffer::m_hInputWaiting, cInputBuffer::m_hTerm };
while( ::WaitForMultipleObjects( 2, hEvent, FALSE, INFINITE ) == WAIT_OBJECT_0 )
{
// Extract the next item from the stream
::EnterCriticalSection( &cInputBuffer::m_csQueue );
cInputBuffer *pBuffer = cInputBuffer::m_queue.front().first;
CComPtr< IInputNotify > pNotify;
::CoGetInterfaceAndReleaseStream( cInputBuffer::m_queue.front().second, IID_IInputNotify, reinterpret_cast< void ** >( &pNotify ) );
cInputBuffer::m_queue.pop_front();
if( cInputBuffer::m_queue.empty() )
::ResetEvent( cInputBuffer::m_hInputWaiting );
::LeaveCriticalSection( &cInputBuffer::m_csQueue );
// Save the keyboard state and take control of the input queue
::BlockInput( TRUE );
::GetKeyboardState( prevKeyboard );
::SetKeyboardState( blankKeyboard );
// Looking good, process the messages
pNotify->NotifyBegin();
// The delay list contains the breaks in the input, so we use the delay list as a master
// and use segments of the input queue
for( cInputBuffer::cActionList::iterator i = pBuffer->m_actions.begin(); i != pBuffer->m_actions.end(); ++ i )
i->second->execute( pNotify );
// Restore keyboard state
::SetKeyboardState( prevKeyboard );
pNotify->NotifyEnd();
::BlockInput( FALSE );
}
::CoUninitialize();
return 0;
}
// The action execution functions
void cInputBuffer::cInput::execute( IInputNotify * )
{
UINT nSent = SendInput( m_input.size(), &(*(m_input.begin())), sizeof( INPUT ) );
DWORD dwError = GetLastError();
_ASSERTE( nSent == m_input.size() );
}
void cInputBuffer::cDelay::execute( IInputNotify *pNotify )
{
if( m_bAllowInput )
{
BlockInput( FALSE );
pNotify->NotifyPause();
}
::Sleep( m_nMillis );
if( m_bAllowInput )
{
BlockInput( TRUE );
pNotify->NotifyBegin();
}
}
void cInputBuffer::cMouseMove::execute( IInputNotify *pib )
{
pib->SetMousePos( m_ptClient.x, m_ptClient.y );
}
STDMETHODIMP cInputBuffer::Clear()
{
if( m_status != eInputIdle )
{
_ASSERTE( FALSE );
return E_FAIL;
}
m_actions.clear();
return S_OK;
}
STDMETHODIMP cInputBuffer::TypeText(BSTR strText)
{
if( m_status != eInputIdle )
{
_ASSERTE( FALSE );
return E_FAIL;
}
processKeyboardChars( strText, *getLastInput() );
return S_OK;
}
STDMETHODIMP cInputBuffer::Delay(long nMilliseconds, VARIANT_BOOL bAllowInput )
{
if( m_status != eInputIdle )
{
_ASSERTE( FALSE );
return E_FAIL;
}
cDelay *pDelay = new cDelay;
pDelay->m_bAllowInput = !!bAllowInput;
pDelay->m_nMillis = nMilliseconds;
m_actions.push_back( cActionList::value_type( eActionDelay, VSBridge::auto_ptr< cAction >( pDelay ) ) );
return S_OK;
}
STDMETHODIMP cInputBuffer::MouseClick(long nX, long nY, eMouseInput eAction)
{
if( m_status != eInputIdle )
{
_ASSERTE( FALSE );
return E_FAIL;
}
// First insert a mouse movement
cMouseMove *pMM = new cMouseMove;
pMM->m_ptClient.x = nX;
pMM->m_ptClient.y = nY;
m_actions.push_back( cActionList::value_type( eActionMouseMove, VSBridge::auto_ptr< cAction >( pMM ) ) );
processMouseInput( eAction, *getLastInput() );
return S_OK;
}
STDMETHODIMP cInputBuffer::get_Status(eInputStatus *pVal)
{
_ASSERTE( pVal != NULL );
*pVal = m_status;
return S_OK;
}
STDMETHODIMP cInputBuffer::Run()
{
postBuffer();
return S_OK;
}
STDMETHODIMP cInputBuffer::NotifyBegin()
{
m_status = eInputRunning;
Fire_Begin();
return S_OK;
}
STDMETHODIMP cInputBuffer::NotifyEnd()
{
m_status = eInputIdle;
Fire_End();
return S_OK;
}
STDMETHODIMP cInputBuffer::SetMousePos(long nX, long nY)
{
// Convert to screen coords
POINT pt = { nX, nY };
::ClientToScreen( cManager::_p->m_hMain, &pt );
::SetCursorPos( pt.x, pt.y );
// Force the mouse message on the into the window proc
// This updates AC to be the new message location
cManager::_p->m_pfnOld( cManager::_p->m_hMain, WM_MOUSEMOVE, 0, MAKELONG( nX, nY ) );
return S_OK;
}
STDMETHODIMP cInputBuffer::NotifyPause()
{
Fire_Pause();
return S_OK;
}