mirror of
https://git.sanhost.net/sanasol/hytale-f2p
synced 2026-02-26 12:51:47 -03:00
- jemalloc helps ~30% of the time but not reliable - Documented all failed approaches (allocators, scheduling, patching variations) - Added potential alternative approaches (network hooking, proxy, container) - Status: UNSOLVED Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
232 lines
6.4 KiB
Markdown
232 lines
6.4 KiB
Markdown
# Steam Deck / Ubuntu LTS Crash Investigation
|
|
|
|
## Status: UNSOLVED
|
|
|
|
**Last updated:** 2026-01-27
|
|
|
|
No stable solution found. jemalloc helps occasionally but crashes still occur randomly.
|
|
|
|
---
|
|
|
|
## Problem Summary
|
|
|
|
The Hytale F2P launcher's client patcher causes crashes on Steam Deck and Ubuntu LTS with the error:
|
|
```
|
|
free(): invalid pointer
|
|
```
|
|
or
|
|
```
|
|
SIGSEGV (Segmentation fault)
|
|
```
|
|
|
|
The crash occurs after successful authentication, specifically right after "Finished handling RequiredAssets".
|
|
|
|
**Affected Systems:**
|
|
- Steam Deck (glibc 2.41)
|
|
- Ubuntu LTS
|
|
|
|
**Working Systems:**
|
|
- macOS
|
|
- Windows
|
|
- Older Arch Linux (glibc < 2.41)
|
|
|
|
**Critical Finding:** The UNPATCHED original binary works fine on Steam Deck. The crash is caused by ANY binary patching.
|
|
|
|
---
|
|
|
|
## What Was Tried (All Failed)
|
|
|
|
### Memory Allocators
|
|
| Approach | Result |
|
|
|----------|--------|
|
|
| `LD_PRELOAD=/usr/lib/libjemalloc.so.2` | Works randomly (3/10 times), not stable |
|
|
| `MALLOC_CHECK_=0` | No effect |
|
|
| `MALLOC_PERTURB_=255` | No effect |
|
|
| `GLIBC_TUNABLES=glibc.malloc.tcache_count=0` | No effect |
|
|
|
|
### Process/Scheduling
|
|
| Approach | Result |
|
|
|----------|--------|
|
|
| `taskset -c 0` (single core) | Game too slow, stuck at connecting |
|
|
| `taskset -c 0,1` or `0-3` | Still crashes |
|
|
| `nice -n 19` | No effect |
|
|
| `chrt --idle 0` | No effect |
|
|
| `strace -f` | No effect |
|
|
|
|
### Linker/Loading
|
|
| Approach | Result |
|
|
|----------|--------|
|
|
| `LD_BIND_NOW=1` | No effect |
|
|
| Wrapper script with LD_PRELOAD | No effect |
|
|
| Shell spawn with inline LD_PRELOAD | No effect |
|
|
|
|
### Patching Variations
|
|
| Approach | Result |
|
|
|----------|--------|
|
|
| Null-padding after replacement | Crashes (made it worse) |
|
|
| No null-padding (develop behavior) | Still crashes |
|
|
| Minimal patches (3 instead of 6) | Still crashes |
|
|
| Ultra-minimal (1 patch - domain only) | Still crashes |
|
|
| Skip sentry patch | Still crashes |
|
|
| Skip subdomain patches | Still crashes |
|
|
|
|
**Key Finding:** Even patching just 1 string (main domain only) causes the crash.
|
|
|
|
---
|
|
|
|
## String Occurrences Found
|
|
|
|
### Length-Prefixed Format
|
|
Found by default patcher mode:
|
|
|
|
| Offset | Content | Notes |
|
|
|--------|---------|-------|
|
|
| 0x1bc5d63 | `hytale.com` | **Surrounded by x86 code!** |
|
|
|
|
### UTF-16LE Format (3 occurrences)
|
|
| Offset | Content |
|
|
|--------|---------|
|
|
| 0x1bc5ad7 | `sentry.hytale.com/...` |
|
|
| 0x1bc5b3f | `https://hytale.com/help...` |
|
|
| 0x1bc5bc9 | `store.hytale.com/?...` |
|
|
|
|
---
|
|
|
|
## Binary Analysis
|
|
|
|
When patching with length-prefixed mode:
|
|
|
|
```
|
|
< 01bc5d60: 5933 b80a 0000 0068 0079 0074 0061 006c Y3.....h.y.t.a.l
|
|
< 01bc5d70: 0065 002e 0063 006f 006d 8933 8807 0000 .e...c.o.m.3....
|
|
---
|
|
> 01bc5d60: 5933 b80a 0000 0073 0061 006e 0061 0073 Y3.....s.a.n.a.s
|
|
> 01bc5d70: 006f 006c 002e 0077 0073 8933 8807 0000 .o.l...w.s.3....
|
|
```
|
|
|
|
**Structure:**
|
|
```
|
|
5933 b8 | 0a000000 | h.y.t.a.l.e...c.o.m | 8933 8807 0000
|
|
???????? | len=10 | string content | mov [rbx],esi?
|
|
```
|
|
|
|
- `5933 b8` before string - could be code or metadata
|
|
- `0a 00 00 00` - .NET length prefix (10 characters)
|
|
- String content in UTF-16LE
|
|
- `89 33` after - this is `mov [rbx], esi` in x86-64!
|
|
|
|
**The string is embedded near executable code, not in a clean data section.**
|
|
|
|
---
|
|
|
|
## GDB Stack Trace
|
|
|
|
```
|
|
#0 0x00007ffff7d3f5a4 in ?? () from /usr/lib/libc.so.6
|
|
#1 raise () from /usr/lib/libc.so.6
|
|
#2 abort () from /usr/lib/libc.so.6
|
|
#3-#4 ?? () from /usr/lib/libc.so.6
|
|
#5 free () from /usr/lib/libc.so.6
|
|
#6 ?? () from libzstd.so <-- CRASH POINT
|
|
#7-#24 HytaleClient code (asset decompression)
|
|
```
|
|
|
|
Crash occurs in `libzstd.so` during `free()` after "Finished handling RequiredAssets".
|
|
|
|
---
|
|
|
|
## Hypotheses
|
|
|
|
### 1. .NET AOT String Metadata (Most Likely)
|
|
.NET AOT may have precomputed hashes, checksums, or relocation info for strings. Modifying string content breaks internal consistency, causing memory corruption when the runtime tries to use related data structures.
|
|
|
|
### 2. Code/Data Interleaving
|
|
The strings are embedded near x86 code (`89 33` = `mov [rbx], esi`). .NET AOT may use relative offsets that get invalidated when we modify nearby bytes.
|
|
|
|
### 3. Binary Checksums
|
|
The binary may have integrity checks for certain sections that we're invalidating by patching.
|
|
|
|
### 4. Timing-Dependent Race Condition
|
|
The fact that it works randomly (~30% of the time with jemalloc) suggests a race condition that's affected by:
|
|
- Memory layout changes from patching
|
|
- Allocator behavior differences
|
|
- CPU scheduling
|
|
|
|
---
|
|
|
|
## Valgrind Results (Misleading)
|
|
|
|
- Valgrind showed NO memory corruption errors
|
|
- Game ran successfully under Valgrind (slower execution)
|
|
- This suggested jemalloc would fix it, but it doesn't consistently work
|
|
|
|
The slowdown from Valgrind likely masks the race condition timing.
|
|
|
|
---
|
|
|
|
## Current Launcher Implementation
|
|
|
|
The launcher attempts:
|
|
1. Auto-detect jemalloc at common paths
|
|
2. Auto-install jemalloc via pkexec if not found
|
|
3. Launch game with `LD_PRELOAD` via shell command
|
|
|
|
But this doesn't provide stable results.
|
|
|
|
---
|
|
|
|
## Potential Alternative Approaches (Not Yet Tried)
|
|
|
|
### 1. LD_PRELOAD Network Hooking
|
|
Instead of patching the binary, hook `getaddrinfo()` / `connect()` to redirect network calls at runtime. No binary modification needed.
|
|
|
|
### 2. Local Proxy + Certificate
|
|
Run a local HTTPS proxy that intercepts hytale.com traffic and redirects to custom server. Requires installing a custom CA certificate.
|
|
|
|
### 3. DNS + iptables Redirect
|
|
Use local DNS to resolve hytale.com to localhost, then iptables to redirect to actual custom server. Requires root/sudo.
|
|
|
|
### 4. Container with Older glibc
|
|
Run the game in a container with glibc < 2.41 where the stricter validation doesn't exist.
|
|
|
|
### 5. Different Patching Location
|
|
Find strings in a pure data section rather than code-adjacent areas.
|
|
|
|
---
|
|
|
|
## Files Reference
|
|
|
|
**Binary:** `HytaleClient` (ELF 64-bit, ~39.9 MB)
|
|
|
|
**Branch:** `fix/steamdeck-jemalloc-crash`
|
|
|
|
---
|
|
|
|
## Install jemalloc (Partial Mitigation)
|
|
|
|
jemalloc may help in some cases (~30% success rate):
|
|
|
|
```bash
|
|
# Steam Deck / Arch Linux
|
|
sudo pacman -S jemalloc
|
|
|
|
# Ubuntu / Debian
|
|
sudo apt install libjemalloc2
|
|
|
|
# Fedora / RHEL
|
|
sudo dnf install jemalloc
|
|
```
|
|
|
|
The launcher automatically uses jemalloc if found. To disable:
|
|
```bash
|
|
HYTALE_NO_JEMALLOC=1 npm start
|
|
```
|
|
|
|
---
|
|
|
|
## Conclusion
|
|
|
|
**No stable solution found.** The binary patching approach may be fundamentally incompatible with glibc 2.41's stricter heap validation when modifying .NET AOT compiled binaries.
|
|
|
|
Alternative approaches (network hooking, proxy, container) may be required for reliable Steam Deck / Ubuntu LTS support.
|