Launcher: when ReSkate stops while starting, the STATUS tile says why instead of blaming anti-virus

DingoSDKDebugInitialize answers the launcher with a bare TRUE or FALSE. On
FALSE the launcher threw "DingoSDKDebugInitialize returned FALSE", which the
STATUS tile turned into "ReSkate could not attach to Skate" with anti-virus
advice, although the attach had worked and the runtime had logged its reason.

ReSkate.dll now exports DingoSDKStartupFailure, a 1 KB buffer that every
FALSE path fills with its reason (the first one stays). The launcher reads it
out of Skate before closing it. With an older ReSkate.dll it reads the ERROR
lines that Skate process wrote to ReSkate.log during the call instead, and
with neither it says it was not told why. problem.h explains these messages
before the attach rules: a save in use or a folder another game holds says
Skate is already running, hook and overlay failures keep the attach advice,
and anything else leads with the runtime's own reason. Bootstrap and the
local profile hand their reason back, and the profile checks that used to
return false without a word now log one.

Fixes #90, fixes #346.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
VebjorhkandClaude Opus 5.5 committed 2026-10-24 23:51:12 +02:00
1 parent 55a7342dc6
commit 97f2406bea
12 files changed
+433 -65

No files matched your search

+59 -35
View File
@@ -73,11 +73,30 @@
#include "Engine/Vfs/content_catalogs.h"
#include "Engine/Vfs/world_layer_scan.h"
#include "Engine/Game/World/world_layer_catalog.h"
#include "Engine/Core/Platform/startup_failure.h"
#include <array>
#include <cstring>
#include <filesystem>
// Why startup stopped, for the launcher to show (startup_failure.h).
extern "C" __declspec(dllexport) char DingoSDKStartupFailure[dingosdk::startup_failure::capacity]{};
namespace dingosdk::runtime::detail {
namespace {
// Keeps the first reason startup stopped. The launcher reads it only after
// DingoSDKDebugInitialize has returned, so nothing reads it while it is written.
BOOL startup_failed(std::string_view reason) noexcept {
if (DingoSDKStartupFailure[0]) return FALSE;
const auto length = dingosdk::startup_failure::utf8_prefix(reason, sizeof(DingoSDKStartupFailure) - 1);
std::memcpy(DingoSDKStartupFailure, reason.data(), length);
DingoSDKStartupFailure[length] = '\0';
return FALSE;
}
BOOL stop_startup(dingosdk::logging::Level level, dingosdk::logging::Channel channel,
std::string_view reason) noexcept {
dingosdk::logging::write(level, channel, reason);
return startup_failed(reason);
}
bool exact_environment_one(const wchar_t* name) noexcept {
const auto incoming_error = GetLastError();
std::array<wchar_t, 2> value{};
@@ -109,20 +128,16 @@ extern "C" __declspec(dllexport) BOOL WINAPI DingoSDKDebugInitialize() {
dingosdk::logging::startup_banner();
if (!files_ready) dingosdk::logging::write(dingosdk::logging::Level::warning,
dingosdk::logging::Channel::runtime, "File logging is unavailable; console and debugger output remain active.");
if (!validate_image(r.base)) {
dingosdk::logging::write(dingosdk::logging::Level::critical, dingosdk::logging::Channel::runtime, "Game image validation failed.");
return FALSE;
}
if (!validate_image(r.base))
return stop_startup(dingosdk::logging::Level::critical, dingosdk::logging::Channel::runtime, "Game image validation failed.");
// Before anything is merged or opened in the folder: one game in it at a time
// (game_instance.h). The launcher checks first; this is two started at the same moment.
{
std::wstring game(32768, L'\0');
game.resize(GetModuleFileNameW(nullptr, game.data(), static_cast<DWORD>(game.size())));
if (!game.empty() && !dingosdk::game_instance::claim(std::filesystem::path(game).parent_path())) {
dingosdk::logging::write(dingosdk::logging::Level::critical, dingosdk::logging::Channel::runtime,
if (!game.empty() && !dingosdk::game_instance::claim(std::filesystem::path(game).parent_path()))
return stop_startup(dingosdk::logging::Level::critical, dingosdk::logging::Channel::runtime,
"Another Skate is already running from this folder; this one does not start.");
return FALSE;
}
}
if (const auto& content = dingosdk::content_cache::catalogs(); content.available) {
dingosdk::logging::log(dingosdk::logging::Level::info, dingosdk::logging::Channel::assets,
@@ -162,7 +177,8 @@ extern "C" __declspec(dllexport) BOOL WINAPI DingoSDKDebugInitialize() {
}
} startup_window;
dingosdk::initialize_client_source_spawn(r.base);
if (!dingosdk::runtime::initialize_bootstrap(r.base)) return FALSE;
std::string bootstrap_failure;
if (!dingosdk::runtime::initialize_bootstrap(r.base, bootstrap_failure)) return startup_failed(bootstrap_failure);
// Skate imports its graphics factory from the sibling Streamline
// interposer. Load that ordinary dependency while the game is still
@@ -173,17 +189,17 @@ extern "C" __declspec(dllexport) BOOL WINAPI DingoSDKDebugInitialize() {
if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCWSTR>(&DingoSDKDebugInitialize), &self)) {
dingosdk::logging::log(dingosdk::logging::Level::error, dingosdk::logging::Channel::runtime,
"Cannot locate the runtime module (Windows error {}).", GetLastError());
return FALSE;
const auto error = GetLastError();
return stop_startup(dingosdk::logging::Level::error, dingosdk::logging::Channel::runtime,
std::format("Cannot locate the runtime module (Windows error {}).", error));
}
#pragma warning(pop)
wchar_t self_path[32768]{};
const auto self_length = GetModuleFileNameW(self, self_path, 32768);
if (!self_length || self_length >= 32768) {
dingosdk::logging::log(dingosdk::logging::Level::error, dingosdk::logging::Channel::runtime,
"Cannot resolve the runtime path (Windows error {}).", GetLastError());
return FALSE;
const auto error = GetLastError();
return stop_startup(dingosdk::logging::Level::error, dingosdk::logging::Channel::runtime,
std::format("Cannot resolve the runtime path (Windows error {}).", error));
}
const auto interposer_path = std::filesystem::path(
std::wstring(self_path, self_length)).parent_path() / L"sl.interposer.dll";
@@ -220,9 +236,9 @@ extern "C" __declspec(dllexport) BOOL WINAPI DingoSDKDebugInitialize() {
if (!interposer) interposer = LoadLibraryExW(interposer_path.c_str(), nullptr,
LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR | LOAD_LIBRARY_SEARCH_SYSTEM32);
if (!interposer) {
dingosdk::logging::log(dingosdk::logging::Level::error, dingosdk::logging::Channel::graphics,
"Cannot load the game's sl.interposer.dll (Windows error {}).", GetLastError());
return FALSE;
const auto error = GetLastError();
return stop_startup(dingosdk::logging::Level::error, dingosdk::logging::Channel::graphics,
std::format("Cannot load the game's sl.interposer.dll (Windows error {}).", error));
}
wchar_t interposer_loaded_path[32768]{};
const auto interposer_length = GetModuleFileNameW(
@@ -231,11 +247,9 @@ extern "C" __declspec(dllexport) BOOL WINAPI DingoSDKDebugInitialize() {
if (!interposer_length || interposer_length >= 32768 ||
!std::filesystem::equivalent(interposer_path,
std::filesystem::path(std::wstring(interposer_loaded_path, interposer_length)),
interposer_error) || interposer_error) {
dingosdk::logging::write(dingosdk::logging::Level::error, dingosdk::logging::Channel::graphics,
interposer_error) || interposer_error)
return stop_startup(dingosdk::logging::Level::error, dingosdk::logging::Channel::graphics,
"Loaded graphics provider does not match the game's sibling sl.interposer.dll.");
return FALSE;
}
dingosdk::logging::write(dingosdk::logging::Level::info, dingosdk::logging::Channel::graphics,
"Game graphics provider loaded: sl.interposer.dll.");
const dingosdk::overlay::CallbacksV3 callbacks{
@@ -317,10 +331,8 @@ extern "C" __declspec(dllexport) BOOL WINAPI DingoSDKDebugInitialize() {
dingosdk::overlay::set_park_paste_queue(dingosdk::queue_local_park_paste);
dingosdk::overlay::set_park_object_set_queue(dingosdk::queue_local_park_object_set);
dingosdk::overlay::set_park_delete_queue(dingosdk::queue_local_park_delete);
if (!DingoSDKOverlayStartV3(&callbacks)) {
dingosdk::logging::write(dingosdk::logging::Level::error, dingosdk::logging::Channel::graphics, "Overlay startup failed.");
return FALSE;
}
if (!DingoSDKOverlayStartV3(&callbacks))
return stop_startup(dingosdk::logging::Level::error, dingosdk::logging::Channel::graphics, "Overlay startup failed.");
// Install the offline boot hooks before the passive observers and other
// native hooks, while Skate's primary thread remains suspended. The
// launcher always sets the global-offline variable; install the dependent
@@ -356,16 +368,19 @@ extern "C" __declspec(dllexport) BOOL WINAPI DingoSDKDebugInitialize() {
if (global_offline_requested && (!global_offline_hook || !progression_guard_armed)) {
record("{\"event\":\"launcher_readiness\",\"ready\":false,"
"\"reason\":\"authored_offline_hooks\"}");
return FALSE;
return stop_startup(dingosdk::logging::Level::error, dingosdk::logging::Channel::runtime,
std::format("The offline hooks could not be installed (offline boot {}, progression guard {}).",
global_offline_hook ? "ready" : "failed", progression_guard_armed ? "armed" : "not armed"));
}
const bool graphics_labels = dingosdk::initialize_graphics_labels(r.base,
global_offline_hook && progression_guard_armed);
record("{\"event\":\"graphics_labels_initialized\",\"active\":" +
std::string(graphics_labels ? "true" : "false") + "}");
std::string profile_failure;
if (global_offline_hook && progression_guard_armed &&
!dingosdk::initialize_local_profile(r.base, true, dingosdk::profile::default_path())) {
!dingosdk::initialize_local_profile(r.base, true, dingosdk::profile::default_path(), profile_failure)) {
record("{\"event\":\"launcher_readiness\",\"ready\":false,\"reason\":\"local_profile\"}");
return FALSE;
return startup_failed(profile_failure);
}
if (global_offline_hook && progression_guard_armed) {
dingosdk::set_local_location_travel_queue(&queue_location_travel);
@@ -539,7 +554,9 @@ extern "C" __declspec(dllexport) BOOL WINAPI DingoSDKDebugInitialize() {
if (global_offline_requested) {
record("{\"event\":\"launcher_readiness\",\"ready\":false,"
"\"reason\":\"native_tick_hook\"}");
return FALSE;
return stop_startup(dingosdk::logging::Level::error, dingosdk::logging::Channel::runtime,
std::format("The engine update hook could not be installed: {} ({}).",
dingosdk::hook_status_string(hook_status), static_cast<long>(hook_status)));
}
startup_window.hand_over();
return TRUE;
@@ -565,13 +582,20 @@ extern "C" __declspec(dllexport) BOOL WINAPI DingoSDKDebugInitialize() {
startup_window.hand_over();
return TRUE;
} catch (const std::exception& error) {
dingosdk::logging::log(dingosdk::logging::Level::critical, dingosdk::logging::Channel::runtime,
"Runtime initialization failed: {}", error.what());
return FALSE;
// Nothing may escape the launcher's remote thread: a reason that cannot
// be built falls back to a fixed one.
using namespace dingosdk::runtime::detail;
try {
return stop_startup(dingosdk::logging::Level::critical, dingosdk::logging::Channel::runtime,
std::string("Runtime initialization failed: ") + error.what());
} catch (...) {
return stop_startup(dingosdk::logging::Level::critical, dingosdk::logging::Channel::runtime,
"Runtime initialization failed.");
}
} catch (...) {
dingosdk::logging::write(dingosdk::logging::Level::critical, dingosdk::logging::Channel::runtime,
using namespace dingosdk::runtime::detail;
return stop_startup(dingosdk::logging::Level::critical, dingosdk::logging::Channel::runtime,
"Runtime initialization failed with an unknown exception.");
return FALSE;
}
}