Skip to content

Add Keep Nvidia Instant Replay on mod - #5030

Open
dixxi1208 wants to merge 4 commits into
ramensoftware:mainfrom
dixxi1208:add-nvidia-keep-instant-replay-on
Open

Add Keep Nvidia Instant Replay on mod#5030
dixxi1208 wants to merge 4 commits into
ramensoftware:mainfrom
dixxi1208:add-nvidia-keep-instant-replay-on

Conversation

@dixxi1208

@dixxi1208 dixxi1208 commented Aug 6, 2026

Copy link
Copy Markdown

Adds nvidia-keep-instant-replay-on, a mod that turns Nvidia ShadowPlay's Instant Replay back on whenever something switches it off.

Instant Replay tends to quietly switch itself off after a driver update, a crash, a stray hotkey or a game that toggles it, and you only find out when you needed the clip you didn't get.

How it works. The state is read from HKCU\SOFTWARE\NVIDIA Corporation\Global\ShadowPlay\NVSPCAPS and the key is watched with RegNotifyChangeKeyValue, so the mod reacts the moment the state changes rather than only on a poll. Changing it is done by replaying the user's configured Toggle Instant Replay shortcut, read fresh from the same key each time. Optional process lists can pause the mod, or restrict Instant Replay to only be on while chosen programs run.

Windhawk injects into every nvcontainer.exe; a session-local named mutex elects a single instance to do the work, and instances running under other accounts can't read the state and stay out of the way on their own.

Same goal as AlwaysShadow, but as a mod so there's no extra program to install or keep running. AlwaysShadow prefers a local HTTP server hosted by NvContainer and only falls back to the shortcut; that server came from GeForce Experience's NvNode backend and isn't present on the Nvidia App, so this mod uses the shortcut only, which works on both.

Source repository: https://github.com/dixxi1208/NvidiaInstantReplayFix

Changelog

If this pull request updates an existing mod, describe the changes below:

Not applicable — this is a new mod, not an update to an existing one.

Mod authorship

If this pull request introduces a new mod, please complete the section below.

This mod was created by:

    • The submitter, without AI assistance
    • The submitter, with AI assistance
    • Claude
    • ChatGPT
    • Gemini
    • Another AI (please specify):
    • Other (please specify):

Please select the options that best apply. Your selection does not affect the acceptance criteria, but it helps reviewers understand the context of the code and provide relevant feedback.

Watches ShadowPlay's Instant Replay state and switches it back on whenever
something turns it off, so Instant Replay isn't quietly disabled when you
needed the clip you didn't get.

The state is read from HKCU and watched with RegNotifyChangeKeyValue, so the
mod reacts the moment the state changes rather than only on a poll. Changing
it is done by replaying the user's configured Toggle Instant Replay shortcut.
Optional process lists can pause the mod, or restrict Instant Replay to only
be on while chosen programs run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@windhawk-reviewer windhawk-reviewer Bot added the waiting-for-author The author's turn: request an AI review, or respond to one that was posted. label Aug 6, 2026
@windhawk-reviewer

Copy link
Copy Markdown

Thanks for the pull request! This repository uses a two-stage review: an AI review that you run yourself, followed by a human review.

To get started, comment /ai-review. Once you're happy with the result, comment /ready-for-reviewer to hand it over to a human reviewer.

See the pull request review process for the full details.

@dixxi1208

Copy link
Copy Markdown
Author

/ai-review

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-ai-review An AI review was requested and is being prepared. and removed waiting-for-author The author's turn: request an AI review, or respond to one that was posted. labels Aug 6, 2026
@dixxi1208

Copy link
Copy Markdown
Author

/ai-review

@windhawk-reviewer

Copy link
Copy Markdown

@dixxi1208 /ai-review can't be applied here: an AI review was already requested, please wait for it to be posted.

@windhawk-reviewer

Copy link
Copy Markdown

Submission review

Note: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding.

Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it.

Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them.


The mod is well written and the README is unusually clear about what it does and why. The main thing to reconsider is where it runs.

1. This should be a tool mod running in windhawk.exe, not injected into nvcontainer.exe.

The mod installs no hooks at all - it only uses RegNotifyChangeKeyValue, RegGetValueW, CreateToolhelp32Snapshot and SendInput, all of which work from any process. It doesn't read or touch any NvContainer state. That's exactly the case the "Mods as tools" wiki page exists for, and the code has all the classic symptoms of not being one:

  • kWatchdogMutexName / ClaimWatchdogRole() (lines 103, 333-352) is a hand-rolled single-instance election. The tool mod launcher already does this with windhawk-tool-mod_<id>, so all of it goes away.
  • The "instances running under other accounts can't read your settings and stay out of the way on their own" design (lines 180-182, 423-431) exists only because the mod is hosted in a process it doesn't control. A tool mod runs in the interactive user's session as that user, so HKCU is unambiguously the right hive and this whole class of problem disappears.
  • Every other nvcontainer.exe still pays for a watchdog thread, a registry notification registration and a poll every 10 s just to discover it isn't the elected one.
  • A bug or a hang in the mod currently lives inside NVIDIA's container process rather than in a disposable helper process.

There's also a smaller angle: IRToggleHKeyCount / IRToggleHKey<n> are user-writable HKCU values, and the mod replays them verbatim through SendInput from whatever integrity level the elected nvcontainer.exe happens to have. Running in a dedicated process keeps that at the user's own level.

The conversion is mechanical: @include windhawk.exe, rename Wh_ModInit/Wh_ModSettingsChanged/Wh_ModUninit to WhTool_*, and paste the snippet from the wiki verbatim at the end of the file. Note that tool mods don't support Wh_ModAfterInit/Wh_ModBeforeUninit, so thread creation moves into WhTool_ModInit and the join into WhTool_ModUninit. mods/caps-ime-switcher.wh.cpp is close to a drop-in template for this shape - tool mod, SendInput, worker thread started in WhTool_ModInit and waited for in WhTool_ModUninit. mods/net-toggle.wh.cpp shows the same with a stop event.

The one thing worth verifying before you convert: SendInput is subject to UIPI, so if NVIDIA's hotkey handler stops seeing the keystroke when an elevated or exclusive-fullscreen game is in the foreground, that would be a real reason to stay in-process - in which case please say so in the PR, since the expectation is to justify in-process hosting rather than default to it.

2. If the mod does stay in nvcontainer.exe, bail out in session 0.

Windhawk injects into session 0 services, and NVIDIA runs an nvcontainer.exe there as SYSTEM. That instance currently spins up a watchdog thread, and its HKEY_CURRENT_USER is HKU\S-1-5-18 - if that hive ever happens to carry the NVSPCAPS key, the instance will claim its own (session-0 Local\) mutex and start firing SendInput into session 0 forever, where nothing can act on it. Add the same guard the tool mod snippet uses:

BOOL Wh_ModInit() {
    DWORD sessionId;
    if (ProcessIdToSessionId(GetCurrentProcessId(), &sessionId) && sessionId == 0) {
        return FALSE;
    }
    ...
}

3. IsAnyProcessRunning returning false on failure can make the mod turn Instant Replay off.

IsAnyProcessRunning (lines 280-321) returns false both for "no process matched" and for "I couldn't enumerate processes". With a non-empty onlyWhileRunning, the second case is indistinguishable from the first:

bool shouldBeOn = settings.onlyWhileRunning.empty() ||
                  IsAnyProcessRunning(settings.onlyWhileRunning);

so a failed CreateToolhelp32Snapshot/Process32FirstW yields shouldBeOn == false and the mod actively presses the toggle to switch Instant Replay off while your game is running - the exact failure the mod exists to prevent. CreateToolhelp32Snapshot is documented to fail transiently with ERROR_BAD_LENGTH and to need a retry, so this isn't hypothetical.

Return a tri-state (or an std::optional<bool> / an out bool* ok) and skip the cycle when the enumeration didn't succeed:

static bool IsAnyProcessRunning(const std::vector<std::wstring>& patterns, bool* enumerated) {
    *enumerated = true;
    ...
    HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (snapshot == INVALID_HANDLE_VALUE) {
        *enumerated = false;
        return false;
    }
    ...
}

and in the loop, continue without touching toggleStreak when !enumerated.

4. Cross-reference the existing ShadowPlay mod.

mods/shadowplay-do-not-disable.wh.cpp already targets nvcontainer.exe and addresses what is probably the single most common reason Instant Replay switches itself off (DRM detection and WDA_EXCLUDEFROMCAPTURE windows). The two are genuinely different - that one prevents a specific cause, this one recovers from any cause - but users searching for "ShadowPlay turns itself off" will find both and won't know which they need. Please add a line to the README explaining the difference and when each applies (and confirm they don't fight each other when both are enabled, since both live in nvcontainer.exe).

Optional improvements

Minor polish - none of this affects users, so it's your call.

  • LoadStringListSetting (lines 138-154) can use WindhawkUtils::StringSetting instead of the raw Wh_GetStringSetting + Wh_FreeStringSetting pair - it's RAII and removes the manual free.
  • Clamp (lines 156-158) duplicates std::clamp, and <algorithm> is already included.
  • static_assert(kMinStreakForConflict >= 2, ...) on a hardcoded 3 a line above it doesn't buy anything, and toggleStreak = kMinStreakForConflict - 2; (line 464) is hard to read - a named constant or a comment stating the intended post-backoff streak would be clearer. Both read a bit like leftovers from generated code.
  • The conflictBackoffSec description says "After 3 attempts in a row, stop trying", but the code only sends two toggles: the third cycle hits the backoff branch and continues instead of toggling. Worth aligning the wording (or the code) so the setting means what it says. 800 is also an odd default - 600 or 900 reads more deliberate.
  • With conflictBackoffSec: 0 ("keep retrying regardless"), conflictUntil = now + 0 still causes the third cycle to be skipped via the continue at line 467, so it isn't quite "regardless". A if (settings.conflictBackoffSec == 0) early-out would match the description.
  • Teardown order at lines 482-487: close notifyKey before notifyEvent, so the pending notification is unregistered before its event handle goes away.
  • ContainsNoCase uses towlower, which under the default C locale only folds ASCII - patterns containing non-ASCII characters won't match case-insensitively against a path. CompareStringOrdinal(..., TRUE) over sliding windows, or FindNLSStringEx with NORM_IGNORECASE, would be correct for paths.
  • The README spends a paragraph on configuring the "Toggle Instant Replay" shortcut - a screenshot of where that lives in the Nvidia App would make it much easier to follow.

Functionality notes

Non-critical observations and ideas about the feature behavior itself.

  • Injected keystrokes go to the foreground window. SendInput presses real keys, so whatever has focus also receives the combo, and the injected key-ups will clear modifiers the user is physically holding at that moment. This is inherent to the shortcut-replay approach (AlwaysShadow has the same characteristic) and there's no clean alternative given the Nvidia App dropped the NvNode server, so this is just an FYI - the README's advice to move off the Alt+Shift default is the right mitigation.
  • onlyWhileRunning can leave Instant Replay off when the mod is disabled. If the mod is turned off while no listed program is running, Instant Replay stays off with no indication that the mod put it there. Worth a sentence in the README, since it's the one case where disabling the mod doesn't restore the pre-mod state.
  • Substring matching against the full image path is broader than users will expect. notepad matches ...\notepad++\notepad++.exe, and a fragment that happens to appear in a directory name matches everything installed under it. The description does say "substrings", but matching the file-name component (or an \<name> suffix) by default would be less surprising.
  • The conflict backoff is coarse. 800 s is a long time to leave Instant Replay off if the "conflict" was actually a one-off (a transient failure, a driver restart mid-toggle). An exponential backoff starting at ~30 s and growing would recover much faster from false positives while behaving the same in a real fight.
  • Non-elected instances still arm a registry notification and wake on every ShadowPlay write. Harmless, and it disappears entirely if you move to a tool mod.


Next steps:

  • /ai-review - after pushing fixes, to get a review of the updated code. You can repeat this as many times as you need, but each review is thorough and usually there's no need for more than 2-3 iterations.
  • /ready-for-reviewer - once you're satisfied with the state of the pull request, to hand it over to a human reviewer. If some findings above are left unaddressed, add a short note explaining why.

See the review process for details.

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-author The author's turn: request an AI review, or respond to one that was posted. and removed waiting-for-ai-review An AI review was requested and is being prepared. labels Aug 6, 2026
Review feedback from the windhawk-mods submission:

- Run as a tool mod in a dedicated windhawk.exe process instead of injecting
  into nvcontainer.exe. The mod installs no hooks and reads no NvContainer
  state, so there was no reason to be in-process. This removes the hand-rolled
  single-instance mutex, the per-instance watchdog thread and registry
  notification in every nvcontainer.exe, and the ambiguity about which user's
  hive HKCU refers to.
- IsAnyProcessRunning now returns std::optional<bool> so a failed process
  enumeration is distinguishable from nothing-matched. Previously a transient
  CreateToolhelp32Snapshot failure with a non-empty onlyWhileRunning list would
  make the mod switch Instant Replay off while a game was running.
- Backing off no longer blinds the mod: it keeps reading the state and clears
  the backoff as soon as Instant Replay is where it should be, so a resolved
  conflict recovers immediately instead of after the full wait.
- Backoff is now exponential, 30s doubling to the configured maximum, so a
  one-off failure costs seconds rather than the full period.
- Program lists match the exact file name unless the entry contains a
  backslash, in which case it is a path substring. Avoids notepad matching
  notepad++.exe.
- Case-insensitive matching uses CompareStringOrdinal instead of towlower,
  which only folds ASCII under the default locale.
- Use WindhawkUtils::StringSetting and std::clamp; close the registry key
  before the notification event it refers to.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dixxi1208

Copy link
Copy Markdown
Author

/ai-review

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-ai-review An AI review was requested and is being prepared. and removed waiting-for-author The author's turn: request an AI review, or respond to one that was posted. labels Aug 6, 2026
@windhawk-reviewer

Copy link
Copy Markdown

Submission review

Note: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding.

Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it.

Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them.


Nice submission overall — the tool-mod conversion is the right call, the README already explains how this differs from shadowplay-do-not-disable (which is the first thing that gets asked), the watchdog thread is properly signalled and joined in WhTool_ModUninit with no way to hang, and the settings block and the code reads line up. The findings below are mostly about the toggle retry loop.

1. The mod can press the shortcut a second time before the first press has taken effect, undoing its own toggle.

After ToggleInstantReplayWithHotkey() the code never confirms that the state actually changed — it just re-evaluates on the next wake-up and relies on kMinToggleIntervalMs = 3000 as the only guard. Nvidia writes several values to the key when the state changes (as the comment at the top notes), so a notification for one of the other values arrives, the 750 ms debounce elapses, the state is read while it is still stale, and once now >= nextToggleAllowed the shortcut is pressed again. Starting the Instant Replay ring buffer is not instantaneous, so exceeding 3 s is realistic on a slower machine or when a game is loading.

The result is a double press that cancels itself, then a mismatch on the next cycle, then toggleStreak >= 2 → a 30 s backoff, i.e. Instant Replay stays off for ~35 s and two extra shortcut presses were injected into whatever window the user was in — while nothing was actually fighting the mod.

Confirm the transition before counting the attempt as failed:

// Returns true if the state reached `expected` before the timeout. `*stop` is
// set when the mod is shutting down, in which case the caller must break out.
static bool WaitForState(InstantReplayState expected, DWORD timeoutMs, bool* stop) {
    ULONGLONG deadline = GetTickCount64() + timeoutMs;
    for (;;) {
        if (GetInstantReplayState() == expected) {
            return true;
        }
        ULONGLONG now = GetTickCount64();
        if (now >= deadline) {
            return false;
        }
        if (StopRequested((DWORD)std::min<ULONGLONG>(500, deadline - now))) {
            *stop = true;
            return false;
        }
    }
}

and after the press, only toggleStreak++ when WaitForState(...) came back false. That way a slow-but-successful toggle costs nothing, and toggleStreak counts real failures only.

2. With conflictBackoffSec set to 0 the mod presses the shortcut every 3 seconds, forever.

The setting description advertises 0 as "keep retrying and never back off". If the toggle can never work — the In-Game Overlay is off, no shortcut is bound, or SendInput is being dropped because the foreground window belongs to a higher-integrity process (SendInput gives no indication when UIPI blocks it, so the mod can't tell) — the state never matches, the backoff branch is skipped entirely, and the mod injects the key combination into whatever the user is typing into every 3 seconds for the rest of the session. Even at the default this settles into a press every 15 minutes forever, with no way for the user to notice why stray keystrokes keep arriving.

At minimum, don't offer "never back off": std::clamp(Wh_GetIntSetting(L"conflictBackoffSec"), kInitialBackoffSec, 86400) and drop that sentence from the description. Better still, give up: after the backoff has sat at its configured maximum for a few rounds, log once and stop pressing until the observed state changes or the settings change — a toggle that has failed a dozen times over half an hour isn't going to start working on attempt thirteen.

3. The default-shortcut fallback blindly presses the combination the README tells users to avoid.

When IRToggleHKeyCount can't be read, ToggleInstantReplayWithHotkey falls back to {VK_MENU, VK_SHIFT, VK_F10} — the Alt+Shift+F10 default that the Requirements section specifically warns about because Alt+Shift cycles the Windows keyboard layout. So the one configuration where the mod has no evidence that a toggle shortcut exists is exactly the one where it repeatedly injects the layout-cycling combination, and combined with item 2 it does so indefinitely.

Since a missing IRToggleHKeyCount most likely means the shortcut was never configured (rather than "it's at the default"), the safer behavior is to not press anything and log that a Toggle Instant Replay shortcut needs to be set. If you're confident Nvidia genuinely leaves the values absent while the default binding is live, make the fallback an explicit opt-in setting instead of silent behavior.

4. IsAnyProcessRunning opens every process on the system on every cycle, even when it doesn't need the path.

For each entry in the snapshot the code calls OpenProcess + QueryFullProcessImageNameW + CloseHandle unconditionally, but the full path is only needed for patterns containing a backslash — entry.szExeFile is already the file name that EqualsNoCase compares against. With onlyWhileRunning configured, IsAnyProcessRunning runs on every poll cycle (before the isOn == shouldBeOn comparison), so at the default 10 s that's a full OpenProcess sweep over a few hundred processes six times a minute, indefinitely — pure waste, and the kind of behavior that draws EDR/AV attention.

Decide once per call whether any pattern needs the path:

bool needsPath = std::any_of(patterns.begin(), patterns.end(), [](const std::wstring& p) {
    return p.find(L'\\') != std::wstring::npos;
});

and skip the OpenProcess block when it's false. Related: when both lists are configured the cycle takes two separate CreateToolhelp32Snapshot snapshots — one snapshot can answer both queries. And std::clamp(..., 1, 3600) on pollIntervalSec lets a user ask for a full process enumeration every second; a minimum of ~5 s would be kinder.

5. The tool-mod boilerplate isn't a verbatim copy of the wiki snippet.

The launcher block differs from https://github.com/ramensoftware/windhawk/wiki/Mods-as-tools:-Running-mods-in-a-dedicated-process in a handful of places — GetCommandLineGetCommandLineW, CreateMutexCreateMutexW, GetModuleFileNameGetModuleFileNameW, GetModuleHandleGetModuleHandleW, STARTUPINFOSTARTUPINFOW. These are semantically identical, but the snippet is meant to be pasted unchanged so it can be diffed across mods when it's updated. Please paste it as-is; mods/explorer-folder-hover-menu.wh.cpp has a verbatim copy at the bottom of the file.

Optional improvements

Minor polish — none of this affects users, so it's your call.

  • The registry key is opened once and never retried. If RegOpenKeyExW fails at watchdog start (ShadowPlay configured for the first time after the mod is already running), notifyKey stays nullptr for the whole session and the mod silently degrades to polling only — the headline "reacts the moment the state changes" behavior is gone until the mod is reloaded. The re-arm block at the top of the loop is a natural place to retry the open. While you're there, both the RegOpenKeyExW and CreateEventW failures are silent; a Wh_Log line each would make "why is it slow to react?" diagnosable.

  • GetInstantReplayState can drop its manual type checks. RRF_RT_DWORD is defined as RRF_RT_REG_BINARY | RRF_RT_REG_DWORD and already enforces the 4-byte size, so it accepts exactly the two forms the comment describes — which is why the IRToggleHKey* reads below get away with it. The RRF_RT_ANY + type/size dance can just become RRF_RT_DWORD with no out-type, matching the rest of the file.

  • Validate the VK codes read from the registry. keys.push_back((WORD)vkey) accepts anything, including 0 and values above 0xFE, and feeds them straight to SendInput. A cheap if (vkey == 0 || vkey > 0xFE) return false; avoids injecting nonsense if the values are ever something other than plain virtual-key codes.

  • Backslash patterns silently can't match processes the mod can't open. When OpenProcess fails (protected processes, other users' sessions), imagePath stays as the bare file name from entry.szExeFile, so a \Netflix\-style pattern quietly won't match. Worth a sentence in the $description so users don't debug it as a mod bug.

  • A screenshot would help. There's no visible UI here, but the README asks the user to go and change the Toggle Instant Replay shortcut — a screenshot of that screen in the Nvidia App would make the Requirements section much easier to act on.

Functionality notes

Non-critical observations and ideas about the feature behavior itself.

  • The injected key-ups can release keys the user is physically holding. The sequence sends key-up for every key in the shortcut, so if the shortcut shares a modifier with something the user is holding down (Shift while sprinting in a game, Ctrl during a drag), the system sees that modifier released. Inherent to driving this through SendInput and there's no cleaner alternative given the Nvidia App dropped the local server — just noting it alongside the "whatever window has focus also receives the combination" caveat already in the README.

  • Instant Replay's state is a persistent Nvidia setting, so the mod's effect outlives it being disabled. Windhawk's usual expectation is that a mod's effects disappear when it's turned off. The mod doesn't write the registry itself — it presses the user's own shortcut, which is about as close to "use the app's own mechanism" as it gets — but the resulting state does persist. The README already calls out the onlyWhileRunning variant of this; worth being aware the maintainer may want to weigh in on it.

  • A settings change resets an in-progress backoff. WhTool_ModSettingsChanged sets the wake event, which clears toggleStreak, currentBackoffSec and conflictUntil. If a user is tweaking, say, the pause list while a genuine conflict is ongoing, the exponential backoff restarts from 30 s each time. Probably intentional, but a growing conflict that keeps getting reset means more stray keystrokes than the backoff design intends.

  • onlyWhileRunning forces Instant Replay off, using a toggle rather than a set. Because the mechanism can only toggle, everything depends on the mod's read of the current state being right. If the registry value ever lags reality (see item 1), the "forced OFF" half of this feature can turn recording off at a moment the user wanted it on — the same failure mode as the double-press, but in the direction the mod exists to prevent.


Next steps:

  • /ai-review - after pushing fixes, to get a review of the updated code. You can repeat this as many times as you need, but each review is thorough and usually there's no need for more than 2-3 iterations.
  • /ready-for-reviewer - once you're satisfied with the state of the pull request, to hand it over to a human reviewer. If some findings above are left unaddressed, add a short note explaining why.

See the review process for details.

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-author The author's turn: request an AI review, or respond to one that was posted. and removed waiting-for-ai-review An AI review was requested and is being prepared. labels Aug 6, 2026
Second round of review feedback from the windhawk-mods submission:

- Confirm the state actually changed after pressing the shortcut, with an
  8 second WaitForState, instead of relying on a 3 second minimum interval.
  Starting the Instant Replay ring buffer isn't instant, and a notification
  for one of the other values in the key could arrive while the state value
  was still stale, so the mod could press again and undo its own toggle. The
  toggle streak now counts real failures only.
- Remove the never-back-off option. The shortcut can fail in ways the mod
  cannot detect (overlay off, UIPI silently dropping the input), so an
  unbounded retry meant injecting keystrokes into the user's session forever.
  The backoff minimum is now 30s, and after a few rounds at the configured
  maximum the mod gives up until the state or the settings change.
- Don't guess at Nvidia's Alt+Shift+F10 default when no shortcut is
  configured. That was the one case with no evidence a shortcut exists, and
  it's the combination the readme tells users to avoid because Alt+Shift
  cycles the keyboard layout. The mod now presses nothing and logs why.
- Only resolve full process paths when a pattern actually needs one. The
  OpenProcess sweep over every process ran on every cycle even though the
  exact-file-name form only needs entry.szExeFile.
- Validate the virtual-key codes read from the registry before feeding them
  to SendInput.
- Paste the tool mod snippet verbatim from the wiki. The previous copy came
  from net-toggle.wh.cpp, which carries a locally modified version using the
  W-suffixed API names.
- Retry opening the registry key inside the loop, so configuring ShadowPlay
  after the mod is running still gets immediate reactions instead of silently
  degrading to polling. Log when the key or event isn't available.
- Simplify GetInstantReplayState to RRF_RT_DWORD, which already accepts the
  REG_BINARY form and enforces the size.
- Minimum check interval raised to 5s.
- Document the key-ups releasing physically held keys, that Instant Replay's
  state persists after the mod is disabled, and that backslash patterns can't
  match processes the mod can't open.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dixxi1208

Copy link
Copy Markdown
Author

/ai-review

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-ai-review An AI review was requested and is being prepared. and removed waiting-for-author The author's turn: request an AI review, or respond to one that was posted. labels Aug 6, 2026
@windhawk-reviewer

Copy link
Copy Markdown

Submission review

Note: This review was done by Claude. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding.

Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it.

Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them.


The mod is in good shape: the tool-mod boilerplate is a verbatim copy of the wiki snippet, every declared setting is read with the matching API and nothing is dead, the watchdog thread is signalled and joined in WhTool_ModUninit with every blocking wait bounded by the stop event, and the README's comparison with shadowplay-do-not-disable is accurate, so this isn't a duplicate. Two things worth changing:

1. The "gave up" state can't be cleared by the fix the mod itself asks the user to make.

Once gaveUp is set, the log says "Check that the In-Game Overlay is on and that a Toggle Instant Replay shortcut is set" (line 654), but the flag is only cleared when the state already matches (line 608) or when the mod's own settings change (line 563). So the user does exactly what they're told - configures a Toggle Instant Replay shortcut, or turns the overlay back on - and nothing happens: writing IRToggleHKey* fires the notification, the mod re-reads the state, sees it's still wrong, hits if (gaveUp) continue; and stays inert until the mod is toggled off/on. Turning the overlay on doesn't touch the key at all, so it doesn't even produce a wake-up.

The same latch also makes the mod permanently useless after a purely transient inability to inject - a workstation locked for an hour or so with Instant Replay off burns the whole ladder (~75 min to gaveUp) with no recovery path. That's the exact silent failure the mod exists to prevent.

Re-arm on the thing the log names. Split the shortcut read out of ToggleInstantReplayWithHotkey and compare it:

// Reads IRToggleHKeyCount + IRToggleHKey<n>; empty when none is configured.
static std::vector<WORD> ReadToggleHotkey();

std::vector<WORD> hotkeyAtGiveUp;   // captured where gaveUp is set
...
if (gaveUp) {
    if (ReadToggleHotkey() == hotkeyAtGiveUp) {
        continue;
    }
    // The shortcut changed - the user acted on the log message, try again.
    gaveUp = false;
    toggleStreak = 0;
    currentBackoffSec = 0;
    roundsAtMaxBackoff = 0;
}

Optionally also re-arm on a long timer (say once every few hours). That's still bounded - it's not the unbounded keystroke injection that the earlier round asked you to remove - and it covers the causes the mod can't observe, like the overlay being switched back on.

2. License: MIT vs AlwaysShadow's GPL-3.0.

Verpous/AlwaysShadow is GPL-3.0, and the specific things this mod depends on (the NVSPCAPS key, the {1B1D3DAA-...} state value, the IRToggleHKeyCount/IRToggleHKey<n> scheme, the "replay the shortcut" fallback) are exactly what that project figured out. Value names and key paths are facts and the code here clearly isn't a translation of theirs, so MIT is very likely fine - but since the PR description positions the mod against AlwaysShadow, please confirm in the PR that nothing beyond those names was derived from its source. If any of it was, the mod's @license needs to be GPL-3.0-compatible.

Optional improvements

Minor polish - none of this affects users, so it's your call.

  • ContainsNoCase (line 343) calls CompareStringOrdinal once per character offset, so a path pattern costs a few hundred API calls per process per cycle. FindNLSStringEx does the whole case-insensitive substring search in one call:

    return FindNLSStringEx(LOCALE_NAME_INVARIANT, FIND_FROMSTART | NORM_IGNORECASE,
                           haystack.data(), (int)haystack.size(),
                           needle.data(), (int)needle.size(),
                           nullptr, nullptr, nullptr, 0) >= 0;
  • QueryFullProcessImageNameW is given a MAX_PATH buffer (line 413), so any process whose image path exceeds 259 characters fails with ERROR_INSUFFICIENT_BUFFER and silently falls back to the bare file name - a \folder\ pattern then can't match it, with no log line. A 1024-WCHAR buffer (or a retry on ERROR_INSUFFICIENT_BUFFER) removes the case.

  • AddKeyInput sends wVk only, with no wScan and no KEYEVENTF_EXTENDEDKEY. If the configured shortcut contains an extended key (arrows, Insert/Delete/Home/End/PgUp/PgDn, right Alt/Ctrl, numpad Enter) the injected event isn't marked as such. Compare edge-hot-corner-desktop-switch.wh.cpp#L100, which sets the flag for the keys that need it. Filling wScan = MapVirtualKeyW(vk, MAPVK_VK_TO_VSC) and deriving the flag from the VK code costs two lines.

  • The "no usable Toggle Instant Replay shortcut" message (line 274) logs on every attempt, unlike the other recurring conditions which use a logged* latch. Worth the same treatment for consistency.

  • pollIntervalSec is silently clamped to 5-3600 and conflictBackoffSec to 30-86400. A user who types 1 gets 5 with no feedback - worth naming the range in the $description.

Functionality notes

Non-critical observations and ideas about the feature behavior itself.

  • Failures that aren't the mod's fault still count toward the backoff ladder. When the workstation is locked, a UAC secure desktop is up, or the foreground window belongs to a higher-integrity process, the press is dropped and WaitForState times out after 8 s - indistinguishable from a real conflict, so it doubles the backoff and moves toward giving up. A cheap guard for the desktop cases, before pressing:

    HDESK input = OpenInputDesktop(0, FALSE, DESKTOP_READOBJECTS);
    if (!input) {
        continue;  // locked / secure desktop - not our failure, don't count it
    }
    CloseDesktop(input);
  • Held keys. If the user happens to be holding a modifier when the mod fires, the combination Nvidia sees isn't the configured one, so the toggle fails and counts as a conflict - on top of the key-ups stealing the held keys, which the README already documents. Checking GetAsyncKeyState for the shortcut's own keys (and Ctrl/Shift/Alt/Win) and deferring the press by a cycle when any is down would avoid both.

  • onlyWhileRunning can leave Instant Replay off for good. Disable the mod while no listed program is running and the setting stays off, as the README says. There's no clean fix - restoring the entry state during WhTool_ModUninit would mean injecting keystrokes on shutdown - so this is just an FYI; the README note is the right call.

  • Injecting the shortcut means the focused window receives it too. Inherent to the approach given that the Nvidia App dropped the local server GeForce Experience exposed, and the README covers it - noting it only so it's on the record.


Next steps:

  • /ai-review - after pushing fixes, to get a review of the updated code. You can repeat this as many times as you need, but each review is thorough and usually there's no need for more than 2-3 iterations.
  • /ready-for-reviewer - once you're satisfied with the state of the pull request, to hand it over to a human reviewer. If some findings above are left unaddressed, add a short note explaining why.

See the review process for details.

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-author The author's turn: request an AI review, or respond to one that was posted. and removed waiting-for-ai-review An AI review was requested and is being prepared. labels Aug 6, 2026
Third round of review feedback from the windhawk-mods submission:

- The give-up latch could not be cleared by the fix its own log message asks
  for. Configuring a Toggle Instant Replay shortcut, or switching the In-Game
  Overlay back on, left the mod inert until it was disabled and re-enabled.
  The shortcut is now read separately, captured when the mod gives up, and
  compared each cycle, so changing it re-arms immediately. A periodic retry
  every few hours covers the causes the mod cannot observe.
- Don't count failures that were never the mod's fault. A locked session or a
  UAC secure desktop means injected input can't reach anything, so the cycle
  is skipped via OpenInputDesktop instead of timing out and driving the
  backoff ladder toward giving up.
- Don't press while the user is holding any of the shortcut's keys or any
  modifier. The combination Nvidia would see isn't the configured one, so the
  toggle fails and counts as a conflict, and the key-ups steal keys the user
  is actually using.
- A missing shortcut no longer counts as a failed attempt, since there was no
  attempt, and its log message is latched like the other recurring ones.
- Set wScan and KEYEVENTF_EXTENDEDKEY so shortcuts containing extended keys
  (arrows, Insert/Delete/Home/End/PgUp/PgDn, right Alt/Ctrl, numpad) describe
  the right physical key.
- ContainsNoCase uses a single FindNLSStringEx call instead of one
  CompareStringOrdinal per character offset.
- QueryFullProcessImageNameW gets a 1024 WCHAR buffer, so paths longer than
  MAX_PATH no longer fall back silently to the bare file name.
- Name the accepted ranges for pollIntervalSec and conflictBackoffSec in
  their descriptions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dixxi1208

Copy link
Copy Markdown
Author

/ai-review

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-ai-review An AI review was requested and is being prepared. and removed waiting-for-author The author's turn: request an AI review, or respond to one that was posted. labels Aug 6, 2026
@windhawk-reviewer

Copy link
Copy Markdown

This pull request has already had 3 AI reviews in the last 24 hours, which is the limit, so no review was posted this time.

Comment /ai-review again after 2026-08-07 09:27 UTC (in 20 hours) to get another one.

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-author The author's turn: request an AI review, or respond to one that was posted. and removed waiting-for-ai-review An AI review was requested and is being prepared. labels Aug 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-for-author The author's turn: request an AI review, or respond to one that was posted.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant