Skip to content
This repository was archived by the owner on Mar 5, 2024. It is now read-only.

Commit a465c66

Browse files
committed
Cherry-pick 263de18
1 parent e17efe3 commit a465c66

File tree

3 files changed

+313
-99
lines changed

3 files changed

+313
-99
lines changed

tf2_bot_detector_updater/Platform/Platform.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ namespace tf2_bot_detector
2525
}
2626

2727
void RebootComputer();
28+
29+
enum class UpdateSystemDependenciesResult
30+
{
31+
Success,
32+
33+
RebootRequired,
34+
};
35+
UpdateSystemDependenciesResult UpdateSystemDependencies();
2836
}
2937
}
3038

tf2_bot_detector_updater/Platform/Windows/Platform_Windows.cpp

Lines changed: 302 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
1+
#define CPPHTTPLIB_OPENSSL_SUPPORT 1
12
#include "Platform/Platform.h"
23

4+
#include <httplib.h>
35
#include <mh/error/ensure.hpp>
46
#include <mh/raii/scope_exit.hpp>
57
#include <mh/source_location.hpp>
68
#include <mh/text/codecvt.hpp>
79
#include <mh/text/format.hpp>
810

911
#include <chrono>
12+
#include <fstream>
1013
#include <iostream>
1114
#include <thread>
1215

1316
#include <Windows.h>
1417

18+
#pragma comment(lib, "Version.lib")
19+
1520
using namespace std::chrono_literals;
1621

1722
void tf2_bot_detector::Platform::WaitForPIDToExit(int pid)
@@ -161,3 +166,300 @@ void tf2_bot_detector::RebootComputer()
161166
throw std::system_error(err, std::system_category(), "ExitWindowsEx failed");
162167
}
163168
}
169+
170+
MH_ENUM_REFLECT_BEGIN(httplib::Error)
171+
MH_ENUM_REFLECT_VALUE(Success)
172+
MH_ENUM_REFLECT_VALUE(Unknown)
173+
MH_ENUM_REFLECT_VALUE(Connection)
174+
MH_ENUM_REFLECT_VALUE(BindIPAddress)
175+
MH_ENUM_REFLECT_VALUE(Read)
176+
MH_ENUM_REFLECT_VALUE(Write)
177+
MH_ENUM_REFLECT_VALUE(ExceedRedirectCount)
178+
MH_ENUM_REFLECT_VALUE(Canceled)
179+
MH_ENUM_REFLECT_VALUE(SSLConnection)
180+
MH_ENUM_REFLECT_VALUE(SSLLoadingCerts)
181+
MH_ENUM_REFLECT_VALUE(SSLServerVerification)
182+
MH_ENUM_REFLECT_VALUE(UnsupportedMultipartBoundaryChars)
183+
MH_ENUM_REFLECT_END()
184+
185+
namespace
186+
{
187+
union Version
188+
{
189+
uint64_t v64{};
190+
191+
struct
192+
{
193+
uint32_t lo;
194+
uint32_t hi;
195+
} v32;
196+
197+
struct
198+
{
199+
uint16_t revision;
200+
uint16_t patch;
201+
uint16_t minor;
202+
uint16_t major;
203+
};
204+
205+
friend constexpr std::strong_ordering operator<=>(const Version& lhs, const Version& rhs)
206+
{
207+
if (auto result = lhs.major <=> rhs.major; !std::is_eq(result))
208+
return result;
209+
if (auto result = lhs.minor <=> rhs.minor; !std::is_eq(result))
210+
return result;
211+
if (auto result = lhs.patch <=> rhs.patch; !std::is_eq(result))
212+
return result;
213+
if (auto result = lhs.revision <=> rhs.revision; !std::is_eq(result))
214+
return result;
215+
216+
return std::strong_ordering::equal;
217+
}
218+
219+
friend constexpr bool operator==(const Version& lhs, const Version& rhs)
220+
{
221+
return lhs.v64 == rhs.v64;
222+
}
223+
};
224+
225+
template<typename CharT, typename Traits>
226+
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os, const Version& v)
227+
{
228+
return os << +v.major << '.' << +v.minor << '.' << +v.patch << '.' << +v.revision;
229+
}
230+
}
231+
232+
static Version GetFileProductVersion(const std::filesystem::path& filename) try
233+
{
234+
DWORD dummy;
235+
const DWORD fileVersionInfoSize = GetFileVersionInfoSizeW(filename.c_str(), &dummy);
236+
if (fileVersionInfoSize == 0)
237+
{
238+
auto err = GetLastError();
239+
throw std::system_error(err, std::system_category(), "GetFileVersionInfoSize failed");
240+
}
241+
242+
auto buf = std::make_unique<std::byte[]>(fileVersionInfoSize);
243+
244+
if (!GetFileVersionInfoW(filename.c_str(), 0, fileVersionInfoSize, buf.get()))
245+
{
246+
auto err = GetLastError();
247+
throw std::system_error(err, std::system_category(), "GetFileVersionInfo failed");
248+
}
249+
250+
VS_FIXEDFILEINFO* fileInfo = nullptr;
251+
UINT fileInfoSize{};
252+
if (!VerQueryValueW(buf.get(), L"\\", reinterpret_cast<LPVOID*>(&fileInfo), &fileInfoSize))
253+
{
254+
auto err = GetLastError();
255+
throw std::system_error(err, std::system_category(), "VerQueryValue failed");
256+
}
257+
258+
Version version;
259+
version.v32.lo = fileInfo->dwProductVersionLS;
260+
version.v32.hi = fileInfo->dwProductVersionMS;
261+
262+
return version;
263+
}
264+
catch (const std::exception& e)
265+
{
266+
std::cerr << mh::format(MH_FMT_STRING("Unhandled exception ({}) in {}: {}"),
267+
typeid(e).name(), __FUNCTION__, e.what()) << std::endl;
268+
throw;
269+
}
270+
271+
static Version GetInstalledVCRedistVersion(const Version& downloadedVersion) try
272+
{
273+
char buf[256]{};
274+
DWORD bufSize = std::size(buf);
275+
DWORD type{};
276+
277+
const auto arch = tf2_bot_detector::Platform::GetArch();
278+
std::string keyPath = "Installer\\Dependencies\\VC,redist.";
279+
{
280+
switch (arch)
281+
{
282+
case tf2_bot_detector::Platform::Arch::x64:
283+
keyPath += "x64,amd64";
284+
break;
285+
case tf2_bot_detector::Platform::Arch::x86:
286+
keyPath += "x86,x86";
287+
break;
288+
default:
289+
throw std::logic_error(mh::format(MH_FMT_STRING("Unsupported arch {} in {}"), ((int)arch), __FUNCTION__));
290+
}
291+
292+
keyPath += mh::format(MH_FMT_STRING(",{}.{},bundle"), downloadedVersion.major, downloadedVersion.minor);
293+
}
294+
295+
LSTATUS result = RegGetValueA(HKEY_CLASSES_ROOT, keyPath.c_str(), "Version",
296+
RRF_RT_REG_SZ, &type, buf, &bufSize);
297+
298+
if (result != ERROR_SUCCESS)
299+
{
300+
throw std::system_error(result, std::system_category(),
301+
mh::format(MH_FMT_STRING("RegGetValueA for {} failed"), std::quoted(keyPath)));
302+
}
303+
304+
Version installedVersion;
305+
{
306+
const auto scannedCount = sscanf_s(buf, "%hi.%hi.%hi.%hi",
307+
&installedVersion.major, &installedVersion.minor, &installedVersion.patch, &installedVersion.revision);
308+
309+
if (scannedCount != 4)
310+
{
311+
throw std::runtime_error(mh::format(
312+
MH_FMT_STRING("Registry key {} contained {}, which could not be parsed into 4 uint16"),
313+
std::quoted(keyPath), std::quoted(buf)));
314+
}
315+
}
316+
317+
return installedVersion;
318+
}
319+
catch (const std::exception& e)
320+
{
321+
std::cerr << mh::format(MH_FMT_STRING("Unhandled exception ({}) in {}: {}"),
322+
typeid(e).name(), __FUNCTION__, e.what()) << std::endl;
323+
throw;
324+
}
325+
326+
static std::filesystem::path DownloadLatestVCRedist() try
327+
{
328+
auto arch = tf2_bot_detector::Platform::GetArch();
329+
const auto filename = mh::format(MH_FMT_STRING("vc_redist.{:v}.exe"), mh::enum_fmt(arch));
330+
const auto urlPath = mh::format(MH_FMT_STRING("/vs/16/release/{}"), filename);
331+
std::cerr << mh::format(MH_FMT_STRING("Downloading latest vcredist from https://aka.ms{}..."), urlPath) << std::endl;
332+
333+
httplib::SSLClient downloader("aka.ms", 443);
334+
downloader.set_follow_location(true);
335+
336+
httplib::Headers headers =
337+
{
338+
{ "User-Agent", "curl/7.58.0" }
339+
};
340+
341+
httplib::Result result = downloader.Get(urlPath.c_str(), headers);
342+
343+
if (!result)
344+
throw std::runtime_error(mh::format(MH_FMT_STRING("Failed to download latest vcredist: {}"), mh::enum_fmt(result.error())));
345+
346+
auto savePath = std::filesystem::temp_directory_path() / "TF2 Bot Detector" / "Updater_VCRedist";
347+
std::cerr << "Creating " << savePath << "..." << std::endl;
348+
std::filesystem::create_directories(savePath);
349+
350+
savePath /= filename;
351+
std::cerr << "Saving latest vcredist to " << savePath << "..." << std::endl;
352+
{
353+
std::ofstream file;
354+
file.exceptions(std::ios::badbit | std::ios::failbit);
355+
file.open(savePath, std::ios::binary);
356+
file.write(result->body.c_str(), result->body.size());
357+
}
358+
359+
return savePath;
360+
}
361+
catch (const std::exception& e)
362+
{
363+
std::cerr << mh::format(MH_FMT_STRING("Unhandled exception ({}) in {}: {}"),
364+
typeid(e).name(), __FUNCTION__, e.what()) << std::endl;
365+
throw;
366+
}
367+
368+
static tf2_bot_detector::Platform::UpdateSystemDependenciesResult InstallVCRedist(const std::filesystem::path& vcredistPath) try
369+
{
370+
using Result = tf2_bot_detector::Platform::UpdateSystemDependenciesResult;
371+
372+
std::cerr << "Installing latest vcredist..." << std::endl;
373+
auto vcRedistResult = tf2_bot_detector::Platform::Processes::LaunchAndWait(vcredistPath, "/install /quiet /norestart");
374+
const auto vcRedistErrorCode = std::error_code(vcRedistResult, std::system_category());
375+
376+
switch (vcRedistResult)
377+
{
378+
case ERROR_SUCCESS:
379+
std::cerr << "vcredist install successful." << std::endl;
380+
break;
381+
382+
case ERROR_PRODUCT_VERSION:
383+
std::cerr << "vcredist already installed." << std::endl;
384+
break;
385+
386+
case ERROR_SUCCESS_REBOOT_REQUIRED:
387+
{
388+
const auto result = MessageBoxA(nullptr,
389+
"TF2 Bot Detector Updater has successfully installed the latest Microsoft Visual C++ Redistributable. Some programs (including TF2 Bot Detector) might not work properly until you restart your computer.\n\nWould you like to restart your computer now?",
390+
"Reboot Recommended", MB_YESNO | MB_ICONINFORMATION);
391+
392+
return result == IDYES ? Result::RebootRequired : Result::Success;
393+
}
394+
395+
default:
396+
MessageBoxA(nullptr,
397+
mh::format("TF2 Bot Detector attempted to install the latest Microsoft Visual C++ Redistributable, but there was an error:\n\n{}\n\nTF2 Bot Detector might not work correctly. If possible, please report this error on the TF2 Bot Detector discord server.", vcRedistErrorCode).c_str(), "Unknown Error", MB_OK | MB_ICONWARNING);
398+
break;
399+
}
400+
401+
std::cerr << "Finished installing latest vcredist.\n\n" << std::flush;
402+
return Result::Success;
403+
}
404+
catch (const std::exception& e)
405+
{
406+
std::cerr << mh::format(MH_FMT_STRING("Unhandled exception ({}) in {}: {}"),
407+
typeid(e).name(), __FUNCTION__, e.what()) << std::endl;
408+
throw;
409+
}
410+
411+
static bool ShouldInstallVCRedist(const std::filesystem::path& vcredistPath) try
412+
{
413+
std::cerr << "\nChecking downloaded vcredist version..." << std::endl;
414+
const auto downloadedVCRedistVersion = GetFileProductVersion(vcredistPath);
415+
std::cerr << "\tDownloaded vcredist version: " << downloadedVCRedistVersion << std::endl;
416+
417+
std::cerr << "\nChecking installed vcredist version..." << std::endl;
418+
Version installedVCRedistVersion;
419+
try
420+
{
421+
installedVCRedistVersion = GetInstalledVCRedistVersion(downloadedVCRedistVersion);
422+
}
423+
catch (...)
424+
{
425+
std::cerr << "\tGetInstalledVCRedistVersion() failed, installing vcredist..." << std::endl;
426+
return true;
427+
}
428+
429+
std::cerr << "\tInstalled vcredist version: " << installedVCRedistVersion << std::endl;
430+
431+
if (installedVCRedistVersion < downloadedVCRedistVersion)
432+
{
433+
std::cerr << "\nInstalled vcredist version older, installing vcredist..." << std::endl;
434+
return true;
435+
}
436+
else
437+
{
438+
std::cerr << "\nInstalled vcredist version newer or identical, skipping vcredist install." << std::endl;
439+
return false;
440+
}
441+
}
442+
catch (const std::exception& e)
443+
{
444+
std::cerr << mh::format(MH_FMT_STRING("Unhandled exception ({}) in {}: {}"),
445+
typeid(e).name(), __FUNCTION__, e.what()) << std::endl;
446+
447+
std::cerr << "\nChoosing to install vcredist due to unknown error determining vcredist versions." << std::endl;
448+
return true;
449+
}
450+
451+
tf2_bot_detector::Platform::UpdateSystemDependenciesResult tf2_bot_detector::Platform::UpdateSystemDependencies() try
452+
{
453+
const auto vcredistPath = DownloadLatestVCRedist();
454+
455+
if (ShouldInstallVCRedist(vcredistPath))
456+
return InstallVCRedist(vcredistPath);
457+
else
458+
return tf2_bot_detector::Platform::UpdateSystemDependenciesResult::Success;
459+
}
460+
catch (const std::exception& e)
461+
{
462+
std::cerr << mh::format(MH_FMT_STRING("Unhandled exception ({}) in {}: {}"),
463+
typeid(e).name(), __FUNCTION__, e.what()) << std::endl;
464+
throw;
465+
}

0 commit comments

Comments
 (0)