Compare commits

...
Author SHA1 Message Date
spoopyghosty0 c93a82dce9 Research: fp_mem cache default 12 regions / 6 GB
Second headset run (MSAA off + multi-block cache): frames shown once 83 -> 95 %, shown twice 16 -> 3.2 %,
~125 ms hitches 17 -> 6; 95 of 101 released blocks were reused. The remaining long hitches came when ~2 GB
were already kept and new blocks did not fit the 3 GB cap.
2026-10-10 19:24:34 -04:00
spoopyghosty0 d367d6666d Research: fp_mem keeps several big blocks and reuses them at other sizes
In the headset First Contact allocates and releases 0.5-1.6 GB blocks, often several at once (~100-128k page
faults per 100 ms, RSS up to 3.5 GB), which the single exact-size cache missed: 17 hitches of ~125 ms in 4 min.
Up to 8 kept regions (3 GB), reuse by start address (shorter: rest released, longer: only the tail mapped fresh),
no protection change while kept. PC test: same size 41.6 -> 1.7 ms, shorter 31.5 -> 1.6 ms, longer 95.5 -> 25.1 ms.
2026-10-10 19:07:30 -04:00
spoopyghosty0 778562261e Research: native arm64 wineserver + ntsync and a big-block cache fix First Contact's hitches under FEX
native/fexwine: prep_ge_wine.sh recreates GE-Proton's exact Wine source, build_wineserver.sh builds a
native aarch64 wineserver from it (same protocol as GE's x86_64 server, x86_64 machine model), fp_mem.c is
a guest preload that keeps Unreal's 514 MB block between uses instead of re-faulting it.
Headless on the dev Frame: hitches 17 -> 0 per 20 s, timed-out frames 40 % -> 5.5 %.
2026-10-10 13:13:20 -04:00
spoopyghosty0 9114553660 Research note: Meta login under Wine and Oculus First Contact running in VR on the Frame (stutters, CPU-bound) 2026-10-09 17:56:18 -04:00
spoopyghosty0 94f84730c8 Research note: Rift Platform SDK handshake on the Frame blocked by OVRLibrarian never exiting under FEX 2026-10-09 16:34:45 -04:00
spoopyghosty0 cc9e27a83a CLAUDE.md: Rift via x86_64 Wine under FEX (research): x86_64 GE-Proton + Revive injection + OpenVR to SteamVR work headless; Platform SDK / Meta app is the blocker 2026-10-08 21:15:58 -04:00
5 changed files with 319 additions and 0 deletions

No files matched your search

+37
View File
@@ -365,6 +365,43 @@ maps a symlink to the already-loaded module) gets past it to **-3021 = ovrError_
an Oculus-signed runtime. Next step (not done): make the runtime check pass without Detours, e.g. an injected helper
that patches the game's import table (IAT) for LoadLibrary*/WinVerifyTrust, or a prefix-level override. This is
runtime interop (what Revive does), not the Platform SDK licence check (which FramePort never touches).
**Rift via x86_64 Wine under FEX (research, 2026-10-08, headless on the dev Frame, SteamOS 0.4.5; no product code):**
why Revive failed is confirmed upstream: x64 Detours returns ERROR_NOT_SUPPORTED on ARM64EC targets (microsoft/Detours
PR #388; only an ARM64EC build of Detours can hook them). A fully x86_64 Wine avoids it: GE-Proton11-7 **x86_64** runs
under the FEX tool (`fex-compat-tool run -- <x86 binary>`; RootFS /usr/share/guestos/fex-mesa, no FreeType there)
— prefix boot 35 s, kernelbase.dll is x86-64. Proton's script needs help without the x86_64 SLR (app 4183110, not
installed): its Python runs natively (the RootFS has none), so `files/bin/wine{,server}` and the two direct
`wine-preloader` argv calls must go through a FEX wrapper that also unsets `FEX_APP_CONFIG(_LOCATION)` (Proton sets
them on aarch64 for its own ARM64EC FEX → "RootFS path set to ''"); GE's game drive needs STEAM_COMPAT_INSTALL_PATH
unset. SteamVR now ships `bin/linux64/vrclient.so` + `bin/vrclient.so` (i386) + `vrclient_x64.dll` (Oct 6 build; not
there on 09-30): Proton's x86_64 vrclient bridge reaches the arm64 vrserver. **Rick and Morty ran as the scene app**
("OpenVR initialized!", controller tracking, 9176 presents / 6 dropped, CPU 11.6 / GPU 17.9 ms, target 72).
**ReviveInjector: "Succesfully injected!"**, LibRevive64.dll loaded next to OVRPlugin.dll in Lies Beneath, which then
died on 0xc06d007e = delay-loaded `libovrplatform64_1.dll` (Platform SDK) before connecting to SteamVR — so Revive's
signature hook and Revive→SteamVR are still unproven. Every Rift game in the library except Rick and Morty uses the
Platform SDK → the Meta (Link) app in the prefix, logged in with the user's own account, is the real blocker; the
only public attempt (github.com/michauMiau/oculus-wine-linux, 2026-10) never got OculusSetup.exe (32-bit .NET)
past its HTTPS config fetch. Scratch files on the Frame: `~/frameport-rifttest/` (scripts t1-t4.sh).
Meta runtime in the same Wine (2026-10-09): Platform SDK games reach OVRServer's IPC on the Frame. The apparent
intermittent handshake hang was **OVRLibrarian.exe** (.NET, Wine Mono): under FEX it finishes its work but never exits,
its AppTracker entry stays, and the runtime then tracks no later client (Meta app, game), so the game waits forever
for the reply event the runtime only sets after identifying it (found with wineserver `+server` traces PC vs Frame).
Ending OVRLibrarian before the game → game tracked, "Missing entitlement" exactly as on the PC (needs a login).
Logged in (2026-10-09, owner's own account, browser sign-in; the `oculus://` answer had to be forwarded into the Wine
app's `\\.\pipe\oculus`), the runtime also needed a WinRT `Windows.Devices.WiFi.WiFiAdapter` stand-in (Air Link code
fail-fasts without it), Wine's DeviceWatcher add/remove_Updated/Removed implemented, and the C: volume name
(`\\?\Volume{...0043}`) linked in dosdevices. Copying the logged-in `sessions/` + CoreData to the Frame prefix kept the
login. **Oculus First Contact then ran in VR on the Frame** (Revive in OpenVR mode; `/openxr` failed in wineopenxr):
scene app at 72 Hz target, but 92 % of frames "timed out" (game CPU-bound under FEX) → stutter/flicker/lag.
Performance (2026-10-10, headless with patched Revive visibility + SteamVR `pauseCompositorOnStandby=false`, numbers from
`IVRCompositor::GetFrameTimings`): the frame loop waited on sync round trips through the FEX-run x86_64 wineserver.
`native/fexwine/`: a **native aarch64 wineserver** built from GE's exact source (protocol 938) + **ntsync** (works once
OVRLibrarian is ended) took hitches 17 → 0-4 per 20 s. The rest were Unreal's large-block allocator committing,
filling (one `rep stos`) and freeing 514 MB every 1-8 s: ~130k 4 KB page faults each under FEX. `fp_mem.so`
(`FP_BIGCACHE_MB=256`, guest preload via `FEX_ENV=LD_PRELOAD=…`: fex-compat-tool deletes LD_PRELOAD) keeps that block
→ 0 hitches, steady 36 fps + motion smoothing, ~5.5 % timed out. 72 fps would need ~30 % less GPU (game ~55 % busy
at 36 fps); THP (fragmentation), MSAA off, Turnip gmem don't help. Also needed: Unreal's
`Slate.DeferWindowsMessageProcessing` = 0 (exe patch; the default deadlocks Wine's IME window ~1/3 of starts).
Uninstall (Quest, saves kept) used to leave `deployment.json` → still "installed"; fixed (agent v16).
**Discovery/network:** Developer-Mode SteamOS devices announce `_steamos-devkit._tcp` (TXT `login=steamos`) — use it;
+32
View File
@@ -0,0 +1,32 @@
# x86_64 Wine under FEX: performance pieces (research)
Research for Oculus Rift games on the Steam Frame through x86_64 GE-Proton under FEX (see CLAUDE.md, "Rift via
x86_64 Wine under FEX"). Nothing here is used by FramePort yet, and no binaries are committed.
| File | What |
|---|---|
| `prep_ge_wine.sh` | Recreates the exact Wine source of a GE-Proton release (its Wine and wine-staging submodules plus GE's patches) by running only the WINE section of GE's `protonprep-valve-staging.sh`. |
| `build_wineserver.sh` | Builds a **native aarch64 wineserver** from that tree on an aarch64 host (the Frame has gcc, make, flex and bison). It speaks the same protocol as GE's x86_64 server (GE-Proton11-7: protocol 938, 321 requests; plain Valve Wine at the same commit lacks 15 of GE's requests). `-DFP_EMULATED_X86_64` gives it the x86_64 machine model, because its clients are x86_64 processes under FEX. Use it by having the `wineserver` wrapper exec it instead of the FEX-run x86_64 server. |
| `fp_mem.c` | x86_64 `LD_PRELOAD` for the guest. `FP_BIGCACHE_MB=<n>` keeps released anonymous regions of at least n MB (up to 12, 6 GB in total) and returns them when the same start address is mapped again; a shorter request releases the rest, a longer one maps only the extra part fresh. In the headset First Contact allocated 0.5-1.6 GB blocks several times at once, which the first, single-block version missed. `FP_THP=1` sets `MADV_HUGEPAGE` on large writable regions. Build it with `gcc -O2 -shared -fPIC -o fp_mem.so fp_mem.c -ldl -lpthread` on an x86_64 host (needs glibc 2.34). **Under FEX, pass it as `FEX_ENV=LD_PRELOAD=<path>`**: Valve's `fex-compat-tool` deletes `LD_PRELOAD`. |
## Measurements: Oculus First Contact, headless, dev Frame, 2026-10-10
SteamVR compositor frame timings were read through `IVRCompositor::GetFrameTimings`.
| Setup | Hitches per 20 s | Frames timed out | Game CPU per frame |
|---|---|---|---|
| FEX-run x86_64 wineserver, server sync | 17 | 40 % | 10.4 ms |
| Native wineserver, server sync | 9 | 32 % | 10.4 ms |
| Native wineserver + ntsync | 0-4 | 9-10 % | 6.2 ms |
| Native wineserver + ntsync + `FP_BIGCACHE_MB=256` | **0** (3 of 3 runs) | 5.4-5.9 % | 5.3 ms |
Each remaining hitch (one app frame shown about 9 times) was Unreal's large-block allocator:
1. It calls VirtualAlloc for 514 MB (First Contact's exe RVA 0x1a188e) every 1-8 s.
2. A game task fills the block with a single `rep stos`/`rep movs` while the game thread waits for it.
3. Another thread then frees the block.
Under FEX every 4 KiB page of it faults in and gets zeroed and charged by the kernel: about 130k faults and 110-140 ms per hitch.
Huge pages work in isolation (514 MB in 22 ms instead of 212 ms), but fail in practice. On a Frame that has been up for a while, memory is fragmented, so allocations fall back to 4 KiB pages (`thp_fault_fallback` far above `thp_fault_alloc`). The kernel's `defrag=madvise` setting also lets those attempts stall in compaction.
The block cache returns the region with stale contents instead of zeroed pages. That is only safe for games that overwrite the whole block, so it has to stay a per-game setting.
+41
View File
@@ -0,0 +1,41 @@
#!/bin/bash
# Build a native aarch64 wineserver from a patched GE Wine tree (prep_ge_wine.sh), for x86_64 Wine under FEX.
# Run it ON an aarch64 Linux host (e.g. the Frame: gcc, make, flex, bison, autoconf are in SteamOS).
# - Wine's configure insists on a PE cross compiler for aarch64 and on generated headers this tree lacks; the server
# needs neither, so configure only produces include/config.h here and the server sources are compiled directly.
# - -DFP_EMULATED_X86_64: the server takes the x86_64 branch of init_supported_machines (native machine AMD64), as
# the clients are x86_64 processes under FEX; otherwise it would advertise ARM64 and refuse/misplace x86_64 images.
# The result talks the same protocol as GE's x86_64 wineserver (check: SERVER_PROTOCOL_VERSION and the request list).
# usage: build_wineserver.sh <patched wine dir> <build dir> → <build dir>/ws/wineserver
set -e
SRC=$(cd "$1" && pwd); B=$2
[ -n "$B" ] || { echo "usage: $0 <wine dir> <build dir>"; exit 2; }
[ "$(uname -m)" = aarch64 ] || { echo "run this on aarch64"; exit 2; }
mkdir -p "$B/obj" "$B/ws"; B=$(cd "$B" && pwd)
grep -q FP_EMULATED_X86_64 "$SRC/server/registry.c" || \
sed -i 's/^#elif defined(__x86_64__)$/#elif defined(__x86_64__) || (defined(__aarch64__) \&\& defined(FP_EMULATED_X86_64))/' "$SRC/server/registry.c"
grep -q FP_EMULATED_X86_64 "$SRC/server/registry.c" || { echo "registry.c layout changed"; exit 1; }
if [ ! -f "$B/obj/include/config.h" ]; then
cp "$SRC/configure" "$B/configure.fp"
sed -i 's/as_fn_error $? "PE cross-compilation is required/echo "(ignored) PE cross-compilation is required/' "$B/configure.fp"
# configure.fp must run from the source dir's point of view: copy it in, run, remove
cp "$B/configure.fp" "$SRC/configure.fp"
(cd "$B/obj" && "$SRC/configure.fp" --srcdir="$SRC" --without-mingw --without-x --without-wayland --without-freetype \
--without-gstreamer --without-vulkan --without-opengl --without-pulse --without-alsa --without-sdl --without-udev \
--without-usb --without-gnutls --without-cups --without-dbus --without-krb5 --without-netapi --without-pcap \
--without-sane --without-v4l2 --without-gphoto --without-unwind --without-ffmpeg --disable-tests > configure.log 2>&1 || true)
rm -f "$SRC/configure.fp"
[ -f "$B/obj/include/config.h" ] || { echo "configure did not produce include/config.h (see $B/obj/configure.log)"; exit 1; }
fi
cd "$B/ws"
CF="-c -O2 -g -pipe -fno-strict-aliasing -D__WINESRC__ -D_GNU_SOURCE -DFP_EMULATED_X86_64 -I. -I$B/obj/include -I$SRC/include -I$SRC/server"
for s in $(grep -oE "^\s+[a-z_0-9]+\.c" "$SRC/server/Makefile.in" | tr -d "\t "); do
extra=()
case $s in
unicode.c) extra=('-DBINDIR="/usr/bin"' '-DDATADIR="/usr/share"') ;; # nls dir = <bin>/../share/wine/nls
user.c) extra=(-include stdarg.h) ;;
esac
gcc $CF "${extra[@]}" -o "${s%.c}.o" "$SRC/server/$s"
done
gcc -o wineserver ./*.o -lrt -lm
echo "built $B/ws/wineserver (protocol $(grep -oE "SERVER_PROTOCOL_VERSION [0-9]+" "$SRC/include/wine/server_protocol.h" | awk '{print $2}'))"
+183
View File
@@ -0,0 +1,183 @@
/* fp_mem.so: x86_64 LD_PRELOAD for Wine under FEX (give it to the guest with FEX_ENV=LD_PRELOAD=..., the FEX
* compat tool strips LD_PRELOAD).
*
* FP_BIGCACHE_MB=<n>: keep the pages of released anonymous regions of >= n MB (up to FP_BIGCACHE_SLOTS regions,
* default 12, and FP_BIGCACHE_MAX_MB in total, default 6144) and hand them back when Wine maps memory at the
* same start address again. Unreal's large-block allocator gives blocks above its cache limit straight back to
* the OS, and some games (Oculus First Contact: 0.5-1.4 GB every few seconds while playing) fill such blocks
* completely each time: under FEX every 4 KB page then faults and is zeroed by the kernel (~130k faults, ~110 ms)
* and releasing them costs about as much again, on a game task the game thread waits for.
* Same start, other size: shorter = the rest is released; longer = only the extra part is mapped fresh.
* Reused memory is NOT zeroed (Windows guarantees zeroed commits), so this is only for games that overwrite
* the whole block; any other use of a kept range drops it first (fresh zero pages).
* Kept regions keep their protection (they are not touched while kept), so keeping and reusing is nearly free.
* FP_THP=1: MADV_HUGEPAGE on large writable anonymous regions (only helps while 2 MB pages are free).
* FP_MEM_DEBUG=1: log to stderr. */
#define _GNU_SOURCE
#include <dlfcn.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
#define MAX_SLOTS 16
static size_t cache_min, cache_max = (size_t)6144 << 20, thp_min = (size_t)32 << 20;
static int debug, thp, slots = 12;
static int (*real_mprotect)(void *, size_t, int);
static int (*real_munmap)(void *, size_t);
static void *(*real_mmap)(void *, size_t, int, int, int, off_t);
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
struct entry { char *addr; size_t len; int prot, via_munmap, used; };
static struct entry cache[MAX_SLOTS];
static size_t kept_total;
static unsigned long hits, partial, stores, drops;
static void resolve(void)
{
if (!real_mmap) real_mmap = dlsym(RTLD_NEXT, "mmap");
if (!real_munmap) real_munmap = dlsym(RTLD_NEXT, "munmap");
if (!real_mprotect) real_mprotect = dlsym(RTLD_NEXT, "mprotect");
}
__attribute__((constructor)) static void fp_mem_init(void)
{
const char *e;
resolve();
debug = getenv("FP_MEM_DEBUG") != NULL;
thp = getenv("FP_THP") != NULL;
if ((e = getenv("FP_BIGCACHE_MB")) && atol(e) > 0) cache_min = (size_t)atol(e) << 20;
if ((e = getenv("FP_BIGCACHE_MAX_MB")) && atol(e) > 0) cache_max = (size_t)atol(e) << 20;
if ((e = getenv("FP_BIGCACHE_SLOTS")) && atoi(e) > 0) slots = atoi(e) > MAX_SLOTS ? MAX_SLOTS : atoi(e);
}
static void advise(void *addr, size_t len)
{
uintptr_t s = ((uintptr_t)addr + 0x1fffff) & ~(uintptr_t)0x1fffff;
uintptr_t e = ((uintptr_t)addr + len) & ~(uintptr_t)0x1fffff;
if (e > s) madvise((void *)s, e - s, MADV_HUGEPAGE);
}
/* lock held: give part of a kept range up the way the program asked for it originally */
static void release_range(const struct entry *c, char *addr, size_t len)
{
if (c->via_munmap) real_munmap(addr, len);
else real_mmap(addr, len, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED | MAP_NORESERVE, -1, 0);
}
static void drop(struct entry *c, const char *why)
{
if (debug) fprintf(stderr, "fp_mem: drop %p+%zx (%s)\n", c->addr, c->len, why);
release_range(c, c->addr, c->len);
kept_total -= c->len; c->used = 0; drops++;
}
/* lock held: drop every kept range overlapping [addr, len) */
static void drop_overlaps(const void *addr, size_t len, const char *why)
{
for (int i = 0; i < slots; i++)
if (cache[i].used && (const char *)addr < cache[i].addr + cache[i].len && cache[i].addr < (const char *)addr + len)
drop(&cache[i], why);
}
static int any_kept(void)
{
for (int i = 0; i < slots; i++) if (cache[i].used) return 1;
return 0;
}
/* lock held: keep [addr, len) instead of releasing it (its pages and protection stay as they are) */
static int store(void *addr, size_t len, int via_munmap, int prot)
{
int free_slot = -1;
if (kept_total + len > cache_max) return 0;
for (int i = 0; i < slots; i++) {
if (!cache[i].used) { if (free_slot < 0) free_slot = i; continue; }
if ((char *)addr < cache[i].addr + cache[i].len && cache[i].addr < (char *)addr + len) return 0;
}
if (free_slot < 0) return 0;
cache[free_slot] = (struct entry){ addr, len, prot, via_munmap, 1 };
kept_total += len; stores++;
if (debug) fprintf(stderr, "fp_mem: keep %p+%zx (%s, kept %zu MB in total; stores %lu, hits %lu, partial %lu, drops %lu)\n",
addr, len, via_munmap ? "munmap" : "release", kept_total >> 20, stores, hits, partial, drops);
return 1;
}
/* protection of a region before it is released: unknown here, so assume read/write (Wine's committed views) */
#define KEPT_PROT (PROT_READ | PROT_WRITE)
void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t off)
{
resolve();
if (cache_min && len >= cache_min / 4 && any_kept()) {
pthread_mutex_lock(&lock);
for (int i = 0; i < slots; i++) {
struct entry *c = &cache[i];
if (!c->used || (char *)addr != c->addr || fd != -1 || !(flags & MAP_ANONYMOUS) || !(prot & PROT_WRITE)) continue;
if (len <= c->len) {
if (prot != c->prot && real_mprotect(addr, len, prot)) break;
if (len < c->len) { release_range(c, c->addr + len, c->len - len); partial++; }
kept_total -= c->len; c->used = 0; hits++;
if (debug) fprintf(stderr, "fp_mem: reuse %p+%zx of %zx\n", addr, len, c->len);
pthread_mutex_unlock(&lock);
return addr;
}
/* longer: map the extra part fresh behind the kept one */
void *t = real_mmap(c->addr + c->len, len - c->len, prot, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0);
if (t == (void *)(c->addr + c->len)) {
if (prot != c->prot) real_mprotect(c->addr, c->len, prot);
kept_total -= c->len; c->used = 0; hits++; partial++;
if (debug) fprintf(stderr, "fp_mem: reuse %p+%zx of %zx (+ fresh tail)\n", addr, len, c->len);
pthread_mutex_unlock(&lock);
if (thp && len - c->len >= thp_min) advise(t, len - c->len);
return addr;
}
if (t != MAP_FAILED) real_munmap(t, len - c->len);
break;
}
pthread_mutex_unlock(&lock);
}
if (cache_min && len >= cache_min && (flags & MAP_ANONYMOUS) && (flags & MAP_FIXED) && prot == PROT_NONE && fd == -1) {
/* Wine releasing (part of) a view inside its reserved areas */
pthread_mutex_lock(&lock);
int ok = store(addr, len, 0, KEPT_PROT);
pthread_mutex_unlock(&lock);
if (ok) return addr;
}
if (any_kept()) {
pthread_mutex_lock(&lock);
drop_overlaps(addr, len, "mmap overlap");
pthread_mutex_unlock(&lock);
}
void *r = real_mmap(addr, len, prot, flags, fd, off);
if (thp && r != MAP_FAILED && (flags & MAP_ANONYMOUS) && (prot & PROT_WRITE) && len >= thp_min) advise(r, len);
return r;
}
void *mmap64(void *addr, size_t len, int prot, int flags, int fd, off_t off) __attribute__((alias("mmap")));
int munmap(void *addr, size_t len)
{
resolve();
if (cache_min && (len >= cache_min || any_kept())) {
pthread_mutex_lock(&lock);
drop_overlaps(addr, len, "munmap overlap");
if (len >= cache_min && store(addr, len, 1, KEPT_PROT)) { pthread_mutex_unlock(&lock); return 0; }
pthread_mutex_unlock(&lock);
}
return real_munmap(addr, len);
}
int mprotect(void *addr, size_t len, int prot)
{
resolve();
if (any_kept()) {
pthread_mutex_lock(&lock);
drop_overlaps(addr, len, "mprotect overlap");
pthread_mutex_unlock(&lock);
}
int r = real_mprotect(addr, len, prot);
if (thp && !r && (prot & PROT_WRITE) && len >= thp_min) advise(addr, len);
return r;
}
+26
View File
@@ -0,0 +1,26 @@
#!/bin/bash
# Reproduce the exact Wine source of a GE-Proton release (Wine submodule + wine-staging + GE's own patches): the WINE
# section of GE's patches/protonprep-valve-staging.sh, without the DXVK/vkd3d/protonfixes steps.
# usage: prep_ge_wine.sh <work dir> [tag=GE-Proton11-7] → <work dir>/src/wine
# Needs git, patch, python3, autoconf/autoreconf (the WINE section ends with autoreconf + tools/make_requests).
set -e
W=$1; TAG=${2:-GE-Proton11-7}
[ -n "$W" ] || { echo "usage: $0 <work dir> [tag]"; exit 2; }
mkdir -p "$W" && cd "$W"
[ -d src ] || git clone -q --depth 1 --branch "$TAG" https://github.com/GloriousEggroll/proton-ge-custom src
cd src
git submodule update --init --depth 1 wine wine-staging
P=patches/protonprep-valve-staging.sh
start=$(grep -n "^### (2) WINE PATCHING ###" $P | cut -d: -f1)
end=$(grep -n "^### END WINE PATCHING ###" $P | cut -d: -f1)
fn_end=$(grep -n "^### (1) PREP SECTION ###" $P | cut -d: -f1)
[ -n "$start" ] && [ -n "$end" ] && [ -n "$fn_end" ] || { echo "unexpected layout of $P"; exit 1; }
# the WINE section reverts commits from the Wine history: fetch them into the shallow clone first
for c in $(sed -n "${start},${end}p" $P | grep -oE "git revert [^#]*" | grep -oE "[0-9a-f]{40}"); do
git -C wine fetch -q --depth 2 origin "$c"
done
{ sed -n "1,$((fn_end - 1))p" $P; sed -n "${start},${end}p" $P; } > ../wine_only.sh
bash ../wine_only.sh > ../prep.log 2>&1
grep -n "SERVER_PROTOCOL_VERSION" wine/include/wine/server_protocol.h
if find wine -name "*.rej" | grep -q .; then echo "rejected hunks, see ../prep.log"; exit 1; fi
echo "patched Wine tree: $W/src/wine"