mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
main.c foreign_hook matches "hook", so a crash in RTSSHooks64.dll got the line that the library is not part of the game and that a recording, overlay or capture program put it there, also when the game's own thread that sets the frame limit (rtss_limit.c's "rtss" worker) had loaded and called it. The one RTSS crash on record (issue #231) faulted in ntdll on that thread, where no note applied. And the crash handler then called rtss_limit_restore from that thread, whose own lock it holds (SRW locks are not recursive), so the restore's trylocks spent a second. The platform layer keeps the name a thread was made with in the thread itself (plat_thread_name, copied at its start: a detached thread's struct may go before it ends). main.c crash_module_note picks the note: on the "rtss" thread, whatever the module, the line says the fault is in the game's calls of RTSS's library or around them and that rtss = 0 keeps the game from calling it; on other threads RTSSHooks64.dll stays foreign, as it is RTSS's overlay there. The restore is skipped on the rtss thread. rtss_limit.c is unchanged: the thread is known by the name it is spawned with. Crash text only, besides the skipped restore. Not reproduced before the change. tests/foreign_hook_check.py checks the note's choice (RTSSHooks64.dll and ntdll on the RTSS thread: the RTSS note; RTSSHooks64.dll elsewhere: foreign; a driver: none), and tests/crash_stack_check.py that the crash report reads the faulting thread's name. They, thread_end_check, fatal_exit_check and the compile checks of every runtime file pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
84 lines
3.8 KiB
Python
84 lines
3.8 KiB
Python
"""The crash report's test for a module another program put into the game (runtime/main.c foreign_hook): the Vulkan
|
|
layers, hooks and overlays of recorders, overlays and capture tools are named as theirs; the game's own libraries and
|
|
the drivers are not, nor the validation layer the game loads itself (NX2NAVI_VK_VALIDATION). A fault on the thread that
|
|
sets RivaTuner Statistics Server's limit gets the RTSS note whatever the module (main.c crash_module_note). Builds those
|
|
functions, cut out of main.c, into a test program. No game files.
|
|
|
|
Usage: python tests/foreign_hook_check.py [path to clang]"""
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
MSYS_BIN = Path(r'C:\msys64\ucrt64\bin')
|
|
source = (ROOT / 'runtime' / 'main.c').read_text(encoding='utf-8')
|
|
|
|
FOREIGN = ['RTSSHooks64.dll', 'graphics-hook64.dll', 'GameOverlayRenderer64.dll', 'ReShade64.dll',
|
|
'EOSOverlayVkLayer-Win64.dll', 'VkLayer_steam_fossilize64.dll', 'VkLayer_obs64.dll', 'obs-vulkan64.dll',
|
|
'nvspcap64.dll']
|
|
OWN = ['VkLayer_khronos_validation.dll', 'amdvlk64.dll', 'nvoglv64.dll', 'nvngx_dlss.dll', 'ucrtbase.dll', 'SDL3.dll',
|
|
'amd_fidelityfx_vk.dll', 'ntdll.dll', 'nx2navi_dlssg.dll', '']
|
|
|
|
|
|
def compiler():
|
|
if len(sys.argv) > 1:
|
|
return sys.argv[1]
|
|
if os.environ.get('CC'):
|
|
return os.environ['CC']
|
|
if os.name == 'nt' and (MSYS_BIN / 'clang.exe').exists():
|
|
return str(MSYS_BIN / 'clang.exe')
|
|
return shutil.which('clang') or 'clang'
|
|
|
|
|
|
def function(signature):
|
|
start = source.index(signature)
|
|
brace = source.index('{', start)
|
|
depth, end = 1, brace + 1
|
|
while depth:
|
|
depth += (source[end] == '{') - (source[end] == '}')
|
|
end += 1
|
|
return source[start:end]
|
|
|
|
|
|
def main():
|
|
names = FOREIGN + OWN
|
|
enum = source[source.index('enum { CRASH_NOTE_NONE'):]
|
|
enum = enum[:enum.index('\n') + 1]
|
|
src = ('#include <ctype.h>\n#include <stdio.h>\n#include <string.h>\n' + function('static int foreign_hook(') + '\n'
|
|
+ enum + function('static int crash_module_note(')
|
|
+ '\nint main(int argc, char **argv) {\n for (int i = 1; i < argc; i++) printf("%d\\n", foreign_hook(argv[i]));\n'
|
|
' printf("%d %d %d %d\\n", crash_module_note("RTSSHooks64.dll", 1), crash_module_note("ntdll.dll", 1),\n'
|
|
' crash_module_note("RTSSHooks64.dll", 0), crash_module_note("amdvlk64.dll", 0));\n'
|
|
' return 0;\n}\n')
|
|
work = ROOT / 'build' / 'test' / 'foreign_hook'
|
|
work.mkdir(parents=True, exist_ok=True)
|
|
(work / 'foreign_hook_check.c').write_text(src, encoding='utf-8')
|
|
exe = work / ('foreign_hook_check.exe' if os.name == 'nt' else 'foreign_hook_check')
|
|
env = dict(os.environ)
|
|
if os.name == 'nt' and MSYS_BIN.exists():
|
|
env['PATH'] = str(MSYS_BIN) + os.pathsep + env.get('PATH', '')
|
|
subprocess.check_call([compiler(), '-O1', '-Wall', str(work / 'foreign_hook_check.c'), '-o', str(exe)], env=env)
|
|
got = subprocess.run([str(exe), *names], env=env, capture_output=True, text=True, timeout=60).stdout.split()
|
|
bad = 0
|
|
for name, g in zip(names, got):
|
|
want = '1' if name in FOREIGN else '0'
|
|
if g != want:
|
|
bad += 1
|
|
print(f'{name or "(outside every module)"}: {"another program" if g == "1" else "not foreign"}'
|
|
f'{"" if g == want else " FAIL"}')
|
|
notes = got[len(names):]
|
|
ok = notes == ['2', '2', '1', '0']
|
|
print(f'crash note: on the RTSS thread (RTSSHooks64.dll, ntdll.dll) the RTSS note, elsewhere RTSSHooks64.dll foreign, '
|
|
f'a driver none: {"ok" if ok else "FAIL " + str(notes)}')
|
|
bad += not ok
|
|
if len(got) != len(names) + 4:
|
|
bad += 1
|
|
print(f'foreign_hook_check: {"FAILED" if bad else "ok"}')
|
|
return 1 if bad else 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|