parent
de0bc06856
commit
0a8725e4a9
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "DolphinQt/Updater.h"
|
#include "DolphinQt/Updater.h"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
|
@ -41,6 +42,16 @@ void Updater::CheckForUpdate()
|
||||||
|
|
||||||
void Updater::OnUpdateAvailable(const NewVersionInformation& info)
|
void Updater::OnUpdateAvailable(const NewVersionInformation& info)
|
||||||
{
|
{
|
||||||
|
if (std::getenv("DOLPHIN_UPDATE_SERVER_URL"))
|
||||||
|
{
|
||||||
|
TriggerUpdate(info, AutoUpdateChecker::RestartMode::RESTART_AFTER_UPDATE);
|
||||||
|
RunOnObject(m_parent, [this] {
|
||||||
|
m_parent->close();
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
bool later = false;
|
bool later = false;
|
||||||
|
|
||||||
std::optional<int> choice = RunOnObject(m_parent, [&] {
|
std::optional<int> choice = RunOnObject(m_parent, [&] {
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "UICommon/AutoUpdate.h"
|
#include "UICommon/AutoUpdate.h"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
|
@ -19,12 +20,13 @@
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
#else
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
#include <sys/types.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(_WIN32) || defined(__APPLE__)
|
#if defined(_WIN32) || defined(__APPLE__)
|
||||||
|
@ -160,6 +162,23 @@ static std::string GetPlatformID()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::string GetUpdateServerUrl()
|
||||||
|
{
|
||||||
|
auto server_url = std::getenv("DOLPHIN_UPDATE_SERVER_URL");
|
||||||
|
if (server_url)
|
||||||
|
return server_url;
|
||||||
|
return "https://dolphin-emu.org";
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 GetOwnProcessId()
|
||||||
|
{
|
||||||
|
#ifdef _WIN32
|
||||||
|
return GetCurrentProcessId();
|
||||||
|
#else
|
||||||
|
return getpid();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void AutoUpdateChecker::CheckForUpdate(std::string_view update_track,
|
void AutoUpdateChecker::CheckForUpdate(std::string_view update_track,
|
||||||
std::string_view hash_override, const CheckType check_type)
|
std::string_view hash_override, const CheckType check_type)
|
||||||
{
|
{
|
||||||
|
@ -172,7 +191,7 @@ void AutoUpdateChecker::CheckForUpdate(std::string_view update_track,
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::string_view version_hash = hash_override.empty() ? Common::GetScmRevGitStr() : hash_override;
|
std::string_view version_hash = hash_override.empty() ? Common::GetScmRevGitStr() : hash_override;
|
||||||
std::string url = fmt::format("https://dolphin-emu.org/update/check/v1/{}/{}/{}", update_track,
|
std::string url = fmt::format("{}/update/check/v1/{}/{}/{}", GetUpdateServerUrl(), update_track,
|
||||||
version_hash, GetPlatformID());
|
version_hash, GetPlatformID());
|
||||||
|
|
||||||
const bool is_manual_check = check_type == CheckType::Manual;
|
const bool is_manual_check = check_type == CheckType::Manual;
|
||||||
|
@ -215,7 +234,15 @@ void AutoUpdateChecker::CheckForUpdate(std::string_view update_track,
|
||||||
// TODO: generate the HTML changelog from the JSON information.
|
// TODO: generate the HTML changelog from the JSON information.
|
||||||
nvi.changelog_html = GenerateChangelog(obj["changelog"].get<picojson::array>());
|
nvi.changelog_html = GenerateChangelog(obj["changelog"].get<picojson::array>());
|
||||||
|
|
||||||
OnUpdateAvailable(nvi);
|
if (std::getenv("DOLPHIN_UPDATE_TEST_DONE"))
|
||||||
|
{
|
||||||
|
// We are at end of updater test flow, send a message to server, which will kill us.
|
||||||
|
req.Get(fmt::format("{}/update-test-done/{}", GetUpdateServerUrl(), GetOwnProcessId()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnUpdateAvailable(nvi);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AutoUpdateChecker::TriggerUpdate(const AutoUpdateChecker::NewVersionInformation& info,
|
void AutoUpdateChecker::TriggerUpdate(const AutoUpdateChecker::NewVersionInformation& info,
|
||||||
|
@ -234,11 +261,7 @@ void AutoUpdateChecker::TriggerUpdate(const AutoUpdateChecker::NewVersionInforma
|
||||||
updater_flags["this-manifest-url"] = info.this_manifest_url;
|
updater_flags["this-manifest-url"] = info.this_manifest_url;
|
||||||
updater_flags["next-manifest-url"] = info.next_manifest_url;
|
updater_flags["next-manifest-url"] = info.next_manifest_url;
|
||||||
updater_flags["content-store-url"] = info.content_store_url;
|
updater_flags["content-store-url"] = info.content_store_url;
|
||||||
#ifdef _WIN32
|
updater_flags["parent-pid"] = std::to_string(GetOwnProcessId());
|
||||||
updater_flags["parent-pid"] = std::to_string(GetCurrentProcessId());
|
|
||||||
#else
|
|
||||||
updater_flags["parent-pid"] = std::to_string(getpid());
|
|
||||||
#endif
|
|
||||||
updater_flags["install-base-path"] = File::GetExeDirectory();
|
updater_flags["install-base-path"] = File::GetExeDirectory();
|
||||||
updater_flags["log-file"] = File::GetUserPath(D_LOGS_IDX) + UPDATER_LOG_FILE;
|
updater_flags["log-file"] = File::GetUserPath(D_LOGS_IDX) + UPDATER_LOG_FILE;
|
||||||
|
|
||||||
|
|
|
@ -29,4 +29,6 @@ void Init();
|
||||||
void Sleep(int seconds);
|
void Sleep(int seconds);
|
||||||
void WaitForPID(u32 pid);
|
void WaitForPID(u32 pid);
|
||||||
void LaunchApplication(std::string path);
|
void LaunchApplication(std::string path);
|
||||||
|
|
||||||
|
bool IsTestMode();
|
||||||
} // namespace UI
|
} // namespace UI
|
||||||
|
|
|
@ -38,6 +38,10 @@
|
||||||
const std::array<u8, 32> UPDATE_PUB_KEY = {
|
const std::array<u8, 32> UPDATE_PUB_KEY = {
|
||||||
0x2a, 0xb3, 0xd1, 0xdc, 0x6e, 0xf5, 0x07, 0xf6, 0xa0, 0x6c, 0x7c, 0x54, 0xdf, 0x54, 0xf4, 0x42,
|
0x2a, 0xb3, 0xd1, 0xdc, 0x6e, 0xf5, 0x07, 0xf6, 0xa0, 0x6c, 0x7c, 0x54, 0xdf, 0x54, 0xf4, 0x42,
|
||||||
0x80, 0xa6, 0x28, 0x8b, 0x6d, 0x70, 0x14, 0xb5, 0x4c, 0x34, 0x95, 0x20, 0x4d, 0xd4, 0xd3, 0x5d};
|
0x80, 0xa6, 0x28, 0x8b, 0x6d, 0x70, 0x14, 0xb5, 0x4c, 0x34, 0x95, 0x20, 0x4d, 0xd4, 0xd3, 0x5d};
|
||||||
|
// The private key for UPDATE_PUB_KEY_TEST is in Tools/test-updater.py
|
||||||
|
const std::array<u8, 32> UPDATE_PUB_KEY_TEST = {
|
||||||
|
0x0c, 0x5f, 0xdc, 0xd1, 0x15, 0x71, 0xfb, 0x86, 0x4f, 0x9e, 0x6d, 0xe6, 0x65, 0x39, 0x43, 0xe1,
|
||||||
|
0x9e, 0xe0, 0x9b, 0x28, 0xc9, 0x1a, 0x60, 0xb7, 0x67, 0x1c, 0xf3, 0xf6, 0xca, 0x1b, 0xdd, 0x1a};
|
||||||
|
|
||||||
// Where to log updater output.
|
// Where to log updater output.
|
||||||
static FILE* log_fp = stderr;
|
static FILE* log_fp = stderr;
|
||||||
|
@ -163,8 +167,9 @@ bool VerifySignature(const std::string& data, const std::string& b64_signature)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto& pub_key = UI::IsTestMode() ? UPDATE_PUB_KEY_TEST : UPDATE_PUB_KEY;
|
||||||
return ed25519_verify(signature, reinterpret_cast<const u8*>(data.data()), data.size(),
|
return ed25519_verify(signature, reinterpret_cast<const u8*>(data.data()), data.size(),
|
||||||
UPDATE_PUB_KEY.data());
|
pub_key.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlushLog()
|
void FlushLog()
|
||||||
|
|
|
@ -194,9 +194,12 @@ static bool VCRuntimeUpdate(const BuildInfo& build_info)
|
||||||
|
|
||||||
Common::ScopeGuard redist_deleter([&] { File::Delete(redist_path_u8); });
|
Common::ScopeGuard redist_deleter([&] { File::Delete(redist_path_u8); });
|
||||||
|
|
||||||
// The installer also supports /passive and /quiet. We pass neither to allow the user to see and
|
// The installer also supports /passive and /quiet. We normally pass neither (the
|
||||||
// interact with the installer.
|
// exception being test automation) to allow the user to see and interact with the installer.
|
||||||
std::wstring cmdline = redist_path.filename().wstring() + L" /install /norestart";
|
std::wstring cmdline = redist_path.filename().wstring() + L" /install /norestart";
|
||||||
|
if (UI::IsTestMode())
|
||||||
|
cmdline += L" /passive /quiet";
|
||||||
|
|
||||||
STARTUPINFOW startup_info{.cb = sizeof(startup_info)};
|
STARTUPINFOW startup_info{.cb = sizeof(startup_info)};
|
||||||
PROCESS_INFORMATION process_info;
|
PROCESS_INFORMATION process_info;
|
||||||
if (!CreateProcessW(redist_path.c_str(), cmdline.data(), nullptr, nullptr, TRUE, 0, nullptr,
|
if (!CreateProcessW(redist_path.c_str(), cmdline.data(), nullptr, nullptr, TRUE, 0, nullptr,
|
||||||
|
@ -213,7 +216,8 @@ static bool VCRuntimeUpdate(const BuildInfo& build_info)
|
||||||
CloseHandle(process_info.hProcess);
|
CloseHandle(process_info.hProcess);
|
||||||
// NOTE: Some nonzero exit codes can still be considered success (e.g. if installation was
|
// NOTE: Some nonzero exit codes can still be considered success (e.g. if installation was
|
||||||
// bypassed because the same version already installed).
|
// bypassed because the same version already installed).
|
||||||
return has_exit_code && exit_code == EXIT_SUCCESS;
|
return has_exit_code &&
|
||||||
|
(exit_code == ERROR_SUCCESS || exit_code == ERROR_SUCCESS_REBOOT_REQUIRED);
|
||||||
}
|
}
|
||||||
|
|
||||||
static BuildVersion CurrentOSVersion()
|
static BuildVersion CurrentOSVersion()
|
||||||
|
@ -287,11 +291,16 @@ bool CheckBuildInfo(const BuildInfos& build_infos)
|
||||||
// Check if application being launched needs more recent version of VC Redist. If so, download
|
// Check if application being launched needs more recent version of VC Redist. If so, download
|
||||||
// latest updater and execute it.
|
// latest updater and execute it.
|
||||||
auto vc_check = VCRuntimeVersionCheck(build_infos);
|
auto vc_check = VCRuntimeVersionCheck(build_infos);
|
||||||
if (vc_check.status != VersionCheckStatus::NothingToDo)
|
const auto is_test_mode = UI::IsTestMode();
|
||||||
|
if (vc_check.status != VersionCheckStatus::NothingToDo || is_test_mode)
|
||||||
{
|
{
|
||||||
// Don't bother checking status of the install itself, just check if we actually see the new
|
auto update_ok = VCRuntimeUpdate(build_infos.next);
|
||||||
// version.
|
if (!update_ok && is_test_mode)
|
||||||
VCRuntimeUpdate(build_infos.next);
|
{
|
||||||
|
// For now, only check return value when test automation is running.
|
||||||
|
// The vc_redist exe may return other non-zero status that we don't check for, yet.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
vc_check = VCRuntimeVersionCheck(build_infos);
|
vc_check = VCRuntimeVersionCheck(build_infos);
|
||||||
if (vc_check.status == VersionCheckStatus::UpdateRequired)
|
if (vc_check.status == VersionCheckStatus::UpdateRequired)
|
||||||
{
|
{
|
||||||
|
|
|
@ -3,12 +3,14 @@
|
||||||
|
|
||||||
#include "UpdaterCommon/UI.h"
|
#include "UpdaterCommon/UI.h"
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
#include <CommCtrl.h>
|
#include <CommCtrl.h>
|
||||||
#include <ShObjIdl.h>
|
#include <ShObjIdl.h>
|
||||||
|
#include <ShlObj.h>
|
||||||
#include <shellapi.h>
|
#include <shellapi.h>
|
||||||
#include <wrl/client.h>
|
#include <wrl/client.h>
|
||||||
|
|
||||||
|
@ -251,11 +253,34 @@ void Stop()
|
||||||
ui_thread.join();
|
ui_thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsTestMode()
|
||||||
|
{
|
||||||
|
return std::getenv("DOLPHIN_UPDATE_SERVER_URL") != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
void LaunchApplication(std::string path)
|
void LaunchApplication(std::string path)
|
||||||
{
|
{
|
||||||
// Indirectly start the application via explorer. This effectively drops admin priviliges because
|
const auto wpath = UTF8ToWString(path);
|
||||||
// explorer is running as current user.
|
if (IsUserAnAdmin())
|
||||||
ShellExecuteW(nullptr, nullptr, L"explorer.exe", UTF8ToWString(path).c_str(), nullptr, SW_SHOW);
|
{
|
||||||
|
// Indirectly start the application via explorer. This effectively drops admin privileges
|
||||||
|
// because explorer is running as current user.
|
||||||
|
ShellExecuteW(nullptr, nullptr, L"explorer.exe", wpath.c_str(), nullptr, SW_SHOW);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::wstring cmdline = wpath;
|
||||||
|
STARTUPINFOW startup_info{.cb = sizeof(startup_info)};
|
||||||
|
PROCESS_INFORMATION process_info;
|
||||||
|
if (IsTestMode())
|
||||||
|
SetEnvironmentVariableA("DOLPHIN_UPDATE_TEST_DONE", "1");
|
||||||
|
if (CreateProcessW(wpath.c_str(), cmdline.data(), nullptr, nullptr, TRUE, 0, nullptr, nullptr,
|
||||||
|
&startup_info, &process_info))
|
||||||
|
{
|
||||||
|
CloseHandle(process_info.hThread);
|
||||||
|
CloseHandle(process_info.hProcess);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sleep(int sleep)
|
void Sleep(int sleep)
|
||||||
|
|
|
@ -0,0 +1,200 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# requirements: pycryptodome
|
||||||
|
from Crypto.PublicKey import ECC
|
||||||
|
from Crypto.Signature import eddsa
|
||||||
|
from hashlib import sha256
|
||||||
|
from pathlib import Path
|
||||||
|
import base64
|
||||||
|
import configparser
|
||||||
|
import gzip
|
||||||
|
import http.server
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import socketserver
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
UPDATE_KEY_TEST = ECC.construct(
|
||||||
|
curve="Ed25519",
|
||||||
|
seed=bytes.fromhex(
|
||||||
|
"543a581db60008bbb978a464e136d686dbc9d594119e928b5276bece3d583d81"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
HTTP_SERVER_ADDR = ("localhost", 8042)
|
||||||
|
DOLPHIN_UPDATE_SERVER_URL = f"http://{HTTP_SERVER_ADDR[0]}:{HTTP_SERVER_ADDR[1]}"
|
||||||
|
|
||||||
|
|
||||||
|
class Manifest:
|
||||||
|
def __init__(self, path: Path):
|
||||||
|
self.path = path
|
||||||
|
self.entries = {}
|
||||||
|
for p in self.path.glob("**/*.*"):
|
||||||
|
if not p.is_file():
|
||||||
|
continue
|
||||||
|
digest = sha256(p.read_bytes()).digest()[:0x10].hex()
|
||||||
|
self.entries[digest] = p.relative_to(self.path).as_posix()
|
||||||
|
|
||||||
|
def get_signed(self):
|
||||||
|
manifest = "".join(
|
||||||
|
f"{name}\t{digest}\n" for digest, name in self.entries.items()
|
||||||
|
)
|
||||||
|
manifest = manifest.encode("utf-8")
|
||||||
|
sig = eddsa.new(UPDATE_KEY_TEST, "rfc8032").sign(manifest)
|
||||||
|
manifest += b"\n" + base64.b64encode(sig) + b"\n"
|
||||||
|
return gzip.compress(manifest)
|
||||||
|
|
||||||
|
def get_path(self, digest):
|
||||||
|
return self.path.joinpath(self.entries.get(digest))
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPRequestHandler(http.server.BaseHTTPRequestHandler):
|
||||||
|
def do_GET(self):
|
||||||
|
if self.path.startswith("/update/check/v1/updater-test"):
|
||||||
|
self.send_response(200)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(
|
||||||
|
bytes(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"status": "outdated",
|
||||||
|
"content-store": DOLPHIN_UPDATE_SERVER_URL + "/content/",
|
||||||
|
"changelog": [],
|
||||||
|
"old": {"manifest": DOLPHIN_UPDATE_SERVER_URL + "/old"},
|
||||||
|
"new": {
|
||||||
|
"manifest": DOLPHIN_UPDATE_SERVER_URL + "/new",
|
||||||
|
"name": "updater-test",
|
||||||
|
"hash": bytes(range(32)).hex(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"utf-8",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif self.path == "/old":
|
||||||
|
self.send_response(200)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(self.current.get_signed())
|
||||||
|
elif self.path == "/new":
|
||||||
|
self.send_response(200)
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(self.next.get_signed())
|
||||||
|
elif self.path.startswith("/content/"):
|
||||||
|
self.send_response(200)
|
||||||
|
self.end_headers()
|
||||||
|
digest = "".join(self.path[len("/content/") :].split("/"))
|
||||||
|
path = self.next.get_path(digest)
|
||||||
|
self.wfile.write(gzip.compress(path.read_bytes()))
|
||||||
|
elif self.path.startswith("/update-test-done/"):
|
||||||
|
self.send_response(200)
|
||||||
|
self.end_headers()
|
||||||
|
HTTPRequestHandler.dolphin_pid = int(self.path[len("/update-test-done/") :])
|
||||||
|
self.done.set()
|
||||||
|
|
||||||
|
|
||||||
|
def http_server():
|
||||||
|
with socketserver.TCPServer(HTTP_SERVER_ADDR, HTTPRequestHandler) as httpd:
|
||||||
|
httpd.serve_forever()
|
||||||
|
|
||||||
|
|
||||||
|
def create_entries_in_ini(ini_path: Path, entries: dict):
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
if ini_path.exists():
|
||||||
|
config.read(ini_path)
|
||||||
|
else:
|
||||||
|
ini_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
for section, options in entries.items():
|
||||||
|
if not config.has_section(section):
|
||||||
|
config.add_section(section)
|
||||||
|
for option, value in options.items():
|
||||||
|
config.set(section, option, value)
|
||||||
|
|
||||||
|
with ini_path.open("w") as f:
|
||||||
|
config.write(f)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
dolphin_bin_path = Path(sys.argv[1])
|
||||||
|
|
||||||
|
threading.Thread(target=http_server, daemon=True).start()
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||||
|
tmp_dir = Path(tmp_dir)
|
||||||
|
|
||||||
|
tmp_dolphin = tmp_dir.joinpath("dolphin")
|
||||||
|
print(f"install to {tmp_dolphin}")
|
||||||
|
shutil.copytree(dolphin_bin_path.parent, tmp_dolphin)
|
||||||
|
tmp_dolphin.joinpath("portable.txt").touch()
|
||||||
|
create_entries_in_ini(
|
||||||
|
tmp_dolphin.joinpath("User/Config/Dolphin.ini"),
|
||||||
|
{
|
||||||
|
"Analytics": {"Enabled": "False", "PermissionAsked": "True"},
|
||||||
|
"AutoUpdate": {"UpdateTrack": "updater-test"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
tmp_dolphin_next = tmp_dir.joinpath("dolphin_next")
|
||||||
|
print(f"install next to {tmp_dolphin_next}")
|
||||||
|
# XXX copies from just-created dir so Dolphin.ini is kept
|
||||||
|
shutil.copytree(tmp_dolphin, tmp_dolphin_next)
|
||||||
|
tmp_dolphin_next.joinpath("updater-test-file").write_text("test")
|
||||||
|
with tmp_dolphin_next.joinpath("build_info.txt").open("a") as f:
|
||||||
|
print("test", file=f)
|
||||||
|
for ext in ("exe", "dll"):
|
||||||
|
for path in tmp_dolphin_next.glob("**/*." + ext):
|
||||||
|
data = bytearray(path.read_bytes())
|
||||||
|
richpos = data[:0x200].find(b"Rich")
|
||||||
|
if richpos < 0:
|
||||||
|
continue
|
||||||
|
data[richpos : richpos + 4] = b"DOLP"
|
||||||
|
path.write_bytes(data)
|
||||||
|
|
||||||
|
HTTPRequestHandler.current = Manifest(tmp_dolphin)
|
||||||
|
HTTPRequestHandler.next = Manifest(tmp_dolphin_next)
|
||||||
|
HTTPRequestHandler.done = threading.Event()
|
||||||
|
|
||||||
|
tmp_env = os.environ
|
||||||
|
tmp_env.update({"DOLPHIN_UPDATE_SERVER_URL": DOLPHIN_UPDATE_SERVER_URL})
|
||||||
|
tmp_dolphin_bin = tmp_dolphin.joinpath(dolphin_bin_path.name)
|
||||||
|
result = subprocess.run(tmp_dolphin_bin, env=tmp_env)
|
||||||
|
assert result.returncode == 0
|
||||||
|
|
||||||
|
assert HTTPRequestHandler.done.wait(60 * 2)
|
||||||
|
# works fine but raises exceptions...
|
||||||
|
try:
|
||||||
|
os.kill(HTTPRequestHandler.dolphin_pid, 0)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
os.waitpid(HTTPRequestHandler.dolphin_pid, 0)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
failed = False
|
||||||
|
for path in tmp_dolphin_next.glob("**/*.*"):
|
||||||
|
if not path.is_file():
|
||||||
|
continue
|
||||||
|
path_rel = path.relative_to(tmp_dolphin_next)
|
||||||
|
if path_rel.parts[0] == "User":
|
||||||
|
continue
|
||||||
|
new_path = tmp_dolphin.joinpath(path_rel)
|
||||||
|
if not new_path.exists():
|
||||||
|
print(f"missing: {new_path}")
|
||||||
|
failed = True
|
||||||
|
continue
|
||||||
|
if (
|
||||||
|
sha256(new_path.read_bytes()).digest()
|
||||||
|
!= sha256(path.read_bytes()).digest()
|
||||||
|
):
|
||||||
|
print(f"bad digest: {new_path} {path}")
|
||||||
|
failed = True
|
||||||
|
continue
|
||||||
|
assert not failed
|
||||||
|
|
||||||
|
print(tmp_dolphin.joinpath("User/Logs/Updater.log").read_text())
|
||||||
|
# while True: time.sleep(1)
|
Loading…
Reference in New Issue