Files
2025-05-18 13:04:45 +08:00

464 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "BootstrapPackagedGame.h"
#define IDI_EXEC_FILE 201
#define IDI_EXEC_ARGS 202
#define IDI_EXEC_FILE_ARM64 203
#define IDI_EXEC_FILE_ARM64EC 204
struct VersionInfo
{
DWORD Major = 0;
DWORD Minor = 0;
DWORD Bld = 0;
DWORD Rbld = 0;
};
// This minimum should match the version installed by
// Engine/Extras/Redist/en-us/vc_redist.x64.exe
static const VersionInfo MinRedistVersion = { 14, 42, 34438, 0 };
bool IsVersionValid(const VersionInfo& Version, const VersionInfo& MinVersion)
{
if (Version.Major > MinVersion.Major) return true;
if (Version.Major == MinVersion.Major && Version.Minor > MinVersion.Minor) return true;
if (Version.Major == MinVersion.Major && Version.Minor == MinVersion.Minor && Version.Bld > MinVersion.Bld) return true;
if (Version.Major == MinVersion.Major && Version.Minor == MinVersion.Minor && Version.Bld == MinVersion.Bld && Version.Rbld >= MinVersion.Rbld) return true;
return false;
}
WCHAR* ReadResourceString(HMODULE ModuleHandle, LPCWSTR Name)
{
WCHAR* Result = NULL;
HRSRC ResourceHandle = FindResource(ModuleHandle, Name, RT_RCDATA);
if(ResourceHandle != NULL)
{
HGLOBAL AllocHandle = LoadResource(ModuleHandle, ResourceHandle);
if(AllocHandle != NULL)
{
WCHAR* Data = (WCHAR*)LockResource(AllocHandle);
DWORD DataLen = SizeofResource(ModuleHandle, ResourceHandle) / sizeof(WCHAR);
Result = new WCHAR[DataLen + 1];
memcpy(Result, Data, DataLen * sizeof(WCHAR));
Result[DataLen] = 0;
}
}
return Result;
}
bool TryLoadDll(const WCHAR* ExecDirectory, const WCHAR* Name)
{
WCHAR AppLocalPath[MAX_PATH];
if (PathCombine(AppLocalPath, ExecDirectory, Name) == nullptr)
{
return false;
}
HMODULE Handle = LoadLibrary(AppLocalPath);
if (Handle != nullptr)
{
FreeLibrary(Handle);
return true;
}
return false;
}
bool TryLoadDll(const WCHAR* Name)
{
return TryLoadDll(nullptr, Name);
}
bool TryGetFileVersionInfo(const WCHAR* ExecDirectory, const WCHAR* Name, VersionInfo& outVersionInfo)
{
WCHAR Path[MAX_PATH];
if (PathCombine(Path, ExecDirectory, Name) == nullptr)
{
return false;
}
DWORD VersionSize = GetFileVersionInfoSize(Path, nullptr);
if (VersionSize == 0)
{
return false;
}
LPSTR pVersionData = new char[VersionSize];
if (!GetFileVersionInfo(Path, 0, VersionSize, pVersionData))
{
delete[] pVersionData;
return false;
}
VS_FIXEDFILEINFO* pFileInfo = nullptr;
UINT FileInfoLen = 0;
if (!VerQueryValue(pVersionData, L"\\", (LPVOID*)&pFileInfo, &FileInfoLen) && FileInfoLen != 0)
{
delete[] pVersionData;
return false;
}
outVersionInfo.Major = (pFileInfo->dwFileVersionMS >> 16) & 0xffff;
outVersionInfo.Minor = (pFileInfo->dwFileVersionMS >> 0) & 0xffff;
outVersionInfo.Bld = (pFileInfo->dwFileVersionLS >> 16) & 0xffff;
outVersionInfo.Rbld = (pFileInfo->dwFileVersionLS >> 0) & 0xffff;
delete[] pVersionData;
return true;
}
bool TryGetFileVersionInfo(const WCHAR* Name, VersionInfo& outVersionInfo)
{
return TryGetFileVersionInfo(nullptr, Name, outVersionInfo);
}
bool IsDllValid(const WCHAR* ExecDirectory, const WCHAR* Name, const VersionInfo& RequiredVersion)
{
VersionInfo DllInfo;
return TryGetFileVersionInfo(ExecDirectory, Name, DllInfo) && IsVersionValid(DllInfo, RequiredVersion) && TryLoadDll(ExecDirectory, Name);
}
bool IsDllValid(const WCHAR* Name, const VersionInfo& RequiredVersion)
{
return IsDllValid(nullptr, Name, RequiredVersion);
}
bool HasAppxPackagedVCRuntime()
{
static const WCHAR* PackageFamilyNameVCLibs = TEXT("Microsoft.VCLibs.140.00.UWPDesktop_8wekyb3d8bbwe");
// try to find the GetCurrentPackageInfo function
HMODULE hModule = GetModuleHandleW(TEXT("kernel32.dll"));
typedef LONG(WINAPI *GetCurrentPackageInfoProc)(const UINT32, UINT32*, BYTE*, UINT32*);
GetCurrentPackageInfoProc fnGetCurrentPackageInfo = hModule ? (GetCurrentPackageInfoProc)GetProcAddress(hModule, "GetCurrentPackageInfo") : nullptr;
// attempt to enumerate the package dependencies & check if there is a dependency on the VCLibs package
bool bHasVCLibs = false;
if (fnGetCurrentPackageInfo != nullptr)
{
UINT32 BufferLength = 0;
LONG Result = fnGetCurrentPackageInfo(PACKAGE_FILTER_DIRECT, &BufferLength, nullptr, nullptr);
if (Result == ERROR_INSUFFICIENT_BUFFER)
{
UINT32 Count = 0;
PACKAGE_INFO* PackageInfo = (PACKAGE_INFO*)malloc(BufferLength);
Result = fnGetCurrentPackageInfo(PACKAGE_FILTER_DIRECT, &BufferLength, (BYTE*)PackageInfo, &Count);
if (Result == ERROR_SUCCESS && PackageInfo != nullptr)
{
for (UINT32 Index = 0; Index < Count; Index++)
{
if (wcscmp(PackageInfo[Index].packageFamilyName, PackageFamilyNameVCLibs ) == 0)
{
// note: not checking PackageInfo[Index].packageId.version against MinRedistVersion because unfortunately the Windows Store version trails behind MSVC
bHasVCLibs = true;
break;
}
}
}
free(PackageInfo);
}
}
return bHasVCLibs;
}
bool IsARM64HostPlatform()
{
// try to find the IsWow64Process2 function
HMODULE hModule = GetModuleHandleW(TEXT("kernel32.dll"));
typedef BOOL( WINAPI *IsWow64Process2Proc)(HANDLE, USHORT*, USHORT*);
IsWow64Process2Proc fnIsWow64Process2 = hModule ? (IsWow64Process2Proc)GetProcAddress(hModule, "IsWow64Process2") : nullptr;
// query the native machine
if (fnIsWow64Process2 != nullptr)
{
USHORT ProcessMachine, NativeMachine;
if (fnIsWow64Process2( GetCurrentProcess(), &ProcessMachine, &NativeMachine))
{
return (NativeMachine == IMAGE_FILE_MACHINE_ARM64);
}
}
return false;
}
int InstallMissingPrerequisites(const WCHAR* BaseDirectory, const WCHAR* ExecDirectory)
{
bool bIsARM64HostPlatform = IsARM64HostPlatform();
// Look for missing prerequisites
WCHAR MissingPrerequisites[1024] = { 0, };
// The Microsoft Visual C++ Runtime includes support for VS2015, VS2017, VS2019, and VS2022
// https://docs.microsoft.com/en-us/cpp/windows/redistributing-visual-cpp-files?view=msvc-170
{
bool bInstallVCRedist = true;
// Check the file version of bundled redist dlls
if (ExecDirectory != nullptr &&
IsDllValid(ExecDirectory, L"msvcp140_2.dll", MinRedistVersion) &&
IsDllValid(ExecDirectory, L"vcruntime140_1.dll", MinRedistVersion))
{
bInstallVCRedist = false;
}
// Check if we are part of an appx package with an embedded dependency on the VC runtime libraries
if (bInstallVCRedist && HasAppxPackagedVCRuntime())
{
bInstallVCRedist = false;
}
// If no bundled redist dlls are available, check the registry for the installed redist,
if (bInstallVCRedist)
{
HKEY Hkey;
LSTATUS KeyOpenStatus = bIsARM64HostPlatform
? RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\arm64", 0, KEY_READ, &Hkey)
: RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\VisualStudio\\14.0\\VC\\Runtimes\\x64", 0, KEY_READ, &Hkey);
;
if (KeyOpenStatus == ERROR_SUCCESS)
{
auto RegGetDwordOrZero = [](HKEY Hkey, LPCWSTR Name) -> DWORD
{
DWORD Value = 0;
DWORD ValueSize = sizeof Value;
LSTATUS Status = RegQueryValueExW(Hkey, Name, NULL, NULL, (LPBYTE)&Value, &ValueSize);
return ERROR_SUCCESS == Status ? Value : 0;
};
const VersionInfo InstalledVersion = {
RegGetDwordOrZero(Hkey, L"Major"),
RegGetDwordOrZero(Hkey, L"Minor"),
RegGetDwordOrZero(Hkey, L"Bld"),
RegGetDwordOrZero(Hkey, L"Rbld")
};
RegCloseKey(Hkey);
if (IsVersionValid(InstalledVersion, MinRedistVersion))
{
// it is possible that the redist has been uninstalled but the registry entries have not been removed
// test that some relatively new dlls are able to be loaded from system32
WCHAR SystemRoot[MAX_PATH] = { 0 };
GetEnvironmentVariable(L"SystemRoot", SystemRoot, MAX_PATH);
WCHAR System32Path[MAX_PATH] = { 0 };
PathCombine(System32Path, SystemRoot, L"system32");
if (IsDllValid(System32Path, L"msvcp140_2.dll", MinRedistVersion) &&
IsDllValid(System32Path, L"vcruntime140_1.dll", MinRedistVersion))
{
bInstallVCRedist = false;
}
// test that some relatively new dlls are able to be loaded with no path if not found in system32
else if (IsDllValid(L"msvcp140_2.dll", MinRedistVersion) &&
IsDllValid(L"vcruntime140_1.dll", MinRedistVersion))
{
bInstallVCRedist = false;
}
}
}
if (bInstallVCRedist)
{
wcscat_s(MissingPrerequisites, TEXT("Microsoft Visual C++ 2015-2022 Redistributable "));
if (bIsARM64HostPlatform)
{
wcscat_s(MissingPrerequisites, TEXT("(arm64)\n"));
}
else
{
wcscat_s(MissingPrerequisites, TEXT("(x64)\n"));
}
}
}
}
// Check if there's anything missing
if(MissingPrerequisites[0] != 0)
{
WCHAR MissingPrerequisitesMsg[1024];
wsprintf(MissingPrerequisitesMsg, L"The following component(s) are required to run this program:\n\n%s", MissingPrerequisites);
// If we don't have the installer, just notify the user and quit
WCHAR PrereqInstaller[MAX_PATH];
if (bIsARM64HostPlatform)
{
PathCombine(PrereqInstaller, BaseDirectory, L"Engine\\Extras\\Redist\\en-us\\vc_redist.arm64.exe");
}
else
{
PathCombine(PrereqInstaller, BaseDirectory, L"Engine\\Extras\\Redist\\en-us\\vc_redist.x64.exe");
}
if(GetFileAttributes(PrereqInstaller) == INVALID_FILE_ATTRIBUTES)
{
MessageBox(NULL, MissingPrerequisitesMsg, NULL, MB_OK);
return 9001;
}
// Otherwise ask them if they want to install them
wcscat_s(MissingPrerequisitesMsg, L"\nWould you like to install them now?");
if(MessageBox(NULL, MissingPrerequisitesMsg, NULL, MB_YESNO) == IDNO)
{
return 9002;
}
WCHAR PrereqParameters[1024] = { 0, };
wcscat_s(PrereqParameters, L"/passive /norestart");
// Start the installer
SHELLEXECUTEINFO ShellExecuteInfo;
ZeroMemory(&ShellExecuteInfo, sizeof(ShellExecuteInfo));
ShellExecuteInfo.cbSize = sizeof(ShellExecuteInfo);
ShellExecuteInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
ShellExecuteInfo.nShow = SW_SHOWNORMAL;
ShellExecuteInfo.lpFile = PrereqInstaller;
ShellExecuteInfo.lpParameters = PrereqParameters;
if(!ShellExecuteExW(&ShellExecuteInfo))
{
return 9003;
}
// Wait for the process to complete, then get its exit code
DWORD ExitCode = 0;
WaitForSingleObject(ShellExecuteInfo.hProcess, INFINITE);
GetExitCodeProcess(ShellExecuteInfo.hProcess, &ExitCode);
CloseHandle(ShellExecuteInfo.hProcess);
// 1638: Newer version already installed
if(ExitCode != 0 && ExitCode != 1638)
{
return 9004;
}
}
return 0;
}
int SpawnTarget(WCHAR* CmdLine)
{
STARTUPINFO StartupInfo;
ZeroMemory(&StartupInfo, sizeof(StartupInfo));
StartupInfo.cb = sizeof(StartupInfo);
PROCESS_INFORMATION ProcessInfo;
ZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
if(!CreateProcess(NULL, CmdLine, NULL, NULL, TRUE, 0, NULL, NULL, &StartupInfo, &ProcessInfo))
{
DWORD ErrorCode = GetLastError();
WCHAR* Buffer = new WCHAR[wcslen(CmdLine) + 50];
wsprintf(Buffer, L"Couldn't start:\n%s\nCreateProcess() returned %x.", CmdLine, ErrorCode);
MessageBoxW(NULL, Buffer, NULL, MB_OK);
delete[] Buffer;
return 9005;
}
WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
DWORD ExitCode = 9006;
GetExitCodeProcess(ProcessInfo.hProcess, &ExitCode);
CloseHandle(ProcessInfo.hThread);
CloseHandle(ProcessInfo.hProcess);
return (int)ExitCode;
}
WCHAR* FindBestExecFile(HINSTANCE hInstance, const WCHAR* BaseDirectory)
{
bool bIsARM64HostPlatform = IsARM64HostPlatform();
if (bIsARM64HostPlatform)
{
WCHAR ExePath[MAX_PATH];
WCHAR* ExecFileARM64 = ReadResourceString(hInstance, MAKEINTRESOURCE(IDI_EXEC_FILE_ARM64));
if (ExecFileARM64 != nullptr)
{
wsprintf(ExePath, L"%s\\%s", BaseDirectory, ExecFileARM64);
if(GetFileAttributes(ExecFileARM64) != INVALID_FILE_ATTRIBUTES)
{
return ExecFileARM64;
}
delete[] ExecFileARM64;
}
WCHAR* ExecFileARM64EC = ReadResourceString(hInstance, MAKEINTRESOURCE(IDI_EXEC_FILE_ARM64EC));
if (ExecFileARM64EC != nullptr)
{
wsprintf(ExePath, L"%s\\%s", BaseDirectory, ExecFileARM64EC);
if(GetFileAttributes(ExecFileARM64EC) != INVALID_FILE_ATTRIBUTES)
{
return ExecFileARM64EC;
}
delete[] ExecFileARM64EC;
}
}
return ReadResourceString(hInstance, MAKEINTRESOURCE(IDI_EXEC_FILE));
}
int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR CmdLine, _In_ int ShowCmd)
{
(void)hPrevInstance;
(void)ShowCmd;
// Get the current module filename
WCHAR CurrentModuleFile[MAX_PATH];
GetModuleFileNameW(hInstance, CurrentModuleFile, MAX_PATH);
// Get the base directory from the current module filename
WCHAR BaseDirectory[MAX_PATH];
PathCanonicalize(BaseDirectory, CurrentModuleFile);
PathRemoveFileSpec(BaseDirectory);
// Get the executable to run
WCHAR* ExecFile = FindBestExecFile(hInstance, BaseDirectory);
if(ExecFile == NULL)
{
MessageBoxW(NULL, L"This program is used for packaged games and is not meant to be run directly.", NULL, MB_OK);
return 9000;
}
// Get the directory containing the target to be executed
WCHAR* TempExecDirectory = new WCHAR[wcslen(BaseDirectory) + wcslen(ExecFile) + 20];
wsprintf(TempExecDirectory, L"%s\\%s", BaseDirectory, ExecFile);
WCHAR ExecDirectory[MAX_PATH];
PathCanonicalize(ExecDirectory, TempExecDirectory);
delete[] TempExecDirectory;
PathRemoveFileSpec(ExecDirectory);
// Create a full command line for the program to run
WCHAR* BaseArgs = ReadResourceString(hInstance, MAKEINTRESOURCE(IDI_EXEC_ARGS));
size_t ChildCmdLineLength = wcslen(BaseDirectory) + wcslen(ExecFile) + wcslen(BaseArgs) + wcslen(CmdLine) + 20;
WCHAR* ChildCmdLine = new WCHAR[ChildCmdLineLength];
swprintf(ChildCmdLine, ChildCmdLineLength, L"\"%s\\%s\" %s %s", BaseDirectory, ExecFile, BaseArgs, CmdLine);
delete[] BaseArgs;
delete[] ExecFile;
// Install the prerequisites
int ExitCode = InstallMissingPrerequisites(BaseDirectory, ExecDirectory);
if(ExitCode != 0)
{
delete[] ChildCmdLine;
return ExitCode;
}
// Spawn the target executable
ExitCode = SpawnTarget(ChildCmdLine);
if(ExitCode != 0)
{
delete[] ChildCmdLine;
return ExitCode;
}
delete[] ChildCmdLine;
return ExitCode;
}