Преглед изворни кода

reduce boot time in the signature sweep and hook attach

Three changes to the boot path, each cutting time spent before activate
stage=main completes. All hooks still attach with no failures, and all
eleven boot-step fixes still report ok.

Anchor pattern scans on the rarest exact byte, not the first.
anchor_of took the first exact byte in a signature. For an x64 function
prologue that is almost always a REX prefix, and 0x48 alone is a large
share of the bytes in compiled x64 code, so memchr stopped to verify
constantly across the sweep's whole-image passes. Pick the least frequent
exact byte instead, measured against the ranges about to be scanned
rather than guessed. A 256-bin histogram of those ranges is built once
and cached, keyed on a fingerprint of the range pointers and sizes;
without the cache every pattern would pay the extra traversal the anchor
choice exists to avoid. Ties go to the earliest byte so one image always
picks the same anchor. next_candidate already shifted its search window
by the anchor index and subtracted it back off the hit, so an anchor
anywhere in the pattern was always supported. A differential harness
comparing the old and new selection across many patterns and seeds,
covering unique, missing, ambiguous and invalid results plus anchors at
the head and tail of a pattern, reports identical matches for every one.

Attach the boot-step fixes in one transaction.
bootflow::install ran eleven fixes, nine of which each opened their own
Detours transaction to attach a single detour. A transaction costs far
more than the attach it guards: it enlists the threads it must suspend,
and enlisting walks every thread on the system twice. Split each of the
nine into a stage that resolves its target and fills a Spec, and a
publish that takes the resulting handle, stores the trampoline and
reports. The group stages all nine, attaches every resolved one together,
then hands each its handle. world_step and fade_release stay outside:
they only find addresses to call and open no transaction. A fix whose
target is missing is simply not in the batch, so one miss still cannot
cost the others their fix; if the batch itself fails, the fixes are
retried one at a time, so a single target Detours refuses cannot take the
group down either. The stage result is a tri-state rather than a bool,
because a missing target and an already-attached fix both stage nothing
but mean opposite things for whether the group succeeded.

Enlist transaction threads without a system-wide snapshot.
A Detours transaction must enlist every thread it may have to suspend.
Enlisting walked a Toolhelp snapshot, which enumerates every thread on
the system to reach this process's own, several passes a transaction.
Walk the process's own thread list through ntdll instead. The export is
undocumented, so a build without it keeps the snapshot, which is also the
fallback when the walk stops early. The walk reaches threads a snapshot
never reports: a thread that has exited still sits on the kernel list for
as long as anything holds a handle to it, and this process leaks two.
That matters more than it looks. DetourUpdateThread suspends a thread the
moment it is handed over, suspending an exited thread fails, and Detours
records that as a transaction-wide pending error which DetourAttach
returns early and commit aborts on. Nothing can clear it, so one exited
thread cost every hook in the transaction. Threads are therefore checked
with GetExitCodeThread before they are offered. The enlist handle still
comes from OpenThread with the access the snapshot pass used, so the set
of threads a transaction holds is unchanged apart from the exited ones it
should never have held.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Joe McNally пре 2 недеља
родитељ
комит
9a95c2faba

+ 169 - 30
Sunrise/src/client/hooking/detour/transaction/detour_thread_transaction.cpp

@@ -13,6 +13,46 @@ namespace {
 /** 4 protected functions per hook bound the fixed range storage, so no heap is used. */
 constexpr std::size_t kProtectedCodeLimit = 64;
 
+/** Access an enlisted thread is opened with. Detours reads and rewrites its context. */
+constexpr DWORD kEnlistAccess =
+    THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_SET_CONTEXT;
+/** Access the walk needs of a thread it only names. Asking for less refuses fewer threads. */
+constexpr DWORD kWalkAccess = THREAD_QUERY_LIMITED_INFORMATION;
+/** The walk is over. NtGetNextThread reports it as a failure status, so it is checked by value. */
+constexpr LONG kStatusNoMoreEntries = static_cast<LONG>(0x8000001AL);
+
+/**
+ * Hands back the next thread of one process, in an order fixed for the length of the walk.
+ * Passing a null cursor starts it. The returned handle carries the requested access.
+ */
+using NextThread = LONG(NTAPI*)(HANDLE process,
+                                HANDLE cursor,
+                                ACCESS_MASK access,
+                                ULONG attributes,
+                                ULONG flags,
+                                HANDLE* next) noexcept;
+
+/**
+ * Finds ntdll's own thread walk, once.
+ * The documented walk is a Toolhelp snapshot, which enumerates every thread on the system to
+ * reach this process's fifty: it costs about 25 ms a pass, twice a transaction, and a boot holds
+ * one transaction per hook. This walk stays inside the process and costs about 0.08 ms. It is
+ * not a documented export, so a build that does not have it keeps the snapshot instead.
+ * @return The entry point, or null when ntdll does not export it.
+ */
+[[nodiscard]] NextThread next_thread_entry() noexcept {
+    static const NextThread entry = [] {
+        const HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
+        if (ntdll == nullptr) {
+            return static_cast<NextThread>(nullptr);
+        }
+        // The cast is through a void function pointer because GetProcAddress returns FARPROC.
+        return reinterpret_cast<NextThread>(
+            reinterpret_cast<void*>(GetProcAddress(ntdll, "NtGetNextThread")));
+    }();
+    return entry;
+}
+
 /** Exact executable range described by one x64 unwind record. */
 struct CodeRange {
     DWORD64 begin{};
@@ -45,6 +85,109 @@ void close_threads(Threads& threads) noexcept {
     return false;
 }
 
+/** How far one enlistment pass got. */
+enum class PassResult {
+    /** Every thread of the process was seen and taken. */
+    complete,
+    /** The walk stopped early without handing Detours anything, so another pass may still run. */
+    enumerationFailed,
+    /** Detours refused a thread. Nothing can continue this transaction. */
+    transactionFailed,
+};
+
+/**
+ * Enlists one process thread by id, unless this transaction already holds it.
+ * The handle comes from OpenThread rather than from whatever named the id. A thread Windows will
+ * not open here is one the transaction must leave alone: handing Detours a thread it cannot
+ * suspend sets a transaction-wide pending error that fails every later attach and that nothing
+ * can clear. The set of enlisted threads therefore stays exactly what a snapshot pass would take,
+ * whichever walk found them.
+ * @param threads Receives the handle, which stays suspended until the transaction ends.
+ * @param threadId Candidate process thread id.
+ * @param currentThreadId The calling thread, which the transaction enlists separately.
+ * @param foundUnseen Set when the thread was new to this transaction.
+ * @return False when Detours refused the thread and the transaction is spent.
+ */
+[[nodiscard]] bool enlist_thread_id(Threads& threads,
+                                    DWORD threadId,
+                                    DWORD currentThreadId,
+                                    bool& foundUnseen) noexcept {
+    if (threadId == 0 || threadId == currentThreadId || contains(threads, threadId)) {
+        return true;
+    }
+    foundUnseen = true;
+    if (threads.count == threads.handles.size()) {
+        return false;
+    }
+    const HANDLE thread = OpenThread(kEnlistAccess, FALSE, threadId);
+    if (thread == nullptr) {
+        // A disappearing thread is absent from the next stable pass.
+        return GetLastError() == ERROR_INVALID_PARAMETER;
+    }
+    if (DetourUpdateThread(thread) != NO_ERROR) {
+        CloseHandle(thread);
+        return false;
+    }
+    threads.handles[threads.count] = thread;
+    threads.ids[threads.count] = threadId;
+    ++threads.count;
+    return true;
+}
+
+/**
+ * Says whether a thread is still running.
+ * The walk reaches threads that have already exited: their objects outlive them for as long as
+ * something holds a handle, and the kernel thread list still carries them. A snapshot never
+ * reports one. Detours suspends a thread the moment it is handed over, suspending an exited
+ * thread fails, and that failure is a transaction-wide error that nothing can clear, so an exited
+ * thread has to be dropped before it is offered.
+ * @param thread Handle opened with at least THREAD_QUERY_LIMITED_INFORMATION.
+ * @return True only when the thread is confirmed running.
+ */
+[[nodiscard]] bool thread_is_running(HANDLE thread) noexcept {
+    DWORD exitCode = 0;
+    return GetExitCodeThread(thread, &exitCode) != FALSE && exitCode == STILL_ACTIVE;
+}
+
+/**
+ * Enlists every unseen live thread of this process using ntdll's own walk.
+ * The walk names and vets each thread; enlisting it then runs on the shared path.
+ * @param threads Receives handles that stay suspended until the transaction ends.
+ * @param foundUnseen Receives true when this pass saw any new thread.
+ * @return How far the pass got.
+ */
+[[nodiscard]] PassResult enlist_process_walk(Threads& threads, bool& foundUnseen) noexcept {
+    const NextThread nextThread = next_thread_entry();
+    if (nextThread == nullptr) {
+        return PassResult::enumerationFailed;
+    }
+    const DWORD currentThreadId = GetCurrentThreadId();
+    HANDLE cursor = nullptr;
+    for (;;) {
+        HANDLE next = nullptr;
+        const LONG status = nextThread(GetCurrentProcess(), cursor, kWalkAccess, 0, 0, &next);
+        // The cursor is only a position in the walk; the transaction never holds it.
+        if (cursor != nullptr) {
+            CloseHandle(cursor);
+        }
+        cursor = nullptr;
+        if (status == kStatusNoMoreEntries) {
+            return PassResult::complete;
+        }
+        if (status < 0 || next == nullptr) {
+            return PassResult::enumerationFailed;
+        }
+        // The walk's own handle answers both questions, so the enlist handle is only opened for
+        // a thread that is going to be offered.
+        const DWORD threadId = thread_is_running(next) ? GetThreadId(next) : 0;
+        if (!enlist_thread_id(threads, threadId, currentThreadId, foundUnseen)) {
+            CloseHandle(next);
+            return PassResult::transactionFailed;
+        }
+        cursor = next;
+    }
+}
+
 /**
  * Enlists every unseen thread present in one process-wide snapshot.
  * @param threads Receives handles that stay suspended until the transaction ends.
@@ -52,7 +195,6 @@ void close_threads(Threads& threads) noexcept {
  * @return True when the whole snapshot was inspected without a hard failure.
  */
 [[nodiscard]] bool enlist_snapshot(Threads& threads, bool& foundUnseen) noexcept {
-    foundUnseen = false;
     const HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
     if (snapshot == INVALID_HANDLE_VALUE) {
         return false;
@@ -65,33 +207,8 @@ void close_threads(Threads& threads) noexcept {
     const DWORD currentThreadId = GetCurrentThreadId();
     bool succeeded = true;
     while (available != FALSE && succeeded) {
-        const bool belongsToProcess = entry.th32OwnerProcessID == processId;
-        const bool needsEnlistment =
-            entry.th32ThreadID != currentThreadId && !contains(threads, entry.th32ThreadID);
-        if (belongsToProcess && needsEnlistment) {
-            foundUnseen = true;
-            if (threads.count == threads.handles.size()) {
-                succeeded = false;
-                break;
-            }
-
-            const HANDLE thread =
-                OpenThread(THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_SET_CONTEXT,
-                           FALSE,
-                           entry.th32ThreadID);
-            if (thread == nullptr) {
-                // A disappearing thread is absent from the next stable snapshot.
-                if (GetLastError() != ERROR_INVALID_PARAMETER) {
-                    succeeded = false;
-                }
-            } else if (DetourUpdateThread(thread) != NO_ERROR) {
-                CloseHandle(thread);
-                succeeded = false;
-            } else {
-                threads.handles[threads.count] = thread;
-                threads.ids[threads.count] = entry.th32ThreadID;
-                ++threads.count;
-            }
+        if (entry.th32OwnerProcessID == processId) {
+            succeeded = enlist_thread_id(threads, entry.th32ThreadID, currentThreadId, foundUnseen);
         }
         available = Thread32Next(snapshot, &entry);
     }
@@ -104,14 +221,36 @@ void close_threads(Threads& threads) noexcept {
 }
 
 /**
- * Enlists new process threads until a full snapshot finds no unseen thread id.
+ * Enlists every unseen process thread in one pass, by whichever walk this build has.
+ * A partly finished process walk leaves its handles enlisted and the snapshot completes the pass:
+ * both dedupe on the thread id, so the fallback cannot enlist a thread twice.
+ * @param threads Receives handles that stay suspended until the transaction ends.
+ * @param foundUnseen Receives true when this pass saw any new thread.
+ * @return True when the pass completed without a hard failure.
+ */
+[[nodiscard]] bool enlist_pass(Threads& threads, bool& foundUnseen) noexcept {
+    foundUnseen = false;
+    const PassResult walked = enlist_process_walk(threads, foundUnseen);
+    if (walked == PassResult::complete) {
+        return true;
+    }
+    // A refused thread has already spent the transaction, so no second walk can rescue it. Only
+    // a walk that stopped before Detours was told anything falls through to the snapshot.
+    if (walked == PassResult::transactionFailed) {
+        return false;
+    }
+    return enlist_snapshot(threads, foundUnseen);
+}
+
+/**
+ * Enlists new process threads until a full pass finds no unseen thread id.
  * @param threads Receives every handle the transaction holds.
  * @return True when a full pass found no new thread.
  */
 [[nodiscard]] bool enlist_until_stable(Threads& threads) noexcept {
     bool foundUnseen{};
     do {
-        if (!enlist_snapshot(threads, foundUnseen)) {
+        if (!enlist_pass(threads, foundUnseen)) {
             return false;
         }
         // Earlier handles stay suspended while a later pass finds newly created threads.

+ 86 - 12
Sunrise/src/client/hooks/bootflow/bootflow_hook_lifecycle.cpp

@@ -1,6 +1,9 @@
 #include "bootflow_hook_lifecycle.h"
 
+#include <array>
 #include <atomic>
+#include <cstddef>
+#include <span>
 
 #include "internal.h"
 
@@ -9,29 +12,100 @@ namespace {
 
 std::atomic_bool g_installed{false};
 
+/** One boot-step fix that attaches a detour, in the order the group installs them. */
+struct Fix {
+    StageResult (*stage)(hooking::detour::Spec&) noexcept;
+    void (*publish)(const hooking::detour::Handle&) noexcept;
+};
+
+/**
+ * Every fix that attaches a detour. `world_step` and `fade_release` are absent: they only find
+ * addresses to call, so they open no transaction and cost the group nothing.
+ */
+constexpr std::array kFixes{
+    Fix{&stage_character_select_hold, &publish_character_select_hold},
+    Fix{&stage_orbit_slice_set, &publish_orbit_slice_set},
+    Fix{&stage_profile_setup_skip, &publish_profile_setup_skip},
+    Fix{&stage_composition_check, &publish_composition_check},
+    Fix{&stage_orbit_handoff, &publish_orbit_handoff},
+    Fix{&stage_owner_activity_slot, &publish_owner_activity_slot},
+    Fix{&stage_region_private, &publish_region_private},
+    Fix{&stage_spawn_hold, &publish_spawn_hold},
+};
+
+/** Marks a fix that staged nothing, so no handle is ever published to it. */
+constexpr std::size_t kNotStaged = kFixes.size();
+
+/** One fix's place in the batch, and what it already was before staging. */
+struct Placement {
+    std::size_t slot{kNotStaged};
+    StageResult result{StageResult::unavailable};
+};
+
 } // namespace
 
 /**
  * Attaches the boot-step fixes that carry sign-in through to orbit.
  * Each fix stands alone at one site, so a miss on one is reported and the others still attach.
+ *
+ * Every resolved fix attaches in one transaction rather than one each. A transaction enlists the
+ * threads it must suspend by walking every thread on the system, which is far more work than the
+ * attach itself, so nine transactions cost nine of those walks and one costs one. A fix whose
+ * target is missing simply is not in the batch, which is what keeps one miss off the others. If
+ * the batch itself fails the fixes are retried one at a time, so a single target Detours refuses
+ * cannot take the rest of the group down with it.
  * @return True when every fix attached.
  */
 bool install() noexcept {
-    const bool hold = install_character_select_hold();
-    const bool sliceSet = install_orbit_slice_set();
-    const bool skip = install_profile_setup_skip();
-    const bool composition = install_composition_check();
-    const bool handoff = install_orbit_handoff();
-    const bool ownerSlot = install_owner_activity_slot();
-    const bool regionPrivate = install_region_private();
+    std::array<hooking::detour::Spec, kFixes.size()> specs{};
+    std::array<hooking::detour::Handle, kFixes.size()> handles{};
+    std::array<Placement, kFixes.size()> placement{};
+    std::size_t staged = 0;
+    for (std::size_t index = 0; index < kFixes.size(); ++index) {
+        hooking::detour::Spec spec{};
+        const StageResult result = kFixes[index].stage(spec);
+        placement[index].result = result;
+        if (result != StageResult::staged) {
+            continue;
+        }
+        specs[staged] = spec;
+        placement[index].slot = staged;
+        ++staged;
+    }
+
+    if (staged != 0
+        && !hooking::detour::install(std::span(specs).first(staged),
+                                     std::span(handles).first(staged))) {
+        // One refused target must not cost the others their fix, so the slow path stands them up
+        // separately. It runs only when the whole batch failed, which no supported build does.
+        for (std::size_t slot = 0; slot < staged; ++slot) {
+            handles[slot] = {};
+            (void)hooking::detour::install(specs[slot], handles[slot]);
+        }
+    }
+
+    bool anyFix = false;
+    bool everyFix = true;
+    for (std::size_t index = 0; index < kFixes.size(); ++index) {
+        const Placement& place = placement[index];
+        if (place.slot == kNotStaged) {
+            // An already-attached fix stays attached; only a missing target is a failure.
+            anyFix = anyFix || place.result == StageResult::attached;
+            everyFix = everyFix && place.result == StageResult::attached;
+            continue;
+        }
+        const hooking::detour::Handle& handle = handles[place.slot];
+        kFixes[index].publish(handle);
+        anyFix = anyFix || handle.attached;
+        everyFix = everyFix && handle.attached;
+    }
+
+    // Neither of these attaches anything, so they stay outside the transaction.
     const bool worldStep = install_world_step();
-    const bool spawn = install_spawn_hold();
     const bool fade = install_fade_release();
-    const bool anyFix = hold || sliceSet || skip || composition || handoff || ownerSlot
-                        || regionPrivate || worldStep || spawn || fade;
+    anyFix = anyFix || worldStep || fade;
     g_installed.store(anyFix, std::memory_order_release);
-    return hold && sliceSet && skip && composition && handoff && ownerSlot && regionPrivate
-           && worldStep && spawn && fade;
+    return everyFix && worldStep && fade;
 }
 
 /** Detaches every boot-step fix, in the reverse order of install. */

+ 15 - 9
Sunrise/src/client/hooks/bootflow/character_select_hold.cpp

@@ -65,32 +65,38 @@ __declspec(noinline) void __fastcall enter_handler(std::byte* step) noexcept {
 } // namespace
 
 /**
- * Attaches the character-select hold.
- * @return True when the target is found and the detour attaches.
+ * Stages the character-select hold.
+ * @param spec Receives the target and replacement.
+ * @return True when the target is found and the fix wants attaching.
  */
-bool install_character_select_hold() noexcept {
+StageResult stage_character_select_hold(hooking::detour::Spec& spec) noexcept {
     if (g_handle.attached) {
-        return true;
+        return StageResult::attached;
     }
     std::byte* const target = scan_main_image_unique(kEnterSignature, "character_signin_enter");
     if (target == nullptr) {
         core::log::write(core::log::Channel::client,
                          core::log::Level::warn,
                          "ev=bootflow stage=character_select result=fail reason=target");
-        return false;
+        return StageResult::unavailable;
     }
-    const hooking::detour::Spec spec{target, reinterpret_cast<void*>(&enter_handler)};
-    if (!hooking::detour::install(spec, g_handle)) {
+    spec = hooking::detour::Spec{target, reinterpret_cast<void*>(&enter_handler)};
+    return StageResult::staged;
+}
+
+/** Takes the character-select hold's attached handle, or a detached one. */
+void publish_character_select_hold(const hooking::detour::Handle& handle) noexcept {
+    if (!handle.attached) {
         core::log::write(core::log::Channel::client,
                          core::log::Level::warn,
                          "ev=bootflow stage=character_select result=fail reason=attach");
-        return false;
+        return;
     }
+    g_handle = handle;
     g_original.store(reinterpret_cast<EnterHandler>(g_handle.original), std::memory_order_release);
     core::log::write(core::log::Channel::client,
                      core::log::Level::info,
                      "ev=bootflow stage=character_select result=ok");
-    return true;
 }
 
 /** Detaches the character-select hold. */

+ 15 - 9
Sunrise/src/client/hooks/bootflow/composition_check.cpp

@@ -104,32 +104,38 @@ __declspec(noinline) std::int64_t __fastcall check(void* config, std::byte* prop
 } // namespace
 
 /**
- * Attaches the solo composition fix.
- * @return True when the target is found and the detour attaches.
+ * Stages the solo composition fix.
+ * @param spec Receives the target and replacement.
+ * @return staged when the target is found, unavailable on a miss.
  */
-bool install_composition_check() noexcept {
+StageResult stage_composition_check(hooking::detour::Spec& spec) noexcept {
     if (g_handle.attached) {
-        return true;
+        return StageResult::attached;
     }
     std::byte* const target = scan_main_image_unique(kCheckSignature, "matchmaking_composition");
     if (target == nullptr) {
         core::log::write(core::log::Channel::client,
                          core::log::Level::warn,
                          "ev=bootflow stage=composition result=fail reason=target");
-        return false;
+        return StageResult::unavailable;
     }
-    const hooking::detour::Spec spec{target, reinterpret_cast<void*>(&check)};
-    if (!hooking::detour::install(spec, g_handle)) {
+    spec = hooking::detour::Spec{target, reinterpret_cast<void*>(&check)};
+    return StageResult::staged;
+}
+
+/** Takes the solo composition fix's attached handle, or a detached one. */
+void publish_composition_check(const hooking::detour::Handle& handle) noexcept {
+    if (!handle.attached) {
         core::log::write(core::log::Channel::client,
                          core::log::Level::warn,
                          "ev=bootflow stage=composition result=fail reason=attach");
-        return false;
+        return;
     }
+    g_handle = handle;
     g_original.store(reinterpret_cast<Check>(g_handle.original), std::memory_order_release);
     core::log::write(core::log::Channel::client,
                      core::log::Level::info,
                      "ev=bootflow stage=composition result=ok");
-    return true;
 }
 
 /** Detaches the solo composition fix. */

+ 75 - 24
Sunrise/src/client/hooks/bootflow/internal.h

@@ -1,5 +1,6 @@
 #pragma once
 
+#include "../../hooking/detour.h"
 #include "../../patterns/image_scan.h"
 
 namespace sunrise::client::hooks::bootflow {
@@ -10,66 +11,112 @@ using patterns::signature;
 using patterns::signature_length;
 
 /**
- * Attaches the character-select hold, which stops the sign-in step auto-selecting.
- * @return True when the target is found and the detour attaches.
+ * One boot-step fix resolves its target, then the group attaches every resolved fix together.
+ * Splitting the two halves is what lets the group hold one detour transaction instead of one per
+ * fix. A transaction enlists every thread on the system to find this process's own, which costs
+ * far more than the attach it guards, so the count of transactions is what the boot pays for.
+ *
+ * A publish call is made only for a fix that staged, and takes a detached handle when the group's
+ * attach did not happen.
  */
-[[nodiscard]] bool install_character_select_hold() noexcept;
+enum class StageResult : unsigned char {
+    /** The target is missing. The fix reported that itself and staged nothing. */
+    unavailable,
+    /** An earlier install already attached this fix, so there is nothing to stage. */
+    attached,
+    /** The spec is filled and the fix wants attaching. */
+    staged,
+};
+
+/**
+ * Stages the character-select hold, which stops the sign-in step auto-selecting.
+ * @param spec Receives the target and replacement.
+ * @return staged when the target was found, unavailable on a miss.
+ */
+[[nodiscard]] StageResult stage_character_select_hold(hooking::detour::Spec& spec) noexcept;
+
+/** Takes the character-select hold's attached handle, or a detached one. */
+void publish_character_select_hold(const hooking::detour::Handle& handle) noexcept;
 
 /** Detaches the character-select hold. */
 void uninstall_character_select_hold() noexcept;
 
 /**
- * Attaches the profile-setup skip, which skips the startup setup screens.
- * @return True when the target is found and the detour attaches.
+ * Stages the profile-setup skip, which skips the startup setup screens.
+ * @param spec Receives the target and replacement.
+ * @return staged when the target was found, unavailable on a miss.
  */
-[[nodiscard]] bool install_profile_setup_skip() noexcept;
+[[nodiscard]] StageResult stage_profile_setup_skip(hooking::detour::Spec& spec) noexcept;
+
+/** Takes the profile-setup skip's attached handle, or a detached one. */
+void publish_profile_setup_skip(const hooking::detour::Handle& handle) noexcept;
 
 /** Detaches the profile-setup skip. */
 void uninstall_profile_setup_skip() noexcept;
 
 /**
- * Attaches the orbit slice-set picker, so the sign-in step's map load finds its target.
- * @return True when the picker is found and the detour attaches.
+ * Stages the orbit slice-set picker, so the sign-in step's map load finds its target.
+ * @param spec Receives the target and replacement.
+ * @return staged when the picker was found, unavailable on a miss.
  */
-[[nodiscard]] bool install_orbit_slice_set() noexcept;
+[[nodiscard]] StageResult stage_orbit_slice_set(hooking::detour::Spec& spec) noexcept;
+
+/** Takes the orbit slice-set picker's attached handle, or a detached one. */
+void publish_orbit_slice_set(const hooking::detour::Handle& handle) noexcept;
 
 /** Detaches the orbit slice-set picker. */
 void uninstall_orbit_slice_set() noexcept;
 
 /**
- * Attaches the solo composition fix, which clears the count the matchmaking check rejects.
- * @return True when the target is found and the detour attaches.
+ * Stages the solo composition fix, which clears the count the matchmaking check rejects.
+ * @param spec Receives the target and replacement.
+ * @return staged when the target was found, unavailable on a miss.
  */
-[[nodiscard]] bool install_composition_check() noexcept;
+[[nodiscard]] StageResult stage_composition_check(hooking::detour::Spec& spec) noexcept;
+
+/** Takes the solo composition fix's attached handle, or a detached one. */
+void publish_composition_check(const hooking::detour::Handle& handle) noexcept;
 
 /** Detaches the solo composition fix. */
 void uninstall_composition_check() noexcept;
 
 /**
- * Attaches the orbit handoff release, which stops the destination step parking.
- * @return True when the target is found and the detour attaches.
+ * Stages the orbit handoff release, which stops the destination step parking.
+ * @param spec Receives the target and replacement.
+ * @return staged when the target was found, unavailable on a miss.
  */
-[[nodiscard]] bool install_orbit_handoff() noexcept;
+[[nodiscard]] StageResult stage_orbit_handoff(hooking::detour::Spec& spec) noexcept;
+
+/** Takes the orbit handoff release's attached handle, or a detached one. */
+void publish_orbit_handoff(const hooking::detour::Handle& handle) noexcept;
 
 /** Detaches the orbit handoff release. */
 void uninstall_orbit_handoff() noexcept;
 
 /**
- * Attaches the owner activity slot force. It pins the participation record to the replicated
+ * Stages the owner activity slot force. It pins the participation record to the replicated
  * snapshot at `comp + 496` instead of the local one at `comp + 1256`.
- * @return True when the target is found and the detour attaches.
+ * @param spec Receives the target and replacement.
+ * @return staged when the target was found, unavailable on a miss.
  */
-[[nodiscard]] bool install_owner_activity_slot() noexcept;
+[[nodiscard]] StageResult stage_owner_activity_slot(hooking::detour::Spec& spec) noexcept;
+
+/** Takes the owner activity slot force's attached handle, or a detached one. */
+void publish_owner_activity_slot(const hooking::detour::Handle& handle) noexcept;
 
 /** Detaches the owner activity slot force. */
 void uninstall_owner_activity_slot() noexcept;
 
 /**
- * Attaches the private-region force, so a public region takes the path a private one takes.
+ * Stages the private-region force, so a public region takes the path a private one takes.
  * A public region otherwise holds its slice-set switch until a public activity host connects.
- * @return True when both targets are found, the call site is unique and the detour attaches.
+ * @param spec Receives the target and replacement.
+ * @return staged when both targets and the call site were found, unavailable on a miss.
  */
-[[nodiscard]] bool install_region_private() noexcept;
+[[nodiscard]] StageResult stage_region_private(hooking::detour::Spec& spec) noexcept;
+
+/** Takes the private-region force's attached handle, or a detached one. */
+void publish_region_private(const hooking::detour::Handle& handle) noexcept;
 
 /** Detaches the private-region force. */
 void uninstall_region_private() noexcept;
@@ -91,10 +138,14 @@ void uninstall_world_step() noexcept;
 void observe_world_step() noexcept;
 
 /**
- * Attaches the spawn hold, which puts the player spawn after the world-transition fade is armed.
- * @return True when the target is found and the detour attaches.
+ * Stages the spawn hold, which puts the player spawn after the world-transition fade is armed.
+ * @param spec Receives the target and replacement.
+ * @return staged when the target was found, unavailable on a miss.
  */
-[[nodiscard]] bool install_spawn_hold() noexcept;
+[[nodiscard]] StageResult stage_spawn_hold(hooking::detour::Spec& spec) noexcept;
+
+/** Takes the spawn hold's attached handle, or a detached one. */
+void publish_spawn_hold(const hooking::detour::Handle& handle) noexcept;
 
 /** Detaches the spawn hold. */
 void uninstall_spawn_hold() noexcept;

+ 15 - 9
Sunrise/src/client/hooks/bootflow/orbit_handoff.cpp

@@ -65,31 +65,37 @@ __declspec(noinline) bool __fastcall destination_hold(void* stepCtx) noexcept {
 } // namespace
 
 /**
- * Attaches the orbit handoff release.
- * @return True when the target is found and the detour attaches.
+ * Stages the orbit handoff release.
+ * @param spec Receives the target and replacement.
+ * @return True when the target is found and the fix wants attaching.
  */
-bool install_orbit_handoff() noexcept {
+StageResult stage_orbit_handoff(hooking::detour::Spec& spec) noexcept {
     if (g_handle.attached) {
-        return true;
+        return StageResult::attached;
     }
     std::byte* const target = scan_main_image_unique(kHoldSignature, "orbit_destination_hold");
     if (target == nullptr) {
         core::log::write(core::log::Channel::client,
                          core::log::Level::warn,
                          "ev=bootflow stage=orbit_handoff result=fail reason=target");
-        return false;
+        return StageResult::unavailable;
     }
-    const hooking::detour::Spec spec{target, reinterpret_cast<void*>(&destination_hold)};
-    if (!hooking::detour::install(spec, g_handle)) {
+    spec = hooking::detour::Spec{target, reinterpret_cast<void*>(&destination_hold)};
+    return StageResult::staged;
+}
+
+/** Takes the orbit handoff release's attached handle, or a detached one. */
+void publish_orbit_handoff(const hooking::detour::Handle& handle) noexcept {
+    if (!handle.attached) {
         core::log::write(core::log::Channel::client,
                          core::log::Level::warn,
                          "ev=bootflow stage=orbit_handoff result=fail reason=attach");
-        return false;
+        return;
     }
+    g_handle = handle;
     core::log::write(core::log::Channel::client,
                      core::log::Level::info,
                      "ev=bootflow stage=orbit_handoff result=ok");
-    return true;
 }
 
 /** Detaches the orbit handoff release. */

+ 13 - 8
Sunrise/src/client/hooks/bootflow/orbit_slice_set.cpp

@@ -68,29 +68,34 @@ std::uint32_t* __fastcall pick_target(LoaderContext* context, std::uint32_t* sel
 
 } // namespace
 
-/** Attaches the picker so the orbit target is found. */
-bool install_orbit_slice_set() noexcept {
+/** Stages the picker so the orbit target is found. */
+StageResult stage_orbit_slice_set(hooking::detour::Spec& spec) noexcept {
     if (g_handle.attached) {
-        return true;
+        return StageResult::attached;
     }
     std::byte* const picker = scan_main_image_unique(kPickerSignature, "slice_set_target_picker");
     if (picker == nullptr) {
         core::log::write(core::log::Channel::client,
                          core::log::Level::warn,
                          "ev=bootflow stage=slice_set result=fail reason=target");
-        return false;
+        return StageResult::unavailable;
     }
-    const hooking::detour::Spec spec{picker, reinterpret_cast<void*>(&pick_target)};
-    if (!hooking::detour::install(spec, g_handle)) {
+    spec = hooking::detour::Spec{picker, reinterpret_cast<void*>(&pick_target)};
+    return StageResult::staged;
+}
+
+/** Takes the picker's attached handle, or a detached one. */
+void publish_orbit_slice_set(const hooking::detour::Handle& handle) noexcept {
+    if (!handle.attached) {
         core::log::write(core::log::Channel::client,
                          core::log::Level::warn,
                          "ev=bootflow stage=slice_set result=fail reason=attach");
-        return false;
+        return;
     }
+    g_handle = handle;
     core::log::write(core::log::Channel::client,
                      core::log::Level::info,
                      "ev=bootflow stage=slice_set result=ok");
-    return true;
 }
 
 /** Detaches the picker. */

+ 13 - 8
Sunrise/src/client/hooks/bootflow/owner_activity_slot.cpp

@@ -95,30 +95,35 @@ __declspec(noinline) std::uint8_t __fastcall check(void* container,
 
 } // namespace
 
-/** Attaches the owner activity slot force. */
-bool install_owner_activity_slot() noexcept {
+/** Stages the owner activity slot force. */
+StageResult stage_owner_activity_slot(hooking::detour::Spec& spec) noexcept {
     if (g_handle.attached) {
-        return true;
+        return StageResult::attached;
     }
     std::byte* const target = scan_main_image_unique(kCheckSignature, "check_activity_bubbles");
     if (target == nullptr) {
         core::log::write(core::log::Channel::client,
                          core::log::Level::warn,
                          "ev=bootflow stage=owner_slot result=fail reason=target");
-        return false;
+        return StageResult::unavailable;
     }
-    const hooking::detour::Spec spec{target, reinterpret_cast<void*>(&check)};
-    if (!hooking::detour::install(spec, g_handle)) {
+    spec = hooking::detour::Spec{target, reinterpret_cast<void*>(&check)};
+    return StageResult::staged;
+}
+
+/** Takes the owner activity slot force's attached handle, or a detached one. */
+void publish_owner_activity_slot(const hooking::detour::Handle& handle) noexcept {
+    if (!handle.attached) {
         core::log::write(core::log::Channel::client,
                          core::log::Level::warn,
                          "ev=bootflow stage=owner_slot result=fail reason=attach");
-        return false;
+        return;
     }
+    g_handle = handle;
     g_original.store(reinterpret_cast<CheckBubbles>(g_handle.original), std::memory_order_release);
     core::log::write(core::log::Channel::client,
                      core::log::Level::info,
                      "ev=bootflow stage=owner_slot result=ok");
-    return true;
 }
 
 /** Detaches the owner activity slot force. */

+ 15 - 9
Sunrise/src/client/hooks/bootflow/profile_setup_skip.cpp

@@ -90,32 +90,38 @@ __declspec(noinline) char __fastcall update(std::byte* step) noexcept {
 } // namespace
 
 /**
- * Attaches the profile-setup skip.
- * @return True when the target is found and the detour attaches.
+ * Stages the profile-setup skip.
+ * @param spec Receives the target and replacement.
+ * @return staged when the target is found, unavailable on a miss.
  */
-bool install_profile_setup_skip() noexcept {
+StageResult stage_profile_setup_skip(hooking::detour::Spec& spec) noexcept {
     if (g_handle.attached) {
-        return true;
+        return StageResult::attached;
     }
     std::byte* const target = scan_main_image_unique(kUpdateSignature, "profile_setup_update");
     if (target == nullptr) {
         core::log::write(core::log::Channel::client,
                          core::log::Level::warn,
                          "ev=bootflow stage=profile_setup result=fail reason=target");
-        return false;
+        return StageResult::unavailable;
     }
-    const hooking::detour::Spec spec{target, reinterpret_cast<void*>(&update)};
-    if (!hooking::detour::install(spec, g_handle)) {
+    spec = hooking::detour::Spec{target, reinterpret_cast<void*>(&update)};
+    return StageResult::staged;
+}
+
+/** Takes the profile-setup skip's attached handle, or a detached one. */
+void publish_profile_setup_skip(const hooking::detour::Handle& handle) noexcept {
+    if (!handle.attached) {
         core::log::write(core::log::Channel::client,
                          core::log::Level::warn,
                          "ev=bootflow stage=profile_setup result=fail reason=attach");
-        return false;
+        return;
     }
+    g_handle = handle;
     g_original.store(reinterpret_cast<Update>(g_handle.original), std::memory_order_release);
     core::log::write(core::log::Channel::client,
                      core::log::Level::info,
                      "ev=bootflow stage=profile_setup result=ok");
-    return true;
 }
 
 /** Detaches the profile-setup skip. */

+ 21 - 13
Sunrise/src/client/hooks/bootflow/region_private.cpp

@@ -135,8 +135,8 @@ __declspec(noinline) bool __fastcall reader(std::uint32_t sliceSet) noexcept {
     return !forced;
 }
 
-/** @param reason Key naming the step that failed. @return False, for a direct return. */
-[[nodiscard]] bool fail(const char* reason) noexcept {
+/** @param reason Key naming the step that failed. */
+void report_failure(const char* reason) noexcept {
     std::array<char, kLineCapacity> line{};
     const int written = std::snprintf(
         line.data(), line.size(), "ev=bootflow stage=region result=fail reason=%s", reason);
@@ -145,39 +145,47 @@ __declspec(noinline) bool __fastcall reader(std::uint32_t sliceSet) noexcept {
                          core::log::Level::warn,
                          {line.data(), static_cast<std::size_t>(written)});
     }
-    return false;
 }
 
 } // namespace
 
-/** Attaches the private-region force. */
-bool install_region_private() noexcept {
+/** Stages the private-region force. */
+StageResult stage_region_private(hooking::detour::Spec& spec) noexcept {
     if (g_handle.attached) {
-        return true;
+        return StageResult::attached;
     }
     std::byte* const target = scan_main_image_unique(kReaderSignature, "slice_set_is_public");
     if (target == nullptr) {
-        return fail("reader");
+        report_failure("reader");
+        return StageResult::unavailable;
     }
     const std::byte* const starter =
         scan_main_image_unique(kStarterSignature, "region_start_transition");
     if (starter == nullptr) {
-        return fail("starter");
+        report_failure("starter");
+        return StageResult::unavailable;
     }
     const std::byte* const returnSite = find_return_site(starter, target);
     if (returnSite == nullptr) {
-        return fail("call_site");
+        report_failure("call_site");
+        return StageResult::unavailable;
     }
     // Published before the detour attaches, so the first call already has its filter.
     g_returnSite.store(returnSite, std::memory_order_release);
-    const hooking::detour::Spec spec{target, reinterpret_cast<void*>(&reader)};
-    if (!hooking::detour::install(spec, g_handle)) {
-        return fail("attach");
+    spec = hooking::detour::Spec{target, reinterpret_cast<void*>(&reader)};
+    return StageResult::staged;
+}
+
+/** Takes the private-region force's attached handle, or a detached one. */
+void publish_region_private(const hooking::detour::Handle& handle) noexcept {
+    if (!handle.attached) {
+        report_failure("attach");
+        return;
     }
+    g_handle = handle;
     g_original.store(reinterpret_cast<Reader>(g_handle.original), std::memory_order_release);
     core::log::write(
         core::log::Channel::client, core::log::Level::info, "ev=bootflow stage=region result=ok");
-    return true;
 }
 
 /** Detaches the private-region force. */

+ 13 - 8
Sunrise/src/client/hooks/bootflow/spawn_hold.cpp

@@ -59,36 +59,41 @@ __declspec(noinline) bool __fastcall spawn_gate(std::int32_t datum) noexcept {
 
 } // namespace
 
-/** Attaches the spawn hold. */
-bool install_spawn_hold() noexcept {
+/** Stages the spawn hold. */
+StageResult stage_spawn_hold(hooking::detour::Spec& spec) noexcept {
     if (g_handle.attached) {
-        return true;
+        return StageResult::attached;
     }
     std::byte* const target = scan_main_image_unique(kSpawnGateSignature, "player_spawn_gate");
     if (target == nullptr) {
         core::log::write(core::log::Channel::client,
                          core::log::Level::warn,
                          "ev=bootflow stage=spawn_hold result=fail reason=target");
-        return false;
+        return StageResult::unavailable;
     }
     if (!spawn::resolve(target)) {
         core::log::write(core::log::Channel::client,
                          core::log::Level::warn,
                          "ev=bootflow stage=current_slice result=fail reason=targets");
     }
-    const hooking::detour::Spec spec{target, reinterpret_cast<void*>(&spawn_gate)};
-    if (!hooking::detour::install(spec, g_handle)) {
+    spec = hooking::detour::Spec{target, reinterpret_cast<void*>(&spawn_gate)};
+    return StageResult::staged;
+}
+
+/** Takes the spawn hold's attached handle, or a detached one. */
+void publish_spawn_hold(const hooking::detour::Handle& handle) noexcept {
+    if (!handle.attached) {
         spawn::forget();
         core::log::write(core::log::Channel::client,
                          core::log::Level::warn,
                          "ev=bootflow stage=spawn_hold result=fail reason=attach");
-        return false;
+        return;
     }
+    g_handle = handle;
     g_original.store(reinterpret_cast<SpawnGate>(g_handle.original), std::memory_order_release);
     core::log::write(core::log::Channel::client,
                      core::log::Level::info,
                      "ev=bootflow stage=spawn_hold result=ok");
-    return true;
 }
 
 /** Detaches the spawn hold. */

+ 114 - 6
Sunrise/src/client/patterns/registry.cpp

@@ -1,5 +1,9 @@
 #include "registry.h"
 
+#include <Windows.h>
+
+#include <array>
+#include <cstdint>
 #include <cstring>
 
 namespace sunrise::client::patterns {
@@ -7,6 +11,91 @@ namespace {
 
 /** Returned by next_candidate when a range holds no further anchor byte. */
 constexpr std::size_t kNoCandidate = static_cast<std::size_t>(-1);
+/** One count per distinct byte value. */
+constexpr std::size_t kByteValueCount = 256;
+/** Most ranges one fingerprint describes. No PE image carries more sections than this. */
+constexpr std::size_t kFingerprintCapacity = 96;
+
+/** How often each byte value occurs across one set of scanned ranges. */
+struct ByteCounts {
+    std::array<std::uint64_t, kByteValueCount> values{};
+};
+
+/** Identity of the range set one histogram was built from. */
+struct Fingerprint {
+    std::array<const std::byte*, kFingerprintCapacity> data{};
+    std::array<std::size_t, kFingerprintCapacity> size{};
+    std::size_t count{};
+    /** False for a range set too large to describe, which must never match a stored print. */
+    bool valid{};
+};
+
+/**
+ * The byte histogram and the ranges it came from.
+ * Building it costs one traversal of the image. Without this cache every pattern would pay that
+ * traversal, which is the very cost the anchor choice exists to avoid.
+ */
+struct FrequencyCache {
+    SRWLOCK lock{SRWLOCK_INIT};
+    Fingerprint fingerprint{};
+    ByteCounts counts{};
+};
+
+FrequencyCache g_frequency;
+
+/** @return Fingerprint of one range set, invalid when it holds more ranges than one can describe. */
+[[nodiscard]] Fingerprint fingerprint_of(std::span<const ImageRange> image) noexcept {
+    Fingerprint print{};
+    if (image.size() > kFingerprintCapacity) {
+        return print;
+    }
+    for (std::size_t index = 0; index < image.size(); ++index) {
+        print.data[index] = image[index].bytes.data();
+        print.size[index] = image[index].bytes.size();
+    }
+    print.count = image.size();
+    print.valid = true;
+    return print;
+}
+
+/** @return True when both fingerprints name the same ranges in the same order. */
+[[nodiscard]] bool same_ranges(const Fingerprint& left, const Fingerprint& right) noexcept {
+    if (!left.valid || !right.valid || left.count != right.count) {
+        return false;
+    }
+    for (std::size_t index = 0; index < left.count; ++index) {
+        if (left.data[index] != right.data[index] || left.size[index] != right.size[index]) {
+            return false;
+        }
+    }
+    return true;
+}
+
+/** Counts every byte value across one range set. */
+void count_bytes(std::span<const ImageRange> image, ByteCounts& counts) noexcept {
+    counts = {};
+    for (const ImageRange range : image) {
+        for (const std::byte value : range.bytes) {
+            ++counts.values[std::to_integer<unsigned char>(value)];
+        }
+    }
+}
+
+/**
+ * Reads the byte histogram for one range set, building it on the first request.
+ * @param image Ranges about to be scanned.
+ * @param counts Receives a copy, so no caller holds the cache lock while it scans.
+ */
+void byte_counts(std::span<const ImageRange> image, ByteCounts& counts) noexcept {
+    const Fingerprint wanted = fingerprint_of(image);
+    AcquireSRWLockExclusive(&g_frequency.lock);
+    if (!same_ranges(g_frequency.fingerprint, wanted)) {
+        count_bytes(image, g_frequency.counts);
+        g_frequency.fingerprint = wanted;
+    }
+    counts = g_frequency.counts;
+    ReleaseSRWLockExclusive(&g_frequency.lock);
+}
 
 /**
  * The one exact byte a pattern's candidate search keys on.
@@ -23,19 +112,34 @@ struct Anchor {
 
 /**
  * Picks the anchor byte for one pattern.
+ * The candidate search keys on this byte, so the rarest exact byte is the one that lets memchr
+ * skip the most. Taking the first exact byte instead lands on a REX prefix for most function
+ * prologues, and those are among the most common bytes there are in compiled x64: the sweep then
+ * stops to verify millions of times per pattern.
  * @param pattern Pattern name, bytes, and exact-byte mask.
+ * @param counts How often each byte value occurs in the ranges about to be scanned.
  * @return A valid anchor when the pattern has a name, bytes, and at least one exact byte.
  */
-[[nodiscard]] Anchor anchor_of(const Pattern& pattern) noexcept {
+[[nodiscard]] Anchor anchor_of(const Pattern& pattern, const ByteCounts& counts) noexcept {
     if (pattern.name.empty() || pattern.bytes.empty()) {
         return {};
     }
+    Anchor best{};
+    std::uint64_t bestCount = 0;
     for (std::size_t index = 0; index < pattern.bytes.size(); ++index) {
-        if (pattern.bytes[index].exact) {
-            return Anchor{index, std::to_integer<unsigned char>(pattern.bytes[index].value), true};
+        if (!pattern.bytes[index].exact) {
+            continue;
+        }
+        const auto value = std::to_integer<unsigned char>(pattern.bytes[index].value);
+        const std::uint64_t occurrences = counts.values[value];
+        // The earliest byte wins a tie, so one image always picks the same anchor.
+        if (best.valid && occurrences >= bestCount) {
+            continue;
         }
+        best = Anchor{index, value, true};
+        bestCount = occurrences;
     }
-    return {};
+    return best;
 }
 
 /**
@@ -109,8 +213,10 @@ bool resolve_all(std::span<const ImageRange> image,
         return false;
     }
 
+    ByteCounts counts;
+    byte_counts(image, counts);
     for (std::size_t index = 0; index < patterns.size(); ++index) {
-        const Anchor anchor = anchor_of(patterns[index]);
+        const Anchor anchor = anchor_of(patterns[index], counts);
         matches[index] = anchor.valid ? Match{MatchStatus::missing, nullptr} : Match{};
         if (!anchor.valid) {
             continue;
@@ -146,7 +252,9 @@ bool resolve_all(std::span<const ImageRange> image,
 std::size_t collect_matches(std::span<const ImageRange> image,
                             const Pattern& pattern,
                             std::span<std::byte*> output) noexcept {
-    const Anchor anchor = anchor_of(pattern);
+    ByteCounts counts;
+    byte_counts(image, counts);
+    const Anchor anchor = anchor_of(pattern, counts);
     if (!anchor.valid || output.empty()) {
         return 0;
     }