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,691 @@
// DenAgent.cpp : Defines the class behaviors for the application.
//
#include "stdafx.h"
#include "DenAgent.h"
#include "DenAgentDlg.h"
#include "TrayWnd.h"
#include <initguid.h>
#include "DenAgent_i.c"
#include "..\Inject\Inject.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
//FILELIST g_FileList[] = { { "messages.xml", "messages.dlc" }, { "memlocs.xml", "messages.dlc" }, { "decalplugins.xml", "messages.dlc" } };
/////////////////////////////////////////////////////////////////////////////
// CDenAgentApp
BEGIN_MESSAGE_MAP(CDenAgentApp, CWinApp)
//{{AFX_MSG_MAP(CDenAgentApp)
// NOTE - the ClassWizard will add and remove mapping macros here.
// DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG
ON_COMMAND(ID_HELP, CWinApp::OnHelp)
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CDenAgentApp construction
CDenAgentApp::CDenAgentApp()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}
/////////////////////////////////////////////////////////////////////////////
// The one and only CDenAgentApp object
CDenAgentApp theApp;
LONG g_fAbortDownload;
/////////////////////////////////////////////////////////////////////////////
// CDenAgentApp initialization
bool CheckForHardwareMode ()
{
RegKey key;
if (key.Open (HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Microsoft Games\\Asheron's Call\\1.00", KEY_READ) != ERROR_SUCCESS)
{
return false;
}
DWORD dwValue = 0;
if (key.QueryDWORDValue ("UseHardware", dwValue) != ERROR_SUCCESS)
{
return false;
}
return dwValue ? true : false;
}
bool CheckForIE5OrLater ()
{
RegKey key;
if (key.Open (HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Internet Explorer", KEY_READ) != ERROR_SUCCESS)
return false;
DWORD dwValue = 0;
TCHAR szVersionBuffer[256];
DWORD dwSize = sizeof (szVersionBuffer);
if (key.QueryStringValue("Version", szVersionBuffer, &dwSize) != ERROR_SUCCESS)
return false;
DWORD dwMajor = 0, dwMinor = 0, dwBuild1 = 0, dwBuild2 = 0;
if (::_stscanf (szVersionBuffer, _T("%ld.%ld.%ld.%ld"), &dwMajor, &dwMinor, &dwBuild1, &dwBuild2) != 4)
return false;
// IE 5.01 is build: 5.00.2919.6307
return !( dwMajor < 5 || ( dwMajor == 5 && dwMinor == 0 ) && ( ( dwBuild1 < 2919 ) || ( dwBuild1 == 2919 && dwBuild2 < 6307 ) ) );
}
bool CDenAgentApp::getVersionString ( LPCTSTR szFilename, CString &strVersion )
{
DWORD dwDummy,
dwVerSize = ::GetFileVersionInfoSize( const_cast< LPTSTR > ( szFilename ), &dwDummy );
if( dwVerSize == 0 )
return false;
BYTE *pbVersionInfo = reinterpret_cast< BYTE * >( ::_alloca( dwVerSize ) );
::GetFileVersionInfo( const_cast< LPTSTR > ( szFilename ), 0, dwVerSize, pbVersionInfo );
VS_FIXEDFILEINFO *vffi;
UINT nLength = sizeof( VS_FIXEDFILEINFO );
if( !::VerQueryValue( pbVersionInfo, _T( "\\" ), reinterpret_cast< LPVOID * >( &vffi ), &nLength ) )
return false;
// Got it, so format it
strVersion.FormatMessage ( IDS_VERSIONTEMPLATE, static_cast< int > ( HIWORD ( vffi->dwFileVersionMS ) ),
static_cast< int > ( LOWORD ( vffi->dwFileVersionMS ) ), static_cast< int > ( HIWORD ( vffi->dwFileVersionLS ) ),
static_cast< int > ( LOWORD ( vffi->dwFileVersionLS ) ) );
return true;
}
bool CDenAgentApp::getVersionInfo ( LPCTSTR szFilename, int &iReleaseMajor, int &iReleaseMinor, int &iBuildMajor, int &iBuildMinor )
{
DWORD dwDummy,
dwVerSize = ::GetFileVersionInfoSize( const_cast< LPTSTR > ( szFilename ), &dwDummy );
if( dwVerSize == 0 )
return false;
BYTE *pbVersionInfo = reinterpret_cast< BYTE * >( ::_alloca( dwVerSize ) );
::GetFileVersionInfo( const_cast< LPTSTR > ( szFilename ), 0, dwVerSize, pbVersionInfo );
VS_FIXEDFILEINFO *vffi;
UINT nLength = sizeof( VS_FIXEDFILEINFO );
if( !::VerQueryValue( pbVersionInfo, _T( "\\" ), reinterpret_cast< LPVOID * >( &vffi ), &nLength ) )
return false;
// Got it, so format it
iReleaseMajor = static_cast< int > ( HIWORD ( vffi->dwFileVersionMS ) );
iReleaseMinor = static_cast< int > ( LOWORD ( vffi->dwFileVersionMS ) );
iBuildMajor = static_cast< int > ( HIWORD ( vffi->dwFileVersionLS ) );
iBuildMinor = static_cast< int > ( LOWORD ( vffi->dwFileVersionLS ) );
return true;
}
bool CDenAgentApp::getACVersionString ( CString &strVersion )
{
RegKey rk;
if ( rk.Open ( HKEY_LOCAL_MACHINE, _T( "Software\\Microsoft\\Microsoft Games\\Asheron's Call\\1.00" ) ) != ERROR_SUCCESS )
{
::AfxMessageBox ( IDE_NOCLIENTEXE, MB_ICONWARNING );
return false;
}
TCHAR szClientPath[ MAX_PATH ];
DWORD dwPathLength = MAX_PATH;
if ( rk.QueryStringValue ( _T( "path" ), szClientPath, &dwPathLength ) != ERROR_SUCCESS )
{
::AfxMessageBox ( IDE_NOCLIENTEXE, MB_ICONWARNING );
return false;
}
::_tcscpy ( szClientPath + ( dwPathLength - 1 ), _T( "\\client.exe" ) );
bool bHasVersion = getVersionString ( szClientPath, strVersion );
if ( !bHasVersion )
::AfxMessageBox ( IDE_NOCLIENTVER, MB_ICONWARNING );
return bHasVersion;
}
bool CDenAgentApp::getCOMObjectDLL ( REFCLSID rclsid, CString &strFilename )
{
USES_CONVERSION;
LPOLESTR oclsid;
HRESULT hRes = ::StringFromCLSID ( rclsid, &oclsid );
_ASSERTE ( SUCCEEDED ( hRes ) );
LPCTSTR clsidName = OLE2T ( oclsid );
::CoTaskMemFree ( oclsid );
TCHAR keyName[ MAX_PATH ];
::_stprintf ( keyName, _T( "CLSID\\%s\\InprocServer32" ), clsidName );
RegKey rk;
if ( rk.Open ( HKEY_CLASSES_ROOT, keyName ) != ERROR_SUCCESS )
return false;
DWORD dwPathSize = MAX_PATH;
long regResult = rk.QueryStringValue ( NULL, strFilename.GetBuffer ( MAX_PATH ), &dwPathSize );
strFilename.ReleaseBuffer();
if (regResult != ERROR_SUCCESS)
return false;
// Check to see if the default KeyValue points to the MS.NET core DLL
const char *dotNetProxy = "mscoree.dll";
CString dotNetMatchString = strFilename.Right(strlen(dotNetProxy));
if (dotNetMatchString.CompareNoCase(dotNetProxy) == 0)
{
// Get CodeBase KeyValue - Should point to the Plugin DLL
dwPathSize = MAX_PATH;
if (rk.QueryStringValue ( "CodeBase", strFilename.GetBuffer ( MAX_PATH ), &dwPathSize ) == ERROR_SUCCESS)
{
// Release extra buffer (Not doing this seem to cause the Left() and Delete() funcs to fail)
strFilename.ReleaseBuffer();
// Check for garbage in KeyValue and delete it
if (strFilename.Left(8) == "file:///")
strFilename.Delete(0,8);
}
else
{
// CodeBase isn't accessable - Possible that plugin is in GAC
// Return Path to MS.NET core DLL
dwPathSize = MAX_PATH;
if (rk.QueryStringValue ( NULL, strFilename.GetBuffer ( MAX_PATH ), &dwPathSize ) != ERROR_SUCCESS)
{
rk.Close();
strFilename.ReleaseBuffer();
return false;
}
}
}
rk.Close();
return true;
}
bool CDenAgentApp::getAgentPath ( LPCTSTR szFilename, CString &strPath )
{
TCHAR *szAgentPath = strPath.GetBuffer ( MAX_PATH );
::GetModuleFileName ( NULL, szAgentPath, MAX_PATH );
TCHAR *filePath = ::_tcsrchr ( szAgentPath, _T( '\\' ) );
::strcpy ( filePath + 1, szFilename );
return true;
}
bool CDenAgentApp::checkXMLVersion ( CString &strClientVersion, CString &strXMLFile, CTrayWnd* pTrayWnd )
{
MSXML::IXMLDOMDocumentPtr pDoc;
try
{
pDoc.CreateInstance ( __uuidof ( MSXML::DOMDocument ), NULL, CLSCTX_INPROC_SERVER );
pDoc->async = false;
BOOL bSuccess = pDoc->load( static_cast< LPCTSTR > ( strXMLFile ) );
if( !bSuccess )
{
std::string szXML;
DecryptXML( static_cast< LPCSTR >( strXMLFile ), szXML );
if( szXML != "" )
bSuccess = pDoc->loadXML( _bstr_t( szXML.c_str() ) );
}
if( ! bSuccess )
{
CString strMessage;
strMessage.FormatMessage ( IDE_NOXMLDOC, static_cast< LPCTSTR > ( strXMLFile ) );
CString strUpdate;
strUpdate.LoadString( IDE_UPDATETEXT );
strMessage += strUpdate;
if ( ::AfxMessageBox ( strMessage, MB_YESNO | MB_ICONERROR ) == IDYES )
pTrayWnd->UpdateXMLFiles( );
return false;
}
// Read the client version string
MSXML::IXMLDOMNodePtr pVersionNode = pDoc->selectSingleNode ( _T( "/*/@version" ) );
if ( pVersionNode == NULL )
{
CString strError;
strError.FormatMessage ( IDE_NOXMLVER, static_cast< LPCTSTR > ( strXMLFile ) );
::AfxMessageBox ( strError, MB_ICONWARNING );
return false;
}
_variant_t vXMLVer = pVersionNode->text;
if ( vXMLVer.vt != VT_BSTR )
{
CString strError;
strError.FormatMessage ( IDE_NOXMLVER, static_cast< LPCTSTR > ( strXMLFile ) );
::AfxMessageBox ( strError, MB_ICONWARNING );
return false;
}
CString strXMLVer ( vXMLVer.bstrVal );
if ( strClientVersion.Compare ( strXMLVer ) != 0 )
{
CString str;
str.FormatMessage ( IDE_XMLCLIENTVERSIONMISMATCH, static_cast< LPCTSTR > ( strXMLFile ),
static_cast< LPCTSTR > ( strClientVersion ), static_cast< LPCTSTR > ( strXMLVer ) );
CString strUpdate;
strUpdate.LoadString( IDE_UPDATETEXT );
str += strUpdate;
if ( ::AfxMessageBox ( str, MB_YESNO | MB_ICONWARNING ) == IDYES )
pTrayWnd->UpdateXMLFiles( );
return false;
}
return true;
}
catch ( _com_error & )
{
CString strMessage;
strMessage.FormatMessage ( IDE_NOXMLDOC, static_cast< LPCTSTR > ( strXMLFile ) );
::AfxMessageBox ( strMessage, MB_ICONERROR );
}
return true;
}
void CDenAgentApp::DecryptXML( const char *szPath, std::string &szXML )
{
if( szPath == NULL )
{
szXML = "";
return;
}
FILE *f = fopen( szPath, "rb" );
if( f == NULL )
{
szXML = "";
return;
}
szXML.clear();
unsigned char szBuffer[1025];
try
{
CCryptProv crypt;
if( crypt.Initialize( PROV_RSA_FULL, "Decal_Memlocs", MS_DEF_PROV ) == NTE_BAD_KEYSET )
crypt.Initialize( PROV_RSA_FULL, "Decal_Memlocs", MS_DEF_PROV, CRYPT_NEWKEYSET );
CCryptMD5Hash hash;
hash.Initialize( crypt );
hash.AddString( DECAL_KEY );
CCryptDerivedKey key;
key.Initialize( crypt, hash );
DWORD dwDecLen = 0;
while( ! feof(f) )
{
memset( szBuffer, 0, sizeof( szBuffer ) );
dwDecLen = fread( szBuffer, 1, 1024, f );
key.Decrypt( feof(f), (BYTE *) szBuffer, &dwDecLen );
szXML += (char *)szBuffer;
}
key.Destroy();
hash.Destroy();
crypt.Release();
}
catch( ... )
{
// crap...
szXML = "";
}
fclose( f );
}
bool CDenAgentApp::checkXMLVersions ( CTrayWnd* pTrayWnd )
{
bool bOK = true;
// Check the versions of the XML files
CString strClientVersion;
if ( getACVersionString ( strClientVersion ) )
{
CString strXMLFile;
if ( getAgentPath ( _T( "memlocs.xml" ), strXMLFile ) )
bOK = bOK && checkXMLVersion ( strClientVersion, strXMLFile, pTrayWnd );
//if ( getAgentPath ( _T( "messages.xml" ), strXMLFile ) )
//bOK = bOK && checkXMLVersion ( strClientVersion, strXMLFile );
}
return bOK;
}
void CDenAgentApp::getXMLBuilds ( cXMLBuild *pFirst, cXMLBuild *pLast )
{
MSXML::IXMLDOMDocumentPtr pDoc;
pDoc.CreateInstance ( __uuidof ( MSXML::DOMDocument ), NULL, CLSCTX_INPROC_SERVER );
pDoc->async = false;
for ( ; pFirst != pLast; ++ pFirst )
{
CString strPath;
getAgentPath ( pFirst->XMLFile, strPath );
try
{
BOOL bSuccess = pDoc->load( static_cast< LPCTSTR > ( pFirst->XMLFile ) );
if( ! bSuccess )
{
std::string szXML;
DecryptXML( static_cast< LPCSTR >( pFirst->XMLFile ), szXML );
if( szXML != "" )
bSuccess = pDoc->loadXML( _bstr_t( szXML.c_str() ) );
}
if( ! bSuccess )
{
CString strMessage;
strMessage.FormatMessage( IDE_NOXMLDOC, pFirst->XMLFile );
::AfxMessageBox ( strMessage, MB_ICONERROR );
pFirst->build = 0;
continue;
}
// Read the client version string
MSXML::IXMLDOMNodePtr pBuildNode = pDoc->selectSingleNode ( _T( "/*/@build" ) );
if ( pBuildNode == NULL )
{
pFirst->build = 0;
continue;
}
pFirst->build = pBuildNode->GetnodeValue ();
}
catch ( _com_error & )
{
pFirst->build = 0;
}
}
}
BOOL CDenAgentApp::InitInstance()
{
HWND hDenAgent = FindWindow(NULL, "Decal Agent");
if(hDenAgent)
{
::MessageBox(hDenAgent, "Decal is already running!", "Decal", 0);
return FALSE;
}
if (!CheckForHardwareMode ())
{
::MessageBox
(
NULL,
_T("Decal requires Asheron's Call to be installed with hardware 3d acceleration enabled! Decal will run, but will be unable to display any UI in-game."),
_T("Decal Agent Startup Warning"),
MB_OK
);
}
if (!CheckForIE5OrLater ())
{
::MessageBox
(
NULL,
"Decal requires Internet Explorer 5.01 or later!",
"Decal Agent Startup Error",
MB_OK
);
return FALSE;
}
if (!InitATL())
return FALSE;
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
if (cmdInfo.m_bRunEmbedded || cmdInfo.m_bRunAutomated)
{
return TRUE;
}
// Set our current directory to the path of DenAgent.exe
// TODO: Fix all the code so that it doesn't rely on the current directory
TCHAR szAppPath[MAX_PATH];
::memset (szAppPath, 0, sizeof (szAppPath));
::GetModuleFileName (0, szAppPath, MAX_PATH);
TCHAR *szLastSlash = ::_tcsrchr (szAppPath, _T('\\'));
if (szLastSlash)
{
*szLastSlash = 0;
::SetCurrentDirectory (szAppPath);
}
// Move V1 plugins into the V2 registry format
importV1Plugins ();
// Standard initialization
// If you are not using these features and wish to reduce the size
// of your final executable, you should remove from the following
// the specific initialization routines you do not need.
m_pTrayWnd = new CTrayWnd;
m_pMainWnd = m_pTrayWnd;
m_pTrayWnd->CreateEx( 0, ::AfxRegisterWndClass( 0, NULL, NULL, NULL ), _T( "Decal Agent" ), WS_POPUP, 0, 0, 0, 0, NULL, NULL, NULL );
checkXMLVersions ( m_pTrayWnd );
return TRUE;
}
CDenAgentModule _Module;
BEGIN_OBJECT_MAP(ObjectMap)
END_OBJECT_MAP()
LONG CDenAgentModule::Unlock()
{
AfxOleUnlockApp();
return 0;
}
LONG CDenAgentModule::Lock()
{
AfxOleLockApp();
return 1;
}
LPCTSTR CDenAgentModule::FindOneOf(LPCTSTR p1, LPCTSTR p2)
{
while (*p1 != NULL)
{
LPCTSTR p = p2;
while (*p != NULL)
{
if (*p1 == *p)
return CharNext(p1);
p = CharNext(p);
}
p1++;
}
return NULL;
}
int CDenAgentApp::ExitInstance()
{
delete m_pTrayWnd;
if (m_bATLInited)
{
_Module.RevokeClassObjects();
_Module.Term();
CoUninitialize();
}
return CWinApp::ExitInstance();
}
void CDenAgentApp::importV1Plugins ()
{
USES_CONVERSION;
RegKey hkGroup;
if ( hkGroup.Open ( HKEY_LOCAL_MACHINE, _T ( "Software\\Decal\\Plugins" ) ) != ERROR_SUCCESS )
// No plugins - no problem
return;
DWORD dwValues = 0;
::RegQueryInfoKey ( hkGroup, NULL, NULL, NULL, NULL, NULL, NULL, &dwValues, NULL, NULL, NULL, NULL );
// Now - iterate through the values backwards
for ( int i = dwValues - 1; i >= 0; -- i )
{
TCHAR szCLSID[ 64 ];
DWORD dwCLSID = sizeof ( szCLSID );
DWORD dwEnabled;
DWORD dwEnabledSize = sizeof ( dwEnabled );
if ( ::RegEnumValue ( hkGroup, i, szCLSID, &dwCLSID, NULL, NULL, reinterpret_cast< BYTE * > ( &dwEnabled ), &dwEnabledSize ) != ERROR_SUCCESS )
continue;
// Try and convert the name to a CLSID
CLSID clsidPlugin;
HRESULT hRes = ::CLSIDFromString ( T2OLE ( szCLSID ), &clsidPlugin );
if ( FAILED ( hRes ) )
continue;
// Delete the value and ask questions later
hkGroup.DeleteValue ( szCLSID );
// Try and create a key to extract the friendly name
CComPtr< IPlugin > pPlugin;
hRes = ::CoCreateInstance ( clsidPlugin, NULL, CLSCTX_INPROC_SERVER, __uuidof ( IPlugin ),
reinterpret_cast< LPVOID * > ( &pPlugin ) );
if ( FAILED ( hRes ) )
// Bad plugin - skip
continue;
CComBSTR strName;
pPlugin->get_FriendlyName ( &strName );
// Create the Real registry key
// Key configuration copied from Decal/cActiveXSurrogate::Register
RegKey hkeyItem;
if ( hkeyItem.Create ( hkGroup, szCLSID ) != ERROR_SUCCESS )
continue;
// Friendly name
hkeyItem.SetStringValue( NULL, OLE2T( strName ) );
// Enabled by default
hkeyItem.SetDWORDValue( _T( "Enabled" ), dwEnabled );
// The V1 surrogate
hkeyItem.SetStringValue( _T( "Surrogate" ), _T( "{3D837F6E-B5CA-4604-885F-7AB45FCFA62A}" ) );
hkeyItem.Close();
}
}
BOOL CDenAgentApp::InitATL()
{
m_bATLInited = TRUE;
HRESULT hRes = CoInitialize(NULL);
if (FAILED(hRes))
{
m_bATLInited = FALSE;
return FALSE;
}
_Module.Init(ObjectMap, AfxGetInstanceHandle());
_Module.dwThreadID = GetCurrentThreadId();
LPTSTR lpCmdLine = GetCommandLine(); //this line necessary for _ATL_MIN_CRT
TCHAR szTokens[] = _T("-/");
BOOL bRun = TRUE;
LPCTSTR lpszToken = _Module.FindOneOf(lpCmdLine, szTokens);
while (lpszToken != NULL)
{
if (lstrcmpi(lpszToken, _T("UnregServer"))==0)
{
_Module.UpdateRegistryFromResource(IDR_DENAGENT, FALSE);
_Module.UnregisterServer(TRUE); //TRUE means typelib is unreg'd
bRun = FALSE;
break;
}
if (lstrcmpi(lpszToken, _T("RegServer"))==0)
{
_Module.UpdateRegistryFromResource(IDR_DENAGENT, TRUE);
_Module.RegisterServer(TRUE);
bRun = FALSE;
break;
}
lpszToken = _Module.FindOneOf(lpszToken, szTokens);
}
if (!bRun)
{
m_bATLInited = FALSE;
_Module.Term();
CoUninitialize();
return FALSE;
}
hRes = _Module.RegisterClassObjects(CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE);
if (FAILED(hRes))
{
m_bATLInited = FALSE;
CoUninitialize();
return FALSE;
}
return TRUE;
}