openvibers/source.openvibe.games Commits
Fix client JS world-load crash (cross-CRT free) + logging + auto-connect
Changes
@@@ -21,3 +21,8 @@ snd_mute_losefocus 0// Reduce first-frame shader/pipeline churn that can trip DXVK during load.mat_dxlevel 90r_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@@@ -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@@@ -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};@@@ -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, JSValueConsJS_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, JSValueConststart = 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;}@@@ -26,11 +26,16 @@ typedef struct OVJSHostvoid (*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 */nnew file mode 100755@@@ -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@@@ -34,6 +34,22 @@ if [ -n "${OPENVIBE_STARTUP_MAP:-}" ]; thenSTARTUP_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_ARGSnnew file mode 100755@@@ -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
@@ -21,3 +21,8 @@ snd_mute_losefocus 0
@@ -21,3 +21,8 @@ snd_mute_losefocus 0
// Reduce first-frame shader/pipeline churn that can trip DXVK during load.
// Reduce first-frame shader/pipeline churn that can trip DXVK during load.
mat_dxlevel 90
mat_dxlevel 90
r_3dsky 0
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
@@ -1,2 +1,5 @@
@@ -1,2 +1,5 @@
// OpenVibe: Source startup script.
// 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
@@ -91,6 +91,10 @@ static char *OVC_listDir( const char *dir, const char *wildcard )
@@ -91,6 +91,10 @@ static char *OVC_listDir( const char *dir, const char *wildcard )
return buf;
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 int OVC_isServer( void ) { return 0; }
static const char *OVC_getMode( void ) { return ov_client_mode.GetString(); }
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 * )
@@ -120,7 +124,7 @@ static void OVC_netEmit( const char *, const char *, const char * )
static OVJSHost g_ClientHost =
static OVJSHost g_ClientHost =
{
{
OVC_log, OVC_warn, OVC_error,
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_isServer, OVC_getMode, OVC_getMapName, OVC_getTime,
OVC_netSendToServer, OVC_netEmit
OVC_netSendToServer, OVC_netEmit
};
};
@@ -21,6 +21,16 @@ static OVJSCore *OVJS_FromCtx(JSContext *ctx)
@@ -21,6 +21,16 @@ static OVJSCore *OVJS_FromCtx(JSContext *ctx)
return (OVJSCore *)JS_GetContextOpaque(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. */
/* OV.* native bindings — thin shims over the host callbacks. */
/* ------------------------------------------------------------------ */
/* ------------------------------------------------------------------ */
@@ -83,7 +93,7 @@ static JSValue ov_readFile(JSContext *ctx, JSValueConst t, int argc, JSValueCons
@@ -83,7 +93,7 @@ static JSValue ov_readFile(JSContext *ctx, JSValueConst t, int argc, JSValueCons
JS_FreeCString(ctx, path);
JS_FreeCString(ctx, path);
if (!buf) return JS_NULL;
if (!buf) return JS_NULL;
JSValue out = JS_NewString(ctx, buf);
JSValue out = JS_NewString(ctx, buf);
- free(buf);
+ OVJS_HostFree(c->host, buf);
return out;
return out;
}
}
static JSValue ov_fileExists(JSContext *ctx, JSValueConst t, int argc, JSValueConst *argv)
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
@@ -118,7 +128,7 @@ static JSValue ov_listDir(JSContext *ctx, JSValueConst t, int argc, JSValueConst
start = p + 1;
start = p + 1;
}
}
}
}
- free(joined);
+ OVJS_HostFree(c->host, joined);
}
}
}
}
if (dir) JS_FreeCString(ctx, dir);
if (dir) JS_FreeCString(ctx, dir);
@@ -218,7 +228,7 @@ static int OVJS_LoadFile(OVJSCore *c, const char *path)
@@ -218,7 +228,7 @@ static int OVJS_LoadFile(OVJSCore *c, const char *path)
return 0;
return 0;
}
}
int ok = ovjs_eval(c, code, path);
int ok = ovjs_eval(c, code, path);
- free(code);
+ OVJS_HostFree(c->host, code);
return ok;
return ok;
}
}
@@ -26,11 +26,16 @@ typedef struct OVJSHost
@@ -26,11 +26,16 @@ typedef struct OVJSHost
void (*warn)(const char *msg);
void (*warn)(const char *msg);
void (*error)(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);
char *(*readFile)(const char *path);
int (*fileExists)(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);
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);
int (*isServer)(void);
const char *(*getMode)(void); /* host-owned static */
const char *(*getMode)(void); /* host-owned static */
new file mode 100755
new file mode 100755
@@ -0,0 +1,39 @@
@@ -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
@@ -34,6 +34,22 @@ if [ -n "${OPENVIBE_STARTUP_MAP:-}" ]; then
@@ -34,6 +34,22 @@ if [ -n "${OPENVIBE_STARTUP_MAP:-}" ]; then
STARTUP_MAP_ARGS="+map ${OPENVIBE_STARTUP_MAP}"
STARTUP_MAP_ARGS="+map ${OPENVIBE_STARTUP_MAP}"
fi
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 "Launching OpenVibe: Source via Proton..."
echo " Game: $GAME_DIR"
echo " Game: $GAME_DIR"
echo " Proton: $GE_PROTON"
echo " Proton: $GE_PROTON"
@@ -65,6 +81,9 @@ export PROTON_LOG="${OPENVIBE_PROTON_LOG:-${PROTON_LOG:-1}}"
@@ -65,6 +81,9 @@ export PROTON_LOG="${OPENVIBE_PROTON_LOG:-${PROTON_LOG:-1}}"
# frame with no error trace. Default off for reliability.
# frame with no error trace. Default off for reliability.
export DXVK_ASYNC="${DXVK_ASYNC:-0}"
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 \
exec "$GE_PROTON/proton" waitforexitandrun \
"$HL2_EXE" \
"$HL2_EXE" \
-game "$GAME_DIR" \
-game "$GAME_DIR" \
@@ -73,6 +92,5 @@ exec "$GE_PROTON/proton" waitforexitandrun \
@@ -73,6 +92,5 @@ exec "$GE_PROTON/proton" waitforexitandrun \
-nojoy -insecure -nohltv \
-nojoy -insecure -nohltv \
+developer 1 \
+developer 1 \
+exec openvibe_proton_stability.cfg \
+exec openvibe_proton_stability.cfg \
- +con_logfile openvibe_proton_console.log \
+exec openvibe_proton_client.cfg \
+exec openvibe_proton_client.cfg \
$CONNECT_ARGS $STARTUP_MAP_ARGS
$CONNECT_ARGS $STARTUP_MAP_ARGS
new file mode 100755
new file mode 100755
@@ -0,0 +1,12 @@
@@ -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