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>
527 lines
No EOL
14 KiB
C++
527 lines
No EOL
14 KiB
C++
#include "stdafx.h"
|
|
|
|
#include <string>
|
|
|
|
#include "DenAgent.h"
|
|
#include "DownloaderDlg.h"
|
|
#include "AutoUpdate.h"
|
|
|
|
static LPCSTR updateList = "updatelist.xml" ;
|
|
|
|
// DownloadAgent
|
|
|
|
/* static */ bool DownloadAgent::downloadFile(std::string remoteFile, std::string localFile) {
|
|
// Perform a direct, one time download of a file
|
|
|
|
cDownloaderDlg dlg;
|
|
|
|
dlg.addFile(remoteFile, localFile);
|
|
|
|
dlg.DoModal();
|
|
|
|
return (dlg.GetDownloadStatus() == cDownloaderDlg::DownloadStatus::DOWNLOAD_SUCCEEDED);
|
|
}
|
|
|
|
DownloadAgent::DownloadAgent() {
|
|
m_ScheduledDownloads.clear();
|
|
}
|
|
|
|
void DownloadAgent::scheduleDownload(std::string remoteFile, std::string localFile) {
|
|
// Encapsulate this download in a download slot, and add it to the schedule list
|
|
|
|
DownloadSlot slot;
|
|
|
|
slot.remoteFile = remoteFile;
|
|
slot.localFile = localFile;
|
|
|
|
m_ScheduledDownloads.push_back(slot);
|
|
}
|
|
|
|
bool DownloadAgent::runDownloads() {
|
|
if (!m_ScheduledDownloads.size())
|
|
return false;
|
|
|
|
cDownloaderDlg dlg;
|
|
|
|
// Add each schduled download to our downloader
|
|
std::list<DownloadSlot>::iterator it;
|
|
|
|
for (it = m_ScheduledDownloads.begin(); it != m_ScheduledDownloads.end(); ++it) {
|
|
DownloadSlot slot = *it;
|
|
|
|
dlg.addFile(slot.remoteFile, slot.localFile);
|
|
}
|
|
|
|
// And run them
|
|
dlg.DoModal();
|
|
|
|
return (dlg.GetDownloadStatus() == cDownloaderDlg::DownloadStatus::DOWNLOAD_SUCCEEDED);
|
|
}
|
|
|
|
// AutoUpdateSource
|
|
|
|
AutoUpdateSource::AutoUpdateSource(std::string decalDir, AutoUpdateType type, std::string localTarget, std::string remoteSource, std::string requiredVersion)
|
|
: m_DecalDir(decalDir)
|
|
, m_LocalTarget(localTarget)
|
|
, m_RequiredVersion(requiredVersion)
|
|
, m_RemoteSource(remoteSource)
|
|
, m_Type(type)
|
|
{
|
|
}
|
|
|
|
bool AutoUpdateSource::needsUpdating() {
|
|
USES_CONVERSION;
|
|
|
|
// Get the version of the local file
|
|
|
|
// Make sure the file exists
|
|
|
|
OFSTRUCT targetFile;
|
|
std::string localTarget = m_DecalDir + "/" + m_LocalTarget;
|
|
|
|
if (OpenFile(localTarget.c_str(), &targetFile, OF_EXIST) == HFILE_ERROR) {
|
|
// It didn't exist, we must update
|
|
return true;
|
|
}
|
|
|
|
if (m_RequiredVersion.length() == 0) {
|
|
// If we don't have a remote version, assume we want to update
|
|
return true;
|
|
}
|
|
|
|
int reqMajor(0), reqMinor(0), reqPatch(0), reqBuild(0) ;
|
|
sscanf(m_RequiredVersion.c_str(), "%i.%i.%i.%i", &reqMajor, &reqMinor, &reqPatch, &reqBuild);
|
|
|
|
if (m_Type == AU_TYPE_XML) {
|
|
// Extract the version node from an XML document
|
|
MSXML::IXMLDOMDocumentPtr pDoc;
|
|
|
|
try {
|
|
pDoc.CreateInstance (__uuidof(MSXML::DOMDocument), NULL, CLSCTX_INPROC_SERVER);
|
|
pDoc->async = false;
|
|
|
|
// Construct a list of update targets based on the XML contents
|
|
if (pDoc->load(static_cast<LPCTSTR>(localTarget.c_str()))) {
|
|
// Read the autoupdate version string
|
|
// MSXML::IXMLDOMNodePtr pVersionNode = pDoc->selectSingleNode ( _T( "/*/@version" ) );
|
|
MSXML::IXMLDOMElementPtr pVersionNode = pDoc->selectSingleNode ("//revision");
|
|
if (pVersionNode != NULL) {
|
|
// _variant_t vXMLVer = pVersionNode->text;
|
|
_variant_t vXMLVer = pVersionNode->getAttribute("version") ;
|
|
if (vXMLVer.vt == VT_BSTR) {
|
|
int locMajor(0), locMinor(0), locPatch(0), locBuild(0) ;
|
|
sscanf(OLE2T(vXMLVer.bstrVal), "%i.%i.%i.%i", &locMajor, &locMinor, &locPatch, &locBuild);
|
|
if (locMajor==reqMajor) {
|
|
if (locMinor==reqMinor) {
|
|
if (locPatch==reqPatch) {
|
|
return locBuild<reqBuild ;
|
|
} else {
|
|
return locPatch<reqPatch ;
|
|
}
|
|
} else {
|
|
return locMinor<reqMinor ;
|
|
}
|
|
} else {
|
|
return locMajor<reqMajor ;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (...) {
|
|
return true ; // if error reading xml, assume needs updating
|
|
}
|
|
} else if (m_Type == AU_TYPE_DLL || m_Type == AU_TYPE_BETADLL) {
|
|
// Check to see if the file itself needs updating
|
|
if (isOutDatedVersion(localTarget, reqMajor, reqMinor, reqPatch, reqBuild))
|
|
{
|
|
// File is outdated, check requirements
|
|
std::vector<UpdateRequirement>::iterator it;
|
|
for (it = m_Requirements.begin(); it != m_Requirements.end(); ++it)
|
|
{
|
|
UpdateRequirement ur = *it;
|
|
localTarget = m_DecalDir + "/" + ur.sFile;
|
|
sscanf(ur.sVers.c_str(), "%i.%i.%i.%i", &reqMajor, &reqMinor, &reqPatch, &reqBuild);
|
|
if (isOutDatedVersion(localTarget, reqMajor, reqMinor, reqPatch, reqBuild))
|
|
{
|
|
std::string sMsg = m_LocalTarget + " cannot be updated because one or more dependencies is outdated";
|
|
::MessageBox(0, _T(sMsg.c_str()), NULL, S_OK);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
else // File is newer, ignore requirements and exit
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
// if anything went wrong or we were unable to tell whether it should be updated
|
|
// or not...assume it needs updating.
|
|
return true ;
|
|
}
|
|
|
|
std::string AutoUpdateSource::getSource() {
|
|
return m_RemoteSource;
|
|
}
|
|
|
|
std::string AutoUpdateSource::getDestination() {
|
|
return m_LocalTarget;
|
|
}
|
|
|
|
AutoUpdateType AutoUpdateSource::getType() {
|
|
return m_Type;
|
|
}
|
|
|
|
bool AutoUpdateSource::isOutDatedVersion(std::string sFile, int nMajor, int nMinor, int nPatch, int nBuild)
|
|
{
|
|
// Extract the DLL version
|
|
DWORD dwDummy, dwVerSize;
|
|
|
|
dwVerSize = ::GetFileVersionInfoSize(const_cast<LPTSTR>(sFile.c_str()), &dwDummy);
|
|
|
|
if( dwVerSize == 0 )
|
|
return true; // if file vsn not available, assume needs updating
|
|
else
|
|
{
|
|
BYTE *pbVersionInfo = reinterpret_cast< BYTE * >(::_alloca(dwVerSize));
|
|
|
|
::GetFileVersionInfo(const_cast<LPTSTR>(sFile.c_str()), 0, dwVerSize, pbVersionInfo);
|
|
|
|
VS_FIXEDFILEINFO *vffi;
|
|
UINT nLength = sizeof(VS_FIXEDFILEINFO);
|
|
|
|
if( ::VerQueryValue( pbVersionInfo, _T("\\"), reinterpret_cast< LPVOID * >( &vffi ), &nLength ) )
|
|
{
|
|
// Got it, so format it
|
|
int locMajor(0), locMinor(0), locPatch(0), locBuild(0);
|
|
locMajor = static_cast< int >( HIWORD( vffi->dwFileVersionMS ) );
|
|
locMinor = static_cast< int >( LOWORD( vffi->dwFileVersionMS ) );
|
|
locPatch = static_cast< int >( HIWORD( vffi->dwFileVersionLS ) );
|
|
locBuild = static_cast< int >( LOWORD( vffi->dwFileVersionLS ) );
|
|
|
|
if( locMajor == nMajor )
|
|
{
|
|
if( locMinor == nMinor )
|
|
{
|
|
if( locPatch == nPatch )
|
|
return locBuild < nBuild;
|
|
else
|
|
return locPatch < nPatch;
|
|
}
|
|
|
|
else
|
|
return locMinor < nMinor ;
|
|
}
|
|
|
|
else
|
|
return locMajor < nMajor ;
|
|
}
|
|
|
|
// problem with VerQueryValue... maybe updating will fix it.
|
|
else
|
|
return true;
|
|
}
|
|
}
|
|
|
|
void AutoUpdateSource::AddRequirement(UpdateRequirement reqUpdate)
|
|
{
|
|
m_Requirements.push_back(reqUpdate);
|
|
}
|
|
|
|
// AutoUpdate
|
|
|
|
AutoUpdate::AutoUpdate(std::string decalDir) {
|
|
m_DecalDir = decalDir;
|
|
m_LocalXML = "";
|
|
|
|
m_RemoteSources.clear();
|
|
}
|
|
|
|
bool AutoUpdate::initialiseFromXML(std::string remoteXML) {
|
|
USES_CONVERSION;
|
|
|
|
m_RemoteXML = remoteXML + "/" + updateList ;
|
|
|
|
// Download the remote XML document to a local file
|
|
if (!DownloadAgent::downloadFile(m_RemoteXML, m_DecalDir + "/" + updateList))
|
|
{
|
|
// We couldn't download the update list. We must bail here, or suffer!
|
|
::MessageBox(NULL, "Could not obtain updatelist.xml. Cannot update - Hosting server down?", NULL, MB_OK);
|
|
|
|
return false;
|
|
}
|
|
|
|
// Parse it as an XML document
|
|
MSXML::IXMLDOMDocumentPtr pDoc;
|
|
|
|
// try {
|
|
pDoc.CreateInstance (__uuidof(MSXML::DOMDocument), NULL, CLSCTX_INPROC_SERVER);
|
|
pDoc->async = false;
|
|
|
|
// Construct a list of update targets based on the XML contents
|
|
std::string localXML = m_DecalDir + "/" + updateList ;
|
|
|
|
if (!pDoc->load( static_cast<LPCTSTR>(localXML.c_str())))
|
|
return false;
|
|
|
|
// Read the autoupdate version string
|
|
MSXML::IXMLDOMNodePtr pVersionNode = pDoc->selectSingleNode ( _T( "/*/@version" ) );
|
|
|
|
if (pVersionNode == NULL) {
|
|
::MessageBox(NULL,"No version information in updatelist.xml. Cannot update",NULL,MB_OK) ;
|
|
return false;
|
|
}
|
|
|
|
// Check to make sure we understand this version
|
|
char *version = OLE2T(pVersionNode->text);
|
|
if (strcmp(version,"1.0.0.0")) {
|
|
::MessageBox(NULL,"Unknown version type in updatelist.xml. Cannot update",NULL,MB_OK) ;
|
|
return false ;
|
|
}
|
|
|
|
// Now iterate over the Files entries, creating an AutoUpdateSource for each
|
|
MSXML::IXMLDOMNodeListPtr xmlFiles = pDoc->selectNodes(_T("/updatelist/file"));
|
|
|
|
if (xmlFiles.GetInterfacePtr() == NULL)
|
|
return false;
|
|
|
|
for (MSXML::IXMLDOMElementPtr xmlFile = xmlFiles->nextNode(); xmlFile.GetInterfacePtr() != NULL; xmlFile = xmlFiles->nextNode()) {
|
|
std::string localFile, remoteFile, version;
|
|
AutoUpdateType type;
|
|
BSTR bResult;
|
|
_variant_t vResult;
|
|
|
|
// Read the type
|
|
vResult = xmlFile->getAttribute("type");
|
|
|
|
if (vResult.vt != VT_BSTR)
|
|
continue;
|
|
|
|
bResult = vResult.bstrVal;
|
|
char *cType = OLE2T(bResult);
|
|
|
|
if (strcmp(cType, "xml") == 0) {
|
|
type = AU_TYPE_XML;
|
|
} else if (strcmp(cType, "dll") == 0) {
|
|
type = AU_TYPE_DLL;
|
|
} else if (strcmp(cType, "beta") == 0) {
|
|
type = AU_TYPE_BETADLL ;
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
// Local name
|
|
vResult = xmlFile->getAttribute("localname");
|
|
|
|
if (vResult.vt != VT_BSTR)
|
|
continue;
|
|
|
|
bResult = vResult.bstrVal;
|
|
localFile = OLE2T(bResult);
|
|
|
|
// Remote name
|
|
vResult = xmlFile->getAttribute("remotename");
|
|
|
|
if (vResult.vt != VT_BSTR)
|
|
continue;
|
|
|
|
bResult = vResult.bstrVal;
|
|
remoteFile = OLE2T(bResult);
|
|
|
|
// And version
|
|
vResult = xmlFile->getAttribute("version");
|
|
|
|
if (vResult.vt != VT_BSTR)
|
|
continue;
|
|
|
|
bResult = vResult.bstrVal;
|
|
version = OLE2T(bResult);
|
|
|
|
AutoUpdateSource aUSource(m_DecalDir, type, localFile, remoteFile, version);
|
|
//if the source is dll || beta check for requirements
|
|
if (type == AU_TYPE_DLL || type == AU_TYPE_BETADLL)
|
|
{
|
|
MSXML::IXMLDOMNodeListPtr xmlReqs = NULL;//xmlFile->selectNodes(_T("requirement")); //pDoc->selectNodes(_T("/updatelist/file"));
|
|
xmlReqs = xmlFile->getElementsByTagName(_T("requirement"));
|
|
|
|
if (xmlReqs.GetInterfacePtr() == NULL)
|
|
continue;
|
|
|
|
for (MSXML::IXMLDOMElementPtr xmlNode = xmlReqs->nextNode(); xmlNode.GetInterfacePtr() != NULL; xmlNode = xmlReqs->nextNode())
|
|
{
|
|
std::string sLocalFile, sVersion;
|
|
|
|
// Read the file name
|
|
vResult = xmlNode->getAttribute("file");
|
|
if (vResult.vt != VT_BSTR)
|
|
continue;
|
|
|
|
bResult = vResult.bstrVal;
|
|
sLocalFile = OLE2T(bResult);
|
|
|
|
// Read the version
|
|
vResult = xmlNode->getAttribute("version");
|
|
if (vResult.vt != VT_BSTR)
|
|
continue;
|
|
|
|
bResult = vResult.bstrVal;
|
|
sVersion = OLE2T(bResult);
|
|
|
|
UpdateRequirement uReq(sLocalFile, sVersion);
|
|
aUSource.AddRequirement(uReq);
|
|
}
|
|
}
|
|
// Add the source to the cache
|
|
m_RemoteSources.push_back(aUSource);
|
|
}
|
|
// } catch (...) {
|
|
// return false;
|
|
// }
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AutoUpdate::needsUpdate() {
|
|
// Ask all the dependancies if they want to update themselves
|
|
|
|
// Check the versions of all our local XML documents to see if we need an update
|
|
std::list<AutoUpdateSource>::iterator it;
|
|
|
|
for (it = m_RemoteSources.begin(); it != m_RemoteSources.end(); ++it) {
|
|
AutoUpdateSource aUSource = *it;
|
|
|
|
if (aUSource.needsUpdating())
|
|
return true;
|
|
}
|
|
|
|
// Nothing needed updating
|
|
return false;
|
|
}
|
|
|
|
bool AutoUpdate::performUpdate() {
|
|
// Construct a list of all the remote components that need updating
|
|
std::list<AutoUpdateSource> updateList;
|
|
DownloadAgent dlAgent;
|
|
|
|
|
|
bool DllUpdate = false ;
|
|
bool BetaUpdate = false ;
|
|
std::list<AutoUpdateSource>::iterator itRemote = m_RemoteSources.begin();;
|
|
while (itRemote != m_RemoteSources.end()) {
|
|
AutoUpdateSource aUSource = *itRemote++;
|
|
|
|
if (aUSource.needsUpdating()) {
|
|
switch(aUSource.getType()) {
|
|
case AU_TYPE_DLL: DllUpdate = true ; break ;
|
|
case AU_TYPE_BETADLL: BetaUpdate = true ; break ;
|
|
}
|
|
if (aUSource.getType()==AU_TYPE_BETADLL) {
|
|
BetaUpdate = true ;
|
|
}
|
|
updateList.push_back(aUSource);
|
|
}
|
|
}
|
|
if (DllUpdate) {
|
|
if ( MessageBox(NULL,
|
|
"Updates to the Decal program are available.\n\n"
|
|
"These are supported updates to the current release\n"
|
|
"version of Decal.\n"
|
|
"Installation of these updates is recommended.\n\n"
|
|
"Do you wish to install these updates now?",
|
|
"Decal Program Updates Available",
|
|
MB_YESNO) == IDNO
|
|
){
|
|
DllUpdate = false ;
|
|
}
|
|
}
|
|
if (BetaUpdate) {
|
|
if (MessageBox(NULL,
|
|
"Decal Beta updates are available.\n\n"
|
|
"NOTE: Beta versions are not supported and have NOT\n"
|
|
"been tested extensively.\n"
|
|
" They can crash your system, and require a full\n"
|
|
"re-install of the original Decal program.\n\n"
|
|
"Do you wish to install these unsupported Beta updates?",
|
|
"Beta program updates available",
|
|
MB_YESNO|MB_DEFBUTTON2) == IDNO
|
|
) {
|
|
BetaUpdate = false ;
|
|
}
|
|
}
|
|
// Schedule downloads and processing for all selected types
|
|
std::list<AutoUpdateSource>::iterator itUpdates = updateList.begin();;
|
|
while (itUpdates != updateList.end()) {
|
|
AutoUpdateSource source = *itUpdates++ ;
|
|
if ( (source.getType() == AU_TYPE_DLL && !DllUpdate)
|
|
|| (source.getType() == AU_TYPE_BETADLL && !BetaUpdate)
|
|
){
|
|
continue ;
|
|
}
|
|
dlAgent.scheduleDownload(source.getSource(), m_DecalDir + "\\" + source.getDestination() + ".tmp");
|
|
}
|
|
|
|
// Download them to the local cache
|
|
if (!dlAgent.runDownloads()) {
|
|
// Something went horribly wrong
|
|
return false;
|
|
}
|
|
|
|
// Delete the destinations and move the cached objects to their new homes
|
|
itUpdates = updateList.begin() ;
|
|
while (itUpdates != updateList.end()) {
|
|
AutoUpdateSource aUSource = *itUpdates++;
|
|
if ( (aUSource.getType() == AU_TYPE_DLL && !DllUpdate)
|
|
|| (aUSource.getType() == AU_TYPE_BETADLL && !BetaUpdate)
|
|
){
|
|
continue ;
|
|
}
|
|
|
|
std::string newFile = (m_DecalDir + "\\" + aUSource.getDestination());
|
|
std::string tmpFile = (m_DecalDir + "\\" + aUSource.getDestination() + ".tmp");
|
|
|
|
BOOL bResult = ::DeleteFile (newFile.c_str ());
|
|
DWORD dwLastError = 0;
|
|
|
|
if (!bResult)
|
|
{
|
|
dwLastError = ::GetLastError ();
|
|
}
|
|
|
|
if (bResult || dwLastError == ERROR_FILE_NOT_FOUND)
|
|
{
|
|
MoveFile(tmpFile.c_str(), newFile.c_str());
|
|
|
|
// We may have to register DLLs
|
|
|
|
if (aUSource.getType() == AU_TYPE_DLL || aUSource.getType() == AU_TYPE_BETADLL )
|
|
{
|
|
HINSTANCE hDLL = LoadLibrary(newFile.c_str());
|
|
|
|
if (hDLL) {
|
|
typedef CRuntimeClass * (*DLLREG)();
|
|
|
|
DLLREG DllReg = (DLLREG)GetProcAddress(hDLL, "DllRegisterServer");
|
|
|
|
if (DllReg != NULL)
|
|
DllReg();
|
|
|
|
FreeLibrary(hDLL);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Delete failed - file is in use
|
|
else
|
|
{
|
|
MessageBox(NULL, "A file being updated is in use - close programs and try again.", "Decal", MB_OK);
|
|
}
|
|
}
|
|
|
|
if (updateList.size() > 0) {
|
|
MessageBox(NULL, "Your Decal components have been updated", "Decal", MB_OK);
|
|
} else {
|
|
MessageBox(NULL, "Your Decal components are already up to date", "Decal", MB_OK);
|
|
}
|
|
|
|
return true;
|
|
} |