openDecal/Native/DenAgent/TrayWnd.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

507 lines
13 KiB
C++

// TrayWnd.cpp : implementation file
//
#include "stdafx.h"
#include "DenAgent.h"
#include "TrayWnd.h"
#include "..\Inject\InjectApi.h"
#include "forcelib.h"
#include "DenAgentDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#define ID_SYSTRAY 1
#define NM_SYSTRAY ( WM_USER + 1000 )
const UINT s_uTaskbarRestart = RegisterWindowMessage(TEXT("TaskbarCreated"));
bool g_bOldInject;
// Inject Enable/Inject Disable function pointers
typedef void (*VoidNoParams)();
VoidNoParams InjEnable = NULL;
VoidNoParams InjDisable = NULL;
// HMODULE for Inject dll
HMODULE g_hInj = NULL;
// This is for our windows enumeration process
BOOL CALLBACK EnumerationCallbackProc( HWND, LPARAM );
CTrayWnd* CTrayWnd::s_pWnd = NULL;
/////////////////////////////////////////////////////////////////////////////
// CTrayWnd
CTrayWnd::CTrayWnd()
: m_pDialog( NULL ), m_uiTimer( NULL )
{
s_pWnd = this;
}
CTrayWnd::~CTrayWnd()
{
s_pWnd = NULL;
}
BEGIN_MESSAGE_MAP(CTrayWnd, CWnd)
//{{AFX_MSG_MAP(CTrayWnd)
ON_WM_CREATE()
ON_WM_DESTROY()
ON_WM_TIMER()
ON_COMMAND(ID_SYSTRAY_CONFIGURE, OnSystrayConfigure)
ON_COMMAND(ID_SYSTRAY_EXIT, OnSystrayExit)
ON_REGISTERED_MESSAGE(s_uTaskbarRestart, OnTaskbarRestart)
//}}AFX_MSG_MAP
ON_MESSAGE(NM_SYSTRAY, OnSysTray)
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CTrayWnd message handlers
LRESULT CTrayWnd::OnTaskbarRestart(WPARAM, LPARAM)
{
NOTIFYICONDATA nid;
::memset( &nid, 0, sizeof( NOTIFYICONDATA ) );
nid.cbSize = sizeof( NOTIFYICONDATA );
nid.hWnd = m_hWnd;
nid.uID = ID_SYSTRAY;
nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
nid.uCallbackMessage = NM_SYSTRAY;
nid.hIcon = AfxGetApp()->LoadIcon( IDR_TRAYICON );
::_tcscpy( nid.szTip, _T( "Decal Agent" ) );
::Shell_NotifyIcon( NIM_DELETE, &nid );
::Shell_NotifyIcon( NIM_ADD, &nid );
DestroyIcon(nid.hIcon);
return TRUE;
}
void CTrayWnd::showDialog()
{
if( m_pDialog == NULL )
{
CDenAgentDlg dlg;
m_pDialog = &dlg;
dlg.DoModal();
m_pDialog = NULL;
}
else
{
m_pDialog->SetForegroundWindow();
m_pDialog->BringWindowToTop();
}
}
int CTrayWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CWnd::OnCreate(lpCreateStruct) == -1)
return -1;
::CoInitialize( NULL );
// Check if asheron's call is already running
HWND wndAC = ::FindWindowEx( NULL, NULL, _T( "Asheron's Call" ), _T( "Asheron's Call" ) );
bEnabled = ( wndAC == NULL );
if( wndAC != NULL )
{
::AfxMessageBox( _T( "Asheron's Call was started before the Agent.\r\nNo plugins will be installed until Asheron's Call and the agent are exited and restarted.\r\n\r\nThe Agent must be run before Asheron's Call to properly install plugins." ), MB_ICONERROR | MB_OK );
::PostQuitMessage( 0 );
}
else
{
RegKey key;
DWORD dwOldInj;
if( key.Create( HKEY_LOCAL_MACHINE, _T( "SOFTWARE\\Decal" )) != ERROR_SUCCESS )
{
g_bOldInject = false;
m_uiTimer = SetTimer (1, 1000, NULL);
}
else
{
if( key.QueryDWORDValue( "OldInjection", dwOldInj ) == ERROR_SUCCESS )
{
if( dwOldInj == 1 )
{
g_bOldInject = true;
TCHAR szInjectDll[ MAX_PATH ];
memset( szInjectDll, 0, sizeof( szInjectDll ) / sizeof( szInjectDll[0] ) );
// Open the COM CoClass for IPager to get Inject.dll path
if( key.Open( HKEY_CLASSES_ROOT, _T( "CLSID\\{C79E2F76-06F8-4CD0-A613-4829237D297D}\\InprocServer32" ), KEY_READ ) == ERROR_SUCCESS )
{
DWORD dwChars = MAX_PATH - 1;
if( key.QueryStringValue( NULL, szInjectDll, &dwChars ) != ERROR_SUCCESS )
{
::AfxMessageBox( _T( "There is a serious problem with the Decal Agent registry settings!!\n\nExiting." ), MB_ICONERROR | MB_OK );
::PostQuitMessage( 0 );
}
}
g_hInj = LoadLibrary( szInjectDll );
InjEnable = (VoidNoParams) GetProcAddress( g_hInj, (LPCSTR) 0x00000012 );
InjDisable = (VoidNoParams) GetProcAddress( g_hInj, (LPCSTR) 0x00000011 );
if( !InjEnable || !InjDisable )
{
::AfxMessageBox( _T( "Can't load Inject.dll. Please turn off old style injection." ), MB_ICONERROR | MB_OK );
g_bOldInject = false;
}
else
::InjEnable();
}
else
{
g_bOldInject = false;
m_uiTimer = SetTimer( 1, 1000, NULL );
}
}
else
{
g_bOldInject = false;
m_uiTimer = SetTimer( 1, 1000, NULL );
}
}
}
// Create the system tray icon
NOTIFYICONDATA nid;
::memset( &nid, 0, sizeof( NOTIFYICONDATA ) );
nid.cbSize = sizeof( NOTIFYICONDATA );
nid.hWnd = m_hWnd;
nid.uID = ID_SYSTRAY;
nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
nid.uCallbackMessage = NM_SYSTRAY;
nid.hIcon = AfxGetApp()->LoadIcon( IDR_TRAYICON );
::_tcscpy( nid.szTip, _T( "Decal Agent" ) );
::Shell_NotifyIcon( NIM_ADD, &nid );
DestroyIcon(nid.hIcon);
// Get the image path (path of parent executable)
TCHAR szImagePath[ MAX_PATH ];
::GetModuleFileName( NULL, szImagePath, MAX_PATH );
LPTSTR strProcessName = ::_tcsrchr( szImagePath, _T( '\\' ) );
*( strProcessName + 1 ) = _T( '\0' );
RegKey key;
key.Create( HKEY_LOCAL_MACHINE, _T( "SOFTWARE\\Decal\\Agent" ) );
key.SetStringValue( _T( "AgentPath" ), szImagePath );
return 0;
}
void CTrayWnd::OnDestroy()
{
CWnd::OnDestroy();
NOTIFYICONDATA nid;
::memset( &nid, 0, sizeof( NOTIFYICONDATA ) );
nid.cbSize = sizeof( NOTIFYICONDATA );
nid.hWnd = m_hWnd;
nid.uID = ID_SYSTRAY;
::Shell_NotifyIcon( NIM_DELETE, &nid );
if( g_bOldInject )
{
if( bEnabled )
::InjDisable();
InjEnable = NULL;
InjDisable = NULL;
FreeLibrary( g_hInj );
}
else
if( m_uiTimer )
{
KillTimer (m_uiTimer);
m_uiTimer = 0;
}
::CoUninitialize();
}
void CTrayWnd::OnTimer (UINT_PTR nIDEvent)
{
::EnumWindows( EnumerationCallbackProc, (LPARAM) NULL );
}
BOOL CALLBACK EnumerationCallbackProc( HWND hwnd, LPARAM lParam )
{
TCHAR szClassName[64];
memset( szClassName, 0, sizeof( szClassName ) / sizeof( szClassName[0] ) );
GetClassName( hwnd, szClassName, 64 );
if( _tcsicmp( _T( "ZoneLobbyWindow" ), szClassName ) != 0 )
{
return TRUE;
}
if( CTrayWnd::s_pWnd != NULL )
return CTrayWnd::s_pWnd->OnEnum( hwnd );
else
return FALSE;
}
BOOL CTrayWnd::OnEnum( HWND hWndLobby )
{
if( hWndLobby != NULL )
{
DWORD dwProcessId = 0;
GetWindowThreadProcessId( hWndLobby, &dwProcessId );
if( dwProcessId != 0 )
{
TCHAR tszBuffer [256];
::_stprintf (tszBuffer, _T("__LOBBYHOOK_%d"), dwProcessId);
HANDLE hLobbySemaphore = ::CreateSemaphore (NULL, 0, 1, tszBuffer);
DWORD dwLastError = ::GetLastError ();
if (hLobbySemaphore)
{
::CloseHandle (hLobbySemaphore);
if (dwLastError == ERROR_ALREADY_EXISTS)
{
// The lobbyhook has already been injected, we know because it created the semaphore.
return TRUE;
}
}
RegKey key;
TCHAR szDll[ MAX_PATH ];
TCHAR szDllPath[ MAX_PATH ];
memset( szDllPath, 0, sizeof( szDllPath ) / sizeof( szDllPath[0] ) );
if( key.Open( HKEY_LOCAL_MACHINE, _T( "Software\\Decal\\Agent" ), KEY_READ ) == ERROR_SUCCESS )
{
DWORD dwChars = MAX_PATH - 1;
if( key.QueryStringValue( _T( "AgentPath" ), szDllPath, &dwChars ) == ERROR_SUCCESS )
{
lstrcpy( szDll, szDllPath );
lstrcat( szDll, _T( "\\ForceLibrary.dll" ) );
}
else
{
DWORD dwError = GetLastError();
char szBuffer[256];
_snprintf( szBuffer, sizeof( szBuffer ), "Couldn't query AgentPath value: 0x%08lx", dwError );
::MessageBox( NULL, szBuffer, _T( "DenAgent" ), MB_OK );
}
}
else
{
DWORD dwError = GetLastError();
char szBuffer[256];
_snprintf( szBuffer, sizeof( szBuffer ), "Couldn't open HKLM\\Software\\Decal\\Agent key: 0x%08lx", dwError );
::MessageBox( NULL, szBuffer, _T( "DenAgent" ), MB_OK );
}
if( szDllPath[0] )
{
HMODULE hLib = (HMODULE) ForceLibraryNow( dwProcessId, szDll );
if( hLib == NULL )
{
DWORD dwError = GetLastError();
char szBuffer[256];
_snprintf( szBuffer, sizeof( szBuffer ), "ForceLibraryNow (LobbyHook.dll) has failed( 0x%08lx )\nDo you want to switch to old style injection?\nIf Decal is loading in AC properly answer no.", dwError );
// Kill timer to avoid 1000 popups
KillTimer( m_uiTimer );
m_uiTimer = 0;
int iRet = ::MessageBox( NULL, szBuffer, _T( "DenAgent" ), MB_YESNO );
if( iRet == IDYES )
{
RegKey key;
key.Create( HKEY_LOCAL_MACHINE, _T( "SOFTWARE\\Decal" ) );
key.SetDWORDValue("OldInjection", 0x1L);
g_bOldInject = true;
TCHAR szInjectDll[ MAX_PATH ];
memset( szInjectDll, 0, sizeof( szInjectDll ) / sizeof( szInjectDll[0] ) );
// Open the COM CoClass for IPager to get Inject.dll path
if( key.Open( HKEY_CLASSES_ROOT, _T( "CLSID\\{C79E2F76-06F8-4CD0-A613-4829237D297D}\\InprocServer32" ), KEY_READ ) == ERROR_SUCCESS )
{
DWORD dwChars = MAX_PATH - 1;
if( key.QueryStringValue( NULL, szInjectDll, &dwChars ) != ERROR_SUCCESS )
{
::AfxMessageBox( _T( "There is a serious problem with the Decal Agent registry settings!!\n\nExiting." ), MB_ICONERROR | MB_OK );
::PostQuitMessage( 0 );
}
}
g_hInj = LoadLibrary( szInjectDll );
InjEnable = (VoidNoParams) GetProcAddress( g_hInj, (LPCSTR) 0x00000012 );
InjDisable = (VoidNoParams) GetProcAddress( g_hInj, (LPCSTR) 0x00000011 );
::InjEnable();
}
else // no
{
_snprintf( szBuffer, sizeof( szBuffer ), "Lobby Injection halted... restart DenAgent to try again." );
::MessageBox( NULL, szBuffer, _T( "DenAgent" ), MB_OK );
}
}
lstrcpy( szDll, szDllPath );
lstrcat( szDll, _T( "\\LobbyHook.dll" ) );
hLib = (HMODULE) ForceLibraryNow( dwProcessId, szDll );
if( hLib == NULL )
{
DWORD dwError = GetLastError();
char szBuffer[256];
_snprintf( szBuffer, sizeof( szBuffer ), "ForceLibraryNow (LobbyHook.dll) has failed( 0x%08lx )\nDo you want to switch to old style injection?\nIf Decal is loading in AC properly answer no.", dwError );
// Kill timer
KillTimer( m_uiTimer );
m_uiTimer = 0;
int iRet = ::MessageBox( NULL, szBuffer, _T( "DenAgent" ), MB_YESNO );
if( iRet == IDYES )
{
RegKey key;
key.Create( HKEY_LOCAL_MACHINE, _T( "SOFTWARE\\Decal" ) );
key.SetDWORDValue("OldInjection", 0x1L);
g_bOldInject = true;
TCHAR szInjectDll[ MAX_PATH ];
memset( szInjectDll, 0, sizeof( szInjectDll ) / sizeof( szInjectDll[0] ) );
// Open the COM CoClass for IPager to get Inject.dll path
if( key.Open( HKEY_CLASSES_ROOT, _T( "CLSID\\{C79E2F76-06F8-4CD0-A613-4829237D297D}\\InprocServer32" ), KEY_READ ) == ERROR_SUCCESS )
{
DWORD dwChars = MAX_PATH - 1;
if( key.QueryStringValue( NULL, szInjectDll, &dwChars ) != ERROR_SUCCESS )
{
::AfxMessageBox( _T( "There is a serious problem with the Decal Agent registry settings!!\n\nExiting." ), MB_ICONERROR | MB_OK );
::PostQuitMessage( 0 );
}
}
g_hInj = LoadLibrary( szInjectDll );
InjEnable = (VoidNoParams) GetProcAddress( g_hInj, (LPCSTR) 0x00000012 );
InjDisable = (VoidNoParams) GetProcAddress( g_hInj, (LPCSTR) 0x00000011 );
::InjEnable();
}
else // no
{
_snprintf( szBuffer, sizeof( szBuffer ), "Lobby Injection halted... restart DenAgent to try again." );
::MessageBox( NULL, szBuffer, _T( "DenAgent" ), MB_OK );
}
}
}
else
{
DWORD dwError = GetLastError();
char szBuffer[256];
_snprintf( szBuffer, sizeof( szBuffer ), "DLL path was blank: 0x%08lx", dwError );
::MessageBox( NULL, szBuffer, _T( "DenAgent" ), MB_OK );
}
}
else
{
DWORD dwError = GetLastError();
char szBuffer[256];
_snprintf( szBuffer, sizeof( szBuffer ), "Couldn't get process id: 0x%08lx", dwError );
::MessageBox( NULL, szBuffer, _T( "DenAgent" ), MB_OK );
}
}
return TRUE;
}
LRESULT CTrayWnd::OnSysTray(WPARAM nID, LPARAM uMsg)
{
// We should only receive message for the one systray we register
ASSERT( nID == ID_SYSTRAY );
switch( uMsg )
{
case WM_RBUTTONUP:
{
POINT pt;
::GetCursorPos( &pt );
CMenu menu;
menu.LoadMenu( IDR_POPUPS );
CMenu *pSystray = menu.GetSubMenu( 0 );
pSystray->SetDefaultItem( 0, TRUE );
SetForegroundWindow();
pSystray->TrackPopupMenu( TPM_RIGHTALIGN | TPM_RIGHTBUTTON, pt.x, pt.y, this );
}
break;
case WM_LBUTTONUP:
showDialog();
break;
}
return 0;
}
void CTrayWnd::OnSystrayConfigure()
{
showDialog();
}
void CTrayWnd::OnSystrayExit()
{
DestroyWindow();
::PostQuitMessage( 0 );
}
void CTrayWnd::UpdateXMLFiles()
{
CDenAgentDlg dlg;
m_pDialog = &dlg;
dlg.m_bDoUpdate = true;
dlg.DoModal( );
m_pDialog = NULL;
}