You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
echo "Release input version : ${INPUT_VERSION} (core: ${INPUT_CORE})"
49
+
echo "CMakeLists project ver: ${PROJECT_VERSION}"
50
+
if [ -z "${PROJECT_VERSION}" ]; then
51
+
echo "::error::Could not parse project(VERSION ...) from CMakeLists.txt"
52
+
exit 1
53
+
fi
54
+
if [ "${INPUT_CORE}" != "${PROJECT_VERSION}" ]; then
55
+
echo "::error::Release version '${INPUT_VERSION}' does not match CMakeLists.txt project(VERSION) '${PROJECT_VERSION}'. Bump project(VERSION) first so the shipped ConfigVersion and DMK_VERSION_* macros match the tag."
Copy file name to clipboardExpand all lines: AGENTS.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -413,6 +413,7 @@ These are called at 60+ fps from game hook callbacks. Never add allocations, exc
413
413
-**Do not weaken** atomic memory orderings without proving correctness.
414
414
-**Do not skip** running the test suite before committing.
415
415
-**Do not publish** release packages before debug tests, release builds, and installed-package smoke tests pass for both MinGW and MSVC.
416
+
-**Do not tag** a release whose version differs from `CMakeLists.txt``project(VERSION ...)`. The release version is single-sourced from there: the generated `DetourModKitConfigVersion.cmake` and the `DMK_VERSION_*` macros derive from it, so a tag that disagrees would ship a package whose `find_package` version check and `DMK_VERSION_AT_LEAST` contradict the tag. The `validate-version` job in `.github/workflows/release.yml` fails closed when the dispatch `version` input does not match, so bump `project(VERSION)` first.
416
417
-**Do not add** Windows API calls without `#ifdef _WIN32` guards in headers (implementation files are Windows-only, but headers should remain clean).
417
418
-**Do not commit** build artifacts, `.exe`, `.a`, `.lib`, `.obj`, or `.pdb` files.
418
419
-**Do not remove** or weaken existing tests. Add new tests for new code.
// Brief sleep to allow any in-flight hook callbacks to complete.
346
-
// SafetyHook freezes threads during hook removal, but callbacks
347
-
// that were already past the hook entry point need time to return.
346
+
// SafetyHook does not freeze threads during removal: it relocates only a
347
+
// thread that faults on the patched page during the brief rewrite window,
348
+
// so a callback already past the hook entry must return on its own.
348
349
Sleep(CALLBACK_DRAIN_MS);
349
350
350
351
FreeLibrary(s_logic_module);
@@ -653,7 +654,7 @@ With this setup, the workflow is always **build, then press reload key**. The po
653
654
654
655
**Problem:** A hook callback may be executing on the game's thread when you trigger a reload. If the logic DLL is unloaded while a callback is mid-execution, the game crashes (code page unmapped leads to access violation).
655
656
656
-
**How DMK handles this:** SafetyHook's `remove_all_hooks()`freezes all threads, patches the original bytes back, then resumes threads. Any thread that was inside a hook trampoline will now execute the original function code. This is safe as long as:
657
+
**How DMK handles this:** SafetyHook's `remove_all_hooks()` patches the original bytes back without freezing threads. While it rewrites the prologue it strips execute on the patched page, and a vectored exception handler relocates the instruction pointer of any thread that *faults* on that page during the rewrite window; a thread already running inside the trampoline or detour body is **not** relocated. So removal is safe only if the hooked function is quiescent at that moment, which is why you must drain or quiesce callers before unloading. As long as that holds:
657
658
658
659
- The hook callback does not store persistent pointers into the logic DLL's code/data segments.
659
660
- The hook callback does not spawn threads that outlive the DLL.
@@ -1216,7 +1217,7 @@ If DMK could only ship one of the two, `DMK_Shutdown` would cover ~90% of consum
1216
1217
1217
1218
### Pre-unload contract: worker-thread quiescence
1218
1219
1219
-
`Bootstrap::on_logic_dll_unload(_all)` removes hooks and bindings, but it cannot prove that every consumer-owned worker thread has stopped firing those hooks. A worker thread that calls into a detoured function between `remove_hook` returning and `FreeLibrary` reclaiming the Logic DLL's `.text` pages will execute freed code; the resulting access violation often points at an address that no longer maps to anything, which is hard to triage from a crash dump. SafetyHook freezes game threads while it patches the original prologue back, but it has no visibility into worker threads spawned by the consumer.
1220
+
`Bootstrap::on_logic_dll_unload(_all)` removes hooks and bindings, but it cannot prove that every consumer-owned worker thread has stopped firing those hooks. A worker thread that calls into a detoured function between `remove_hook` returning and `FreeLibrary` reclaiming the Logic DLL's `.text` pages will execute freed code; the resulting access violation often points at an address that no longer maps to anything, which is hard to triage from a crash dump. SafetyHook does not freeze threads while it patches the original prologue back; it only relocates a thread that faults on the patched page during the rewrite window, and has no visibility into worker threads spawned by the consumer, so a worker already inside a detour body is never rescued.
1220
1221
1221
1222
Stop and join every consumer-owned worker BEFORE you call the unload helper. The canonical Logic-DLL `Shutdown()` ordering for the persistent-host topology is:
// 3. Return from Shutdown(). The loader's FreeLibrary call follows.
1238
1240
}
1239
1241
```
1240
1242
1241
-
A common worker case to watch for: a hook callback runs on a game thread, but a separate consumer-owned thread pool *also* calls into the same detour body (e.g. an off-thread snapshot capture, a deferred re-scan, a periodic poller that touches game state through a hooked accessor). Both paths must be quiet before the unload helper runs. If a worker calls into game-side code that the host module also hooks, joining the worker before unload is sufficient; SafetyHook handles the game-thread side.
1243
+
A common worker case to watch for: a hook callback runs on a game thread, but a separate consumer-owned thread pool *also* calls into the same detour body (e.g. an off-thread snapshot capture, a deferred re-scan, a periodic poller that touches game state through a hooked accessor). Both paths must be quiet before the unload helper runs. If a worker calls into game-side code that the host module also hooks, joining the worker before unload handles the worker side; the game-thread side still depends on the hooked function being quiescent during removal, since SafetyHook only relocates threads that fault on the patched page and does not drain a thread already inside a detour.
0 commit comments