openDecal/Native/DecalInput/InputService.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

408 lines
11 KiB
C++

// InputService.cpp : Implementation of cInputService
#include "stdafx.h"
#include "DecalInput.h"
#include "InputService.h"
#include "Timer.h"
#include "Hotkey.h"
#include "WinMsgHook.h"
#include "InputBuffer.h"
using cInputService::cCharNames;
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 },
{ _T( "SHIFT" ), VK_SHIFT },
{ _T( "CTRL" ), VK_CONTROL },
{ _T( "ALT" ), VK_MENU },
{ _T( "LBUTTON" ), VK_LBUTTON },
{ _T( "RBUTTON" ), VK_RBUTTON } },
*_end_charnames = _charnames + ( sizeof(_charnames ) / sizeof( cCharNames ) );
/////////////////////////////////////////////////////////////////////////////
// cInputService
LRESULT cInputService::wndProc( HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam )
{
if( g_p == NULL || g_p->m_hWndHook != hWnd )
{
// We have no idea how to continue - this hook has been orphaned
_ASSERT( FALSE );
return 0;
}
if( g_p->m_msghooks.size() > 0 )
{
// Fill out the message
g_p->m_pMessage->loadMessage( hWnd, nMsg, wParam, lParam );
for( cWinMsgHookList::iterator i = g_p->m_msghooks.begin(); i != g_p->m_msghooks.end(); ++ i )
{
( *i )->Fire_Message( *i, g_p->m_pMessage );
if( g_p->m_pMessage->m_bEaten )
// This message was eaten, yummy
return 0;
}
}
// NOTE: The hotkeys are not dispatched while an input buffer is running - tough luck
if( nMsg == WM_KEYUP && g_p->m_pActive == NULL )
{
int nVK = static_cast< int >( wParam );
// Check if there's a matching hotkey
for( cHotkeyList::iterator i = g_p->m_hotkeys.begin(); i != g_p->m_hotkeys.end(); ++ i )
{
if( ( *i )->m_nVK == nVK )
{
// Found a hotkey - fire the event and eat it
( *i )->Fire_Hotkey( *i );
break;
}
}
}
return g_p->m_pfnHook( hWnd, nMsg, wParam, lParam );
}
cInputService *cInputService::g_p = NULL;
HRESULT cInputService::onInitialize()
{
// ::DebugBreak();
USES_CONVERSION;
if( g_p == NULL )
g_p = this;
// Create the message object
CComObject< cWndMsg >::CreateInstance( &m_pMessage );
m_pMessage->AddRef();
// Load the list of action types from the configuration data
{
static _bstr_t _strPrefix( _T( "Prefix" ) );
CComPtr< IDecalEnum > pActions;
m_pDecal->get_Configuration( _bstr_t( _T( "InputActions" ) ), GUID_NULL, &pActions );
while( pActions->Next() == S_OK )
{
VARIANT_BOOL bEnabled;
HRESULT hRes = pActions->get_Enabled ( &bEnabled );
_ASSERTE ( SUCCEEDED ( hRes ) );
if ( !bEnabled )
continue;
cActionType at;
pActions->get_ComClass( &at.m_clsid );
CComVariant v;
if( FAILED( pActions->get_Property( _strPrefix, &v ) ) )
continue;
::_tcscpy( at.szAction, OLE2T( v.bstrVal ) );
::_tcslwr( at.szAction );
m_actiontypes.push_back( at );
}
}
// Get the OS version
OSVERSIONINFO ovi;
ovi.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );
::GetVersionEx( &ovi );
if( ovi.dwPlatformId == VER_PLATFORM_WIN32_NT )
{
m_eWindows = eWindowsNT;
return S_OK;
}
// First check if there's version info in the registry
RegKey key;
key.Open( HKEY_LOCAL_MACHINE, _T( "Software\\Decal" ) );
DWORD dwRegMajor, dwRegMinor;
if( key.QueryDWORDValue( _T( "ACMajorVersion" ), dwRegMajor ) != ERROR_SUCCESS ||
key.QueryDWORDValue( _T( "ACMinorVersion" ), dwRegMinor ) != ERROR_SUCCESS )
{
m_eWindows = eWindows98NoMemLocs;
return S_OK;
}
TCHAR szFilename[ MAX_PATH ];
DWORD dwVerSize;
::GetModuleFileName( NULL, szFilename, MAX_PATH );
if( ::GetFileVersionInfoSize( szFilename, &dwVerSize ) )
{
m_eWindows = eWindows98NoMemLocs;
return S_OK;
}
BYTE *pbVersionInfo = reinterpret_cast< BYTE * >( ::_alloca( dwVerSize ) );
::GetFileVersionInfo( szFilename, 0, dwVerSize, pbVersionInfo );
VS_FIXEDFILEINFO *vffi;
UINT nLength = sizeof( VS_FIXEDFILEINFO );
if( ::VerQueryValue( pbVersionInfo, _T( "\\" ), reinterpret_cast< LPVOID * >( &vffi ), &nLength ) )
{
if( dwRegMajor == vffi->dwFileVersionMS && dwRegMinor == vffi->dwFileVersionLS )
{
DWORD dwOffset1, dwOffset2;
if( key.QueryDWORDValue( _T( "MouseOffset1" ), dwOffset1 ) == ERROR_SUCCESS &&
key.QueryDWORDValue( _T( "MouseOffset2" ), dwOffset2 ) == ERROR_SUCCESS )
{
m_eWindows = eWindows98WithMemLocs;
m_pnOffset1 = reinterpret_cast< long * >( dwOffset1 );
m_pnOffset2 = reinterpret_cast< long * >( dwOffset2 );
return S_OK;
}
}
}
m_eWindows = eWindows98NoMemLocs;
return S_OK;
}
void cInputService::onTerminate()
{
// Create the message object
m_pMessage->Release();
m_pMessage = NULL;
if( g_p == this )
g_p = NULL;
}
cCharNames *cInputService::begin_chars()
{
return _charnames;
}
cCharNames *cInputService::end_chars()
{
return _end_charnames;
}
cCharNames *cInputService::charFromVK( int nVK )
{
for( cCharNames *i_chars = _charnames; i_chars != _end_charnames; ++ i_chars )
{
if( i_chars->m_nVKey == nVK )
break;
}
return i_chars;
}
cCharNames *cInputService::charFromName( LPCTSTR szName, int nLength )
{
for( cCharNames *i_chars = _charnames; i_chars != _end_charnames; ++ i_chars )
{
if( ::strlen( i_chars->szName ) == nLength && std::equal( szName, szName + nLength, i_chars->szName ) )
break;
}
return i_chars;
}
STDMETHODIMP cInputService::BeforePlugins()
{
// Hook the main plugin window
long nWnd;
m_pDecal->get_HWND( &nWnd );
if( nWnd != 0 )
{
m_hWndHook = reinterpret_cast< HWND >( nWnd );
m_pfnHook = reinterpret_cast< WNDPROC >( ::SetWindowLong( m_hWndHook, GWL_WNDPROC, reinterpret_cast< LONG >( wndProc ) ) );
}
// Load the keymap
RegKey key;
if(key.Open( HKEY_LOCAL_MACHINE, _T( "SOFTWARE\\Microsoft\\Microsoft Games\\Asheron's Call\\1.00" ), KEY_READ ) != ERROR_SUCCESS)
return E_FAIL;
TCHAR szFilename[ MAX_PATH ];
TCHAR *pszInsert = szFilename;
DWORD dwSize = MAX_PATH;
key.QueryStringValue( _T("Path"), szFilename, &dwSize );
pszInsert = &szFilename[dwSize-1];
//pszInsert += dwSize;
::_tcscat( pszInsert, _T( "\\" ) );
++ pszInsert;
dwSize = MAX_PATH - ( dwSize + 1 );
key.QueryStringValue( _T( "CurrentInputMap" ), pszInsert, &dwSize );
// Looking good, open up the file and load the keys
std::ifstream is( szFilename );
if( is.bad() )
// Could not load the file
return E_FAIL;
int nCount = 0;
is >> nCount;
for( ; nCount > 0; -- nCount )
{
int nInputType, nVK, nChording, nCommandType, nAnalogType;
TCHAR szCommandName[ 64 ];
is >> nInputType >> nVK >> nChording >> szCommandName >> nCommandType >> nAnalogType;
m_commands.insert( cCommandMap::value_type( szCommandName, static_cast< WORD >( nVK ) ) );
}
return S_OK;
}
STDMETHODIMP cInputService::AfterPlugins()
{
m_commands.clear();
if( m_hWndHook != NULL )
{
_ASSERTE( reinterpret_cast< LONG >( wndProc ) == ::GetWindowLong( m_hWndHook, GWL_WNDPROC ) );
::SetWindowLong( m_hWndHook, GWL_WNDPROC, reinterpret_cast< LONG >( m_pfnHook ) );
}
return S_OK;
}
STDMETHODIMP cInputService::get_Command(BSTR strCommandName, BSTR *pVal)
{
USES_CONVERSION;
// First try to find the command in the list
_bstr_t strCommand( strCommandName );
cCommandMap::iterator i = m_commands.find( strCommand );
if( i == m_commands.end() )
{
*pVal = T2BSTR( _T( "" ) );
return S_FALSE;
}
WORD nCommand = i->second;
// Scan to see if this command has a special name
cCharNames *i_chars = charFromVK( nCommand );
if( i_chars != end_chars() )
{
TCHAR szFormat[ 12 ];
::_stprintf( szFormat, _T( "{%s}" ), i_chars->szName );
*pVal = T2BSTR( szFormat );
return S_OK;
}
// Ok, we'll assume the char is printable
TCHAR szString[ 2 ] = { nCommand, _T( '\0' ) };
*pVal = T2BSTR( szString );
return S_OK;
}
STDMETHODIMP cInputService::Render2D()
{
long nTime = static_cast< long >( ::timeGetTime() );
cTimerList::iterator i = m_timers.begin();
while( i != m_timers.end() )
{
cTimerList::iterator j = i++; // Make a copy of the iterator, increment it, then call the timeout.
if( ( ( *j )->m_nStart + ( *j )->m_nInterval ) <= nTime )
{
( *j )->m_nStart = nTime;
( *j )->Fire_Timeout( *j );
}
}
// Check to see if there's a delay paused
if( m_pActive != NULL && m_pActive->m_bWaiting && m_pActive->m_nUntilTime <= nTime )
// We've passed the timeout, continue processing actions
m_pActive->runActions();
return S_OK;
}
STDMETHODIMP cInputService::ChangeHWND()
{
return S_OK;
}
STDMETHODIMP cInputService::get_Decal(IDecal **pVal)
{
return m_pDecal->QueryInterface( pVal );
}
STDMETHODIMP cInputService::get_KeyByName(BSTR strName, long *pVal)
{
USES_CONVERSION;
LPTSTR szName = OLE2T( strName );
cCharNames *i_c = charFromName( szName, ::_tcslen( szName ) );
if( i_c == end_chars() )
return E_INVALIDARG;
*pVal = i_c->m_nVKey;
return S_OK;
}
STDMETHODIMP cInputService::get_CommandKey(BSTR strCommandName, long *pVal)
{
_bstr_t strCommand( strCommandName );
cCommandMap::iterator i = m_commands.find( strCommand );
if( i == m_commands.end() )
return E_FAIL;
*pVal = i->second;
return S_OK;
}