Skip to content

Record where the runtime chain actually stops on Unreal - #88

Merged
rouges78 merged 1 commit into
mainfrom
claude/log-ue-runtime-finding
Aug 21, 2026
Merged

Record where the runtime chain actually stops on Unreal#88
rouges78 merged 1 commit into
mainfrom
claude/log-ue-runtime-finding

Conversation

@rouges78

Copy link
Copy Markdown
Owner

First real-game test of the runtime chain, on Father's Day (UE, Steam). Two entries in the log, one of them a correction to the priorities.

The transport works. The text source does not.

The DLL connects to the Rust server inside a shipping title, and the game stays healthy — SendMessageTimeout(WM_NULL, 3000ms) answers, 103 s CPU, 608 MB. That confirms the overlapped-I/O fix from #85 on something other than the test app, which matters: that bug froze the process instantly.

But zero requests reach the server:

[gs-hook] connesso a GameStringer via IPC
[gs-hook/UE] pattern FText::ToString ambiguo: 4 match, hook RIFIUTATO
             (aggancerebbe la funzione sbagliata)
[gs-hook] sorgente attiva: GDI (ExtTextOutW/DrawTextW) (livello 2)
[gs-hook] sorgente attiva: GDI/GetGlyphOutline (estrazione) (livello 2)

The Unreal L1 source declines to hook FText::ToString when its byte pattern is ambiguous, and refusing is the right call — a wrong hook in a shipping game means a crash. The GDI sources stay active but see nothing, because UE draws through Slate/Direct3D. The missing link is symbol resolution, not IPC.

The trap worth recording: connesso a GameStringer via IPC reads like success and is only half the chain. The number that decides is how many requests reach the server; at zero, the problem is upstream of the IPC, and staring at the pipe will never find it.

Corollary for #87

Hooking the runtime fallback to the "Unreal without .locres" dead end is right in principle, but while that pattern stays ambiguous the runtime path has no text to translate on UE — it would offer a road that leads nowhere yet. RPG_RT and GDI games are different: that source works and has been seen working. The entry says so plainly so the next person does not assume UE is covered.

Second entry: the Paks decoy

Surveying the library for a test candidate, I fell into the trap the log's own first entry warns about — in a variant it did not cover. Every UE game has at least two Paks folders, and the first one found is usually Engine/Programs/CrashReportClient/Content/Paks. I measured that one and concluded REANIMAL shipped no localization: 45 MB of crash reporter against 15.6 GB of game; TerraTech Legion, 46 MB against 5.5 GB.

The control that catches it is The Skin Stapler, which must come back positive at 1679 entries. While a method says otherwise, the method is what is broken — and find_all_paks_dirs() exists precisely so nobody has to rediscover this with an improvised find.

Docs only — no code changes.

🤖 Generated with Claude Code

First real-game test of the chain, on Father's Day. The transport half
works: the DLL connects to the Rust server inside a shipping title, and
the game stays healthy — SendMessageTimeout answers, 103s CPU, 608 MB.
That confirms the overlapped-I/O fix on something other than the test
app, which matters, because that bug froze the process instantly.

The other half does not. Zero requests reach the server, because the
Unreal L1 source refuses to hook FText::ToString when its byte pattern
is ambiguous — four matches here — and declines rather than hooking the
wrong function. The GDI sources stay active but see nothing: UE draws
through Slate/Direct3D, not ExtTextOutW. The missing link is symbol
resolution, not IPC.

The trap is the log line "connesso a GameStringer via IPC", which reads
like success and is only half the chain. The number that decides is how
many requests reach the server; at zero, the problem is upstream of the
IPC and staring at the pipe will never find it.

Carries the corollary for #87: hooking the fallback to the "Unreal
without .locres" dead end is right in principle, but while that pattern
stays ambiguous the runtime path has no text to translate on UE, so it
would offer a road that leads nowhere yet. RPG_RT and GDI games are
different — that source works and has been seen working.

Second entry, on the Paks folder: every UE game has at least two, and
the first one found is usually the decoy —
Engine/Programs/CrashReportClient/Content/Paks. I measured that one and
concluded REANIMAL had no localization: 45 MB of crash reporter against
15.6 GB of game. The control that catches it is The Skin Stapler, which
must come back positive at 1679 entries; while a method says otherwise,
the method is what is broken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@rouges78
rouges78 merged commit f628e40 into main Aug 21, 2026
7 checks passed
@rouges78
rouges78 deleted the claude/log-ue-runtime-finding branch August 21, 2026 14:36
rouges78 added a commit that referenced this pull request Aug 21, 2026
…us (#89)

A symbol is deterministic: it is either there and correct, or absent.
That is strictly better than a byte signature, so it now runs first, with
the pattern scan as the fallback. ResolveFTextToStringBySymbol looks for
?ToString@FText@@QEBAAEBVFString@@xz across every loaded module.

It does not fix Father's Day, and it cannot: symbol resolution needs the
engine in DLLs, and a monolithic Shipping build has no UE symbols at all.
Measured, not assumed — 312 exports in the exe and every one a driver
hint (NvOptimusEnablement, ags*), the PDB named in the header but not
shipped, turbojpeg.dll the only DLL beside it, and 142 modules
interrogated at runtime with zero hits. The value is for non-monolithic
and editor builds; commercial games still fall through to the pattern.

The module count is logged on purpose. Without it a zero cannot separate
"looked everywhere, not there" from "the snapshot failed and I looked at
nothing" — the same trap as the "connesso via IPC" line recorded in #88.
The check that confirms it is comparing against Modules.Count: 142
against 142.

The four ambiguous candidates are measured and written down: four
distinct functions sharing a prologue, diverging at byte 13, inside the
17 the signature covers. Lengthening it would mean retuning against the
four games where it is already unique, with no ground truth to say which
candidate is even correct. Refusing to guess remains right: a wrong hook
in a shipping game is a crash or silent heap corruption. The entry names
the promising direction instead — anchoring on a string reference rather
than a prologue.

Verified on the real game: symbol path exercised, pattern still correctly
refuses, GDI fallback activates, game responsive, no regression. Both
architectures rebuilt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant