openvibers/source.openvibe.games Commits

Fix client JS world-load crash (cross-CRT free) + logging + auto-connect

CO Copilot committed 2 Jul 2026 · 4b84861749 · 8 files, +104 −8

Root cause of the "crashes when the level loads" bug: the client glue
(openvibe_js_client.cpp, MSVC CRT) malloc'd readFile/listDir buffers,
but ovjs_core.c (clang-cl /MT CRT) free()'d them. Freeing across two C
runtimes corrupts the heap and crashes — right at the first JS file load
(hook.js), Windows-only (Linux is single-CRT). Trace pinpointed it:
init reached "loading js/core/hook.js" then died.
Fix: add OVJSHost.freeMem; the core releases host-allocated buffers via
the host's allocator (OVC_freeMem=free) instead of its own free().

Logging: drop +con_logfile (it split the log with -condebug); -condebug
now writes one complete game/openvibe.games/console.log. Add
run-client-logged.sh (unified proton+console logs under
artifacts/client-logs/, latest symlinks) and tail-client-log.sh.

Auto-connect: valve.rc stuffcmds so +connect/+map actually run; launcher
auto-targets a running local server (sandbox 27020 / hub 27015) or loads
a local world; ov_menu_auto_open 0 stops the doomed auto ov_join hub.

Changes

ddiff --git a/game/openvibe.games/cfg/openvibe_proton_stability.cfg b/game/openvibe.games/cfg/openvibe_proton_stability.cfg
iindex 93adbdc..95892c2 100644
---- a/game/openvibe.games/cfg/openvibe_proton_stability.cfg
++++ b/game/openvibe.games/cfg/openvibe_proton_stability.cfg
@@@ -21,3 +21,8 @@ snd_mute_losefocus 0
// Reduce first-frame shader/pipeline churn that can trip DXVK during load.
mat_dxlevel 90
r_3dsky 0
++
++// Don't auto-open the HTML menu on startup: it was auto-navigating to
++// openvibe://join?mode=hub, firing an ov_join to a (often-down) hub server and
++// showing "Connection failed" over the world. Open it manually with ov_menu.
++ov_menu_auto_open 0
ddiff --git a/game/openvibe.games/cfg/valve.rc b/game/openvibe.games/cfg/valve.rc
iindex 74bd39e..fdc37ae 100644
---- a/game/openvibe.games/cfg/valve.rc
++++ b/game/openvibe.games/cfg/valve.rc
@@@ -1,2 +1,5 @@
// OpenVibe: Source startup script.
--// Intentionally small; mode-specific server config is passed with +exec.
++// stuffcmds runs the command-line "+" commands (e.g. +connect / +map) that the
++// launcher passes. Without it those are ignored and the client sits at the menu
++// — which is why launching didn't auto-connect.
++stuffcmds
ddiff --git a/sdk/openvibe/client/hl2mp/openvibe_js_client.cpp b/sdk/openvibe/client/hl2mp/openvibe_js_client.cpp
iindex 50bcaa3..0160c46 100644
---- a/sdk/openvibe/client/hl2mp/openvibe_js_client.cpp
++++ b/sdk/openvibe/client/hl2mp/openvibe_js_client.cpp
@@@ -91,6 +91,10 @@ static char *OVC_listDir( const char *dir, const char *wildcard )
return buf;
}
++// Free buffers OVC_readFile/OVC_listDir allocated, using THIS module's CRT
++// (must match the malloc that created them; the core must not free them).
++static void OVC_freeMem( void *p ) { free( p ); }
++
static int OVC_isServer( void ) { return 0; }
static const char *OVC_getMode( void ) { return ov_client_mode.GetString(); }
@@@ -120,7 +124,7 @@ static void OVC_netEmit( const char *, const char *, const char * )
static OVJSHost g_ClientHost =
{
OVC_log, OVC_warn, OVC_error,
-- OVC_readFile, OVC_fileExists, OVC_listDir,
++ OVC_readFile, OVC_fileExists, OVC_listDir, OVC_freeMem,
OVC_isServer, OVC_getMode, OVC_getMapName, OVC_getTime,
OVC_netSendToServer, OVC_netEmit
};
ddiff --git a/sdk/openvibe/shared/ovjs_core.c b/sdk/openvibe/shared/ovjs_core.c
iindex 306443f..5e224e5 100644
---- a/sdk/openvibe/shared/ovjs_core.c
++++ b/sdk/openvibe/shared/ovjs_core.c
@@@ -21,6 +21,16 @@ static OVJSCore *OVJS_FromCtx(JSContext *ctx)
return (OVJSCore *)JS_GetContextOpaque(ctx);
}
++/* Release a buffer the host allocated (readFile/listDir). Must use the host's
++ * allocator, never the core's free(): on Windows the glue and this core can be
++ * built against different C runtimes, and freeing across CRTs crashes. */
++static void OVJS_HostFree(const OVJSHost *host, void *p)
++{
++ if (!p) return;
++ if (host && host->freeMem) host->freeMem(p);
++ /* else: leak rather than risk a cross-CRT free() crash. */
++}
++
/* ------------------------------------------------------------------ */
/* OV.* native bindings — thin shims over the host callbacks. */
/* ------------------------------------------------------------------ */
@@@ -83,7 +93,7 @@ static JSValue ov_readFile(JSContext *ctx, JSValueConst t, int argc, JSValueCons
JS_FreeCString(ctx, path);
if (!buf) return JS_NULL;
JSValue out = JS_NewString(ctx, buf);
-- free(buf);
++ OVJS_HostFree(c->host, buf);
return out;
}
static JSValue ov_fileExists(JSContext *ctx, JSValueConst t, int argc, JSValueConst *argv)
@@@ -118,7 +128,7 @@ static JSValue ov_listDir(JSContext *ctx, JSValueConst t, int argc, JSValueConst
start = p + 1;
}
}
-- free(joined);
++ OVJS_HostFree(c->host, joined);
}
}
if (dir) JS_FreeCString(ctx, dir);
@@@ -218,7 +228,7 @@ static int OVJS_LoadFile(OVJSCore *c, const char *path)
return 0;
}
int ok = ovjs_eval(c, code, path);
-- free(code);
++ OVJS_HostFree(c->host, code);
return ok;
}
ddiff --git a/sdk/openvibe/shared/ovjs_core.h b/sdk/openvibe/shared/ovjs_core.h
iindex 228c3da..f712009 100644
---- a/sdk/openvibe/shared/ovjs_core.h
++++ b/sdk/openvibe/shared/ovjs_core.h
@@@ -26,11 +26,16 @@ typedef struct OVJSHost
void (*warn)(const char *msg);
void (*error)(const char *msg);
-- /* Returns malloc'd NUL-terminated file contents (core frees), or NULL. */
++ /* Returns host-allocated NUL-terminated file contents, or NULL. The core
++ * releases it via freeMem() below — NOT free() — because the host (MSVC
++ * CRT) and core (clang-cl /MT CRT) may use different heaps on Windows, and
++ * freeing across CRTs corrupts the heap and crashes. */
char *(*readFile)(const char *path);
int (*fileExists)(const char *path);
-- /* Returns malloc'd newline-joined entry names (core frees), or NULL. */
++ /* Returns host-allocated newline-joined entry names (freed via freeMem). */
char *(*listDir)(const char *dir, const char *wildcard);
++ /* Frees a buffer returned by readFile/listDir, using the host's allocator. */
++ void (*freeMem)(void *p);
int (*isServer)(void);
const char *(*getMode)(void); /* host-owned static */
ddiff --git a/tools/run-client-logged.sh b/tools/run-client-logged.sh
nnew file mode 100755
iindex 0000000..a55287b
---- /dev/null
++++ b/tools/run-client-logged.sh
@@@ -0,0 +1,39 @@
++#!/usr/bin/env bash
++# OpenVibe client launcher with unified logging.
++#
++# Captures BOTH streams into artifacts/client-logs/ so the game can be monitored
++# and self-improved from one place:
++# - proton-latest.log : Proton/DXVK/wine stdout+stderr from this launch
++# - console-latest.log : the in-game Source console (-condebug), copied live
++# Also keeps timestamped copies. Usage mirrors run-client-proton-x64.sh:
++# tools/run-client-logged.sh # auto: connect local sandbox or load ov_hub
++# tools/run-client-logged.sh 127.0.0.1 27020 # connect to a specific server
++# OPENVIBE_STARTUP_MAP=ov_hub tools/run-client-logged.sh # local listen world
++set -uo pipefail
++
++ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
++GAME_CONSOLE="$ROOT/game/openvibe.games/console.log"
++LOGDIR="$ROOT/artifacts/client-logs"
++mkdir -p "$LOGDIR"
++TS="$(date +%Y%m%d-%H%M%S)"
++PROTON_LOG="$LOGDIR/proton-$TS.log"
++CONSOLE_COPY="$LOGDIR/console-$TS.log"
++
++# Fresh game console each launch so console-latest.log is only this session.
++: > "$GAME_CONSOLE" 2>/dev/null || true
++
++ln -sf "$(basename "$PROTON_LOG")" "$LOGDIR/proton-latest.log"
++ln -sf "$(basename "$CONSOLE_COPY")" "$LOGDIR/console-latest.log"
++
++echo "[openvibe] client logs:"
++echo " proton : $LOGDIR/proton-latest.log"
++echo " console: $LOGDIR/console-latest.log"
++
++# Mirror the in-game console.log into the timestamped copy live.
++( tail -F "$GAME_CONSOLE" 2>/dev/null > "$CONSOLE_COPY" ) &
++TAIL_PID=$!
++
++# Launch the real client, tee Proton output to the stable log.
++"$ROOT/tools/run-client-proton-x64.sh" "$@" 2>&1 | tee "$PROTON_LOG"
++
++kill "$TAIL_PID" 2>/dev/null || true
ddiff --git a/tools/run-client-proton-x64.sh b/tools/run-client-proton-x64.sh
iindex 8353d9e..2ba3874 100755
---- a/tools/run-client-proton-x64.sh
++++ b/tools/run-client-proton-x64.sh
@@@ -34,6 +34,22 @@ if [ -n "${OPENVIBE_STARTUP_MAP:-}" ]; then
STARTUP_MAP_ARGS="+map ${OPENVIBE_STARTUP_MAP}"
fi
++# Auto-target: with no explicit connect/map, connect to a running local dev
++# server (sandbox 27020, else hub 27015); if none is up, load a local world so
++# launching always drops you in-game instead of sitting at the menu.
++if [ -z "$CONNECT_ARGS" ] && [ -z "$STARTUP_MAP_ARGS" ]; then
++ if ss -uln 2>/dev/null | grep -q '127.0.0.1:27020'; then
++ CONNECT_ARGS="+connect 127.0.0.1:27020"
++ echo " Auto-target: sandbox dedicated server 127.0.0.1:27020"
++ elif ss -uln 2>/dev/null | grep -q '127.0.0.1:27015'; then
++ CONNECT_ARGS="+connect 127.0.0.1:27015"
++ echo " Auto-target: hub dedicated server 127.0.0.1:27015"
++ else
++ STARTUP_MAP_ARGS="+map ${OPENVIBE_DEFAULT_MAP:-ov_hub}"
++ echo " Auto-target: local listen world (no dedicated server up)"
++ fi
++fi
++
echo "Launching OpenVibe: Source via Proton..."
echo " Game: $GAME_DIR"
echo " Proton: $GE_PROTON"
@@@ -65,6 +81,9 @@ export PROTON_LOG="${OPENVIBE_PROTON_LOG:-${PROTON_LOG:-1}}"
# frame with no error trace. Default off for reliability.
export DXVK_ASYNC="${DXVK_ASYNC:-0}"
++# Single canonical log via -condebug (writes game/openvibe.games/console.log).
++# Do NOT also set +con_logfile: it hijacks the console log mid-startup, so the
++# two split and neither is complete. -condebug alone keeps one full log.
exec "$GE_PROTON/proton" waitforexitandrun \
"$HL2_EXE" \
-game "$GAME_DIR" \
@@@ -73,6 +92,5 @@ exec "$GE_PROTON/proton" waitforexitandrun \
-nojoy -insecure -nohltv \
+developer 1 \
+exec openvibe_proton_stability.cfg \
-- +con_logfile openvibe_proton_console.log \
+exec openvibe_proton_client.cfg \
$CONNECT_ARGS $STARTUP_MAP_ARGS
ddiff --git a/tools/tail-client-log.sh b/tools/tail-client-log.sh
nnew file mode 100755
iindex 0000000..2c97fad
---- /dev/null
++++ b/tools/tail-client-log.sh
@@@ -0,0 +1,12 @@
++#!/usr/bin/env bash
++# Tail the latest OpenVibe client logs (in-game console + Proton output).
++# Usage: tools/tail-client-log.sh [console|proton|both]
++ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
++LOGDIR="$ROOT/artifacts/client-logs"
++WHICH="${1:-console}"
++case "$WHICH" in
++ console) exec tail -F "$LOGDIR/console-latest.log" ;;
++ proton) exec tail -F "$LOGDIR/proton-latest.log" ;;
++ both) exec tail -F "$LOGDIR/console-latest.log" "$LOGDIR/proton-latest.log" ;;
++ *) echo "usage: $0 [console|proton|both]" >&2; exit 2 ;;
++esac