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>
507 lines
13 KiB
C++
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;
|
|
}
|