mirror of
https://github.com/Dingo-Shenanigans/ReSkate.git
synced 2026-10-18 19:05:21 +02:00
257 lines
10 KiB
C++
257 lines
10 KiB
C++
#include "progression_service_guard.h"
|
|
#include "Engine/Game/Build/supported_build.h"
|
|
#include "Engine/Game/Build/image_identity.h"
|
|
#include "Engine/Game/Build/addresses.h"
|
|
#include "Engine/Game/Build/20260929/progression_service_guard.h"
|
|
|
|
#include <Windows.h>
|
|
#include "Engine/Core/Hooks/hooks.h"
|
|
#include "Engine/Core/Platform/memory.h"
|
|
#include <intrin.h>
|
|
#include <array>
|
|
#include <atomic>
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <mutex>
|
|
#include <sstream>
|
|
|
|
namespace dingosdk {
|
|
namespace {
|
|
namespace guard = addr::progression_service_guard;
|
|
|
|
using ProgressionDispatch = void (*)(std::uintptr_t, std::uintptr_t, std::uintptr_t);
|
|
using ImageVerifier = bool (*)(std::uintptr_t) noexcept;
|
|
using CallerClassifier = bool (*)(std::uintptr_t, std::uintptr_t) noexcept;
|
|
|
|
constexpr std::uintptr_t image_size = supported_build::game_image_size;
|
|
|
|
static_assert(guard::dispatch + guard::dispatch_prefix.size() <= image_size &&
|
|
guard::progression_call + guard::progression_call_prefix.size() <= image_size,
|
|
"Guard fingerprints must remain within the inspected image");
|
|
|
|
|
|
enum class GateStatus : unsigned char {
|
|
not_attempted,
|
|
image_mismatch,
|
|
dispatch_fingerprint_mismatch,
|
|
callsite_fingerprint_mismatch,
|
|
create_failed,
|
|
enable_failed,
|
|
enable_failed_disable_failed,
|
|
preparing,
|
|
prepared,
|
|
active
|
|
};
|
|
|
|
bool exact_progression_caller(std::uintptr_t return_address, std::uintptr_t base) noexcept;
|
|
|
|
struct ProgressionServiceGuardState {
|
|
std::uintptr_t base{};
|
|
std::atomic<ProgressionDispatch> original{};
|
|
std::atomic<GateStatus> gate{GateStatus::not_attempted};
|
|
std::atomic<std::uint64_t> invocations{};
|
|
std::atomic<std::uint64_t> null_service_skips{};
|
|
std::atomic<std::uint64_t> forwards{};
|
|
std::atomic<std::uint64_t> matching_nonnull_forwards{};
|
|
std::atomic<std::uint64_t> unmatched_null_forwards{};
|
|
std::atomic<std::uint64_t> native_exceptions{};
|
|
std::atomic<std::uint64_t> unarmed_forwards{};
|
|
std::atomic<std::uint64_t> preparing_invocations{};
|
|
std::mutex initialization_mutex;
|
|
ImageVerifier verify_image{&supported_build::running_image_matches};
|
|
CallerClassifier classify_caller{&exact_progression_caller};
|
|
bool attempted{};
|
|
};
|
|
|
|
struct PreserveError {
|
|
DWORD value{GetLastError()};
|
|
~PreserveError() { SetLastError(value); }
|
|
};
|
|
|
|
ProgressionServiceGuardState& progression_service_guard_state() {
|
|
static auto* value = new ProgressionServiceGuardState;
|
|
return *value;
|
|
}
|
|
|
|
template<std::size_t N>
|
|
bool fingerprint_matches(std::uintptr_t address, const std::array<unsigned char, N>& expected) noexcept {
|
|
std::array<unsigned char, N> actual{};
|
|
return memory::read(address, actual) && actual == expected;
|
|
}
|
|
|
|
bool exact_progression_caller(
|
|
std::uintptr_t return_address, std::uintptr_t base) noexcept {
|
|
return base && return_address == base + guard::progression_caller_return;
|
|
}
|
|
|
|
const char* gate_name(GateStatus gate) noexcept {
|
|
switch (gate) {
|
|
case GateStatus::not_attempted: return "not_attempted";
|
|
case GateStatus::image_mismatch: return "image_mismatch";
|
|
case GateStatus::dispatch_fingerprint_mismatch: return "dispatch_fingerprint_mismatch";
|
|
case GateStatus::callsite_fingerprint_mismatch: return "callsite_fingerprint_mismatch";
|
|
case GateStatus::create_failed: return "create_failed";
|
|
case GateStatus::enable_failed: return "enable_failed";
|
|
case GateStatus::enable_failed_disable_failed: return "enable_failed_disable_failed";
|
|
case GateStatus::preparing: return "preparing";
|
|
case GateStatus::prepared: return "prepared";
|
|
case GateStatus::active: return "active";
|
|
}
|
|
return "unknown";
|
|
}
|
|
|
|
void progression_dispatch_hook(
|
|
std::uintptr_t service, std::uintptr_t events, std::uintptr_t context) {
|
|
const auto incoming_error = GetLastError();
|
|
auto& state = progression_service_guard_state();
|
|
const auto gate = state.gate.load(std::memory_order_acquire);
|
|
const bool armed = gate == GateStatus::active;
|
|
const bool preparing = gate == GateStatus::preparing;
|
|
// The gate publishes the trampoline and image base.
|
|
const auto original = state.original.load(std::memory_order_acquire);
|
|
bool matching_caller{};
|
|
if (armed) {
|
|
++state.invocations;
|
|
matching_caller = state.classify_caller(
|
|
reinterpret_cast<std::uintptr_t>(_ReturnAddress()), state.base);
|
|
if (!service && matching_caller) {
|
|
++state.null_service_skips;
|
|
SetLastError(incoming_error);
|
|
return;
|
|
}
|
|
++state.forwards;
|
|
if (matching_caller) ++state.matching_nonnull_forwards;
|
|
else if (!service) ++state.unmatched_null_forwards;
|
|
}
|
|
else {
|
|
++state.unarmed_forwards;
|
|
if (preparing) ++state.preparing_invocations;
|
|
}
|
|
|
|
SetLastError(incoming_error);
|
|
try {
|
|
original(service, events, context);
|
|
} catch (...) {
|
|
const auto native_error = GetLastError();
|
|
if (armed) ++state.native_exceptions;
|
|
SetLastError(native_error);
|
|
throw;
|
|
}
|
|
const auto native_error = GetLastError();
|
|
SetLastError(native_error);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool prepare_progression_service_guard(std::uintptr_t base) noexcept {
|
|
PreserveError preserve;
|
|
auto& state = progression_service_guard_state();
|
|
try {
|
|
std::lock_guard lock(state.initialization_mutex);
|
|
if (state.attempted)
|
|
return state.base == base &&
|
|
(state.gate.load(std::memory_order_acquire) == GateStatus::prepared ||
|
|
state.gate.load(std::memory_order_acquire) == GateStatus::active);
|
|
state.attempted = true;
|
|
state.base = base;
|
|
if (!state.verify_image(base)) {
|
|
state.gate.store(GateStatus::image_mismatch, std::memory_order_release);
|
|
return false;
|
|
}
|
|
if (!fingerprint_matches(base + guard::dispatch, guard::dispatch_prefix)) {
|
|
state.gate.store(GateStatus::dispatch_fingerprint_mismatch, std::memory_order_release);
|
|
return false;
|
|
}
|
|
if (!fingerprint_matches(base + guard::progression_call,
|
|
guard::progression_call_prefix)) {
|
|
state.gate.store(GateStatus::callsite_fingerprint_mismatch, std::memory_order_release);
|
|
return false;
|
|
}
|
|
|
|
auto* target = reinterpret_cast<void*>(base + guard::dispatch);
|
|
void* trampoline{};
|
|
const auto created = hook_prepare(target,
|
|
reinterpret_cast<void*>(&progression_dispatch_hook), &trampoline);
|
|
if (created != HookOk || !trampoline) {
|
|
if (created == HookOk) hook_remove(target);
|
|
state.gate.store(GateStatus::create_failed, std::memory_order_release);
|
|
return false;
|
|
}
|
|
state.original.store(reinterpret_cast<ProgressionDispatch>(trampoline),
|
|
std::memory_order_release);
|
|
// If redirection becomes visible inside hook_enable, every call still
|
|
// forwards. A failed enable disables the target and retains the
|
|
// trampoline for threads that may still be returning.
|
|
state.gate.store(GateStatus::preparing, std::memory_order_release);
|
|
if (hook_enable(target) != HookOk) {
|
|
state.gate.store(GateStatus::enable_failed, std::memory_order_release);
|
|
const auto disabled = hook_disable(target);
|
|
state.gate.store(disabled == HookOk || disabled == HookDisabled ?
|
|
GateStatus::enable_failed : GateStatus::enable_failed_disable_failed,
|
|
std::memory_order_release);
|
|
return false;
|
|
}
|
|
state.gate.store(GateStatus::prepared, std::memory_order_release);
|
|
return true;
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool arm_progression_service_guard(std::uintptr_t base) noexcept {
|
|
PreserveError preserve;
|
|
auto& state = progression_service_guard_state();
|
|
try {
|
|
std::lock_guard lock(state.initialization_mutex);
|
|
const auto gate = state.gate.load(std::memory_order_acquire);
|
|
if (state.base != base || (gate != GateStatus::prepared && gate != GateStatus::active))
|
|
return false;
|
|
if (gate == GateStatus::active) return true;
|
|
state.gate.store(GateStatus::active, std::memory_order_release);
|
|
return true;
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
ProgressionServiceGuardObservation progression_service_guard_observation() {
|
|
PreserveError preserve;
|
|
auto& state = progression_service_guard_state();
|
|
const auto gate = state.gate.load(std::memory_order_acquire);
|
|
const bool armed = gate == GateStatus::active;
|
|
const auto invocations = state.invocations.load(std::memory_order_relaxed);
|
|
const auto skips = state.null_service_skips.load(std::memory_order_relaxed);
|
|
const auto forwards = state.forwards.load(std::memory_order_relaxed);
|
|
const auto matching_nonnull =
|
|
state.matching_nonnull_forwards.load(std::memory_order_relaxed);
|
|
const auto unmatched_null = state.unmatched_null_forwards.load(std::memory_order_relaxed);
|
|
const auto exceptions = state.native_exceptions.load(std::memory_order_relaxed);
|
|
const auto unarmed = state.unarmed_forwards.load(std::memory_order_relaxed);
|
|
const auto preparing = state.preparing_invocations.load(std::memory_order_relaxed);
|
|
|
|
std::ostringstream json;
|
|
json << "{\"event\":\"progression_service_guard_observation\",\"gate\":\""
|
|
<< gate_name(gate) << "\",\"global_offline_prerequisite\":"
|
|
<< (armed ? "true" : "false")
|
|
<< ",\"armed\":"
|
|
<< (armed ? "true" : "false")
|
|
<< ",\"dispatch_rva\":\"0x" << std::hex << guard::dispatch << "\""
|
|
<< ",\"caller_return_rva\":\"0x" << guard::progression_caller_return << std::dec << "\""
|
|
<< ",\"invocations\":" << invocations
|
|
<< ",\"null_service_skips\":" << skips
|
|
<< ",\"forwards\":" << forwards
|
|
<< ",\"matching_nonnull_forwards\":" << matching_nonnull
|
|
<< ",\"unmatched_null_forwards\":" << unmatched_null
|
|
<< ",\"native_exceptions\":" << exceptions
|
|
<< ",\"unarmed_forwards\":" << unarmed
|
|
<< ",\"preparing_invocations\":" << preparing << '}';
|
|
|
|
std::ostringstream detail;
|
|
detail << "Progression service guard: " << gate_name(gate);
|
|
if (gate == GateStatus::active)
|
|
detail << " | null skips " << skips << " | native forwards " << forwards;
|
|
return {json.str(), detail.str()};
|
|
}
|
|
|
|
} // namespace dingosdk
|