mirror of
https://github.com/simplysanatori/LegendFrame.git
synced 2026-10-29 04:02:42 +01:00
toolchain: mingw container build, SEH shim, ABI rewrite, run scripts
This commit is contained in:
1 parent
02a7210b4c
commit
4b85f24bb4
7 files changed
+358
No files matched your search
@@ -0,0 +1,6 @@
|
||||
FROM registry.fedoraproject.org/fedora:43
|
||||
RUN dnf -y install --setopt=install_weak_deps=False \
|
||||
mingw32-gcc mingw32-gcc-c++ mingw32-winpthreads-static mingw32-headers mingw32-crt \
|
||||
mingw32-binutils meson ninja-build glslang git python3 make cmake file diffutils patch which findutils \
|
||||
&& dnf clean all
|
||||
WORKDIR /work
|
||||
Executable
+18
@@ -0,0 +1,18 @@
|
||||
#!/bin/bash
|
||||
# Reproducible mingw build of the TRL VR mod (DXVK fork + d3d9 proxy + launcher).
|
||||
# usage: build.sh <source-tree> <output-dir> [dxvk|proxy|all]
|
||||
# source-tree: TRLVR layout (src/, dxvk-trlvr/, third_party/), mounted read-only.
|
||||
# output-dir : receives dist/{d3d9.dll,d3d9_dxvk.dll,trlvr_launcher.exe}, work/, logs/
|
||||
# Env: FIXES=<dir with dxvk-*.patch / proxy-*.patch applied to the in-container copy>
|
||||
# JOBS, EXTRA_CXXFLAGS, EXTRA_LIBS. Needs podman and image legendframe-mingw
|
||||
# (podman build -t legendframe-mingw <this dir>).
|
||||
set -euo pipefail
|
||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||
SRC=$(readlink -f "${1:?source tree}"); OUT=$(readlink -m "${2:?output dir}"); WHAT=${3:-all}
|
||||
mkdir -p "$OUT"
|
||||
FIXMNT=()
|
||||
if [ -n "${FIXES:-}" ]; then FIXMNT=(-v "$(readlink -f "$FIXES"):/fixes:ro,Z"); fi
|
||||
podman image exists localhost/legendframe-mingw || podman build -t legendframe-mingw "$HERE"
|
||||
exec podman run --rm -e JOBS="${JOBS:-}" -e WHAT="$WHAT" -e EXTRA_CXXFLAGS="${EXTRA_CXXFLAGS:-}" -e EXTRA_LIBS="${EXTRA_LIBS:-}" \
|
||||
-v "$SRC:/src:ro,Z" -v "$OUT:/out:Z" -v "$HERE/shims:/shims:ro,Z" -v "$HERE/container-build.sh:/container-build.sh:ro,Z" \
|
||||
"${FIXMNT[@]}" localhost/legendframe-mingw bash /container-build.sh
|
||||
Executable
+93
@@ -0,0 +1,93 @@
|
||||
#!/bin/bash
|
||||
# Runs INSIDE the legendframe-mingw container. Invoked by build.sh.
|
||||
# /src read-only source tree (TRLVR layout: src/, dxvk-trlvr/, third_party/)
|
||||
# /out writable output dir
|
||||
# /shims read-only toolchain/shims
|
||||
# /fixes optional read-only dir of *.patch files applied to the COPY (patch -p1)
|
||||
# Env: JOBS, WHAT=all|dxvk|proxy
|
||||
set -euo pipefail
|
||||
JOBS=${JOBS:-$(nproc)}
|
||||
WHAT=${WHAT:-all}
|
||||
W=/out/work
|
||||
mkdir -p "$W" /out/dist /out/logs
|
||||
|
||||
ts() { date +%s; }
|
||||
|
||||
if [ "$WHAT" = all ] || [ "$WHAT" = dxvk ]; then
|
||||
t0=$(ts)
|
||||
echo "=== DXVK: copy tree"
|
||||
rm -rf "$W/dxvk-trlvr" "$W/b-dxvk"
|
||||
cp -r /src/dxvk-trlvr "$W/dxvk-trlvr"; rm -rf "$W/dxvk-trlvr/build-win32"
|
||||
for p in /fixes/dxvk-*.patch; do [ -f "$p" ] && (cd "$W/dxvk-trlvr" && patch -p1 < "$p"); done || true
|
||||
cd "$W/dxvk-trlvr"
|
||||
# Same options as build_dxvk.bat (d3d9 only), mingw cross file, asserts off.
|
||||
meson setup --backend ninja --cross-file build-win32.txt --buildtype release \
|
||||
-Denable_d3d8=false -Denable_d3d10=false -Denable_d3d11=false -Denable_dxgi=false \
|
||||
-Db_ndebug=if-release "$W/b-dxvk" .
|
||||
ninja -C "$W/b-dxvk" -j"$JOBS"
|
||||
i686-w64-mingw32-strip -o /out/dist/d3d9_dxvk.dll "$W/b-dxvk/src/d3d9/d3d9.dll"
|
||||
echo "DXVK_SECONDS=$(( $(ts) - t0 ))"
|
||||
fi
|
||||
|
||||
if [ "$WHAT" = all ] || [ "$WHAT" = proxy ]; then
|
||||
t0=$(ts)
|
||||
echo "=== proxy + launcher: copy tree"
|
||||
rm -rf "$W/src" "$W/obj" "$W/third_party"; mkdir -p "$W/obj" "$W/third_party"
|
||||
cp -r /src/src "$W/src"
|
||||
for d in openvr openxr imgui; do [ -d /src/third_party/$d ] && cp -r /src/third_party/$d "$W/third_party/"; done
|
||||
for p in /fixes/proxy-*.patch; do [ -f "$p" ] && (cd "$W" && patch -p1 < "$p"); done || true
|
||||
cd "$W"
|
||||
# Struct-returning OpenVR virtuals: MSVC and GCC disagree on thiscall+sret, so route the
|
||||
# calls through trlvr_abi::call_sret (see shims/seh_compat.h). Mechanical rewrite, diffed.
|
||||
mkdir -p "$W/abi-orig"; rm -rf "$W/abi-orig/src"; cp -r "$W/src" "$W/abi-orig/src"
|
||||
SRET='GetEyeToHeadTransform|GetProjectionMatrix|GetSeatedZeroPoseToStandingAbsoluteTrackingPose|GetRawZeroPoseToStandingAbsoluteTrackingPose|GetMatrix34TrackedDeviceProperty|GetHiddenAreaMesh|GetCurrentFadeColor|GetHeadsetViewMode'
|
||||
find "$W/src" -name '*.cpp' -o -name '*.h' | xargs sed -i -E \
|
||||
"s/\b([A-Za-z_][A-Za-z0-9_]*)->($SRET)\(\)/trlvr_abi::call_sret(\1, \&std::remove_pointer_t<decltype(\1)>::\2)/g; \
|
||||
s/\b([A-Za-z_][A-Za-z0-9_]*)->($SRET)\(/trlvr_abi::call_sret(\1, \&std::remove_pointer_t<decltype(\1)>::\2, /g"
|
||||
(cd "$W" && diff -ru abi-orig/src src > /out/abi-fix.diff) || true
|
||||
echo "ABI rewrite sites: $(grep -c '^+.*call_sret' /out/abi-fix.diff || true)"
|
||||
# Source list, defines and include dirs come straight from build.bat.
|
||||
SRCS=$(grep -E '^set SRC=%SRC% "%ROOT%[^"]+\.cpp"' /src/build.bat | sed -E 's/.*%ROOT%([^"]+)"/\1/; s#\\#/#g')
|
||||
IMGUI_SRCS=""
|
||||
if [ -d "$W/third_party/imgui" ]; then
|
||||
for f in $(grep -oE 'for %%F in \(([a-z0-9_ ]+)\)' /src/build.bat | sed -E 's/.*\((.*)\)/\1/'); do IMGUI_SRCS="$IMGUI_SRCS third_party/imgui/$f.cpp"; done
|
||||
fi
|
||||
DEFS=$(grep -E '^set CFLAGS=' /src/build.bat | grep -oE '/D[A-Za-z_0-9]+' | sed 's#^/D#-D#' | tr '\n' ' ')
|
||||
INC="-I$W/third_party/openvr/headers -I$W/third_party/openxr/include -I$W/third_party/imgui -I/src/dxvk-trlvr/src/d3d9 -I/src/dxvk-trlvr/include/vulkan/include"
|
||||
# -include seh_compat.h: MSVC __try/__except shim + _ReturnAddress + sret helper; sources stay unmodified.
|
||||
CXXFLAGS="-std=gnu++17 -O2 $DEFS -fno-strict-aliasing -include /shims/seh_compat.h $INC ${EXTRA_CXXFLAGS:-}"
|
||||
echo "sources: $(echo $SRCS $IMGUI_SRCS | wc -w); defs: $DEFS"
|
||||
objs=(); pids=()
|
||||
for s in $SRCS $IMGUI_SRCS; do
|
||||
s=${s%.cpp}
|
||||
o="$W/obj/$(echo $s | tr / _).o"; objs+=("$o")
|
||||
f="$W/$s.cpp"; [ -f "$f" ] || f="$W/src/${s#src/}.cpp"
|
||||
i686-w64-mingw32-g++ $CXXFLAGS -c "$f" -o "$o" > "${o%.o}.log" 2>&1 &
|
||||
pids+=($!)
|
||||
while [ "$(jobs -r | wc -l)" -ge "$JOBS" ]; do sleep 0.2; done
|
||||
done
|
||||
rc=0; for p in "${pids[@]}"; do wait "$p" || rc=1; done
|
||||
cat "$W"/obj/*.log | grep -E "error|Error" | head -80 || true
|
||||
[ $rc = 0 ] || { echo "PROXY COMPILE FAILED"; exit 1; }
|
||||
# GNU ld wants a name after a bare LIBRARY keyword (MSVC link does not).
|
||||
sed 's/^LIBRARY[[:space:]]*$/LIBRARY "d3d9.dll"/' "$W/src/proxy/d3d9.def" > "$W/d3d9.mingw.def"
|
||||
XRLIB=""; [ -f "$W/third_party/openxr/lib/win32/openxr_loader.lib" ] && XRLIB="$W/third_party/openxr/lib/win32/openxr_loader.lib"
|
||||
# Not linking d3d9: this DLL is d3d9.dll. --kill-at = undecorated stdcall exports like MSVC.
|
||||
i686-w64-mingw32-g++ -shared -static -static-libgcc -static-libstdc++ -s \
|
||||
-o /out/dist/d3d9.dll "${objs[@]}" "$W/d3d9.mingw.def" $XRLIB \
|
||||
-Wl,--kill-at -Wl,--enable-stdcall-fixup -Wl,--subsystem,windows -Wl,--large-address-aware \
|
||||
-Wl,-Map,/out/dist/d3d9.map \
|
||||
-ldxguid -luser32 -lkernel32 -ladvapi32 -lgdi32 -ldwmapi ${EXTRA_LIBS:-}
|
||||
# Launcher: its own exe (large-address-aware bit must be in the file).
|
||||
i686-w64-mingw32-g++ -std=gnu++17 -O2 -DWIN32 -DNDEBUG -D_CRT_SECURE_NO_WARNINGS \
|
||||
-include /shims/seh_compat.h -static -static-libgcc -static-libstdc++ -s \
|
||||
-o /out/dist/trlvr_launcher.exe "$W/src/launcher/main.cpp" \
|
||||
-mwindows -municode -Wl,--large-address-aware -luser32 -lkernel32 -lshell32
|
||||
# d3d9.dll links openxr_loader statically (MSVC delay-loads it): the loader must ship beside it.
|
||||
if [ -f /src/third_party/openxr/bin/win32/openxr_loader.dll ]; then
|
||||
cp /src/third_party/openxr/bin/win32/openxr_loader.dll /out/dist/
|
||||
echo "NOTE: dist/openxr_loader.dll must be installed next to d3d9.dll (static import)"
|
||||
fi
|
||||
echo "PROXY_SECONDS=$(( $(ts) - t0 ))"
|
||||
fi
|
||||
ls -l /out/dist
|
||||
Executable
+42
@@ -0,0 +1,42 @@
|
||||
#!/bin/bash
|
||||
# usage: run-trio.sh <label> <dist dir> [key=value ini overrides...]
|
||||
# Swaps the trio into the game dir, launches headless-style (DISPLAY=:1, 90 s), kills, restores.
|
||||
LABEL=$1; DIST=$(readlink -f $2); shift 2
|
||||
G="/home/steamos/.local/share/Steam/steamapps/common/Tomb Raider Legend"; ST=/home/steamos/.local/share/Steam
|
||||
B=/home/steamos/Games/LegendFrame/build-m1; L=$B/logs/$LABEL; mkdir -p $L
|
||||
pgrep trl_vr && { echo "game running"; exit 1; }
|
||||
# verify shipped state
|
||||
(cd $B/shipped/1.0.0 && grep -E " (d3d9.dll|d3d9_dxvk.dll|trlvr_launcher.exe|openvr_api.dll|trl_vr.exe)$" SHA256SUMS | sed "s# # $G/#;s# *$G/# $G/#" >/dev/null)
|
||||
for f in d3d9.dll d3d9_dxvk.dll trlvr_launcher.exe; do
|
||||
want=$(grep " $f\$" $B/shipped/1.0.0/SHA256SUMS | cut -d' ' -f1); have=$(sha256sum "$G/$f" | cut -d' ' -f1)
|
||||
[ "$want" = "$have" ] || { echo "game-dir $f is not the shipped one; abort"; exit 2; }
|
||||
done
|
||||
mkdir -p $B/shipped/backup-$LABEL; cp -p "$G"/trlvr.ini "$G"/trlvr.log "$G"/trlvr.log.prev "$G"/trlvr_launcher.log $B/shipped/backup-$LABEL/ 2>/dev/null
|
||||
restore() {
|
||||
cp -p $B/shipped/1.0.0/d3d9.dll $B/shipped/1.0.0/d3d9_dxvk.dll $B/shipped/1.0.0/trlvr_launcher.exe "$G/"
|
||||
cp -p $B/shipped/backup-$LABEL/trlvr.ini "$G/trlvr.ini"
|
||||
rm -f "$G/openxr_loader.dll" "$G/trlvr_crash.dmp"
|
||||
[ -f $B/shipped/backup-$LABEL/trlvr.log ] && cp -p $B/shipped/backup-$LABEL/trlvr.log "$G/"
|
||||
[ -f $B/shipped/backup-$LABEL/trlvr.log.prev ] && cp -p $B/shipped/backup-$LABEL/trlvr.log.prev "$G/"
|
||||
[ -f $B/shipped/backup-$LABEL/trlvr_launcher.log ] && cp -p $B/shipped/backup-$LABEL/trlvr_launcher.log "$G/"
|
||||
(cd "$G" && sha256sum d3d9.dll d3d9_dxvk.dll trlvr_launcher.exe trlvr.ini) > $L/restore-sha.txt
|
||||
}
|
||||
trap restore EXIT
|
||||
for kv in "$@"; do
|
||||
k=${kv%%=*}; v=${kv#*=}
|
||||
if grep -q "^$k=" "$G/trlvr.ini"; then sed -i "s|^$k=.*|$k=$v|" "$G/trlvr.ini"; else sed -i "/^\[vr\]/a $k=$v" "$G/trlvr.ini"; fi
|
||||
done
|
||||
cp $DIST/d3d9.dll $DIST/d3d9_dxvk.dll $DIST/trlvr_launcher.exe "$G/"
|
||||
[ -f $DIST/openxr_loader.dll ] && cp $DIST/openxr_loader.dll "$G/"
|
||||
T0=$(date +%s)
|
||||
SP=$(pgrep -x steam | head -1)
|
||||
(
|
||||
while IFS= read -r -d '' kv; do export "$kv"; done < /proc/$SP/environ
|
||||
export DISPLAY=:1 STEAM_COMPAT_DATA_PATH=$ST/steamapps/compatdata/7000 STEAM_COMPAT_CLIENT_INSTALL_PATH=$ST
|
||||
export WINEDLLOVERRIDES="d3d9=n,b" SteamAppId=7000 SteamGameId=7000
|
||||
timeout 90 $ST/steamapps/common/SteamLinuxRuntime_4-arm64/_v2-entry-point --verb=waitforexitandrun -- "$ST/steamapps/common/Proton 11.0 (ARM64)/proton" waitforexitandrun "$G/trlvr_launcher.exe" > $L/run.out 2>&1
|
||||
)
|
||||
pkill trl_vr; pkill trlvr_launch; sleep 3
|
||||
cp "$G"/trlvr.log "$G"/trlvr_launcher.log $L/ 2>/dev/null; cp "$G"/trl_vr_d3d9.log $L/ 2>/dev/null; cp "$G"/trlvr_crash.dmp $L/ 2>/dev/null
|
||||
for f in vrserver vrcompositor; do awk -v t="$(date -d @$T0 '+%b %d %H:%M:%S')" '1' ~/.local/share/Steam/logs/$f.txt | tail -n 4000 > $L/$f.tail.txt; done
|
||||
echo "ran $((`date +%s`-T0)) s"
|
||||
@@ -0,0 +1,106 @@
|
||||
// seh_compat.h -- MSVC __try/__except(EXCEPTION_EXECUTE_HANDLER) for i686 mingw-w64.
|
||||
//
|
||||
// GCC has no __try/__except on 32-bit Windows. The TRL VR mod only uses the
|
||||
// catch-everything form, with no filter expression use, no __finally, and no
|
||||
// GetExceptionCode. This header maps that form onto a hand-registered x86 SEH
|
||||
// frame (fs:[0] chain) + __builtin_setjmp/longjmp, so nesting with foreign SEH
|
||||
// frames (the game's, the CRT's) keeps its order, exactly like MSVC's.
|
||||
//
|
||||
// __try { A } __except(EXCEPTION_EXECUTE_HANDLER) { B }
|
||||
// becomes
|
||||
// if (Scope s; (s.arm(), __builtin_setjmp(s.jb)) == 0) { A } else { B }
|
||||
//
|
||||
// Injected with -include so the mod's sources stay unmodified. Not applicable
|
||||
// to x86_64 (native SEH there). Hardware faults only (not C++ throws).
|
||||
#pragma once
|
||||
#if defined(__MINGW32__) && defined(__i386__) && !defined(TRLVR_SEH_COMPAT_DONE)
|
||||
#define TRLVR_SEH_COMPAT_DONE 1
|
||||
// libstdc++ defines its own __try/__catch macros (bits/exception_defines.h) and
|
||||
// would clobber ours if first included later; pull the whole library in now.
|
||||
#ifndef MINGW_HAS_SECURE_API
|
||||
#define MINGW_HAS_SECURE_API 1 // swprintf_s and friends, as MSVC provides
|
||||
#endif
|
||||
#ifdef __cplusplus
|
||||
#include <bits/stdc++.h>
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <setjmp.h>
|
||||
|
||||
namespace seh_compat {
|
||||
// Raw x86 SEH frame on the FS:[0] chain (what MSVC's __try does). On a fault the
|
||||
// handler redirects the thread into resume(), which __builtin_longjmp's back to the
|
||||
// guard and runs the else-branch. No C++ EH, no unwinding through ntdll frames.
|
||||
// Design and code from the probe-clang lane (probe-clang/seh_compat.h).
|
||||
struct Scope {
|
||||
Scope* prev; // == EXCEPTION_REGISTRATION_RECORD layout
|
||||
void* handler;
|
||||
void* jb[5]; // __builtin_setjmp buffer
|
||||
bool armed;
|
||||
Scope() : armed(false) {}
|
||||
~Scope() { disarm(); }
|
||||
void arm();
|
||||
void disarm() { if (armed) { asm volatile("movl %0, %%fs:0" :: "r"(prev) : "memory"); armed = false; } }
|
||||
};
|
||||
__attribute__((noreturn, noinline, used)) static void __cdecl resume(Scope* s) {
|
||||
s->disarm();
|
||||
__builtin_longjmp(s->jb, 1);
|
||||
}
|
||||
static EXCEPTION_DISPOSITION __cdecl handler(EXCEPTION_RECORD* rec, void* est, CONTEXT* ctx, void*) {
|
||||
if (rec->ExceptionFlags & (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND)) return ExceptionContinueSearch;
|
||||
DWORD* sp = reinterpret_cast<DWORD*>(ctx->Esp) - 2; // fake cdecl call: [ret][arg]
|
||||
sp[0] = 0; sp[1] = reinterpret_cast<DWORD>(est);
|
||||
ctx->Esp = reinterpret_cast<DWORD>(sp);
|
||||
ctx->Eip = reinterpret_cast<DWORD>(&resume);
|
||||
return ExceptionContinueExecution;
|
||||
}
|
||||
__attribute__((noinline)) inline void Scope::arm() {
|
||||
asm volatile("movl %%fs:0, %0" : "=r"(prev));
|
||||
handler = reinterpret_cast<void*>(&seh_compat::handler);
|
||||
asm volatile("movl %0, %%fs:0" :: "r"(this) : "memory");
|
||||
armed = true;
|
||||
}
|
||||
}
|
||||
|
||||
#include <intrin.h>
|
||||
// MSVC intrinsic; mingw's <intrin.h> declares it without a definition.
|
||||
#undef _ReturnAddress
|
||||
#define _ReturnAddress() __builtin_return_address(0)
|
||||
|
||||
#undef __try
|
||||
#undef __except
|
||||
// The asm barrier matters: without it GCC -O2 schedules __builtin_setjmp's save of ESP into jb[]
|
||||
// AFTER the first guarded load, so a fault on that first load longjmps with a garbage ESP.
|
||||
#define __try if (::seh_compat::Scope seh_scope_; (seh_scope_.arm(), __builtin_setjmp(seh_scope_.jb)) == 0 && ({ __asm__ __volatile__("" ::: "memory"); 1; }))
|
||||
#define __except(x) else
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GCC/MSVC thiscall mismatch for virtuals that return a struct in memory:
|
||||
// MSVC passes `this` in ECX and the hidden return pointer first on the stack;
|
||||
// GCC puts the hidden pointer in ECX and `this` on the stack. OpenVR's
|
||||
// openvr_api.dll (MSVC ABI) therefore needs these called by hand.
|
||||
// container-build.sh rewrites `obj->GetEyeToHeadTransform(a)` (and the other
|
||||
// struct-returning OpenVR methods) to
|
||||
// trlvr_abi::call_sret(obj, &decltype(*obj)::GetEyeToHeadTransform, a)
|
||||
// on its working copy and saves the result as abi-fix.diff.
|
||||
#if __has_include(<openvr.h>)
|
||||
#include <openvr.h>
|
||||
#include <type_traits>
|
||||
namespace trlvr_abi
|
||||
{
|
||||
template <class R, class C, class... A>
|
||||
inline R call_sret(C* obj, R (C::*pmf)(A...), A... args)
|
||||
{
|
||||
static_assert(sizeof(pmf) == 2 * sizeof(void*), "unexpected pmf layout");
|
||||
size_t words[2];
|
||||
__builtin_memcpy(words, &pmf, sizeof(words));
|
||||
// Itanium-style PMF to a virtual: first word = vtable byte offset + 1.
|
||||
const size_t slot = (words[0] - 1) / sizeof(void*);
|
||||
void** vtbl = *reinterpret_cast<void***>(obj);
|
||||
typedef void (__attribute__((thiscall)) *Fn)(void*, R*, A...);
|
||||
R out{};
|
||||
reinterpret_cast<Fn>(vtbl[slot])(obj, &out, args...);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,68 @@
|
||||
// Runtime check of shims/seh_compat.h (no game involved). Built with -include seh_compat.h.
|
||||
// usage: seh_test.exe <logfile> (stdout is swallowed by Proton, so results go to the file too)
|
||||
#include <windows.h>
|
||||
#include <cstdio>
|
||||
|
||||
static FILE* g_log;
|
||||
static void say(const char* fmt, int v = 0)
|
||||
{
|
||||
char b[256]; snprintf(b, sizeof b, fmt, v);
|
||||
if (g_log) { fputs(b, g_log); fputc('\n', g_log); fflush(g_log); }
|
||||
puts(b); fflush(stdout);
|
||||
}
|
||||
|
||||
static volatile int* volatile g_null = nullptr;
|
||||
static int __attribute__((noinline)) faulting_callee(int x) { return *g_null + x; }
|
||||
typedef int (*Fn)(int);
|
||||
static volatile Fn g_fn = faulting_callee;
|
||||
static int __attribute__((noinline)) good_callee(int x) { return x * 2; }
|
||||
|
||||
static bool peek(volatile int* p, int* out)
|
||||
{
|
||||
__try { *out = *p; return true; }
|
||||
__except (EXCEPTION_EXECUTE_HANDLER) { return false; }
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
if (argc > 1) g_log = fopen(argv[1], "w");
|
||||
int failures = 0;
|
||||
say("start");
|
||||
|
||||
// 1. null read inside __try
|
||||
int got = 0;
|
||||
if (!peek(g_null, &got)) say("recovered (null read)"); else { say("FAIL null read not caught"); ++failures; }
|
||||
|
||||
// 2. guarded call through a function pointer into faulting code
|
||||
volatile int r2 = 0;
|
||||
__try { r2 = g_fn(1); say("FAIL call did not fault"); ++failures; }
|
||||
__except (EXCEPTION_EXECUTE_HANDLER) { r2 = -1; }
|
||||
if (r2 == -1) say("recovered (faulting call through function pointer)");
|
||||
|
||||
// 3. no fault: body runs, `return` leaves the frame cleanly
|
||||
int ok = 0;
|
||||
if (peek(&got, &ok) && ok == got) say("ok (no-fault path returns from inside __try)"); else { say("FAIL no-fault path"); ++failures; }
|
||||
|
||||
// 4. nested: inner fault is caught by the inner handler, outer body continues
|
||||
int stage = 0;
|
||||
__try {
|
||||
__try { (void)*g_null; stage = 1; }
|
||||
__except (EXCEPTION_EXECUTE_HANDLER) { stage = 2; }
|
||||
stage += 10;
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER) { stage = -99; }
|
||||
if (stage == 12) say("recovered (nested)"); else { say("FAIL nested stage=%d", stage); ++failures; }
|
||||
|
||||
// 5. many faults in a row (frame chain must not leak or corrupt)
|
||||
int n = 0;
|
||||
for (int i = 0; i < 2000; ++i) { int t; if (!peek(g_null, &t)) ++n; }
|
||||
if (n == 2000) say("recovered (2000 consecutive faults)"); else { say("FAIL repeated faults n=%d", n); ++failures; }
|
||||
|
||||
// 6. FS:[0] chain intact afterwards: a function pointer call that does NOT fault still works
|
||||
if (g_fn != nullptr && good_callee(21) == 42) say("ok (chain intact)"); else ++failures;
|
||||
|
||||
// 7. unguarded fault still reaches the unhandled path? skipped on purpose (would kill the process)
|
||||
say(failures ? "RESULT FAIL" : "RESULT PASS");
|
||||
if (g_log) fclose(g_log);
|
||||
return failures ? 1 : 0;
|
||||
}
|
||||
Executable
+25
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
# Build and run the seh_compat runtime check headless under Proton (scratch prefix, no game).
|
||||
# usage: seh_test.sh <output dir>
|
||||
set -uo pipefail
|
||||
HERE=$(cd "$(dirname "$0")/.." && pwd)
|
||||
OUT=$(readlink -m "${1:?output dir}"); mkdir -p "$OUT"
|
||||
podman image exists localhost/legendframe-mingw || podman build -t legendframe-mingw "$HERE"
|
||||
podman run --rm -v "$HERE:/tc:ro,Z" -v "$OUT:/out:Z" localhost/legendframe-mingw \
|
||||
i686-w64-mingw32-g++ -std=gnu++17 -O2 -DWIN32 -DNDEBUG -include /tc/shims/seh_compat.h \
|
||||
-static -static-libgcc -static-libstdc++ -o /out/seh_test.exe /tc/test/seh_test.cpp || exit 70
|
||||
ST=$HOME/.local/share/Steam; SP=$(pgrep -x steam | head -1)
|
||||
[ -n "$SP" ] || { echo "steam not running" >&2; exit 71; }
|
||||
PFX=$HOME/Games/LegendFrame/build-m2c/pfx; mkdir -p "$PFX"
|
||||
rm -f "$OUT/seh_test.log"
|
||||
(
|
||||
while IFS= read -r -d '' kv; do export "$kv"; done < "/proc/$SP/environ"
|
||||
export DISPLAY=:1 STEAM_COMPAT_DATA_PATH="$PFX" STEAM_COMPAT_CLIENT_INSTALL_PATH="$ST" SteamAppId=0 SteamGameId=0
|
||||
cd "$OUT"
|
||||
timeout "${TIMEOUT:-120}" "$ST/steamapps/common/SteamLinuxRuntime_4-arm64/_v2-entry-point" --verb=waitforexitandrun -- \
|
||||
"$ST/steamapps/common/Proton 11.0 (ARM64)/proton" waitforexitandrun "$OUT/seh_test.exe" "Z:/${OUT#/}/seh_test.log" > "$OUT/seh_test.out" 2>&1
|
||||
echo $? > "$OUT/exitcode"
|
||||
)
|
||||
cat "$OUT/seh_test.log" 2>/dev/null || tail -20 "$OUT/seh_test.out"
|
||||
echo "exit code $(cat "$OUT/exitcode")"
|
||||
exit "$(cat "$OUT/exitcode")"
|
||||
Reference in new issue
Block a user