https://github.com/shdwmtr/resident
Resident: An incredibly small, performant, independent utility tool that un-obfuscates minified class names in Steams CEF on-the-fly.
https://github.com/shdwmtr/resident
css-loader decky deobfuscator millennium patcher sfp steam
Last synced: 2 days ago
JSON representation
Resident: An incredibly small, performant, independent utility tool that un-obfuscates minified class names in Steams CEF on-the-fly.
- Host: GitHub
- URL: https://github.com/shdwmtr/resident
- Owner: shdwmtr
- License: gpl-3.0
- Created: 2026-06-29T21:56:42.000Z (7 days ago)
- Default Branch: main
- Last Pushed: 2026-06-30T20:58:27.000Z (6 days ago)
- Last Synced: 2026-06-30T22:24:48.869Z (6 days ago)
- Topics: css-loader, decky, deobfuscator, millennium, patcher, sfp, steam
- Language: C
- Homepage:
- Size: 3.9 MB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
README
# Resident 🪑[^1]
**RES**tore Steam's internal class **IDENT**ifiers.
An incredibly small, performant, independent utility tool that un-obfuscates minified class names in Steams CEF on-the-fly in memory by modifying the heap. There is **no runtime overhead**. This tool is particularly useful for creating stable, future proof themes.
```diff
diff --git a/tmp/before.html b/tmp/after.html
index ec676a2..1ca47d7 100644
--- a/tmp/before.html
+++ b/tmp/after.html
@@ -1,17 +1,17 @@
-
+
```
`resident` is fully native, dependencyless, self-contained, portable, does not require CEF remote debugging, has virtually no memory footprint, and does NOT write to disk.
It entirely self-bootstraps itself, requiring **absolutely zero** user intervention.
This tool is entirely plugin loader agnostic, and supports both the Steam deck and client.
If you like this tool, consider starring it ⭐
# Installation[^2]
Packing notice: resident is entirely stateless, and has no config. If you are packaging resident, it is recommended to simply keep it in your installation folder, and
simply hard-link/soft-link (on windows, by default, softlinks need admin. hard don't) the soname/dll into the webhelpers directory. Killing Steam's webhelper will cause Steam to restart it, which will load/unload resident depending.
## Linux
```bash
# Installing resident
$ mv /path/to/resident.so ~/.steam/steam/ubuntu12_64/libXtst.so.6
# uninstalling resident
$ rm ~/.steam/steam/ubuntu12_64/libXtst.so.6
```
## Windows
Copy the installed `resident.dll` binary into the `steamwebhelper.exe`'s owning directory, renaming it to `version.dll`.
Same applies if you are hardlinking to the webhelpers owning directory; it must be named `version.dll`.
# Parser[^3]
```mermaid
stateDiagram-v2
[*] --> Scanning
Scanning --> ReadKey : identifier or '"'
Scanning --> Scanning : other
ReadKey --> AwaitHash : key + ':"'
ReadKey --> Scanning : no match / rewind
AwaitHash --> Validate : 18–30 chars + '"'
AwaitHash --> Scanning : out of range
Validate --> Rewrite : is_css_hash ✓
Validate --> Scanning : plain word / false positive
Rewrite --> Scanning : flush + emit 'hash key'
```

`resident` implements a linear-time lexical transducer (a [finite-state transducer](https://en.wikipedia.org/wiki/Finite-state_transducer)) over a byte stream.
It uses a [greedy](https://en.wikipedia.org/wiki/Greedy_algorithm), left-anchored PEG recognize with a single bounded lookahead rewind, scanning for two token patterns defined by the EBNF grammar below.
## [EBNF](https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form) (Extended Backus–Naur) grammar
```
pattern ::= quoted_key | bare_key
quoted_key ::= '"' key_q '"' ':"' hash '"'
bare_key ::= key_b ':"' hash '"'
key_q ::= [a-zA-Z_][a-zA-Z0-9_-]*
key_b ::= [a-zA-Z_][a-zA-Z0-9_]*
hash ::= [a-zA-Z0-9_-]{18,30}
```
## High Level
Class modules are rewritten as diagramed below. This happens at the CEF level, both Steam and CEF are under the impression that content `B` is the original, they both have no idea.
At the native C bindings, CDP (Inspector Network tab), and JS level, this *is* Steam's original JS - even though it's not.
```js
e.exports = {
"duration-app-launch": "800ms",
VCenter: "_1T7c8767I5SNmJ3DC5uSr8",
BackgroundAnimation: "_1noPMemGV6O50ZVadg1Cxf",
"ItemFocusAnim-darkGrey": "_1ulNzAcgjuBmpgvc5wqLMD",
"ItemFocusAnim-translucent-white-10": "_3pmHfms_Y73-eEZ6-BSy3j",
"ItemFocusAnimBorder-darkGrey": "_335Bbo4P8V_X7iOeO9gc2e",
focusAnimation: "_2v_k6SupaG0hlrpoCWsND4"
}
```
```js
e.exports = {
"duration-app-launch": "800ms", // skipped, not a class defined by our lang.
VCenter:"_1T7c8767I5SNmJ3DC5uSr8 VCenter",
BackgroundAnimation:"_1noPMemGV6O50ZVadg1Cxf BackgroundAnimation",
"ItemFocusAnim-darkGrey":"_1ulNzAcgjuBmpgvc5wqLMD ItemFocusAnim-darkGrey",
"ItemFocusAnim-translucent-white-10":"_3pmHfms_Y73-eEZ6-BSy3j ItemFocusAnim-translucent-white-10",
"ItemFocusAnimBorder-darkGrey":"_335Bbo4P8V_X7iOeO9gc2e ItemFocusAnimBorder-darkGrey",
focusAnimation:"_2v_k6SupaG0hlrpoCWsND4 focusAnimation"
}
```
## Other Rewrites
`*.classList.add/remove`: Both of these methods expect a single class, which used to be the case. Not after our patches though.
* detects: `classList.add(IDENT[()].IDENT) and classList.remove(IDENT[()].IDENT)`
* rewrites: `classList.add(...IDENT[()].IDENT.split(" "))`
Without this patch, add/remove will be directly called with a hooked class.
### Before:
```js
// focusAnimation: "_2v_k6SupaG0hlrpoCWsND4 focusAnimation"
...classList.add(A().focusAnimation) // err: add/remove only accept 1 class per
```
### After:
```js
// focusAnimation: "_2v_k6SupaG0hlrpoCWsND4 focusAnimation"
...classList.add(...A().focusAnimation.split(" ")) // adds both, hell yeah
```
From what I've googled, this is the only situation where we would face issues.
All other API's are safe at runtime. (excluding `document.querySelector`, etc. Valve dev's are intelligent and wouldn't logically use raw DOM APIs with react)
# Benchmarks[^4]
These are benchmarks are the average patch time over 100 runs on an Intel i9-14900k.
In fact; this libraries server is an order of magnitude faster than Steams inbuilt loopback.
On startup, Steam reads ALL files from steamui/ linearly, (seemingly) no chunking
and caches them in page cache. With this library, steam *forcefully* misses `chunk~*.js` when initially caching.
This leads to faster, less resourceful startup (measured to be about 3 seconds on my machine).
This is why Millennium moved themes outside of steamui/ as any very deep directories with alot of individual reads (.git, node_modules/ etc) cause the CPU to panic
under L3 cache pressure, making startup sometimes 15,20, or even n seconds (linearly) slower depending on your setup.

# Accuracy & Reliability[^5]
The patcher cannot generate invalid syntax, it's fully rulled out of the language. It *technically* can produce runtime errors by modifying strings that
aren't in class modules, but the parameters are very strict - this likely won't happen.
# Hooking[^6]
## Windows
Hooking relies on DLL lookup path hijacking. The **steamwebhelper.exe** *links* **version.dll** (meaning the wloader loads version.dll into the process before `main()/__constructor__()`) for version utilities.
**version.dll** is officially shipped with Windows as a builtin system component, in System32. Historically, **version.dll** was never made a [**KnownDLL**](https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order) for security reasons (debated), meaning
when wloader calls LoadLibrary on **version.dll**, it will actually try to look it up in the cwd instead of System32 directly. This means we can create a *fake* **version.dll** wherever the **steamwebhelper.exe** lives, and if we import and re-export the real **version.dll**s symbol table
from System32, we have internal memory access to steams webhelper. From there we setup the hooks documented below.
## Unix
Same idea as Windows, but using an X11 dependency the hijack target. `libXtst.so.6` is always loaded by the webhelper which is spawned under Steams pressure vessel. The pressure vessel points `LD_LIBRARY_PATH` to a container at `~/.steam/steam/ubuntu12_32/`.
However, their library path specifications aren't tight enough. We can sneak in a shim at `~/.steam/steam/ubuntu12_64/libXtst.so.6`, take precedence over `~/.steam/steam/ubuntu12_32/steam-runtime/amd64/usr/lib/x86_64-linux-gnu/libXtst.so.6` in search path resolution, `HOOK_FUNC` to pipe calls back to the original, and an `__attribute__((constructor))` to setup the hooks documented below.
```mermaid
flowchart LR
S[steam.exe] --> W[Starts steamwebhelper.exe]
W --> K[Requests version.dll\nchecks KnownDLLs registry key]
K --> N[Not listed in KnownDLLs\nfalls back to standard search order]
N --> A[Loader loads our ./version.dll]
A --> R[Re-exports real version.dll exports]
R --> SW[steamwebhelper.exe runs]
A --> CT[constructor runs\ninstall_hook]
CT --> DL[dl_iterate_phdr\nfinds cef_browser_host_create_browser\nin libcef.so]
DL --> IN[snare_inline_new\npatches bytes at cef fn entry\nto redirect to tramp_]
SW --> CEF[CEF calls\ncef_browser_host_create_browser\nclient, url, settings...]
IN --> CEF
CEF --> TR[tramp_ intercepts\ncef_browser_host_create_browser]
TR --> PH[Patches client vtable\nclient->get_request_handler\n-> hooked_get_request_handler]
PH --> OC[Calls original\ncef_browser_host_create_browser\nvia snare trampoline]
OC --> SW
SW --> GRQ[CEF calls\nclient->get_request_handler]
GRQ --> HRQ[hooked_get_request_handler\ncalls original get_request_handler\ngets Steam's cef_request_handler_t]
HRQ --> PRQ[Patches returned handler\nhandler->get_resource_request_handler\n-> hooked_get_resource]
PRQ --> RH[Returns modified\ncef_request_handler_t to CEF]
RH --> GRES[CEF calls\nget_resource_request_handler\nwith cef_request_t]
GRES --> HRS[hooked_get_resource\nreads request->get_url]
HRS --> CHK{chunk~*.js\n+ has local file?}
CHK -->|No| PASS[Calls orig_get_resource\nreturns Steam's handler]
CHK -->|Yes| DROP[Disposes Steam's handler\nif it returned one]
DROP --> CUST[Returns custom\nsteamloopback_request_handler_t\nwith resident read callbacks]
```
# Building[^7]
All build instructions assume the host is linux. It compiles fine on windows, I just don't have docs for it.
```bash
# build resident
$ make
# permanently install resident into steam
$ make install
# Cross-compile for Windows from Linux (requires `mingw-w64-gcc`)
$ make cross
```
On Arch Linux, install the cross-compiler with:
```bash
$ sudo pacman -S mingw-w64-gcc
```
# Third party libraries[^8]
[libsnare.h](https://github.com/shdwmtr/libsnare.h)
My highly stable c/cxx/asm compatible single-header hooking library for x86/x64/arm64. inline hooks and PLT/IAT hooks. linux/windows/macos.
[^1]: About resident
[^2]: Installing resident
[^3]: Learn about residents parser semantics
[^4]: Resident benchmarking results
[^5]: Accuracy and reliability
[^6]: Hooking algorithms
[^7]: Building libresident
[^8]: Thirdparty library notices