mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
3338 lines
92 KiB
C++
3338 lines
92 KiB
C++
// Bootstrapper.cpp : Defines the entry point for the application.
|
|
//
|
|
|
|
#include "StdAfx.h"
|
|
#include "Bootstrapper.h"
|
|
|
|
#include <windows.h>
|
|
#include <Psapi.h>
|
|
|
|
#include "VersionInfo.h"
|
|
#include "FileSystem.h"
|
|
#include "atlpath.h"
|
|
#include "atlsync.h"
|
|
#include "format_string.h"
|
|
#include "ShutdownRobloxApp.h"
|
|
#include "shellapi.h"
|
|
#include "ShutdownDialog.h"
|
|
#include "MD5Hasher.h"
|
|
#include "SharedLauncher.h"
|
|
#include "StringConv.h"
|
|
#include "RobloxServicesTools.h"
|
|
|
|
#include <boost/thread/thread.hpp>
|
|
#include <boost/bind.hpp>
|
|
#include <boost/lexical_cast.hpp>
|
|
#include <vector>
|
|
#include <strstream>
|
|
|
|
#include <shlguid.h>
|
|
#include <shobjidl.h>
|
|
#include <iphlpapi.h>
|
|
|
|
#include "VistaTools.h"
|
|
|
|
#include "Aclapi.h"
|
|
#include "Sddl.h"
|
|
#include "processinformation.h"
|
|
#include "CookiesEngine.h"
|
|
#include "SharedHelpers.h"
|
|
#include "HttpTools.h"
|
|
#include "GoogleAnalyticsHelper.h"
|
|
|
|
#include <rapidjson/document.h>
|
|
#include <rapidjson/writer.h>
|
|
#include <rapidjson/stringbuffer.h>
|
|
|
|
#include "Sensapi.h"
|
|
#pragma comment(lib, "Sensapi.lib")
|
|
|
|
// TODO - translation/localization
|
|
using RBX::utf8_encode;
|
|
|
|
void Bootstrapper::CreateProcess(const TCHAR* module, const TCHAR* args, PROCESS_INFORMATION& pi)
|
|
{
|
|
LOG_ENTRY2("Bootstrapper::CreateProcess module = %S, args = %S", module, args);
|
|
const int BufSize = 2048;
|
|
STARTUPINFO si = {0};
|
|
si.cb = sizeof(si);
|
|
|
|
TCHAR cmd[BufSize] = {0};
|
|
if (module[0] != _T('\"'))
|
|
{
|
|
wcscpy_s(cmd, BufSize, _T("\""));
|
|
wcscat_s(cmd, BufSize, module);
|
|
wcscat_s(cmd, BufSize, _T("\""));
|
|
}
|
|
else
|
|
{
|
|
wcscpy_s(cmd, BufSize, module);
|
|
}
|
|
|
|
wcscat_s(cmd, BufSize, _T(" "));
|
|
wcscat_s(cmd, BufSize, args);
|
|
|
|
throwLastError(::CreateProcess(NULL, cmd, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi), format_string("CreateProcess %S failed", module));
|
|
}
|
|
|
|
class CSimpleModule : public CAtlModule
|
|
{
|
|
public:
|
|
CSimpleModule() {}
|
|
virtual HRESULT AddCommonRGSReplacements(IRegistrarBase*) { return S_OK; }
|
|
int WinMain(HINSTANCE hInstance, int nShowCmd, Bootstrapper*(*newBootstrapper)(HINSTANCE)) throw()
|
|
{
|
|
if (CAtlBaseModule::m_bInitFailed)
|
|
{
|
|
ATLASSERT(0);
|
|
return -1;
|
|
}
|
|
|
|
int result = 0;
|
|
{
|
|
boost::shared_ptr<Bootstrapper> bootstrapper;
|
|
bool encounteredProblem = false;
|
|
try
|
|
{
|
|
bootstrapper = Bootstrapper::Create(hInstance, newBootstrapper);
|
|
|
|
if (bootstrapper)
|
|
{
|
|
bootstrapper->RegisterEvent(_T("BootstrapperStarted"));
|
|
}
|
|
if (bootstrapper->isWindowed())
|
|
{
|
|
if (bootstrapper->isSilentMode())
|
|
{
|
|
LLOG_ENTRY(bootstrapper->logger, "CSimpleModule::WinMain Waiting for bootstrapper to finish in silent mode");
|
|
bool result = bootstrapper->WaitForCompletion();
|
|
LLOG_ENTRY1(bootstrapper->logger, "CSimpleModule::WinMain - robloxReady, wait timeout = %d", result);
|
|
}
|
|
else
|
|
{
|
|
LLOG_ENTRY1(bootstrapper->logger, "CSimpleModule::WinMain current thread id = %d", ::GetCurrentThreadId());
|
|
|
|
MSG msg;
|
|
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR));
|
|
while (GetMessage(&msg, NULL, 0, 0))
|
|
{
|
|
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
|
|
{
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
}
|
|
}
|
|
|
|
// windowed mode runs tasks asynchronously,
|
|
// throw exceptions here so we can catch anything that happened in worker thread
|
|
if (bootstrapper->hasUserCancel())
|
|
throw cancel_exception(true);
|
|
|
|
if (bootstrapper->hasErrors())
|
|
throw std::exception();
|
|
}
|
|
|
|
|
|
bootstrapper->Close();
|
|
|
|
LLOG_ENTRY(bootstrapper->logger, "CSimpleModule::WinMain Bring app to front.");
|
|
HWND hWnd = bootstrapper->getLaunchedAppHwnd();
|
|
if (hWnd)
|
|
{
|
|
::SetFocus(hWnd);
|
|
SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
|
|
SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
|
|
SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
|
|
}
|
|
|
|
LLOG_ENTRY(bootstrapper->logger, "Left CSimpleModule::WinMain");
|
|
}
|
|
catch (cancel_exception&)
|
|
{
|
|
if (bootstrapper)
|
|
{
|
|
if (bootstrapper->PerModeLoggingEnabled()) {
|
|
encounteredProblem = true;
|
|
bootstrapper->ReportFinishing("Cancel");
|
|
}
|
|
bootstrapper->RegisterEvent(_T("BootstrapperFinishedCancel"));
|
|
}
|
|
result = -1;
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
if (bootstrapper)
|
|
{
|
|
if (bootstrapper->PerModeLoggingEnabled()) {
|
|
encounteredProblem = true;
|
|
bootstrapper->ReportFinishing("Error");
|
|
}
|
|
bootstrapper->error(e);
|
|
|
|
if (bootstrapper->IsPlayMode())
|
|
{
|
|
bootstrapper->RegisterEvent(_T("BootstrapperPlayFinishedFailure"));
|
|
}
|
|
|
|
bootstrapper->RegisterEvent(_T("BootstrapperFinishedFailure"));
|
|
}
|
|
else
|
|
{
|
|
CString message;
|
|
message.Format(_T("An error occured, watrbx cannot continue.\n\n%S"), e.what());
|
|
::MessageBox(NULL, message, _T("Error"), MB_OK | MB_ICONEXCLAMATION);
|
|
}
|
|
result = -1;
|
|
}
|
|
|
|
if(bootstrapper){
|
|
if (bootstrapper->PerModeLoggingEnabled() && !encounteredProblem) {
|
|
bootstrapper->ReportFinishing("Success");
|
|
}
|
|
if (bootstrapper->hasErrors()){
|
|
bootstrapper->postLogFile(true);
|
|
}
|
|
else if(bootstrapper->hasLongUserCancel()){
|
|
if (((rand() % 10) == 0))
|
|
{
|
|
bootstrapper->postLogFile(false);
|
|
}
|
|
} else {
|
|
if (bootstrapper->IsPlayMode())
|
|
{
|
|
bootstrapper->RegisterEvent(_T("BootstrapperPlayFinishedSuccess"));
|
|
bootstrapper->RegisterEvent(_T("BootstrapperPlayFinishedSuccess2")); // TODO: remove
|
|
}
|
|
bootstrapper->RegisterEvent(_T("BootstrapperFinishedSuccess"));
|
|
bootstrapper->RegisterEvent(_T("BootstrapperFinishedSuccess2")); // TODO: remove
|
|
bootstrapper->ReportElapsedEvent(bootstrapper->IsPlayMode());
|
|
}
|
|
}
|
|
|
|
if(bootstrapper && bootstrapper->hasReturnValue()){
|
|
result = bootstrapper->ExitCode();
|
|
//CString message;
|
|
//message.Format("FailIfNotUpToDate result = %d\n",result);
|
|
//::MessageBox(NULL, message, "Error", MB_OK | MB_ICONEXCLAMATION);
|
|
}
|
|
|
|
if (bootstrapper)
|
|
{
|
|
bootstrapper->FlushEvents();
|
|
|
|
// one more time for good measure
|
|
LLOG_ENTRY(bootstrapper->logger, "CSimpleModule::WinMain Bring app to front again.");
|
|
HWND hWnd = bootstrapper->getLaunchedAppHwnd();
|
|
if (hWnd)
|
|
{
|
|
::SetFocus(hWnd);
|
|
if (!SetWindowPos(hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE))
|
|
{
|
|
LLOG_ENTRY1(bootstrapper->logger, "SetWindowPos TOPMOST failed: %S", (LPCTSTR)GetLastErrorDescription());
|
|
}
|
|
|
|
SetWindowPos(hWnd, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
|
|
SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE|SWP_NOSIZE);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef _DEBUG
|
|
// Prevent false memory leak reporting. ~CAtlWinModule may be too late.
|
|
_AtlWinModule.Term();
|
|
#endif // _DEBUG
|
|
|
|
return result;
|
|
}
|
|
};
|
|
|
|
CSimpleModule module;
|
|
|
|
|
|
int BootstrapperMain(HINSTANCE hInstance, int nCmdShow, Bootstrapper*(*newBootstrapper)(HINSTANCE))
|
|
{
|
|
return module.WinMain(hInstance, nCmdShow, newBootstrapper);
|
|
}
|
|
|
|
bool IsWinXP()
|
|
{
|
|
OSVERSIONINFO osver = {0};
|
|
|
|
osver.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );
|
|
|
|
if (::GetVersionEx(&osver) && osver.dwPlatformId == VER_PLATFORM_WIN32_NT && osver.dwMajorVersion == 5)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool IsWin7()
|
|
{
|
|
OSVERSIONINFO osver = {0};
|
|
|
|
osver.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );
|
|
|
|
if (::GetVersionEx(&osver) && osver.dwMajorVersion == 6 && osver.dwMinorVersion == 1)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
#include <atlsecurity.h>
|
|
|
|
bool IsAdminRunning(void)
|
|
{
|
|
ATL::CAccessToken ProcToken;
|
|
ATL::CAccessToken ImpersonationToken;
|
|
ATL::CSid UserSid(Sids::Admins());
|
|
|
|
ProcToken.GetEffectiveToken(TOKEN_READ | TOKEN_DUPLICATE);
|
|
ProcToken.CreateImpersonationToken(&ImpersonationToken);
|
|
bool result = false;
|
|
ImpersonationToken.CheckTokenMembership(UserSid, &result);
|
|
|
|
return result;
|
|
}
|
|
|
|
Bootstrapper::Bootstrapper(HINSTANCE hInstance)
|
|
:hInstance(hInstance),
|
|
dialog(CMainDialog::Create(hInstance)),
|
|
mainThreadId(::GetCurrentThreadId()),
|
|
robloxAppArgs(_T("-browser")), // By default start RobloxApp.exe as a web browser
|
|
force(false),
|
|
isUninstall(false),
|
|
isNop(false),
|
|
requestedInstall(false),
|
|
performedNewInstall(false),
|
|
performedUpdateInstall(false),
|
|
performedBootstrapperUpdateAndQuit(false),
|
|
isFailIfNotUpToDate(false),
|
|
stage(0),
|
|
windowed(true),
|
|
editMode(false),
|
|
debug(false),
|
|
windowDelay(0),
|
|
userCancelled(false),
|
|
perUser(true),
|
|
adminInstallDetected(false),
|
|
exitCode(0),
|
|
fullCheck(false),
|
|
silentMode(false),
|
|
dontStartApp(false),
|
|
robloxReady(FALSE, FALSE, NULL, NULL),
|
|
noRun(false),
|
|
waitOnStart(true),
|
|
launchedAppHwnd(NULL),
|
|
influxdbLottery(0)
|
|
{
|
|
startTickCounter = GetTickCount();
|
|
LOG_ENTRY("Bootstrapper::Bootstrapper");
|
|
LOG_ENTRY1("Main threadID %d", mainThreadId);
|
|
|
|
SYSTEMTIME sysTime = {0};
|
|
GetSystemTime(&sysTime);
|
|
|
|
CString strMessage;
|
|
strMessage.Format(_T("%02d-%02d-%d %02d:%02d:%02d"), sysTime.wMonth, sysTime.wDay, sysTime.wYear, sysTime.wHour, sysTime.wMinute, sysTime.wSecond);
|
|
|
|
LOG_ENTRY1("Start time: %S", strMessage.GetString());
|
|
|
|
LogMACaddress();
|
|
|
|
srand(GetTickCount());
|
|
|
|
reportStatGuid = rand();
|
|
|
|
{
|
|
CString foo;
|
|
foo.LoadString(IDS_INSTALLHOST);
|
|
installHost = convert_w2s(foo.GetString());
|
|
LOG_ENTRY1("installHost: %s", installHost.c_str());
|
|
}
|
|
{
|
|
CString foo;
|
|
foo.LoadString(IDS_BASEHOST);
|
|
baseHost = convert_w2s(foo.GetString());
|
|
LOG_ENTRY1("baseHost: %s", baseHost.c_str());
|
|
}
|
|
}
|
|
|
|
void Bootstrapper::SetupInfluxDb()
|
|
{
|
|
// throttle
|
|
influxdbLottery = rand() % 10000;
|
|
|
|
InfluxDb::init(convert_w2s(GetBootstrapperFileName()), InfluxDbUrl(), InfluxDbDatabase(), InfluxDbUser(), InfluxDbPassword());
|
|
|
|
TCHAR buffer[256];
|
|
GEOID id = GetUserGeoID(GEOCLASS_NATION);
|
|
if (id != GEOID_NOT_AVAILABLE)
|
|
GetGeoInfo(id, GEO_FRIENDLYNAME, buffer, 256, 0);
|
|
char loc[256];
|
|
size_t outSize;
|
|
wcstombs_s(&outSize, loc, 256, buffer, 256);
|
|
InfluxDb::setLocation((id != GEOID_NOT_AVAILABLE) ? loc : "");
|
|
|
|
CVersionInfo vi;
|
|
vi.Load(_AtlBaseModule.m_hInst);
|
|
std::string v = vi.GetFileVersionAsDotString();
|
|
InfluxDb::setAppVersion(v.c_str());
|
|
}
|
|
|
|
void Bootstrapper::ReportToInfluxDbImpl(const std::string& json)
|
|
{
|
|
if (InfluxDb::getUrlHost().empty() || InfluxDb::getUrlPath().empty())
|
|
return;
|
|
|
|
try
|
|
{
|
|
std::ostrstream result;
|
|
HttpTools::httpPost(this, InfluxDb::getUrlHost(), InfluxDb::getUrlPath(), std::stringstream(json), "application/json",
|
|
result, false, boost::bind(&Bootstrapper::dummyProgress, _1, _2), false);
|
|
}
|
|
catch(std::exception &e)
|
|
{
|
|
LOG_ENTRY1("Error in reporting influx: %s", e.what());
|
|
}
|
|
}
|
|
void Bootstrapper::ReportToInfluxDb(int throttle, const std::string& json, bool blocking)
|
|
{
|
|
if (influxdbLottery >= throttle)
|
|
return;
|
|
|
|
if (!blocking)
|
|
boost::thread(boost::bind(&Bootstrapper::ReportToInfluxDbImpl, this, json));
|
|
else
|
|
ReportToInfluxDbImpl(json);
|
|
}
|
|
|
|
void Bootstrapper::ReportInstallMetricsToInfluxDb(bool isUpdate, const std::string& result, const std::string& message)
|
|
{
|
|
InfluxDb points;
|
|
points.addPoint("Type", isUpdate ? "Update" : "Install");
|
|
points.addPoint("Result", result.c_str());
|
|
points.addPoint("Message", message.c_str());
|
|
points.addPoint("ElapsedTime", (GetElapsedTime() / 1000.0f));
|
|
points.addPoint("BytesDownloaded", (int)deployer->GetBytesDownloaded());
|
|
ReportToInfluxDb(InfluxDbInstallHundredthsPercentage(), points.getJsonStringForPosting("PCInstall"), false);
|
|
}
|
|
|
|
DWORD Bootstrapper::GetElapsedTime()
|
|
{
|
|
return (GetTickCount() - startTickCounter);
|
|
}
|
|
|
|
void Bootstrapper::ReportElapsedEvent(bool startingGameClient)
|
|
{
|
|
DWORD elapsedTime = GetElapsedTime();
|
|
std::wstring eventName;
|
|
std::wstring prefix;
|
|
std::wstring timeDelta;
|
|
if (startingGameClient)
|
|
{
|
|
prefix = _T("Play");
|
|
}
|
|
|
|
if (elapsedTime < 5000)
|
|
{
|
|
timeDelta = _T("0_5");
|
|
}
|
|
else if (elapsedTime >= 5000 && elapsedTime < 15000)
|
|
{
|
|
timeDelta = _T("5_15");
|
|
}
|
|
else
|
|
{
|
|
timeDelta = _T("15_INF");
|
|
}
|
|
|
|
eventName = format_string(_T("Bootstrapper%sRun%s"), prefix.c_str(), timeDelta.c_str());
|
|
RegisterEvent(eventName.c_str());
|
|
}
|
|
|
|
void Bootstrapper::reportDurationAndSizeEvent(const char* category, const char* result, DWORD duration, DWORD size) {
|
|
try {
|
|
|
|
// TODO: Remove calls to JoinRate.ashx
|
|
std::string handlerUrlPart = format_string(
|
|
"/Game/JoinRate.ashx?c=%s&r=%s&d=%ld&platform=Win32",
|
|
category, result, duration);
|
|
if (size > 0) {
|
|
handlerUrlPart += format_string("&b=%ld", size);
|
|
}
|
|
|
|
std::ostrstream result;
|
|
HttpTools::httpGet(this, baseHost, handlerUrlPart, std::string(),
|
|
result, false, boost::bind(&Bootstrapper::dummyProgress, _1, _2));
|
|
////////////////////////////////
|
|
|
|
|
|
// new stats reporting api
|
|
char* apiPathFormatStr = "/game/report-stats?name=%s&value=%ld";
|
|
|
|
std::string durationName = format_string("%s%s_Duration", category, result);
|
|
HttpTools::httpPost(this, baseHost, format_string(apiPathFormatStr, durationName.c_str(), duration), std::stringstream(""), "*/*",
|
|
result, false, boost::bind(&Bootstrapper::dummyProgress, _1, _2));
|
|
|
|
if (size > 0)
|
|
{
|
|
std::string sizeName = format_string("%s%s_Size", category, result);
|
|
HttpTools::httpPost(this, baseHost, format_string(apiPathFormatStr, sizeName.c_str(), size), std::stringstream(""), "*/*",
|
|
result, false, boost::bind(&Bootstrapper::dummyProgress, _1, _2));
|
|
}
|
|
|
|
} catch (const std::exception& e) {
|
|
LOG_ENTRY1("Error in reporting: %s", e.what());
|
|
}
|
|
}
|
|
|
|
void Bootstrapper::reportFinishingHelper(const char* category, const char* result) {
|
|
// Use bootstrapper type to differentiate reports by pre-pending Type().
|
|
std::string builtCategory(Type());
|
|
builtCategory += category;
|
|
reportDurationAndSizeEvent(builtCategory.c_str(), result, GetElapsedTime(), 0/*no size metric*/);
|
|
|
|
// Add result to category for ephemeral counter
|
|
builtCategory += result;
|
|
RegisterEvent(convert_s2w(builtCategory).c_str());
|
|
|
|
// Report to influxdb
|
|
influxDb.addPoint("Category", category);
|
|
influxDb.addPoint("Result", result);
|
|
influxDb.addPoint("Duration", GetElapsedTime() / 1000.0f); // report duration in seconds
|
|
ReportToInfluxDb(InfluxDbReportHundredthsPercentage(), influxDb.getJsonStringForPosting("BootstrapperDuration"), true);
|
|
|
|
// log client failure by cdn
|
|
if (result != "Success" && Type() == "Client")
|
|
{
|
|
std::string installUrl;
|
|
if (UseNewCdn())
|
|
installUrl = HttpTools::getCdnHost(this);
|
|
if (installUrl.empty())
|
|
installUrl = HttpTools::getPrimaryCdnHost(this);
|
|
|
|
std::string action(category);
|
|
action += result;
|
|
GoogleAnalyticsHelper::trackEvent(logger, GA_CATEGORY_NAME, action.c_str(), installUrl.empty() ? installHost.c_str() : installUrl.c_str());
|
|
}
|
|
}
|
|
|
|
void Bootstrapper::ReportFinishing(const char* result) {
|
|
LOG_ENTRY1("Doing final report with result = %s", result);
|
|
|
|
influxDb.addPoint("ReportFinishing", GetElapsedTime() / 1000.0f);
|
|
|
|
if (requestedInstall) {
|
|
if (performedBootstrapperUpdateAndQuit) {
|
|
reportFinishingHelper("BootstrapperUpdateSelf", result);
|
|
} else {
|
|
// Don't pollute "BootstrapperUpdate*" and "BootstrapperInstall*"
|
|
// ephemeral counter namespaces
|
|
if (performedUpdateInstall) {
|
|
reportFinishingHelper("BootstrapperDetailedUpdate", result);
|
|
} else if (performedNewInstall) {
|
|
reportFinishingHelper("BootstrapperDetailedInstall", result);
|
|
} else {
|
|
reportFinishingHelper("BootstrapperDetailedUnknownInstallOrUpdate", result);
|
|
}
|
|
}
|
|
}
|
|
if (isFailIfNotUpToDate) {
|
|
if (exitCode != 0) {
|
|
reportFinishingHelper("BootstrapperFailIfNotUpToDateNonZeroExit", result);
|
|
} else {
|
|
reportFinishingHelper("BootstrapperFailIfNotUpToDateZeroExit", result);
|
|
}
|
|
}
|
|
if (isNop) {
|
|
reportFinishingHelper("BootstrapperPrePlay", result);
|
|
}
|
|
if (IsPlayMode()) {
|
|
// don't pollute "BootstrapperPlay*" ephermeral counter namespace
|
|
reportFinishingHelper("BootstrapperDetailedPlay", result);
|
|
}
|
|
reportFinishingHelper("BootstrapperDetailed", result);
|
|
}
|
|
|
|
void Bootstrapper::LogMACaddress(void)
|
|
{
|
|
IP_ADAPTER_INFO AdapterInfo[16];
|
|
DWORD dwBufLen = sizeof(AdapterInfo);
|
|
|
|
DWORD dwStatus = GetAdaptersInfo(AdapterInfo, &dwBufLen);
|
|
assert(dwStatus == ERROR_SUCCESS);
|
|
|
|
PIP_ADAPTER_INFO pAdapterInfo = AdapterInfo;
|
|
do {
|
|
if (pAdapterInfo->Type == MIB_IF_TYPE_ETHERNET)
|
|
{
|
|
std::string str = format_string("%02x-%02x-%02x-%02x-%02x-%02x-%02x-%02x", pAdapterInfo->Address[0], pAdapterInfo->Address[1], pAdapterInfo->Address[2], pAdapterInfo->Address[3], pAdapterInfo->Address[4], pAdapterInfo->Address[5], pAdapterInfo->Address[6], pAdapterInfo->Address[7]);
|
|
LOG_ENTRY2("MAC: %s Name: %s", str.c_str(), pAdapterInfo->Description);
|
|
}
|
|
pAdapterInfo = pAdapterInfo->Next;
|
|
} while(pAdapterInfo);
|
|
}
|
|
|
|
void Bootstrapper::initialize()
|
|
{
|
|
LOG_ENTRY("Bootstrapper::initialize");
|
|
|
|
try
|
|
{
|
|
createDialog();
|
|
|
|
dialog->closeCallback = boost::bind(&Bootstrapper::userCancel, this);
|
|
dialog->setTitle(GetFriendlyName());
|
|
|
|
parseCmdLine();
|
|
|
|
bool result = ComModule::Init(perUser);
|
|
if (result != perUser)
|
|
{
|
|
LOG_ENTRY("::GetProcAddress(hmodOleAut, \"RegisterTypeLibForUser\") FAILED, install farced to global mode");
|
|
}
|
|
perUser = result;
|
|
|
|
LOG_ENTRY1("IsVistaPlus = %d", IsVistaPlus());
|
|
if (IsVistaPlus())
|
|
{
|
|
LOG_ENTRY1("IsElevated = %d", IsElevated());
|
|
LOG_ENTRY1("IsUacEnabled = %d", IsUacEnabled());
|
|
}
|
|
else
|
|
{
|
|
LOG_ENTRY1("IsWinXP = %d", IsWinXP());
|
|
if (IsWinXP())
|
|
{
|
|
LOG_ENTRY1("IsAdminRunning = %d", IsAdminRunning());
|
|
}
|
|
}
|
|
|
|
if (perUser)
|
|
{
|
|
if (debug)
|
|
{
|
|
dialog->MessageBox(IsVistaPlus() ? _T("This is Vista") : _T("This is not Vista"), _T("Debug"), MB_OK);
|
|
if (IsVistaPlus())
|
|
{
|
|
dialog->MessageBox(IsElevated() ? _T("This is Elevated") : _T("This is not Elevated"), _T("Debug"), MB_OK);
|
|
if (IsElevated())
|
|
perUser = dialog->MessageBox(_T("Do you want an all-user install?"), _T("DEBUG"), MB_YESNO)==IDNO;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (IsVistaPlus() && IsElevated())
|
|
{
|
|
perUser = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
LOG_ENTRY1("perUser = %d", perUser);
|
|
deployer.reset(new FileDeployer(this, perUser));
|
|
LOG_ENTRY("Bootstrapper::initialize - calling loadPrevVersions");
|
|
loadPrevVersions();
|
|
|
|
std::string installedVersion = queryInstalledVersion();
|
|
if (installedVersion.empty())
|
|
{
|
|
LOG_ENTRY("isUpdating = false");
|
|
}
|
|
else
|
|
{
|
|
LOG_ENTRY1("isUpdating = true, %s", installedVersion.c_str());
|
|
if (IsPlayMode() && HasUnhideGuid())
|
|
{
|
|
silentMode = true;
|
|
}
|
|
}
|
|
|
|
if ((!windowed && dontStartApp) || ForceNoDialogMode())
|
|
{
|
|
silentMode = true;
|
|
}
|
|
|
|
dialog->SetSilentMode(silentMode);
|
|
|
|
if (!perUser)
|
|
if (IsVistaPlus())
|
|
if (!IsElevated())
|
|
{
|
|
if(IsUacEnabled())
|
|
{
|
|
LOG_ENTRY("re-launching elevated");
|
|
// Uh-oh. We're want to do something elevated but we aren't elevated
|
|
std::wstring args;
|
|
for (int i = 1; i < __argc; i++)
|
|
{
|
|
if (!args.empty())
|
|
args += _T(" ");
|
|
args += _T('\"');
|
|
args += __targv[i];
|
|
args += _T('\"');
|
|
}
|
|
|
|
TCHAR bootstrapperFile[_MAX_PATH];
|
|
::GetModuleFileName(NULL, bootstrapperFile, _MAX_PATH);
|
|
|
|
SHELLEXECUTEINFO TempInfo = {0};
|
|
|
|
TempInfo.cbSize = sizeof(SHELLEXECUTEINFO);
|
|
TempInfo.fMask = 0;
|
|
TempInfo.hwnd = NULL;
|
|
TempInfo.lpVerb = _T("runas");
|
|
TempInfo.lpFile = bootstrapperFile;
|
|
TempInfo.lpParameters = args.c_str();
|
|
TempInfo.lpDirectory = NULL;
|
|
TempInfo.nShow = SW_NORMAL;
|
|
|
|
if (::ShellExecuteEx(&TempInfo))
|
|
{
|
|
LOG_ENTRY("ShellExecuteEx succeeded");
|
|
}
|
|
else
|
|
{
|
|
LOG_ENTRY1("ShellExecuteEx failed: %S", (LPCTSTR)GetLastErrorDescription());
|
|
}
|
|
throw installer_error_exception(installer_error_exception::VistaElevation);
|
|
}
|
|
else{
|
|
if (windowed)
|
|
dialog->DisplayError("This operation requires administrator priveleges", NULL);
|
|
|
|
throw installer_error_exception(installer_error_exception::AdminAccountRequiredWithoutUac);
|
|
}
|
|
}
|
|
|
|
|
|
throwHRESULT(classesKey.Create(perUser ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE, _T("Software\\Classes")), "Failed to create HK**\\Software\\Classes");
|
|
LOG_ENTRY1("classesKey = %s\\Software\\Classes", (perUser ? "HKEY_CURRENT_USER" : "HKEY_LOCAL_MACHINE"));
|
|
|
|
if (CookiesEngine::getCookiesFilePath().empty())
|
|
{
|
|
std::wstring dir = FileSystem::getSpecialFolder(FileSystem::RobloxUserApplicationDataLow, true, NULL, NULL);
|
|
// lets make sure that we have roblox dir at this point
|
|
try
|
|
{
|
|
createDirectory(dir.c_str());
|
|
CookiesEngine::setCookiesFilePath(dir + _T("rbxcsettings.rbx"));
|
|
}
|
|
catch (...)
|
|
{
|
|
// we should not fail the game if our reproting is broken somehow
|
|
LOG_ENTRY("Failure during cookie path initialization");
|
|
}
|
|
}
|
|
}
|
|
catch( CAtlException e )
|
|
{
|
|
error(std::runtime_error(format_string("AtlException 0x%x", e.m_hr)));
|
|
throw;
|
|
}
|
|
catch (silent_exception&)
|
|
{
|
|
throw;
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
error(e);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
Bootstrapper::~Bootstrapper(void)
|
|
{
|
|
}
|
|
|
|
void Bootstrapper::postData(std::fstream &data)
|
|
{
|
|
std::strstream result;
|
|
|
|
CVersionInfo vi;
|
|
vi.Load(_AtlBaseModule.m_hInst);
|
|
std::string v = vi.GetFileVersionAsDotString();
|
|
|
|
std::string url = format_string("/Error/InstallLog.ashx?version=%s&stage=%02d&guid=%d", v.c_str(), stage, reportStatGuid);
|
|
HttpTools::httpPost(this, GetUseDataDomain() ? ReplaceTopSubdomain(baseHost, "data") : baseHost, url, data, "text/plain", result, true, boost::bind(&Bootstrapper::dummyProgress, _1, _2));
|
|
result << (char)0;
|
|
LOG_ENTRY1("Uploading log file result: %s", result.str());
|
|
}
|
|
|
|
static bool CmpFileTime(const WIN32_FIND_DATA &left, const WIN32_FIND_DATA &right)
|
|
{
|
|
return (CompareFileTime(&right.ftCreationTime, &left.ftCreationTime) == -1);
|
|
}
|
|
|
|
void Bootstrapper::postLogFile(bool uploadApp)
|
|
{
|
|
LOG_ENTRY("Posting log file");
|
|
try
|
|
{
|
|
const char *corrID = "CorrIDString";
|
|
if (uploadApp)
|
|
{
|
|
LOG_ENTRY2("%s: %d", corrID, reportStatGuid);
|
|
}
|
|
|
|
try
|
|
{
|
|
if (uploadApp)
|
|
{
|
|
std::wstring searchPattern(simple_logger<wchar_t>::get_tmp_path());
|
|
searchPattern += _T("*.rbal");
|
|
|
|
WIN32_FIND_DATA findFileData = {};
|
|
HANDLE handle = INVALID_HANDLE_VALUE;
|
|
handle = FindFirstFile(searchPattern.c_str(), &findFileData);
|
|
std::vector<WIN32_FIND_DATA> files;
|
|
|
|
if(handle != INVALID_HANDLE_VALUE)
|
|
{
|
|
do
|
|
{
|
|
if(findFileData.dwFileAttributes != FILE_ATTRIBUTE_DIRECTORY)
|
|
{
|
|
files.push_back(findFileData);
|
|
}
|
|
}
|
|
while(FindNextFile(handle, &findFileData));
|
|
}
|
|
FindClose(handle);
|
|
|
|
if (!files.empty())
|
|
{
|
|
std::sort(files.begin(), files.end(), CmpFileTime);
|
|
std::wstring file = simple_logger<wchar_t>::get_tmp_path() + std::wstring(files[0].cFileName);
|
|
{
|
|
std::fstream data(file.c_str(), std::ios_base::app);
|
|
data << "Info: RobloxAppLog" << "\n";
|
|
data << corrID << ": " << reportStatGuid << "\n";
|
|
data.flush();
|
|
}
|
|
|
|
std::fstream data(file.c_str(), std::ios_base::in | std::ios_base::binary);
|
|
LOG_ENTRY("Uploading app log file");
|
|
postData(data);
|
|
}
|
|
}
|
|
}
|
|
catch (std::exception &e)
|
|
{
|
|
LOG_ENTRY1("Exception during app log upload %s", e.what());
|
|
}
|
|
|
|
LOG_ENTRY("Uploading install log file");
|
|
std::fstream data(logger.log_filename().c_str(), std::ios_base::in | std::ios_base::binary);
|
|
postData(data);
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
LOG_ENTRY1("Error uploading log file: %s", exceptionToString(e).c_str());
|
|
}
|
|
}
|
|
|
|
void Bootstrapper::parseCmdLine()
|
|
{
|
|
LOG_ENTRY1("Bootstrapper::parseCmdLine params counter = %d", __argc);
|
|
if(__argc == 1){
|
|
fullCheck = true;
|
|
}
|
|
|
|
// check if cmd comes from protocol handler
|
|
wchar_t** args = __targv;
|
|
const wchar_t* protocolScheme = GetProtocolHandlerUrlScheme();
|
|
int protocolSchemeLen = lstrlen(protocolScheme);
|
|
if (!StrNCmp(args[1], protocolScheme, protocolSchemeLen))
|
|
{
|
|
// split args by +
|
|
std::vector<std::wstring> argList = splitOn(args[1], _T('+'));
|
|
|
|
// create arg map so we can find values by name
|
|
std::map<std::wstring, std::wstring> argMap;
|
|
for (unsigned int i = 0; i < argList.size(); i++)
|
|
{
|
|
int separatorPos = argList[i].find(_T(":"));
|
|
if (separatorPos != std::string::npos)
|
|
{
|
|
std::wstring argName = argList[i].substr(0, separatorPos);
|
|
std::transform(argName.begin(), argName.end(), argName.begin(), tolower);
|
|
std::wstring argValue = argList[i].substr(separatorPos + 1, std::wstring::npos);
|
|
argMap.insert(std::make_pair(argName, argValue)); // @cetcat
|
|
}
|
|
else
|
|
{
|
|
std::wstring argName = argList[i];
|
|
std::transform(argName.begin(), argName.end(), argName.begin(), tolower);
|
|
argMap.insert(std::make_pair(argName, std::wstring(_T("")))); // @cetcat
|
|
}
|
|
}
|
|
|
|
// set browser tracking id
|
|
std::map<std::wstring, std::wstring>::const_iterator iter = argMap.find(_T("browsertrackerid"));
|
|
if (iter != argMap.end())
|
|
{
|
|
browserTrackerId = convert_w2s(iter->second);
|
|
LOG_ENTRY1("Bootstrapper::parseCmdLine: browserTracerId, value = %s", browserTrackerId.c_str());
|
|
}
|
|
|
|
iter = argMap.find(_T("launchtime"));
|
|
if (iter != argMap.end())
|
|
{
|
|
int64_t lt = boost::lexical_cast<int64_t>(iter->second);
|
|
|
|
boost::posix_time::ptime const epoch(boost::gregorian::date(1970, 1, 1));
|
|
int64_t ct = (boost::posix_time::microsec_clock::universal_time() - epoch).total_milliseconds();
|
|
|
|
influxDb.addPoint("LaunchTime", (ct-lt)/1000.0f);
|
|
}
|
|
|
|
ProcessProtocolHandlerArgs(argMap);
|
|
influxDb.addPoint("ProtocolHandler", true);
|
|
return;
|
|
}
|
|
|
|
influxDb.addPoint("ProtocolHandler", false);
|
|
for (int i=1; i<__argc; ++i)
|
|
{
|
|
TCHAR* arg1 = __targv[i];
|
|
LOG_ENTRY2("Bootstrapper::parseCmdLine paramIndex = %d, value = %S", i, arg1);
|
|
|
|
if (CAtlModule::WordCmpI(arg1, _T("-wait"))==0) // wait before processing more commands
|
|
{
|
|
if ( i + 1 >= __argc )
|
|
throw std::runtime_error(format_string("Bad argument %s", arg1));
|
|
|
|
int sleepTime = _wtoi(__targv[i + 1]);
|
|
::Sleep(sleepTime);
|
|
i++; // skip time parameter
|
|
}
|
|
else if (CAtlModule::WordCmpI(arg1, _T("-ide"))==0)
|
|
{
|
|
LOG_ENTRY("option: ide");
|
|
windowDelay = 1;
|
|
robloxAppArgs = convert_s2w(SharedLauncher::IDEArgument);
|
|
if (i+1 < __argc && __targv[i+1][0]!='-')
|
|
{
|
|
++i;
|
|
|
|
// Check to see if the next parameter is a file location
|
|
WIN32_FIND_DATA FileInformation; // File information
|
|
HANDLE hFile = ::FindFirstFile(__targv[i], &FileInformation);
|
|
if (hFile != INVALID_HANDLE_VALUE)
|
|
{
|
|
robloxAppArgs += _T(" ");
|
|
robloxAppArgs += convert_s2w(SharedLauncher::FileLocationArgument);
|
|
robloxAppArgs += _T(" \"");
|
|
robloxAppArgs += __targv[i];
|
|
robloxAppArgs += _T("\"");
|
|
}
|
|
}
|
|
}
|
|
else if (CAtlModule::WordCmpI(arg1, _T("-q"))==0)
|
|
{
|
|
LOG_ENTRY("option: q");
|
|
windowed = false;
|
|
}
|
|
else if (CAtlModule::WordCmpI(arg1, _T("-qbg"))==0)
|
|
{
|
|
LOG_ENTRY("option: qbg");
|
|
windowed = false;
|
|
dontStartApp = true;
|
|
}
|
|
else if (CAtlModule::WordCmpI(arg1, _T("-qbgnorun"))==0)
|
|
{
|
|
LOG_ENTRY("option: qbgnorun");
|
|
windowed = false;
|
|
dontStartApp = true;
|
|
noRun = true;
|
|
}
|
|
else if (CAtlModule::WordCmpI(arg1, _T("-alluser"))==0)
|
|
{
|
|
LOG_ENTRY("option: alluser");
|
|
perUser = false;
|
|
}
|
|
else if (CAtlModule::WordCmpI(arg1, _T("-browser"))==0)
|
|
{
|
|
LOG_ENTRY("option: browser");
|
|
windowDelay = 1;
|
|
robloxAppArgs = _T("-browser");
|
|
}
|
|
else if (CAtlModule::WordCmpI(arg1, _T("-uninstall"))==0)
|
|
{
|
|
LOG_ENTRY("option: Uninstall");
|
|
isUninstall = true;
|
|
robloxAppArgs = _T("");
|
|
}
|
|
else if (CAtlModule::WordCmpI(arg1, _T("-install"))==0)
|
|
{
|
|
LOG_ENTRY("option: Install");
|
|
requestedInstall = true;
|
|
// Just update/install roblox, but don't run it
|
|
robloxAppArgs = _T("");
|
|
windowDelay = 10;
|
|
}
|
|
else if(CAtlModule::WordCmpI(arg1, _T("-failIfNotUpToDate"))==0)
|
|
{
|
|
LOG_ENTRY("option: FailIfNotUpToDate");
|
|
//Just check roblox version number, and return "success (0)" if it is up to date
|
|
isFailIfNotUpToDate = true;
|
|
robloxAppArgs = _T("");
|
|
windowDelay = 10;
|
|
}
|
|
else if (CAtlModule::WordCmpI(arg1, _T("-force"))==0)
|
|
{
|
|
LOG_ENTRY("option: force");
|
|
force = true;
|
|
}
|
|
else if (CAtlModule::WordCmpI(arg1, _T("-debug"))==0)
|
|
{
|
|
LOG_ENTRY("option: debug");
|
|
debug = true;
|
|
}
|
|
else if (CAtlModule::WordCmpI(arg1, _T("-prePlay"))==0)
|
|
{
|
|
LOG_ENTRY("option: prePlay");
|
|
robloxAppArgs = _T("-nop");
|
|
isNop = true;
|
|
}
|
|
else if (!ProcessArg(__targv, i, __argc))
|
|
{
|
|
if ( !robloxAppArgs.empty() )
|
|
robloxAppArgs += _T(" ");
|
|
robloxAppArgs += arg1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Bootstrapper::Close()
|
|
{
|
|
dialog->CloseDialog();
|
|
}
|
|
|
|
bool Bootstrapper::hasSse2()
|
|
{
|
|
const unsigned int kCpuVendorCmd = 0;
|
|
const unsigned int kCpuFeatures = 1;
|
|
const unsigned int kSse2BitLoc = 26;
|
|
|
|
// Determine if the feature register is available. I have not idea how this
|
|
// would fail on any computer built in the last 15 years.
|
|
unsigned int highestFunction = 0;
|
|
__asm {
|
|
mov eax, kCpuVendorCmd
|
|
mov ecx, 0
|
|
cpuid
|
|
mov highestFunction, eax
|
|
}
|
|
if (highestFunction < kCpuFeatures)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check for SSE2 Support
|
|
unsigned int featureBits;
|
|
__asm {
|
|
mov eax, kCpuFeatures
|
|
mov ecx, 0
|
|
cpuid
|
|
mov featureBits, edx
|
|
}
|
|
return ( featureBits & (1 << kSse2BitLoc));
|
|
}
|
|
|
|
boost::shared_ptr<Bootstrapper> Bootstrapper::Create(HINSTANCE hInstance, Bootstrapper*(*newBootstrapper)(HINSTANCE))
|
|
{
|
|
boost::shared_ptr<Bootstrapper> result(newBootstrapper(hInstance));
|
|
|
|
result->initialize();
|
|
|
|
// Now schedule the async tasks
|
|
if (!result->windowed)
|
|
{
|
|
if (!result->noRun)
|
|
{
|
|
if (!result->CanRunBgTask())
|
|
{
|
|
return result;
|
|
}
|
|
|
|
CMutex m1(NULL, FALSE, result->GetStartAppMutexName());
|
|
CTimedMutexLock l1(m1);
|
|
if (l1.Lock(1) == WAIT_TIMEOUT)
|
|
{
|
|
LLOG_ENTRY(result->logger, "No bg update, Roblox App is running");
|
|
return result;
|
|
}
|
|
|
|
CMutex m2(NULL, FALSE, result->GetBootstrapperMutexName());
|
|
CTimedMutexLock l2(m2);
|
|
if (l2.Lock(1) == WAIT_TIMEOUT)
|
|
{
|
|
LLOG_ENTRY(result->logger, "No bg update, Bootstrapper is running");
|
|
return result;
|
|
}
|
|
|
|
LLOG_ENTRY(result->logger, "Running bootstrapper in sync mode");
|
|
result->run(); // do it synchronously
|
|
}
|
|
else
|
|
{
|
|
result->LoadSettings();
|
|
result->SetupGoogleAnalytics();
|
|
result->SetupInfluxDb();
|
|
|
|
}
|
|
|
|
result->RunPreDeploy();
|
|
}
|
|
else
|
|
{
|
|
boost::thread(boost::bind(&Bootstrapper::run, result));
|
|
if (result->windowDelay)
|
|
{
|
|
boost::thread(boost::bind(&Bootstrapper::showWindowAfterDelay, result));
|
|
}
|
|
else
|
|
{
|
|
if (result->isUninstall)
|
|
{
|
|
LLOG_ENTRY1(result->logger, "Bootstrapper::Create delay showing dialog window due to uninstall, thread id = %d", ::GetCurrentThreadId());
|
|
result->dialog->ShowWindow(CMainDialog::WindowTimeDelayShow);
|
|
}
|
|
else
|
|
{
|
|
LLOG_ENTRY1(result->logger, "Bootstrapper::Create showing dialog window, thread id = %d", ::GetCurrentThreadId());
|
|
result->dialog->ShowWindow(CMainDialog::WindowSomethingInterestingShow);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Downloads a file from the install site. Uses a cached file when possible
|
|
void Bootstrapper::message(const std::string& message)
|
|
{
|
|
LOG_ENTRY1("UI Message: %s", message.c_str());
|
|
if(dialog)
|
|
dialog->SetMessage(message.c_str());
|
|
}
|
|
|
|
// http://blogs.msdn.com/ie/archive/2007/06/13/new-api-smoothes-extension-development-in-protected-mode.aspx
|
|
UINT __stdcall RefreshPolicies()
|
|
{
|
|
UINT hr = ERROR_SUCCESS;
|
|
HMODULE hDll = LoadLibrary(_T("ieframe.dll"));
|
|
if (NULL != hDll)
|
|
{
|
|
typedef HRESULT (*PFNIEREFRESHELEVATIONPOLICY)();
|
|
PFNIEREFRESHELEVATIONPOLICY pfnIERefreshElePol = (PFNIEREFRESHELEVATIONPOLICY) GetProcAddress(hDll, "IERefreshElevationPolicy");
|
|
if (pfnIERefreshElePol)
|
|
{
|
|
hr = pfnIERefreshElePol();
|
|
} else {
|
|
DWORD error = GetLastError();
|
|
hr = HRESULT_FROM_WIN32(error);
|
|
}
|
|
FreeLibrary(hDll);
|
|
} else {
|
|
DWORD error = GetLastError();
|
|
hr = HRESULT_FROM_WIN32(error);
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
void Bootstrapper::RegisterElevationPolicy(const TCHAR* appName, const TCHAR* appPath)
|
|
{
|
|
std::string guid;
|
|
{
|
|
GUID g;
|
|
::CoCreateGuid(&g);
|
|
OLECHAR s[256];
|
|
::StringFromGUID2(g, s, 256);
|
|
guid = convert_w2s((LPCTSTR)CString(s));
|
|
}
|
|
|
|
std::wstring skey = format_string(_T("Software\\Microsoft\\Internet Explorer\\Low Rights\\ElevationPolicy\\%S"), guid.c_str());
|
|
|
|
LOG_ENTRY1("RegisterElevationPolicy: %S", skey.c_str());
|
|
CRegKey key;
|
|
throwHRESULT(key.Create(perUser ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE, skey.c_str()), format_string("Failed to create key for %s elevation policy", appName));
|
|
throwHRESULT(key.SetStringValue(_T("AppName"), appName), format_string("Failed to create AppName for %s elevation policy", appName));
|
|
throwHRESULT(key.SetDWORDValue(_T("Policy"), 3), format_string("Failed to create Policy for %s elevation policy", appName));
|
|
throwHRESULT(key.SetStringValue(_T("AppPath"), appPath), format_string("Failed to create AppPath for %s elevation policy", appName));
|
|
key.Close();
|
|
|
|
HRESULT hr = RefreshPolicies();
|
|
if (FAILED(hr))
|
|
{
|
|
LOG_ENTRY1("WARNING: RefreshPolicies failed - HR=0x%X", hr);
|
|
}
|
|
}
|
|
|
|
bool Bootstrapper::isInstalled(std::wstring productKey)
|
|
{
|
|
CRegKey key;
|
|
LONG lRes = key.Open(perUser ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE, productKey.c_str(), KEY_READ);
|
|
return (lRes == 0);
|
|
}
|
|
|
|
void Bootstrapper::RegisterUninstall(const TCHAR *productName)
|
|
{
|
|
// http://msdn.microsoft.com/en-us/library/aa372105(VS.85).aspx
|
|
|
|
// Register Add/Remove Programs registry
|
|
CRegKey keyProductCode = CreateKey(perUser ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE, GetProductCodeKey().c_str(), NULL);
|
|
|
|
std::wstring uninstallString = format_string(_T("\"%s%s\" -uninstall%s"), programDirectory().c_str(), GetBootstrapperFileName().c_str(), perUser ? _T("") : _T(" -alluser"));
|
|
throwHRESULT (keyProductCode.SetStringValue(_T("UninstallString"), uninstallString.c_str(), REG_EXPAND_SZ), "Failed to set UninstallString key");
|
|
throwHRESULT (keyProductCode.SetStringValue(_T("Publisher"), _T("ROBLOX Corporation")), "Failed to set Publisher key");
|
|
throwHRESULT (keyProductCode.SetStringValue(_T("URLInfoAbout"), _T("http://www.watrbx.wtf")), "Failed to set URLInfoAbout key");
|
|
throwHRESULT (keyProductCode.SetStringValue(_T("Comments"), convert_s2w(installVersion).c_str()), "Failed to set Comments key");
|
|
throwHRESULT (keyProductCode.SetStringValue(_T("InstallLocation"), programDirectory().c_str()), "Failed to set InstallLocation key");
|
|
throwHRESULT (keyProductCode.SetDWORDValue(_T("NoModify"), 1), "Failed to set NoModify key");
|
|
throwHRESULT (keyProductCode.SetDWORDValue(_T("NoRepair"), 1), "Failed to set NoRepair key");
|
|
std::wstring iconPath = format_string(_T("%s%s,0"), programDirectory().c_str(), GetBootstrapperFileName().c_str());
|
|
throwHRESULT (keyProductCode.SetStringValue(_T("DisplayIcon"), iconPath.c_str()), "Failed to set IconPath key");
|
|
|
|
SYSTEMTIME lt;
|
|
GetLocalTime(<);
|
|
throwHRESULT (keyProductCode.SetStringValue(_T("InstallDate"), format_string(_T("%04d%02d%02d"), lt.wYear, lt.wMonth, lt.wDay).c_str()), "Failed to set InstallDate key");
|
|
|
|
std::wstring name = productName;
|
|
if (perUser)
|
|
{
|
|
wchar_t buff[256];
|
|
buff[0] = 0;
|
|
DWORD len = 256;
|
|
::GetUserNameW(buff, &len);
|
|
name += L" for ";
|
|
name += buff;
|
|
}
|
|
throwHRESULT (keyProductCode.SetStringValue(_T("DisplayName"), name.c_str()), "Failed to set DisplayName key");
|
|
}
|
|
|
|
void Bootstrapper::RegisterProtocolHandler(const std::wstring& protocolScheme, const std::wstring& exePath, const std::wstring& installKeyRegPath)
|
|
{
|
|
if (protocolScheme.length() == 0)
|
|
return;
|
|
|
|
// query and remove what's already installed
|
|
CRegKey installKey;
|
|
if (ERROR_SUCCESS==installKey.Open(isPerUser() ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE, installKeyRegPath.c_str(), KEY_READ | KEY_WRITE))
|
|
{
|
|
std::string result;
|
|
result = QueryStringValue(installKey, _T("protocol handler scheme"));
|
|
LOG_ENTRY2("HKCU\\%S :protocol handler scheme = %s", GetRegistryPath().c_str(), result.c_str());
|
|
|
|
if (!result.empty())
|
|
{
|
|
UnregisterProtocolHandler(convert_s2w(result));
|
|
}
|
|
}
|
|
|
|
// register the protocol handler scheme
|
|
CRegKey key = CreateKey(isPerUser() ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE, (_T("SOFTWARE\\Classes\\") + protocolScheme).c_str());
|
|
throwHRESULT(key.SetStringValue(_T(""), _T("URL: Roblox Protocol")), format_string("failed to set value for protocol"));
|
|
throwHRESULT(key.SetStringValue(_T("URL Protocol"), _T("")), format_string("failed to set value for protocol"));
|
|
|
|
CreateKey(key, _T("DefaultIcon"), exePath.c_str());
|
|
|
|
std::wstring value = format_string(_T("\"%s\" %%1"), exePath.c_str());
|
|
CreateKey(key, _T("shell\\open\\command"), value.c_str());
|
|
|
|
// update value in install key
|
|
throwHRESULT(installKey.SetStringValue(_T("protocol handler scheme"), protocolScheme.c_str()), "Failed to set protocol handler scheme key value");
|
|
|
|
// Do not warn the End User to launch with protocol for IE & Edge Browsers.
|
|
if (CreateEdgeRegistry())
|
|
{
|
|
key = CreateKey(isPerUser() ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE, (_T("SOFTWARE\\Microsoft\\Internet Explorer\\ProtocolExecute\\") + protocolScheme).c_str());
|
|
throwHRESULT(key.SetDWORDValue(_T("WarnOnOpen"), 0), format_string("Failed to set Protocol WarnOnOpen Key"));
|
|
}
|
|
|
|
// Really is the above key causing issues, delete it
|
|
if (DeleteEdgeRegistry())
|
|
DeleteKey(logger, isPerUser() ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE, (_T("SOFTWARE\\Microsoft\\Internet Explorer\\ProtocolExecute\\") + protocolScheme).c_str());
|
|
}
|
|
|
|
void Bootstrapper::UnregisterProtocolHandler(const std::wstring& protocolScheme)
|
|
{
|
|
CRegKey hk(isPerUser() ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE);
|
|
std::wstring path = _T("SOFTWARE\\Classes\\");
|
|
path += protocolScheme;
|
|
hk.RecurseDeleteKey(path.c_str());
|
|
}
|
|
|
|
void Bootstrapper::validateAndFixChromeState()
|
|
{
|
|
// open Chrome Local State file
|
|
rapidjson::Document doc;
|
|
std::wstring folder = FileSystem::getSpecialFolder(FileSystem::AppData, false, NULL, NULL);
|
|
folder += _T("\\Google\\Chrome\\User Data");
|
|
|
|
// parse file
|
|
{
|
|
std::ifstream ifs(convert_w2s(folder + _T("\\Local State")).c_str());
|
|
if (!ifs.is_open())
|
|
return;
|
|
|
|
std::stringstream localStateStr;
|
|
localStateStr << ifs.rdbuf();
|
|
|
|
doc.Parse<rapidjson::kParseDefaultFlags>(localStateStr.str().c_str());
|
|
}
|
|
|
|
rapidjson::Value& ph = doc["protocol_handler"];
|
|
rapidjson::Value& es = ph["excluded_schemes"];
|
|
|
|
bool found = false;
|
|
|
|
for (rapidjson::Value::MemberIterator iter = es.MemberBegin(); iter != es.MemberEnd(); ++iter)
|
|
{
|
|
if (iter->name.IsString())
|
|
{
|
|
std::string name = iter->name.GetString();
|
|
if (name.find("roblox") != std::string::npos)
|
|
{
|
|
if (iter->value.GetBool())
|
|
found = true;
|
|
|
|
iter->value.SetBool(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if (found && GetLogChromeProtocolFix())
|
|
GoogleAnalyticsHelper::trackEvent(logger, GA_CATEGORY_NAME, "ChromeProtocolHandler", "Blocked");
|
|
|
|
// try detect chrome and prompt shutdown if detected
|
|
bool terminate = false;
|
|
bool retried = false;
|
|
while (found)
|
|
{
|
|
found = false;
|
|
DWORD process_id_array[1024];
|
|
DWORD bytes_returned;
|
|
EnumProcesses(process_id_array, 1024*sizeof(DWORD), &bytes_returned);
|
|
|
|
const DWORD num_processes = bytes_returned/sizeof(DWORD);
|
|
for(DWORD i=0; i<num_processes; i++)
|
|
{
|
|
DWORD desiredAccess = PROCESS_QUERY_INFORMATION;
|
|
if (terminate)
|
|
desiredAccess |= PROCESS_TERMINATE;
|
|
|
|
CHandle hProcess(OpenProcess( desiredAccess, FALSE, process_id_array[i] ));
|
|
TCHAR image_name[1024];
|
|
if(GetProcessImageFileName(hProcess,image_name,1024 ))
|
|
{
|
|
image_name[1023] = 0;
|
|
std::wstring s = image_name;
|
|
if (s.find(_T("chrome.exe")) != std::wstring::npos)
|
|
{
|
|
if (terminate)
|
|
{
|
|
UINT exitCode = 0;
|
|
TerminateProcess(hProcess, exitCode);
|
|
}
|
|
else
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (found)
|
|
{
|
|
if (!retried)
|
|
{
|
|
if (IDCANCEL == dialog->MessageBox(_T("Please close all instances of Google Chrome before continuing."), _T(""), MB_RETRYCANCEL))
|
|
retried = true;
|
|
}
|
|
|
|
if (retried)
|
|
{
|
|
int res = dialog->MessageBox(
|
|
_T("watrbx might not launch correctly if chrome is open or running in the background during installation.\n\n")
|
|
_T("Automatically shutdown all instances of Chrome and continue?"), _T("Warning"), MB_YESNOCANCEL);
|
|
|
|
if (res == IDYES)
|
|
{
|
|
terminate = true;
|
|
|
|
if (GetLogChromeProtocolFix())
|
|
GoogleAnalyticsHelper::trackEvent(logger, GA_CATEGORY_NAME, "ChromeProtocolHandler", "AutoTerminate");
|
|
}
|
|
else if (res == IDNO)
|
|
{
|
|
retried = false;
|
|
continue;
|
|
}
|
|
else if (res == IDCANCEL)
|
|
{
|
|
if (GetLogChromeProtocolFix())
|
|
GoogleAnalyticsHelper::trackEvent(logger, GA_CATEGORY_NAME, "ChromeProtocolHandler", "BlockedAndCanceled");
|
|
break;
|
|
}
|
|
}
|
|
|
|
retried = true;
|
|
}
|
|
}
|
|
|
|
// save updated file
|
|
rapidjson::StringBuffer buffer;
|
|
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
|
doc.Accept(writer);
|
|
const char* output = buffer.GetString();
|
|
|
|
std::ofstream ofs(convert_w2s(folder + _T("\\Local State")).c_str());
|
|
if (ofs.is_open())
|
|
{
|
|
ofs << output;
|
|
}
|
|
}
|
|
|
|
std::wstring Bootstrapper::GetSelfName() const
|
|
{
|
|
return programDirectory() + GetBootstrapperFileName();
|
|
}
|
|
|
|
void Bootstrapper::deploySelf(bool commitData)
|
|
{
|
|
LOG_ENTRY("deploySelf");
|
|
if (!commitData)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// TODO: Worry about errors? (If so, then check to see if we are trying to copy to ourselves)
|
|
std::wstring destFile = GetSelfName();
|
|
LOG_ENTRY1("Bootstrapper::deploySelf - Final file path=%S", destFile.c_str());
|
|
|
|
// Copy the file by hand
|
|
// (If we use CopyFile then when IE7 is installed the file won't be trusted)
|
|
if (!::PathFileExists(destFile.c_str()))
|
|
{
|
|
LOG_ENTRY("!PathFileExists");
|
|
wchar_t srcFileLong[_MAX_PATH];
|
|
throwLastError(::GetModuleFileNameW(NULL, srcFileLong, _MAX_PATH),"Error when getting bootstrapper filename for selfDeploy");
|
|
|
|
std::wstring srcFile = srcFileLong;
|
|
LOG_ENTRY1("GetModuleFileName: %S", srcFile.c_str());
|
|
|
|
copyFile(logger, srcFile.c_str(), destFile.c_str());
|
|
LOG_ENTRY2("Copy: from '%S' to '%S'", srcFile.c_str(), destFile.c_str());
|
|
}
|
|
else
|
|
{
|
|
LOG_ENTRY("PathFileExists");
|
|
}
|
|
|
|
std::wstring dir = programDirectory();
|
|
RegisterElevationPolicy(GetBootstrapperFileName().c_str(), dir.c_str());
|
|
}
|
|
|
|
|
|
void Bootstrapper::createDirectory(const TCHAR* dir)
|
|
{
|
|
if (!::CreateDirectory(dir, 0))
|
|
{
|
|
DWORD error = ::GetLastError();
|
|
if (error!=ERROR_ALREADY_EXISTS)
|
|
throwLastError(FALSE, format_string("CreateDirectory '%S' failed", dir));
|
|
}
|
|
}
|
|
|
|
void Bootstrapper::DoDeployComponents(const std::vector<std::pair<std::wstring, std::wstring> > &files, bool isUpdating, bool commitData)
|
|
{
|
|
std::string text = std::string(isUpdating ? "Upgrading " : "Installing ") + convert_w2s(GetFriendlyName()) + " ...";
|
|
message(text);
|
|
progressBar(0, 100);
|
|
|
|
if (PerModeLoggingEnabled())
|
|
{
|
|
bool gotCdn = false;
|
|
if (UseNewCdn())
|
|
{
|
|
if (!HttpTools::getCdnHost(this).empty())
|
|
gotCdn = true;
|
|
else if (!HttpTools::getPrimaryCdnHost(this).empty())
|
|
gotCdn = true;
|
|
}
|
|
else
|
|
gotCdn = !HttpTools::getPrimaryCdnHost(this).empty();
|
|
|
|
if (!gotCdn) {
|
|
RegisterEvent(_T("BootstrapperUpdateOrInstallUnableToResolveCdnHost"));
|
|
} else {
|
|
RegisterEvent(_T("BootstrapperUpdateOrInstallResolvedCdnHost"));
|
|
}
|
|
}
|
|
|
|
DWORD startTickCount = GetTickCount();
|
|
|
|
// Create a single array of all zip files that need to be deployed
|
|
std::vector<Progress> zips(files.size());
|
|
for (size_t i = 0;i < files.size();i ++)
|
|
{
|
|
const wchar_t *subFolder = (const wchar_t*)NULL;
|
|
if (!files[i].second.empty())
|
|
{
|
|
subFolder = files[i].second.c_str();
|
|
}
|
|
boost::thread(boost::bind(&FileDeployer::deployVersionedFile, deployer.get(), files[i].first.c_str(), subFolder, boost::ref(zips[i]), commitData));
|
|
}
|
|
|
|
int time = 0;
|
|
while (true)
|
|
{
|
|
int value = 0;
|
|
int maxValue = 0;
|
|
int maxSetCount = 0;
|
|
int doneCount = 0;
|
|
for (size_t i=0; i<zips.size(); ++i)
|
|
{
|
|
const Progress& p = zips[i];
|
|
if (p.done)
|
|
doneCount++;
|
|
if (p.maxValue>0)
|
|
{
|
|
maxSetCount++;
|
|
maxValue += p.maxValue;
|
|
}
|
|
value += p.value;
|
|
|
|
}
|
|
|
|
if (doneCount==zips.size())
|
|
break;
|
|
|
|
if (maxSetCount==zips.size())
|
|
progressBar(value, maxValue);
|
|
|
|
::Sleep(10);
|
|
if (time>=1000)
|
|
{
|
|
dialog->SetMarquee(false);
|
|
}
|
|
else
|
|
{
|
|
time += 10;
|
|
}
|
|
checkCancel();
|
|
}
|
|
|
|
if (PerModeLoggingEnabled()) {
|
|
// Above loop throws an exception if the user aborts the deploy or if
|
|
// there is a deploy problem, so at this point we know the deploy
|
|
// succeeded.
|
|
// TODO: explicitly enforce that this event only gets registered for the
|
|
// primary download, and not for other downloads (e.g. bootstrapper
|
|
// self-upgrade).
|
|
DWORD elapsedMillis = GetTickCount() - startTickCount;
|
|
std::string typeAndCategory(Type());
|
|
typeAndCategory += "BootstrapperDeploy";
|
|
reportDurationAndSizeEvent(typeAndCategory.c_str(), "Success",
|
|
elapsedMillis, deployer->GetBytesDownloaded());
|
|
|
|
// Track client install time using GA
|
|
|
|
std::string installUrl;
|
|
if (UseNewCdn())
|
|
installUrl = HttpTools::getCdnHost(this);
|
|
if (installUrl.empty())
|
|
installUrl = HttpTools::getPrimaryCdnHost(this);
|
|
|
|
if (Type() == "Client")
|
|
{
|
|
GoogleAnalyticsHelper::trackUserTiming(logger, GA_CATEGORY_NAME, "ClientInstallTime", elapsedMillis, installUrl.empty() ? installHost.c_str() : installUrl.c_str());
|
|
GoogleAnalyticsHelper::trackEvent(logger, GA_CATEGORY_NAME, "ClientInstallThroughput bytes/sec", installUrl.empty() ? installHost.c_str() : installUrl.c_str(), deployer->GetBytesDownloaded() / (elapsedMillis / 1000.0f));
|
|
|
|
// also log new installs to influxdb
|
|
if (!isUpdating)
|
|
{
|
|
InfluxDb points;
|
|
points.addPoint("CDN", installUrl.empty() ? installHost.c_str() : installUrl.c_str());
|
|
double sec = elapsedMillis / 1000.0f;
|
|
points.addPoint("Duration", sec);
|
|
points.addPoint("BytesRecv", (int)deployer->GetBytesDownloaded());
|
|
points.addPoint("BytesPerSec", (int)deployer->GetBytesDownloaded() / sec);
|
|
ReportToInfluxDb(InfluxDbInstallHundredthsPercentage(), points.getJsonStringForPosting("PCInstallTime"), false);
|
|
}
|
|
}
|
|
}
|
|
|
|
dialog->SetMarquee(true);
|
|
}
|
|
|
|
std::wstring Bootstrapper::programDirectory() const
|
|
{
|
|
return programDirectory(perUser);
|
|
};
|
|
|
|
std::wstring Bootstrapper::baseProgramDirectory(bool isPerUser) const
|
|
{
|
|
return FileSystem::getSpecialFolder(isPerUser ? FileSystem::RobloxUserApplicationData : FileSystem::RobloxProgramFiles, true, "Versions");
|
|
}
|
|
|
|
std::wstring Bootstrapper::programDirectory(bool isPerUser) const
|
|
{
|
|
// Prepare the program directory
|
|
std::wstring dir = baseProgramDirectory(isPerUser);
|
|
if (dir.empty())
|
|
throw std::runtime_error("Failed to create programDirectory");
|
|
dir += convert_s2w(installVersion) + _T("\\");
|
|
|
|
createDirectory(dir.c_str());
|
|
#if 1
|
|
return dir;
|
|
#else
|
|
// http://support.microsoft.com/kb/185126
|
|
char path[_MAX_PATH];
|
|
throwLastError(::GetShortPathName(dir.c_str(), path, _MAX_PATH), format_string("failed GetShortPathName %s", dir.c_str()));
|
|
return path;
|
|
#endif
|
|
};
|
|
|
|
|
|
std::string Bootstrapper::fetchVersionGuid(std::string product = std::string())
|
|
{
|
|
// By default look up the most recent version of the process / product that is running
|
|
if (product.empty())
|
|
product = convert_w2s(GetGuidName());
|
|
|
|
LOG_ENTRY("Bootstrapper::fetchVersionGuid");
|
|
static std::map<std::string, std::string> mostRecentVersions;
|
|
if (mostRecentVersions.find(product) != mostRecentVersions.end())
|
|
{
|
|
LOG_ENTRY1("Bootstrapper::fetchVersionGuid - found in cache: %s", mostRecentVersions[product].c_str());
|
|
return mostRecentVersions[product];
|
|
}
|
|
|
|
mostRecentVersions[product] = fetchVersionGuidFromWeb(product);
|
|
LOG_ENTRY1("Bootstrapper::fetchVersionGuid - installVersion: %s", mostRecentVersions[product].c_str());
|
|
|
|
return mostRecentVersions[product];
|
|
}
|
|
|
|
std::string Bootstrapper::fetchVersionGuidFromWeb(std::string product)
|
|
{
|
|
if (GetUseNewVersionFetch() && BinaryType() != "")
|
|
{
|
|
//std::ostrstream result;
|
|
//HttpTools::httpGet(this, BaseHost(), "/game/ClientVersion.ashx", std::string(), result, false, boost::bind(&Bootstrapper::dummyProgress, _1, _2));
|
|
//result << (char) 0;
|
|
//return result.str();
|
|
|
|
std::string url = GetClientVersionUploadUrl(baseHost, "76e5a40c-3ae1-4028-9f10-7c62520bd94f");
|
|
url += "&binaryType=" + BinaryType();
|
|
std::string result = HttpTools::httpGetString(url);
|
|
|
|
// remove quotes
|
|
result.erase(std::remove(result.begin(), result.end(), '\"' ), result.end());
|
|
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
std::ostrstream result;
|
|
std::string vsubpath = "/" + product + "?guid%d";
|
|
HttpTools::httpGet(this, installHost, format_string(vsubpath.c_str(), reportStatGuid), std::string(), result, false, boost::bind(&Bootstrapper::dummyProgress, _1, _2));
|
|
result << (char) 0;
|
|
|
|
return result.str();
|
|
}
|
|
}
|
|
|
|
bool Bootstrapper::checkBootstrapperVersion()
|
|
{
|
|
LOG_ENTRY("checkBootstrapperVersion()");
|
|
|
|
CVersionInfo vi;
|
|
vi.Load(_AtlBaseModule.m_hInst);
|
|
moduleVersionNumber = vi.GetFileVersionAsString();
|
|
LOG_ENTRY1("module file version: %s", moduleVersionNumber.c_str());
|
|
|
|
message("connecting to watrbx...");
|
|
try
|
|
{
|
|
installVersion = fetchVersionGuid(); // TODO: Why is this setting the installVersion?
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
// it seems right now that the main reason why it is failing that often is users local internet hiccup
|
|
// logs upload usually happens after 3-5 secs and it is working, so lets try to sleep here before doing anything else
|
|
Sleep(3000);
|
|
LOG_ENTRY1("fetchVersionGuid - failed with: %s retrying after 3 sec sleep", exceptionToString(e).c_str());
|
|
installVersion = fetchVersionGuid(); // TODO: Why is this setting the installVersion?
|
|
|
|
}
|
|
|
|
std::string bootstrapperVersion;
|
|
try
|
|
{
|
|
std::ostrstream result;
|
|
HttpTools::httpGet(this, installHost, format_string("/%s-%S", installVersion.c_str(), GetVersionFileName()), std::string(), result, false, boost::bind(&Bootstrapper::dummyProgress, _1, _2));
|
|
result << (char) 0;
|
|
bootstrapperVersion = result.str();
|
|
}
|
|
catch (cancel_exception&)
|
|
{
|
|
throw;
|
|
}
|
|
catch (installer_error_exception&)
|
|
{
|
|
throw;
|
|
}
|
|
catch (previous_error&)
|
|
{
|
|
return false;
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
LOG_ENTRY1("Error: %s", exceptionToString(e).c_str());
|
|
return false;
|
|
}
|
|
|
|
if (moduleVersionNumber != bootstrapperVersion)
|
|
{
|
|
if(isFailIfNotUpToDate)
|
|
throw non_zero_exit_exception();
|
|
|
|
std::wstring newBootstrapper;
|
|
|
|
// Lets kill task before updating and installing
|
|
UninstallRobloxUpdater();
|
|
|
|
try
|
|
{
|
|
|
|
if(queryInstalledInstallHost() == installHost)
|
|
{
|
|
//Check that the Installed bootstrapper matches, but only if our install host matches
|
|
newBootstrapper = getInstalledBootstrapperPath();
|
|
if(!newBootstrapper.empty() && ATLPath::FileExists(newBootstrapper.c_str())){
|
|
CVersionInfo vi;
|
|
|
|
vi.Load(newBootstrapper);
|
|
moduleVersionNumber = vi.GetFileVersionAsString();
|
|
LOG_ENTRY1("installed bootstrapper file version: %s", moduleVersionNumber.c_str());
|
|
}
|
|
}
|
|
}
|
|
catch(std::exception&)
|
|
{
|
|
//Something went wrong, fall back to the old method of fetching from website
|
|
moduleVersionNumber = "";
|
|
}
|
|
if(moduleVersionNumber != bootstrapperVersion){
|
|
// This bootstrapper is the wrong version. Download the correct version
|
|
dialog->ShowWindow(CMainDialog::WindowSomethingInterestingShow);
|
|
|
|
if (debug && windowed)
|
|
if (dialog->MessageBox(_T("Download latest bootstrapper?"), _T("DEBUG"), MB_YESNO)==IDNO)
|
|
return true;
|
|
|
|
try
|
|
{
|
|
message("getting the latest woblox...");
|
|
|
|
// We could use an "exe" extension, but hiding the type isn't a bad idea?
|
|
newBootstrapper = simple_logger<wchar_t>::get_temp_filename(_T("tmp"));
|
|
{
|
|
std::ofstream bootstrapperFile(newBootstrapper.c_str(), std::ios::binary);
|
|
// This version of the downloader doesn't show progress
|
|
HttpTools::httpGetCdn(this, installHost, format_string("/%s-%S", installVersion.c_str(), GetBootstrapperFileName().c_str()), std::string(), bootstrapperFile, false, boost::bind(&Bootstrapper::dummyProgress, _1, _2));
|
|
}
|
|
|
|
}
|
|
catch (...)
|
|
{
|
|
newBootstrapper = simple_logger<wchar_t>::get_temp_filename(_T("tmp")); // We could use an "exe" extension, but hiding the type isn't a bad idea?
|
|
{
|
|
std::ofstream bootstrapperFile(newBootstrapper.c_str(), std::ios::binary);
|
|
// this we might need during rollback, lets be on safe side
|
|
HttpTools::httpGetCdn(this, installHost, format_string("/%s-Roblox.exe", installVersion.c_str()), std::string(), bootstrapperFile, false, boost::bind(&Bootstrapper::dummyProgress, _1, _2));
|
|
}
|
|
}
|
|
|
|
message("Please Wait...");
|
|
}
|
|
|
|
std::wstring args;
|
|
for (int i = 1; i < __argc; i++)
|
|
{
|
|
if (!args.empty())
|
|
args += _T(" ");
|
|
args += std::wstring(_T("\"")) + __targv[i] + _T("\"");
|
|
}
|
|
|
|
CProcessInformation pi;
|
|
CreateProcess(newBootstrapper.c_str(), args.c_str(), pi);
|
|
|
|
performedBootstrapperUpdateAndQuit = true;
|
|
|
|
throw installer_error_exception(installer_error_exception::BootstrapVersion);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void Bootstrapper::writeAppSettings()
|
|
{
|
|
message("Configuring watrbx...");
|
|
|
|
std::wstring appSettings(programDirectory() + _T("AppSettings.xml"));
|
|
std::ofstream file(appSettings.c_str());
|
|
|
|
file << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
|
|
file << "<Settings>\n";
|
|
file << "\t<ContentFolder>content</ContentFolder>\n";
|
|
file << "\t<BaseUrl>http://" << (const char *) baseHost.c_str() << "</BaseUrl>\n";
|
|
file << "</Settings>\n";
|
|
}
|
|
|
|
void Bootstrapper::CreateShortcuts(bool forceDesktopIconCreation, const TCHAR *linkName, const TCHAR *exeName, const TCHAR *params)
|
|
{
|
|
bool success = true;
|
|
RegisterEvent(_T("BoostrapperCreateShortcutsStarted"));
|
|
try
|
|
{
|
|
LOG_ENTRY4("Bootstrapper::CreateShortcuts force = %d, link = '%S', exeName = '%S', params = '%S'", forceDesktopIconCreation, linkName, exeName, params);
|
|
std::wstring exePath = GetSelfName();
|
|
createRobloxShortcut(logger, isPerUser(), linkName, exePath.c_str(), params, false, true);
|
|
createRobloxShortcut(logger, isPerUser(), linkName, exePath.c_str(), params, true, forceDesktopIconCreation);
|
|
|
|
long start = GetTickCount();
|
|
std::wstring desktop = FileSystem::getSpecialFolder(FileSystem::Desktop, false, NULL, NULL);
|
|
std::wstring versionedPath = programDirectory();
|
|
std::wstring unversionedPath = baseProgramDirectory(isPerUser());
|
|
updateExistingRobloxShortcuts(logger, isPerUser(), desktop.c_str(), exeName, versionedPath.c_str(), unversionedPath.c_str());
|
|
|
|
// if user pinned our R link to task bar or start menu we want update that link as well
|
|
if (IsWin7())
|
|
{
|
|
std::wstring folder = FileSystem::getSpecialFolder(FileSystem::RoamingAppData, false, NULL, NULL);
|
|
|
|
std::wstring taskBar = folder + _T("Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\TaskBar\\");
|
|
updateExistingRobloxShortcuts(logger, isPerUser(), taskBar.c_str(), exeName, versionedPath.c_str(), unversionedPath.c_str());
|
|
|
|
std::wstring startMenu = folder + _T("Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\StartMenu\\");
|
|
updateExistingRobloxShortcuts(logger, isPerUser(), startMenu.c_str(), exeName, versionedPath.c_str(), unversionedPath.c_str());
|
|
}
|
|
else if (IsWinXP()) //WinXP
|
|
{
|
|
std::wstring folder = FileSystem::getSpecialFolder(FileSystem::AppData, false, NULL, NULL);
|
|
folder += _T("Microsoft\\Internet Explorer\\Quick Launch\\");
|
|
updateExistingRobloxShortcuts(logger, isPerUser(), folder.c_str(), exeName, versionedPath.c_str(), unversionedPath.c_str());
|
|
}
|
|
else // Win Vista case
|
|
{
|
|
std::wstring folder = FileSystem::getSpecialFolder(FileSystem::RoamingAppData, false, NULL, NULL);
|
|
folder += _T("Microsoft\\Internet Explorer\\Quick Launch\\");
|
|
updateExistingRobloxShortcuts(logger, isPerUser(), folder.c_str(), exeName, versionedPath.c_str(), unversionedPath.c_str());
|
|
}
|
|
|
|
long end = GetTickCount();
|
|
LOG_ENTRY1("Folders update time %d msec", end - start);
|
|
}
|
|
catch (std::runtime_error &e)
|
|
{
|
|
LOG_ENTRY(e.what());
|
|
success = false;
|
|
}
|
|
if (success)
|
|
{
|
|
RegisterEvent(_T("BoostrapperCreateShortcutsSuccess"));
|
|
}
|
|
else
|
|
{
|
|
RegisterEvent(_T("BoostrapperCreateShortcutsFailure"));
|
|
}
|
|
}
|
|
|
|
|
|
// The LABEL_SECURITY_INFORMATION SDDL SACL to be set for low integrity
|
|
LPCWSTR LOW_INTEGRITY_SDDL_SACL_W = L"S:(ML;;NW;;;LW)";
|
|
|
|
// http://www.codeproject.com/KB/vista-security/PMSurvivalGuide.aspx#creatingipc
|
|
// This function lets RobloxProxy.dll access the mutex
|
|
|
|
bool SetObjectToLowIntegrity(HANDLE hObject, SE_OBJECT_TYPE type = SE_KERNEL_OBJECT)
|
|
{
|
|
bool bRet = false;
|
|
DWORD dwErr = ERROR_SUCCESS;
|
|
PSECURITY_DESCRIPTOR pSD = NULL;
|
|
PACL pSacl = NULL;
|
|
BOOL fSaclPresent = FALSE;
|
|
BOOL fSaclDefaulted = FALSE;
|
|
|
|
if ( ConvertStringSecurityDescriptorToSecurityDescriptorW (
|
|
LOW_INTEGRITY_SDDL_SACL_W, SDDL_REVISION_1, &pSD, NULL ) )
|
|
{
|
|
if ( GetSecurityDescriptorSacl (
|
|
pSD, &fSaclPresent, &pSacl, &fSaclDefaulted ) )
|
|
{
|
|
dwErr = SetSecurityInfo (
|
|
hObject, type, LABEL_SECURITY_INFORMATION,
|
|
NULL, NULL, NULL, pSacl );
|
|
|
|
bRet = (ERROR_SUCCESS == dwErr);
|
|
}
|
|
|
|
LocalFree ( pSD );
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
void Bootstrapper::checkOSPrerequisit()
|
|
{
|
|
LOG_ENTRY("checkOSPrerequisit");
|
|
// http://msdn.microsoft.com/en-us/library/ms725491(VS.85).aspx
|
|
|
|
{
|
|
OSVERSIONINFOEX osvi = {0};
|
|
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
|
|
GetVersionEx((OSVERSIONINFO*)&osvi);
|
|
LOG_ENTRY1("OSVERSIONINFO.dwMajorVersion %d", osvi.dwMajorVersion);
|
|
LOG_ENTRY1("OSVERSIONINFO.wServicePackMajor %d", osvi.wServicePackMajor);
|
|
LOG_ENTRY1("OSVERSIONINFO.wServicePackMajor %d", osvi.wServicePackMajor);
|
|
LOG_ENTRY1("OSVERSIONINFO.wServicePackMinor %d", osvi.wServicePackMinor);
|
|
LOG_ENTRY1("OSVERSIONINFO.dwBuildNumber %d", osvi.dwBuildNumber);
|
|
LOG_ENTRY1("OSVERSIONINFO.dwPlatformId %d", osvi.dwPlatformId);
|
|
}
|
|
|
|
// Initialize the condition mask.
|
|
|
|
OSVERSIONINFOEX osvi = {0};
|
|
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
|
|
osvi.dwMajorVersion = 5;
|
|
osvi.dwMinorVersion = 1;
|
|
osvi.wServicePackMajor = 1; // Roblox requires WinXP SP1 because it links to WinHttp
|
|
osvi.wServicePackMinor = 0;
|
|
|
|
DWORDLONG dwlConditionMask = 0;
|
|
VER_SET_CONDITION( dwlConditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL );
|
|
VER_SET_CONDITION( dwlConditionMask, VER_MINORVERSION, VER_GREATER_EQUAL );
|
|
VER_SET_CONDITION( dwlConditionMask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL );
|
|
VER_SET_CONDITION( dwlConditionMask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL );
|
|
|
|
// Perform the test.
|
|
if (VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR, dwlConditionMask))
|
|
return;
|
|
|
|
LOG_ENTRY("checkOSPrerequisit failed");
|
|
if (windowed)
|
|
dialog->DisplayError("watrbx requires Microsoft Windows XP SP1 or greater", NULL);
|
|
throw installer_error_exception(installer_error_exception::OsPrerequisite);
|
|
}
|
|
|
|
void Bootstrapper::checkCPUPrerequisit()
|
|
{
|
|
if (!Bootstrapper::hasSse2())
|
|
{
|
|
LOG_ENTRY("checkCPUPrerequisit failed");
|
|
if (windowed)
|
|
dialog->DisplayError("watrbx requires SSE2 support", NULL);
|
|
throw installer_error_exception(installer_error_exception::CpuPrerequisite);
|
|
}
|
|
}
|
|
|
|
void Bootstrapper::checkDirectXPrerequisit()
|
|
{
|
|
// DirectX is not required
|
|
#if 0
|
|
if (!isDirectXUpToDate())
|
|
{
|
|
log << "checkDirectXPrerequisit failed\n"; log.flush();
|
|
if (windowed)
|
|
dialog->DisplayError("watrbx requires DirectX 9.0 or greater", NULL);
|
|
throw installer_error_exception(installer_error_exception::DirectxPrerequisite);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void Bootstrapper::checkIEPrerequisit()
|
|
{
|
|
if (!isIEUpToDate())
|
|
{
|
|
LOG_ENTRY("checkIEPrerequisit failed");
|
|
if (windowed)
|
|
dialog->DisplayError("watrbx requires Microsoft Internet Explorer 6.0 or greater", NULL);
|
|
throw installer_error_exception(installer_error_exception::IePrerequisite);
|
|
}
|
|
}
|
|
|
|
bool Bootstrapper::isIEUpToDate()
|
|
{
|
|
CRegKey key;
|
|
if (ERROR_SUCCESS!=key.Open(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\Internet Explorer"), KEY_READ))
|
|
return false;
|
|
|
|
TCHAR buffer[256];
|
|
ULONG size = 256;
|
|
if (ERROR_SUCCESS!=key.QueryStringValue(_T("Version"), buffer, &size))
|
|
return false;
|
|
|
|
// TODO: Parse version string properly
|
|
// http://support.microsoft.com/kb/164539
|
|
if (buffer[0]<'6')
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool Bootstrapper::isDirectXUpToDate()
|
|
{
|
|
CRegKey key;
|
|
if (ERROR_SUCCESS!=key.Open(HKEY_LOCAL_MACHINE, _T("SOFTWARE\\Microsoft\\DirectX"), KEY_READ))
|
|
return false;
|
|
|
|
TCHAR buffer[256];
|
|
ULONG size = 256;
|
|
if (ERROR_SUCCESS!=key.QueryStringValue(_T("Version"), buffer, &size))
|
|
return false;
|
|
|
|
std::vector<std::wstring> version = splitOn(buffer,_T('.'));
|
|
if (version.size()<4)
|
|
throw std::runtime_error(format_string("bad DirectX version %S", buffer));
|
|
|
|
// TODO: Parse version string properly
|
|
if ( atoi(convert_w2s(version[1]).c_str()) < 9 )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void Bootstrapper::shutdownRobloxApp(std::wstring appExeName)
|
|
{
|
|
int time = 0;
|
|
ShutdownRobloxApp s(hInstance, appExeName, boost::bind(&CMainDialog::GetHWnd, dialog.get()), 15, boost::bind(&Bootstrapper::shutdownProgress, this, boost::ref(time), _1, _2));
|
|
if(s.run()){
|
|
//If we have to close Roblox, show the "updating" dialog right away
|
|
dialog->ShowWindow(CMainDialog::WindowSomethingInterestingShow);
|
|
}
|
|
dialog->SetMarquee(true);
|
|
}
|
|
|
|
bool Bootstrapper::shutdownProgress(int& time, int pos, int max)
|
|
{
|
|
if (stopRequested())
|
|
return false;
|
|
time += 100;
|
|
if (time==1000)
|
|
{
|
|
std::string text = "Shutting down " + convert_w2s(GetFriendlyName());
|
|
message(text);
|
|
dialog->SetMarquee(false);
|
|
}
|
|
progressBar(pos, max);
|
|
return true;
|
|
}
|
|
|
|
std::wstring Bootstrapper::getInstalledBootstrapperPath(const std::wstring& RegistryPath)
|
|
{
|
|
std::wstring result;
|
|
TCHAR buffer[_MAX_PATH];
|
|
ULONG size = _MAX_PATH;
|
|
|
|
if (perUser)
|
|
{
|
|
// First check HKCU then try HKLM
|
|
CRegKey key;
|
|
if (ERROR_SUCCESS == key.Open(HKEY_CURRENT_USER, RegistryPath.c_str(), KEY_READ))
|
|
{
|
|
if (ERROR_SUCCESS == key.QueryStringValue(_T(""), buffer, &size))
|
|
{
|
|
result = buffer;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (result.empty())
|
|
{
|
|
CRegKey key;
|
|
if (ERROR_SUCCESS == key.Open(HKEY_LOCAL_MACHINE, RegistryPath.c_str(), KEY_READ))
|
|
{
|
|
if (ERROR_SUCCESS == key.QueryStringValue(_T(""), buffer, &size))
|
|
{
|
|
result = buffer;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::string Bootstrapper::queryInstalledInstallHost()
|
|
{
|
|
std::string result;
|
|
|
|
if (perUser)
|
|
{
|
|
// First check HKCU then try HKLM
|
|
CRegKey key;
|
|
if (ERROR_SUCCESS==key.Open(HKEY_CURRENT_USER, GetRegistryPath().c_str(), KEY_READ))
|
|
{
|
|
result = QueryStringValue(key, _T("install host"));
|
|
LOG_ENTRY2("HKCU\\%S :install host = %s", GetRegistryPath().c_str(), result.c_str());
|
|
}
|
|
}
|
|
|
|
if (result.empty())
|
|
{
|
|
CRegKey key;
|
|
if (ERROR_SUCCESS == key.Open(HKEY_LOCAL_MACHINE, GetRegistryPath().c_str(), KEY_READ))
|
|
{
|
|
result = QueryStringValue(key, _T("install host"));
|
|
LOG_ENTRY2("HKLM\\%S :install host = %s", GetRegistryPath().c_str(), result.c_str());
|
|
}
|
|
adminInstallDetected = true;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::string Bootstrapper::queryInstalledVersion(const std::wstring& RegistryPath)
|
|
{
|
|
std::string result;
|
|
CRegKey key;
|
|
|
|
if (perUser)
|
|
{
|
|
// First check HKCU then try HKLM
|
|
if (ERROR_SUCCESS == key.Open(HKEY_CURRENT_USER, RegistryPath.c_str(), KEY_READ))
|
|
{
|
|
result = QueryStringValue(key, _T("version"));
|
|
LOG_ENTRY2("HKCU\\%S :version = %s", RegistryPath.c_str(), result.c_str());
|
|
}
|
|
}
|
|
|
|
if (result.empty())
|
|
{
|
|
if (ERROR_SUCCESS == key.Open(HKEY_LOCAL_MACHINE, RegistryPath.c_str(), KEY_READ))
|
|
{
|
|
result = QueryStringValue(key, _T("version"));
|
|
LOG_ENTRY2("HKLM\\%S :version = %s", RegistryPath.c_str(), result.c_str());
|
|
}
|
|
adminInstallDetected = true;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void Bootstrapper::checkDiskSpace()
|
|
{
|
|
ULARGE_INTEGER freeBytesAvailableToCaller;
|
|
::GetDiskFreeSpaceEx(programDirectory().c_str(), &freeBytesAvailableToCaller, NULL, NULL);
|
|
if (freeBytesAvailableToCaller.QuadPart < 40*1e6)
|
|
{
|
|
dialog->DisplayError("you do not have enough space on your hard drive for watrbx.", NULL);
|
|
throw installer_error_exception(installer_error_exception::DiskSpacePrerequisite);
|
|
}
|
|
}
|
|
|
|
void Bootstrapper::run()
|
|
{
|
|
srand(GetTickCount());
|
|
|
|
if (!robloxAppArgs.empty())
|
|
setLatestProcess();
|
|
|
|
if (isNop)
|
|
goto done;
|
|
|
|
::CoInitialize(NULL);
|
|
|
|
// TODO remove this code next iteration
|
|
UninstallRobloxUpdater();
|
|
|
|
try
|
|
{
|
|
forceUninstallMFCStudio();
|
|
}
|
|
catch (...)
|
|
{
|
|
LOG_ENTRY("Bootstrapper::run - forceUninstallMFCStudio failed");
|
|
}
|
|
|
|
|
|
startTime = time(NULL);
|
|
|
|
try
|
|
{
|
|
try
|
|
{
|
|
checkCPUPrerequisit();
|
|
checkOSPrerequisit();
|
|
checkIEPrerequisit();
|
|
checkDirectXPrerequisit();
|
|
}
|
|
catch(...)
|
|
{
|
|
const bool isUpdating = !queryInstalledVersion().empty();
|
|
throw;
|
|
}
|
|
|
|
if (EarlyInstall(isUninstall))
|
|
{
|
|
goto done;
|
|
}
|
|
|
|
dialog->SetMarquee(true);
|
|
|
|
DWORD result = 0;
|
|
if (!IsNetworkAlive(&result) || result == 0)
|
|
{
|
|
if(isFailIfNotUpToDate){
|
|
LOG_ENTRY("The network is done, but -FailIfNotUpToDate should not lock us up, so returning true");
|
|
goto done;
|
|
}
|
|
|
|
LOG_ENTRY("Error: IsNetworkAlive failed");
|
|
if (windowed && isLatestProcess())
|
|
{
|
|
CString message = _T("watrbx cannot connect to the internet.\n\nDoes your computer have a working network connection? Is antivirus software preventing Roblox from accessing the Internet?");
|
|
if (!robloxAppArgs.empty())
|
|
{
|
|
message += _T("\n\nIf you continue watrbx may not work properly.");
|
|
// TODO: CTaskDialog should use nice command buttons
|
|
if (dialog->MessageBox(message, _T("Error"), MB_OKCANCEL | MB_ICONEXCLAMATION)==IDOK)
|
|
{
|
|
installVersion = queryInstalledVersion();
|
|
StartRobloxApp(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dialog->DisplayError(convert_w2s(message.GetString()).c_str(), NULL);
|
|
}
|
|
}
|
|
goto done;
|
|
}
|
|
|
|
LoadSettings();
|
|
|
|
influxDb.addPoint("SettingsLoaded", GetElapsedTime() / 1000.0f); // adding points does not need setup
|
|
|
|
SetupGoogleAnalytics();
|
|
SetupInfluxDb();
|
|
|
|
#ifdef LOCAL_TESTING
|
|
installVersion = fetchVersionGuid();
|
|
#else
|
|
if (!checkBootstrapperVersion())
|
|
{
|
|
if(isFailIfNotUpToDate)
|
|
throw non_zero_exit_exception();
|
|
|
|
if (windowed && isLatestProcess())
|
|
{
|
|
CString message = _T("Cannot connect to the watrbx website.\n\nIs antivirus software preventing watrbx from accessing the Internet?");
|
|
if (!robloxAppArgs.empty())
|
|
{
|
|
message += _T("\n\nIf you continue watrbx may not work properly.");
|
|
if (dialog->MessageBox(message, _T("Error"), MB_OKCANCEL | MB_ICONEXCLAMATION)==IDOK)
|
|
{
|
|
installVersion = queryInstalledVersion();
|
|
StartRobloxApp(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dialog->DisplayError(convert_w2s(message.GetString()).c_str(), NULL);
|
|
}
|
|
}
|
|
goto done;
|
|
}
|
|
#endif
|
|
|
|
if(isFailIfNotUpToDate)
|
|
{
|
|
if(queryInstalledVersion() != installVersion)
|
|
throw non_zero_exit_exception();
|
|
|
|
LOG_ENTRY("watrbx is up to date, returning success");
|
|
goto done;
|
|
}
|
|
|
|
influxDb.addPoint("SelfVersionChecked", GetElapsedTime() / 1000.0f);
|
|
|
|
if (queryInstalledVersion() == installVersion)
|
|
{
|
|
if (fullCheck)
|
|
{
|
|
message("Performing file check...");
|
|
LOG_ENTRY("Performing file check...");
|
|
|
|
std::wstring manifestFileName = simple_logger<wchar_t>::get_temp_filename(_T("manifest"));
|
|
int status_code = -1;
|
|
{
|
|
std::ofstream ofs(manifestFileName.c_str());
|
|
|
|
// This version of the downloader doesn't show progress
|
|
status_code = HttpTools::httpGetCdn(this, installHost, format_string("/%s-rbxManifest.txt", installVersion.c_str()), std::string(), ofs, false, boost::bind(&Bootstrapper::dummyProgress, _1, _2));
|
|
}
|
|
|
|
if (status_code == 200 || status_code == 304)
|
|
{
|
|
std::fstream manifestFile;
|
|
manifestFile.open(manifestFileName.c_str());
|
|
if (manifestFile.is_open())
|
|
{
|
|
// try to find each file
|
|
std::string fileName;
|
|
std::string fileHash;
|
|
std::ifstream ifs;
|
|
while (!manifestFile.eof())
|
|
{
|
|
std::getline(manifestFile, fileName);
|
|
if (fileName.length() == 0)
|
|
break;
|
|
|
|
if (*fileName.rbegin() == '\r')
|
|
fileName.erase(fileName.length() - 1);
|
|
|
|
ifs.open((programDirectory() + convert_s2w(fileName)).c_str(), std::ios::in | std::ios::binary);
|
|
if (ifs.is_open())
|
|
{
|
|
MD5Hasher hasher;
|
|
hasher.addData(ifs);
|
|
std::getline(manifestFile, fileHash);
|
|
if (fileHash.length() && *fileHash.rbegin() == '\r')
|
|
fileHash.erase(fileHash.length() - 1);
|
|
|
|
if (hasher.toString() != fileHash)
|
|
{
|
|
LOG_ENTRY1("%s corruption detected, forcing reinstall.",fileName.c_str());
|
|
force = true;
|
|
ifs.close();
|
|
break;
|
|
}
|
|
|
|
ifs.close();
|
|
}
|
|
else
|
|
{
|
|
LOG_ENTRY1("%s not found, forcing reinstall.", fileName.c_str());
|
|
force = true;
|
|
ifs.close();
|
|
break;
|
|
}
|
|
}
|
|
|
|
manifestFile.close();
|
|
|
|
LOG_ENTRY("File check complete.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOG_ENTRY("Failed to get manifest file, full check skipped.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// if the system is indicating we have roblox installed, try and find the exe
|
|
HANDLE robloxAppFile;
|
|
WIN32_FIND_DATA robloxAppFileData;
|
|
robloxAppFile = FindFirstFile((programDirectory() + GetRobloxAppFileName()).c_str(), &robloxAppFileData);
|
|
|
|
LOG_ENTRY1("RobloxAppFileName = %S", GetRobloxAppFileName().c_str());
|
|
LOG_ENTRY1("programDirectory = %S", programDirectory().c_str());
|
|
|
|
// if RobloxApp.exe is not found in the indicated install folder, force uninstall/install
|
|
if ((robloxAppFile == INVALID_HANDLE_VALUE))
|
|
{
|
|
LOG_ENTRY1("%S not found, forcing reinstall.", GetRobloxAppFileName().c_str());
|
|
force = true;
|
|
}
|
|
else
|
|
{
|
|
if (!IsInstalledVersionUptodate())
|
|
{
|
|
// track version mismatch
|
|
if (!GoogleAnalyticsHelper::trackEvent(logger, GA_CATEGORY_NAME, "VersionMismatch"))
|
|
LOG_ENTRY("WARNING: failed to log version mismatch");
|
|
|
|
if (ValidateInstalledExeVersion())
|
|
{
|
|
LOG_ENTRY("RobloxApp version mismatch, forcing reinstall.");
|
|
force = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
::FindClose(robloxAppFile);
|
|
}
|
|
}
|
|
|
|
bool isNewInstall = false;
|
|
{
|
|
CMutex mutex(NULL, FALSE, GetBootstrapperMutexName());
|
|
|
|
CTimedMutexLock lock(mutex);
|
|
bool firstTime = true;
|
|
while (lock.Lock(100) == WAIT_TIMEOUT )
|
|
{
|
|
if (firstTime)
|
|
{
|
|
LOG_ENTRY("Waiting for mutex");
|
|
dialog->SetMessage("Please wait...");
|
|
firstTime = false;
|
|
}
|
|
checkCancel();
|
|
if (!isUninstall && !force)
|
|
{
|
|
if (!isLatestProcess())
|
|
{
|
|
// This process has been superceded by another one, so don't bother running
|
|
throw cancel_exception(false);
|
|
}
|
|
}
|
|
}
|
|
dialog->ShowWindow(CMainDialog::WindowNoLongerBoringShow);
|
|
|
|
if (!perUser)
|
|
{
|
|
uninstall(true); // first uninstall per-user Roblox
|
|
}
|
|
|
|
if (isUninstall)
|
|
{
|
|
std::string text = "Uninstalling " + convert_w2s(GetFriendlyName());
|
|
message(text);
|
|
uninstall();
|
|
if (windowed)
|
|
{
|
|
text = convert_w2s(GetFriendlyName()) + " has been uninstalled";
|
|
dialog->FinalMessage(text.c_str());
|
|
}
|
|
}
|
|
else if (force || (queryInstalledVersion() != installVersion))
|
|
{
|
|
cancelDelay = 2;
|
|
if (cancelDelay)
|
|
{
|
|
dialog->ShowCancelButton(CMainDialog::CancelTimeDelayHide);
|
|
boost::thread(boost::bind(&Bootstrapper::showCancelAfterDelay, this));
|
|
}
|
|
|
|
//Update/Install required
|
|
checkDiskSpace();
|
|
|
|
isNewInstall = queryInstalledVersion().empty();
|
|
|
|
try
|
|
{
|
|
install();
|
|
|
|
installVersion = queryInstalledVersion();
|
|
if (!IsInstalledVersionUptodate())
|
|
{
|
|
std::string label;
|
|
if (!isNewInstall)
|
|
label += "Update";
|
|
else
|
|
label += "Install";
|
|
if (force)
|
|
label += "Force";
|
|
|
|
// track version mismatch
|
|
if (!GoogleAnalyticsHelper::trackEvent(logger, GA_CATEGORY_NAME, "VersionMismatchPostInstall", label.c_str()))
|
|
LOG_ENTRY("WARNING: failed to log version mismatch post install");
|
|
|
|
LOG_ENTRY("WARNING: client version not up to date after install.");
|
|
postLogFile(false);
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
if (isNewInstall)
|
|
{
|
|
RegisterEvent(_T("BootstrapperInstallFailed"));
|
|
}
|
|
else
|
|
{
|
|
RegisterEvent(_T("BootstrapperUpdateFailed"));
|
|
}
|
|
throw;
|
|
}
|
|
}
|
|
else{
|
|
//Roblox is already installed
|
|
//If it is installed for all users
|
|
if(adminInstallDetected)
|
|
{
|
|
perUser = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string text = convert_w2s(GetFriendlyName()) + " is up-to-date";
|
|
message(text);
|
|
if (debug)
|
|
{
|
|
dialog->SetMarquee(false);
|
|
for (int i = 5; i>0; --i)
|
|
{
|
|
dialog->SetProgress(20*(5-i+1));
|
|
|
|
std::string text = format_string(
|
|
"%s is up-to-date - starting in %d seconds",
|
|
convert_w2s(GetFriendlyName()).c_str(),
|
|
i );
|
|
message(text);
|
|
::Sleep(1*1000);
|
|
checkCancel();
|
|
}
|
|
}
|
|
|
|
checkCancel();
|
|
if (!robloxAppArgs.empty() && isLatestProcess())
|
|
StartRobloxApp(isNewInstall);
|
|
|
|
LOG_ENTRY("Done!");
|
|
}
|
|
catch (non_zero_exit_exception&)
|
|
{
|
|
exitCode = 1;
|
|
LOG_ENTRY("watrbx is not up to date, returning failure");
|
|
}
|
|
catch (silent_exception&)
|
|
{
|
|
// TODO: Message to user???
|
|
}
|
|
catch( CAtlException e )
|
|
{
|
|
error(std::runtime_error(format_string("AtlException 0x%x", e.m_hr)));
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
error(e);
|
|
}
|
|
|
|
done:
|
|
{
|
|
boost::lock_guard<boost::mutex> lock(mut);
|
|
done.notify_all();
|
|
}
|
|
|
|
if (!isSilentMode())
|
|
{
|
|
if (!Dialog()->isSuccessPromptShown())
|
|
{
|
|
LOG_ENTRY1("WM_QUIT message to the thread id = %d", mainThreadId);
|
|
::PostThreadMessage(mainThreadId, WM_QUIT, 0, 0);
|
|
}
|
|
else
|
|
LOG_ENTRY("Install success prompt shown, waiting for input");
|
|
|
|
}
|
|
else
|
|
{
|
|
LOG_ENTRY("Setting up finish event in silent mode");
|
|
robloxReady.Set();
|
|
}
|
|
|
|
LOG_ENTRY("Fully Done!");
|
|
::CoUninitialize();
|
|
}
|
|
|
|
|
|
|
|
|
|
bool Bootstrapper::deleteDirectory(const std::wstring &refcstrRootDirectory, bool childrenOnly)
|
|
{
|
|
LOG_ENTRY2("deleteDirectory %S childrenOnly=%d", refcstrRootDirectory.c_str(), childrenOnly);
|
|
|
|
bool failure = false;
|
|
std::wstring strFilePath; // Filepath
|
|
std::wstring strPattern; // Pattern
|
|
WIN32_FIND_DATA FileInformation; // File information
|
|
|
|
strPattern = refcstrRootDirectory + _T("*.*");
|
|
HANDLE hFile = ::FindFirstFile(strPattern.c_str(), &FileInformation);
|
|
if(hFile != INVALID_HANDLE_VALUE)
|
|
{
|
|
do
|
|
{
|
|
try
|
|
{
|
|
checkCancel();
|
|
}
|
|
catch (...)
|
|
{
|
|
// Close handle
|
|
::FindClose(hFile);
|
|
throw;
|
|
}
|
|
|
|
if(FileInformation.cFileName[0] != '.')
|
|
{
|
|
strFilePath.erase();
|
|
strFilePath = refcstrRootDirectory + FileInformation.cFileName;
|
|
|
|
if(FileInformation.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
{
|
|
// Delete subdirectory
|
|
failure = deleteDirectory(strFilePath + _T("\\")) || failure;
|
|
}
|
|
else
|
|
{
|
|
// Set file attributes
|
|
if(::SetFileAttributes(strFilePath.c_str(), FILE_ATTRIBUTE_NORMAL) == FALSE)
|
|
{
|
|
failure = true;
|
|
}
|
|
else
|
|
{
|
|
// Delete file
|
|
if(::DeleteFile(strFilePath.c_str()) == FALSE)
|
|
{
|
|
failure = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} while(::FindNextFile(hFile, &FileInformation) == TRUE);
|
|
|
|
// Close handle
|
|
::FindClose(hFile);
|
|
|
|
DWORD dwError = ::GetLastError();
|
|
if(dwError != ERROR_NO_MORE_FILES)
|
|
{
|
|
failure = true;
|
|
}
|
|
else if (!childrenOnly)
|
|
{
|
|
// Set directory attributes
|
|
if(::SetFileAttributes(refcstrRootDirectory.c_str(), FILE_ATTRIBUTE_NORMAL) == FALSE)
|
|
{
|
|
failure = true;
|
|
}
|
|
// Delete directory
|
|
else if(::RemoveDirectory(refcstrRootDirectory.c_str()) == FALSE)
|
|
{
|
|
failure = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return failure;
|
|
}
|
|
|
|
|
|
void Bootstrapper::cleanKey(const std::wstring regPathToDelete, CRegKey &hk)
|
|
{
|
|
CRegKey key;
|
|
|
|
if( key.Open(hk, std::wstring( _T("SOFTWARE\\") + regPathToDelete ).c_str(), KEY_READ) == ERROR_SUCCESS )
|
|
{
|
|
CRegKey versionKey;
|
|
|
|
// if this is true, that means we never have gotten a version of studio, we can delete
|
|
if( versionKey.Open(hk, std::wstring(_T("SOFTWARE\\") + regPathToDelete + _T("\\Versions")).c_str(), KEY_READ) != ERROR_SUCCESS )
|
|
{
|
|
CRegKey softwareKey;
|
|
if (ERROR_SUCCESS != softwareKey.Open(hk, _T("Software"), KEY_WRITE))
|
|
{
|
|
LOG_ENTRY("WARNING: Failed to open HK**\\Software");
|
|
return;
|
|
}
|
|
DeleteKey(logger, softwareKey, regPathToDelete.c_str());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Bootstrapper::uninstall()
|
|
{
|
|
uninstall(perUser);
|
|
}
|
|
|
|
void Bootstrapper::uninstall(bool isPerUser)
|
|
{
|
|
LOG_ENTRY("Bootstrapper::uninstall");
|
|
|
|
CRegKey hk(isPerUser ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE);
|
|
{
|
|
CRegKey key;
|
|
if (ERROR_SUCCESS != key.Open(hk, GetProductCodeKey().c_str(), KEY_READ))
|
|
{
|
|
LOG_ENTRY1("Uninstall %s nothing to do", (isPerUser ? "per user" : "all users"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
LOG_ENTRY1("Uninstall %s", (isPerUser ? "per user" : "all users"));
|
|
|
|
shutdownRobloxApp(GetRobloxAppFileName());
|
|
|
|
LOG_ENTRY1("Deleting HKCU\\Software\\%S", GetRegistryPath().c_str());
|
|
|
|
CRegKey key;
|
|
if (ERROR_SUCCESS!=key.Open(hk, _T("Software"), KEY_WRITE))
|
|
{
|
|
LOG_ENTRY("WARNING: Failed to open HK**\\Software");
|
|
return;
|
|
}
|
|
|
|
DoUninstallApp(hk);
|
|
|
|
std::string version = installVersion;
|
|
installVersion = "";
|
|
|
|
LOG_ENTRY("Remove from Add/Remove programs");
|
|
if (FAILED(hk.DeleteSubKey(GetProductCodeKey().c_str())))
|
|
{
|
|
LOG_ENTRY("WARNING: failed to delete ProductCodeKey");
|
|
}
|
|
|
|
deleteVersionFolder(version);
|
|
deleteLegacyShortcuts();
|
|
|
|
// check for components still installed
|
|
|
|
std::vector<std::wstring> components;
|
|
components.push_back(getStudioInstallKey());
|
|
components.push_back(getPlayerInstallKey());
|
|
components.push_back(getQTStudioInstallKey());
|
|
|
|
bool finalCleanup = true;
|
|
for (size_t i = 0;i < components.size();i ++)
|
|
{
|
|
if (components[i].c_str() != GetProductCodeKey() && isInstalled(components[i].c_str()))
|
|
{
|
|
LOG_ENTRY1("Component %S still installed, no cleanup needed", components[i].c_str());
|
|
finalCleanup = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// final cleanup when we have no more components installed -- nuke all shortcuts
|
|
if (finalCleanup)
|
|
{
|
|
std::wstring folder = getRobloxProgramsFolder(logger, isPerUser);
|
|
if (!folder.empty())
|
|
{
|
|
LOG_ENTRY1("Deleting roblox shortcuts folder = %S", folder.c_str());
|
|
deleteDirectory(folder, false);
|
|
}
|
|
|
|
deleteVersionsDirectoryContents();
|
|
|
|
LOG_ENTRY("Bootstrapper::uninstall - deleteDesktopShortcut");
|
|
deleteDesktopShortcut(logger, _T(PLAYERLINKNAME_CUR));
|
|
deleteDesktopShortcut(logger, _T(STUDIOQTLINKNAME_CUR));
|
|
|
|
LOG_ENTRY("Bootstrapper::uninstall - deleteProgramsShortcut");
|
|
deleteProgramsShortcut(logger, isPerUser, _T(PLAYERLINKNAME_CUR));
|
|
deleteProgramsShortcut(logger, isPerUser, _T(STUDIOQTLINKNAME_CUR));
|
|
|
|
// explicitly delete the Qt key since Client deletes its key
|
|
// the Qt key stays around since it's needed by the shared launcher to install Studio for
|
|
// Build and Edit modes
|
|
DeleteKey(logger,key,getQTStudioRegistrySubPath().c_str());
|
|
UnregisterProtocolHandler(getQTStudioProtocolScheme(BaseHost()));
|
|
}
|
|
|
|
LOG_ENTRY1("Uninstall %s", (isPerUser ? "per user done" : "all users done"));
|
|
}
|
|
|
|
void Bootstrapper::deleteVersionsDirectoryContents()
|
|
{
|
|
try
|
|
{
|
|
// Clear out old versions as much as possible
|
|
std::wstring dir = FileSystem::getSpecialFolder(perUser ? FileSystem::RobloxUserApplicationData : FileSystem::RobloxProgramFiles, true, "Versions");
|
|
deleteDirectory(dir, true);
|
|
}
|
|
catch (cancel_exception&)
|
|
{
|
|
throw;
|
|
}
|
|
catch (installer_error_exception&)
|
|
{
|
|
throw;
|
|
}
|
|
catch (...)
|
|
{
|
|
// ignore this error
|
|
}
|
|
}
|
|
|
|
void Bootstrapper::deleteVersionFolder(std::string version)
|
|
{
|
|
LOG_ENTRY1("Bootstrapper::deleteVersionFolder - version=%s", version.c_str());
|
|
|
|
if (version.empty()) // probably clean install
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (version == queryInstalledVersion())
|
|
{
|
|
LOG_ENTRY("Bootstrapper::deleteVersionFolder - WARNING trying to delete current version");
|
|
return;
|
|
}
|
|
|
|
std::wstring dir = FileSystem::getSpecialFolder(perUser ? FileSystem::RobloxUserApplicationData : FileSystem::RobloxProgramFiles, false, "Versions");
|
|
if (dir.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
std::wstring curDir = dir + convert_s2w(version) + _T('\\');
|
|
try
|
|
{
|
|
deleteDirectory(curDir, false);
|
|
}
|
|
catch (cancel_exception&)
|
|
{
|
|
throw;
|
|
}
|
|
catch (installer_error_exception&)
|
|
{
|
|
throw;
|
|
}
|
|
catch (...)
|
|
{
|
|
// ignore this error
|
|
}
|
|
}
|
|
|
|
void Bootstrapper::DeleteOldVersionsDirectories()
|
|
{
|
|
try
|
|
{
|
|
// Clear out old versions as much as possible
|
|
std::wstring dir = FileSystem::getSpecialFolder(perUser ? FileSystem::RobloxUserApplicationData : FileSystem::RobloxProgramFiles, false, "Versions");
|
|
if (dir.empty())
|
|
return;
|
|
WIN32_FIND_DATA FileInformation; // File information
|
|
HANDLE hFile = ::FindFirstFile((dir + _T("*.*")).c_str(), &FileInformation);
|
|
if (hFile != INVALID_HANDLE_VALUE)
|
|
{
|
|
try
|
|
{
|
|
do
|
|
{
|
|
if ((FileInformation.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)==0)
|
|
continue;
|
|
if (FileInformation.cFileName[0] == _T('.'))
|
|
continue;
|
|
if (installVersion==convert_w2s(FileInformation.cFileName))
|
|
continue;
|
|
|
|
std::wstring curDir = dir + FileInformation.cFileName + _T('\\');
|
|
deleteDirectory(curDir, false);
|
|
}
|
|
while(::FindNextFile(hFile, &FileInformation) == TRUE);
|
|
}
|
|
catch (...)
|
|
{
|
|
// Close handle
|
|
::FindClose(hFile);
|
|
throw;
|
|
}
|
|
// Close handle
|
|
::FindClose(hFile);
|
|
}
|
|
}
|
|
catch (cancel_exception&)
|
|
{
|
|
throw;
|
|
}
|
|
catch (installer_error_exception&)
|
|
{
|
|
throw;
|
|
}
|
|
catch (...)
|
|
{
|
|
// ignore this error
|
|
}
|
|
}
|
|
|
|
|
|
static bool reportValue(CookiesEngine &engine, std::string key, std::string value)
|
|
{
|
|
int rCounter = 10;
|
|
|
|
while (rCounter >= 0)
|
|
{
|
|
int result = engine.SetValue(key, value);
|
|
if (result == 0)
|
|
{
|
|
break;
|
|
}
|
|
::Sleep(50);
|
|
rCounter --;
|
|
}
|
|
|
|
return rCounter >= 0 ? true : false;
|
|
}
|
|
|
|
std::wstring Bootstrapper::prevVersionRegKey()
|
|
{
|
|
return std::wstring(GetRegistryPath()) + _T("\\Versions");
|
|
}
|
|
|
|
std::wstring Bootstrapper::prevVersionValueKey(int index)
|
|
{
|
|
return format_string(_T("version%d"), index);
|
|
}
|
|
|
|
void Bootstrapper::loadPrevVersions()
|
|
{
|
|
std::wstring keyPath = prevVersionRegKey();
|
|
CRegKey versionsKey = CreateKey(isPerUser() ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE, keyPath.c_str());
|
|
LOG_ENTRY1("Bootstrapper::loadPrevVersions path = %S", keyPath.c_str());
|
|
|
|
int i = 0;
|
|
while (true)
|
|
{
|
|
std::wstring keyName = prevVersionValueKey(i);
|
|
TCHAR verString[MAX_PATH];
|
|
ULONG size = MAX_PATH;
|
|
if (ERROR_SUCCESS != versionsKey.QueryStringValue(keyName.c_str(), verString, &size))
|
|
{
|
|
break;
|
|
}
|
|
|
|
prevVersions.push_back(convert_w2s(verString));
|
|
LOG_ENTRY1("Bootstrapper::loadPrevVersions - key found name = %S", verString);
|
|
i ++;
|
|
}
|
|
}
|
|
|
|
void Bootstrapper::storePrevVersions()
|
|
{
|
|
std::wstring keyPath = prevVersionRegKey();
|
|
CRegKey versionsKey = CreateKey(isPerUser() ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE, keyPath.c_str());
|
|
LOG_ENTRY2("Bootstrapper::storePrevVersions path = %S, perUser = %d", keyPath.c_str(), isPerUser());
|
|
|
|
int i = 0;
|
|
while (true)
|
|
{
|
|
std::wstring keyName = prevVersionValueKey(i);
|
|
TCHAR verString[MAX_PATH];
|
|
ULONG size = MAX_PATH;
|
|
if (ERROR_SUCCESS != versionsKey.QueryStringValue(keyName.c_str(), verString, &size))
|
|
{
|
|
break;
|
|
}
|
|
|
|
LOG_ENTRY2("Bootstrapper::storePrevVersions deleting key = %S, value = %S", keyName.c_str(), verString);
|
|
versionsKey.DeleteValue(keyName.c_str());
|
|
i ++;
|
|
}
|
|
|
|
i = 0;
|
|
for (std::list<std::string>::iterator j = prevVersions.begin();j != prevVersions.end();j ++)
|
|
{
|
|
std::string version = *j;
|
|
std::wstring path = baseProgramDirectory(isPerUser()) + convert_s2w(version);
|
|
if (FileSystem::IsFolderExists(path.c_str()))
|
|
{
|
|
std::wstring keyName = prevVersionValueKey(i);
|
|
LOG_ENTRY2("Bootstrapper::storePrevVersions storing key = %S, value = %s", keyName.c_str(), version.c_str());
|
|
throwHRESULT(versionsKey.SetStringValue(keyName.c_str(), convert_s2w(version).c_str()), "SetKeyFailed");
|
|
versionsKey.Flush();
|
|
i ++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
|
|
The order that items are deployed are important.
|
|
|
|
First we deploy this module, so that the proxy dll can find it.
|
|
Then we deploy and configure the proxy dll. It can now launch this module again, even if it isn't fully installed
|
|
Then we deploy all the other components.
|
|
Finally, we update the version strings in the registry.
|
|
*/
|
|
void Bootstrapper::install()
|
|
{
|
|
LOG_ENTRY("Bootstrapper::install");
|
|
|
|
const bool isUpdating = !queryInstalledVersion().empty();
|
|
|
|
LOG_ENTRY1("isUpdating = %d", isUpdating);
|
|
|
|
CookiesEngine engine(CookiesEngine::getCookiesFilePath());
|
|
bool reportSuccess = false;
|
|
|
|
if (!isUpdating)
|
|
{
|
|
reportSuccess = reportValue(engine, "rbx_evt_initial_install_start", "{\"os\" : \"Win32\"}");
|
|
RegisterEvent(_T("BootstrapperInstallStarted"));
|
|
performedNewInstall = true;
|
|
}
|
|
else
|
|
{
|
|
RegisterEvent(_T("BootstrapperUpdateStarted"));
|
|
performedUpdateInstall = true;
|
|
}
|
|
|
|
//reportStat(IsVista() ? "Vista" : "XP");
|
|
try
|
|
{
|
|
if (IsWinXP())
|
|
{
|
|
if (!perUser && !IsAdminRunning())
|
|
{
|
|
if (windowed)
|
|
dialog->DisplayError("On this machine you must install Roblox using an Administrator account", NULL);
|
|
throw installer_error_exception(installer_error_exception::AdminAccountRequired);
|
|
}
|
|
}
|
|
|
|
shutdownRobloxApp(GetRobloxAppFileName());
|
|
checkCancel();
|
|
|
|
std::string text = "Configuring " + convert_w2s(GetFriendlyName());
|
|
message(text);
|
|
|
|
// set install status
|
|
if (!browserTrackerId.empty())
|
|
{
|
|
try
|
|
{
|
|
std::ostrstream result;
|
|
HttpTools::httpPost(this, baseHost, format_string("/client-status/set?browserTrackerId=%s&status=BootstrapperInstalling", browserTrackerId.c_str()), std::stringstream(""), "*/*",
|
|
result, false, boost::bind(&Bootstrapper::dummyProgress, _1, _2));
|
|
}
|
|
catch (std::exception& ex)
|
|
{
|
|
LOG_ENTRY1("Error: %s", exceptionToString(ex).c_str());
|
|
}
|
|
}
|
|
|
|
DoInstallApp();
|
|
|
|
// nukes all the old versions
|
|
for (std::list<std::string>::iterator j = prevVersions.begin();j != prevVersions.end();j ++)
|
|
{
|
|
deleteVersionFolder(*j);
|
|
}
|
|
prevVersions.push_back(installVersion);
|
|
|
|
storePrevVersions();
|
|
|
|
if (reportSuccess)
|
|
{
|
|
reportSuccess = reportValue(engine, "rbx_evt_initial_install_success", "{\"os\" : \"Win32\"}");
|
|
}
|
|
}
|
|
catch (cancel_exception& e)
|
|
{
|
|
LOG_ENTRY1("Error: %s", exceptionToString(e).c_str());
|
|
std::string message = format_string("%s: %s", e.what(), e.getErrorType().c_str());
|
|
ReportInstallMetricsToInfluxDb(isUpdating, "Cancel", message);
|
|
throw;
|
|
}
|
|
catch(installer_error_exception& e)
|
|
{
|
|
LOG_ENTRY1("Error: %s", exceptionToString(e).c_str());
|
|
std::string message = format_string("%s: %s", e.what(), e.getErrorType().c_str());
|
|
ReportInstallMetricsToInfluxDb(isUpdating, "Error", message);
|
|
throw;
|
|
}
|
|
catch (previous_error& e)
|
|
{
|
|
LOG_ENTRY1("Error: %s", exceptionToString(e).c_str());
|
|
ReportInstallMetricsToInfluxDb(isUpdating, "Error", e.what());
|
|
throw;
|
|
}
|
|
catch (std::exception& ex)
|
|
{
|
|
LOG_ENTRY1("Error: %s", exceptionToString(ex).c_str());
|
|
ReportInstallMetricsToInfluxDb(isUpdating, "Error", ex.what());
|
|
throw;
|
|
}
|
|
|
|
if (!isUpdating)
|
|
{
|
|
RegisterEvent(_T("BootstrapperInstallSuccess"));
|
|
}
|
|
else
|
|
{
|
|
RegisterEvent(_T("BootstrapperUpdateSuccess"));
|
|
}
|
|
|
|
ReportInstallMetricsToInfluxDb(isUpdating, "Success", "");
|
|
}
|
|
|
|
void Bootstrapper::checkCancel()
|
|
{
|
|
if (userCancelled)
|
|
throw cancel_exception(true);
|
|
if (hasErrors()){
|
|
std::string errorString="";
|
|
int i = 0;
|
|
for(std::vector<std::string>::const_iterator iter = errors.begin();
|
|
iter != errors.end(); ++iter){
|
|
errorString += format_string("%d: %s ", ++i, (*iter).c_str());
|
|
}
|
|
throw previous_error(errorString);
|
|
}
|
|
}
|
|
|
|
void Bootstrapper::userCancel()
|
|
{
|
|
userCancelledTime = time(NULL);
|
|
userCancelled = true;
|
|
}
|
|
|
|
void Bootstrapper::error(const std::exception& e)
|
|
{
|
|
LOG_ENTRY1("ERROR: %s", exceptionToString(e).c_str());
|
|
if (errors.size() == 0 && windowed)
|
|
{
|
|
std::string text = "An error occurred while starting " + convert_w2s(GetFriendlyName());
|
|
dialog->DisplayError(text.c_str(),e.what());
|
|
}
|
|
errors.push_back(exceptionToString(e));
|
|
}
|
|
|
|
std::string __cdecl format(const char* fmt,...) {
|
|
va_list argList;
|
|
va_start(argList,fmt);
|
|
std::string result = vformat(fmt, argList);
|
|
va_end(argList);
|
|
|
|
return result;
|
|
}
|
|
|
|
static std::string urlEncode(const std::string url)
|
|
{
|
|
std::string result;
|
|
|
|
const size_t strLen = url.size();
|
|
for (unsigned i=0; i < strLen; i++)
|
|
{
|
|
char c = url[i];
|
|
if (
|
|
(c<=47) ||
|
|
(c>=58 && c<=64) ||
|
|
(c>=91 && c<=96) ||
|
|
(c>=123)
|
|
)
|
|
{
|
|
result += format("%%%2X", c);
|
|
}
|
|
else
|
|
result += c;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void Bootstrapper::setLatestProcess()
|
|
{
|
|
int pid = _getpid();
|
|
|
|
const std::string latestProcessName = format_string("www.watrbx.wtf/%s/%s/latestProcess", installHost.c_str(), Type().c_str());
|
|
|
|
latestProcess.Attach(CreateFileMapping(
|
|
INVALID_HANDLE_VALUE, // use paging file
|
|
NULL, // default security
|
|
PAGE_READWRITE, // read/write access
|
|
0, // max. object size
|
|
sizeof(pid), // buffer size
|
|
convert_s2w(latestProcessName).c_str())); // name of mapping object
|
|
if (latestProcess == NULL)
|
|
{
|
|
LOG_ENTRY1("Could not create file mapping object: %S", (LPCTSTR)GetLastErrorDescription());
|
|
return;
|
|
}
|
|
|
|
LPVOID pBuf = MapViewOfFile(latestProcess, FILE_MAP_ALL_ACCESS, 0, 0, 2048);
|
|
|
|
if (pBuf == NULL)
|
|
{
|
|
LOG_ENTRY1("Could not map view of file: %S", (LPCTSTR)GetLastErrorDescription());
|
|
latestProcess.Close();
|
|
return;
|
|
}
|
|
|
|
CopyMemory((PVOID)pBuf, &pid, sizeof(pid));
|
|
|
|
UnmapViewOfFile(pBuf);
|
|
}
|
|
|
|
bool Bootstrapper::isLatestProcess()
|
|
{
|
|
LPVOID pBuf = MapViewOfFile(latestProcess, FILE_MAP_ALL_ACCESS, 0, 0, 2048);
|
|
|
|
if (pBuf == NULL)
|
|
{
|
|
LOG_ENTRY1("Could not map view of file: %S", (LPCTSTR)GetLastErrorDescription());
|
|
latestProcess.Close();
|
|
return true;
|
|
}
|
|
|
|
DWORD pid;
|
|
CopyMemory(&pid, (PVOID)pBuf, sizeof(pid));
|
|
|
|
UnmapViewOfFile(pBuf);
|
|
|
|
return pid == ::_getpid();
|
|
}
|
|
|
|
void Bootstrapper::progressBar(int pos, int max)
|
|
{
|
|
int progress = (int)((__int64)max ? 100 * (__int64)pos / (__int64)max : 0);
|
|
dialog->SetProgress(progress);
|
|
}
|
|
|
|
void Bootstrapper::forceUninstallMFCStudio()
|
|
{
|
|
LOG_ENTRY("Bootstrapper::forceUninstallMFCStudio");
|
|
|
|
// check if Studio is installed
|
|
if ( !isInstalled(getStudioInstallKey()) )
|
|
{
|
|
forceMFCStudioCleanup();
|
|
LOG_ENTRY("Bootstrapper::forceUninstallMFCStudio - not installed");
|
|
return;
|
|
}
|
|
|
|
// find the uninstall registry key
|
|
CRegKey key;
|
|
if ( key.Open(
|
|
perUser ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE,
|
|
getStudioInstallKey().c_str(),
|
|
KEY_READ ) != ERROR_SUCCESS )
|
|
{
|
|
LOG_ENTRY("Bootstrapper::forceUninstallMFCStudio - failed to find install key");
|
|
return;
|
|
}
|
|
|
|
// get the uninstall command
|
|
TCHAR value[512];
|
|
ULONG length = sizeof(value) / sizeof(TCHAR);
|
|
if ( key.QueryStringValue(_T("UninstallString"),value,&length) != ERROR_SUCCESS )
|
|
{
|
|
LOG_ENTRY("Bootstrapper::forceUninstallMFCStudio - failed to find uninstall string");
|
|
return;
|
|
}
|
|
|
|
// run the command
|
|
std::wstring uninstall_path = value;
|
|
if ( !uninstall_path.empty() )
|
|
{
|
|
LOG_ENTRY1(
|
|
"Bootstrapper::forceUninstallMFCStudio - path: %S",
|
|
uninstall_path.c_str() );
|
|
|
|
// do this before you execute the uninstall or you'll lose the keys
|
|
std::string version = queryInstalledVersion(getStudioRegistryPath());
|
|
std::wstring filePath = getInstalledBootstrapperPath(getStudioRegistryPath());
|
|
|
|
// execute command
|
|
CProcessInformation pi;
|
|
CreateProcess(uninstall_path.c_str(),_T(""),pi); // Launch the uninstaller RobloxStudio.exe -uninstall
|
|
|
|
// make sure the stub is deleted
|
|
LOG_ENTRY1("Bootstrapper::forceUninstallMFCStudio - deleting studio stub: %S",filePath.c_str());
|
|
::DeleteFile(filePath.c_str());
|
|
|
|
// delete empty directory
|
|
std::wstring basePath = baseProgramDirectory(isPerUser());
|
|
std::wstring path = basePath + convert_s2w(version);
|
|
LOG_ENTRY1("Bootstrapper::forceUninstallMFCStudio - deleting studio directory: %S",path.c_str());
|
|
deleteVersionFolder(convert_w2s(path));
|
|
}
|
|
|
|
forceMFCStudioCleanup();
|
|
}
|
|
|
|
void Bootstrapper::forceMFCStudioCleanup()
|
|
{
|
|
LOG_ENTRY("Bootstrapper::forceMFCStudioCleanup - deleteCurVersionKeys");
|
|
deleteCurVersionKeys(logger, isPerUser(), getStudioCode().c_str());
|
|
|
|
deleteLegacyShortcuts();
|
|
|
|
std::wstring basePath = baseProgramDirectory(isPerUser());
|
|
std::wstring fileName = _T(STUDIOBOOTSTAPPERNAME);
|
|
std::wstring filePath = basePath + fileName;
|
|
|
|
LOG_ENTRY1("Bootstrapper::forceMFCStudioCleanup - deleting studio stub: %S",filePath.c_str());
|
|
if (FileSystem::IsFileExists(filePath.c_str()))
|
|
::DeleteFile(filePath.c_str());
|
|
}
|
|
|
|
bool Bootstrapper::hasLegacyStudioDesktopShortcut()
|
|
{
|
|
return hasDesktopShortcut(logger, _T(STUDIOQTLINKNAME))
|
|
|| hasDesktopShortcut(logger, _T(STUDIOQTLINKNAME20))
|
|
|| hasDesktopShortcut(logger, _T(STUDIOQTLINKNAME20BETA))
|
|
|| hasDesktopShortcut(logger, _T(STUDIOLINKNAMELEGACY));
|
|
}
|
|
|
|
void Bootstrapper::deleteLegacyShortcuts()
|
|
{
|
|
LOG_ENTRY("Bootstrapper::deleteLegacyShortcuts - deleteDesktopShortcut");
|
|
deleteDesktopShortcut(logger, _T(PLAYERLINKNAMELEGACY));
|
|
deleteDesktopShortcut(logger, _T(STUDIOQTLINKNAME));
|
|
deleteDesktopShortcut(logger, _T(STUDIOQTLINKNAME20));
|
|
deleteDesktopShortcut(logger, _T(STUDIOQTLINKNAME20BETA));
|
|
|
|
LOG_ENTRY("Bootstrapper::deleteLegacyShortcuts - deleteProgramsShortcut");
|
|
deleteProgramsShortcut(logger, isPerUser(), _T(PLAYERLINKNAMELEGACY));
|
|
deleteProgramsShortcut(logger, isPerUser(), _T(STUDIOQTLINKNAME));
|
|
deleteProgramsShortcut(logger, isPerUser(), _T(STUDIOQTLINKNAME20));
|
|
deleteProgramsShortcut(logger, isPerUser(), _T(STUDIOQTLINKNAME20BETA));
|
|
}
|
|
|
|
struct HandleInfo
|
|
{
|
|
unsigned long pid;
|
|
HWND hWnd;
|
|
};
|
|
|
|
BOOL CALLBACK FindHwndFromPID( HWND hwnd, LPARAM lParam)
|
|
{
|
|
HandleInfo &info = *(HandleInfo*)lParam;
|
|
DWORD pid = 0;
|
|
|
|
if (GetWindowThreadProcessId(hwnd, &pid))
|
|
{
|
|
if (info.pid == pid)
|
|
{
|
|
// found
|
|
info.hWnd = hwnd;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
HWND Bootstrapper::GetHwndFromPID(DWORD pid)
|
|
{
|
|
HandleInfo handleInfo;
|
|
handleInfo.pid = pid;
|
|
handleInfo.hWnd = 0;
|
|
|
|
EnumWindows(FindHwndFromPID, (LPARAM)&handleInfo);
|
|
|
|
return handleInfo.hWnd;
|
|
}
|