openvibers/source.openvibe.games Commits

hella changes

CO Copilot committed 3 Jul 2026 · 58ac427ec5 · 33 files, +32 −7531


Changes

ddiff --git a/game/openvibe.games/bin/client.dll b/game/openvibe.games/bin/client.dll
iindex 04d3fed..ae5ada9 100755
BBinary files a/game/openvibe.games/bin/client.dll and b/game/openvibe.games/bin/client.dll differ
ddiff --git a/game/openvibe.games/bin/server.dll b/game/openvibe.games/bin/server.dll
iindex 02f1c20..ec4a4f8 100755
BBinary files a/game/openvibe.games/bin/server.dll and b/game/openvibe.games/bin/server.dll differ
ddiff --git a/game/openvibe.games/bin/x64/client.dll b/game/openvibe.games/bin/x64/client.dll
nnew file mode 100644
iindex 0000000..ae5ada9
BBinary files /dev/null and b/game/openvibe.games/bin/x64/client.dll differ
ddiff --git a/game/openvibe.games/bin/x64/server.dll b/game/openvibe.games/bin/x64/server.dll
nnew file mode 100644
iindex 0000000..ec4a4f8
BBinary files /dev/null and b/game/openvibe.games/bin/x64/server.dll differ
ddiff --git a/game/openvibe.games/cfg/config.cfg b/game/openvibe.games/cfg/config.cfg
iindex db9688c..a8731c8 100644
---- a/game/openvibe.games/cfg/config.cfg
++++ b/game/openvibe.games/cfg/config.cfg
@@@ -13,7 +13,7 @@ bind "e" "+use"
bind "f" "impulse 100"
bind "g" "dropprimary"
bind "p" "headtrack_reset_home_pos"
--bind "q" "lastinv"
++bind "q" "say !q"
bind "r" "+reload"
bind "s" "+back"
bind "t" "impulse 201"
@@@ -25,7 +25,7 @@ bind "z" "+zoom"
bind "[" "invprev"
bind "]" "invnext"
bind "'" "+moveup"
--bind "`" "toggleconsole"
++bind "`" "ov_console"
bind "," "+moveleft"
bind "." "+moveright"
bind "/" "+movedown"
@@@ -47,19 +47,34 @@ bind "UPARROW" "+forward"
bind "LEFTARROW" "+left"
bind "DOWNARROW" "+back"
bind "RIGHTARROW" "+right"
++bind "F1" "ov_menu_main"
bind "F3" "askconnect_accept"
bind "F5" "jpeg"
bind "F6" "save quick"
bind "F7" "load quick"
bind "F9" "vr_toggle"
--bind "F10" "quit prompt"
++bind "F10" "ov_console"
bind "MOUSE1" "+attack"
bind "MOUSE2" "+attack2"
bind "MWHEELUP" "invprev"
bind "MWHEELDOWN" "invnext"
++r_lightmap_bicubic_set "1"
++cl_chatfilter_version "1"
++m_pitch "0.022000"
++m_rawinput_onetime_reset "1"
++mat_software_aa_strength "0.000000"
++mat_software_aa_strength_vgui "1.000000"
++cl_playermodel "models/combine_soldier.mdl"
++ov_join_token "local-dev-hub"
++sv_skyname "sky_day01_01"
++snd_surround_speakers "2"
++snd_mute_losefocus "0"
name "Goosely"
--cl_logofile "materials/vgui/logos/spray.vtf"
++cl_logofile "materials/vgui/logos/spray_canned.vtf"
mat_colorcorrection "1"
++con_enable "1"
r_shadowrendertotexture "1"
r_waterforceexpensive "1"
mat_motion_blur_enabled "1"
++mat_queue_mode "0"
++mat_hdr_level "0"
ddiff --git a/game/openvibe.games/maps/graphs/ov_hub.ain b/game/openvibe.games/maps/graphs/ov_hub.ain
nnew file mode 100644
iindex 0000000..126e1ff
BBinary files /dev/null and b/game/openvibe.games/maps/graphs/ov_hub.ain differ
ddiff --git a/tools/dev-up.sh b/tools/dev-up.sh
iindex 9592186..c9ff6b5 100755
---- a/tools/dev-up.sh
++++ b/tools/dev-up.sh
@@@ -10,16 +10,23 @@ fi
"$ROOT/tools/dev-db-up.sh"
--if tmux has-session -t ov-api 2>/dev/null; then
-- tmux kill-session -t ov-api
--fi
--if tmux has-session -t ov-client-ui 2>/dev/null; then
-- tmux kill-session -t ov-client-ui
--fi
++for session in ov-api ov-client-ui ov-runtime-server ov-runtime-client; do
++ if tmux has-session -t "$session" 2>/dev/null; then
++ tmux kill-session -t "$session"
++ fi
++done
tmux new-session -d -s ov-api "$ROOT/tools/dev-api.sh"
tmux new-session -d -s ov-client-ui "$ROOT/tools/run-client-ui.sh"
++# GModJS Node runtimes: back the GUI console (SSE logs + eval + npm on
++# 41997/41996) and serve as the game bridge when ov_js_backend is "node".
++OV_RUNTIME_MODE="${OPENVIBE_RUNTIME_MODE:-hub}"
++tmux new-session -d -s ov-runtime-server \
++ "node '$ROOT/engine/openvibe-js-runtime/ov-runtime.js' --realm server --mode $OV_RUNTIME_MODE --root '$ROOT'"
++tmux new-session -d -s ov-runtime-client \
++ "node '$ROOT/engine/openvibe-js-runtime/ov-runtime.js' --realm client --mode $OV_RUNTIME_MODE --root '$ROOT'"
++
echo "[openvibe] waiting for api"
for _ in {1..60}; do
if curl -fsS http://127.0.0.1:3000/health >/dev/null 2>&1; then
ddiff --git a/tools/fix-duplicate-ov-js-cmd.sh b/tools/fix-duplicate-ov-js-cmd.sh
ddeleted file mode 100755
iindex 9701d85..0000000
---- a/tools/fix-duplicate-ov-js-cmd.sh
++++ /dev/null
@@@ -1,441 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--SDK="${OPENVIBE_SDK:-$ROOT/engine/source-sdk-2013}"
--STAMP="$(date +%Y%m%d-%H%M%S)"
--
--cd "$ROOT"
--
--backup_file() {
-- local file="$1"
-- [[ -f "$file" ]] && cp "$file" "$file.bak.$STAMP"
--}
--
--echo "[openvibe] fixing duplicate ov_js_cmd and canonicalizing embedded JS server bridge"
--
--mkdir -p sdk/openvibe/server/hl2mp
--
--backup_file sdk/openvibe/server/hl2mp/openvibe_js_server.cpp
--backup_file sdk/openvibe/server/hl2mp/openvibe_server.cpp
--
--cat > sdk/openvibe/server/hl2mp/openvibe_js_server.cpp <<'CPP'
--#include "cbase.h"
--#include "hl2mp_player.h"
--#include "openvibe_js_server.h"
--#include "openvibe/ov_js_runtime.h"
--#include "openvibe/ov_js_player.h"
--
--#include "tier0/memdbgon.h"
--
--static ConVar ov_mode(
-- "ov_mode",
-- "hub",
-- FCVAR_GAMEDLL,
-- "OpenVibe mode: hub, prophunt, deathrun, fortwars, traitortown." );
--
--static ConVar ov_js_enabled(
-- "ov_js_enabled",
-- "1",
-- FCVAR_GAMEDLL,
-- "Enable OpenVibe JavaScript runtime." );
--
--static COpenVibeJSRuntime g_OVServerJS;
--static bool g_OVServerJSStarted = false;
--
--static bool OpenVibeJS_IsRunning()
--{
-- return ov_js_enabled.GetBool() && g_OVServerJS.Context() != nullptr;
--}
--
--static void OpenVibeJS_EnsureStarted()
--{
-- if ( g_OVServerJSStarted )
-- return;
--
-- g_OVServerJSStarted = true;
--
-- if ( !ov_js_enabled.GetBool() )
-- {
-- Msg( "[OV JS] disabled by ov_js_enabled=0\n" );
-- return;
-- }
--
-- if ( g_OVServerJS.Init( true, ov_mode.GetString() ) )
-- {
-- Msg( "[OV JS] server runtime initialized for mode '%s'\n", ov_mode.GetString() );
-- g_OVServerJS.CallHookVoid( "Initialize" );
--
-- JSContext *ctx = g_OVServerJS.Context();
-- JSValue mapName = JS_NewString( ctx, gpGlobals ? STRING( gpGlobals->mapname ) : "" );
-- JSValueConst argv[] = { mapName };
-- g_OVServerJS.CallHookVoid( "MapInitialize", 1, argv );
-- JS_FreeValue( ctx, mapName );
-- }
-- else
-- {
-- Warning( "[OV JS] server runtime failed to initialize\n" );
-- }
--}
--
--static void OpenVibeJS_CallConsoleCommand( const char *pszCommand )
--{
-- OpenVibeJS_EnsureStarted();
--
-- if ( !OpenVibeJS_IsRunning() )
-- {
-- Warning( "[OV JS] runtime is not running; command ignored: %s\n", pszCommand ? pszCommand : "" );
-- return;
-- }
--
-- JSContext *ctx = g_OVServerJS.Context();
-- JSValue command = JS_NewString( ctx, pszCommand ? pszCommand : "" );
-- JSValueConst argv[] = { command };
--
-- g_OVServerJS.CallHookVoid( "ConsoleCommand", 1, argv );
--
-- JS_FreeValue( ctx, command );
--}
--
--void OpenVibeJS_ServerInit()
--{
-- OpenVibeJS_EnsureStarted();
--}
--
--void OpenVibeJS_ServerShutdown()
--{
-- if ( OpenVibeJS_IsRunning() )
-- g_OVServerJS.CallHookVoid( "Shutdown" );
--
-- g_OVServerJS.Shutdown();
-- g_OVServerJSStarted = false;
--}
--
--void OpenVibeJS_ServerThink()
--{
-- OpenVibeJS_EnsureStarted();
--
-- if ( !OpenVibeJS_IsRunning() )
-- return;
--
-- g_OVServerJS.CallHookVoid( "Think" );
--}
--
--void OpenVibeJS_Server_PlayerInitialSpawn( CHL2MP_Player *player )
--{
-- OpenVibeJS_EnsureStarted();
--
-- if ( !OpenVibeJS_IsRunning() || !player )
-- return;
--
-- JSContext *ctx = g_OVServerJS.Context();
-- JSValue ply = OVJS_NewPlayer( ctx, player );
--
-- JSValueConst argv[] = { ply };
-- g_OVServerJS.CallHookVoid( "PlayerInitialSpawn", 1, argv );
--
-- JS_FreeValue( ctx, ply );
--}
--
--void OpenVibeJS_Server_PlayerSpawn( CHL2MP_Player *player )
--{
-- OpenVibeJS_EnsureStarted();
--
-- if ( !OpenVibeJS_IsRunning() || !player )
-- return;
--
-- JSContext *ctx = g_OVServerJS.Context();
-- JSValue ply = OVJS_NewPlayer( ctx, player );
--
-- JSValueConst argv[] = { ply };
-- g_OVServerJS.CallHookVoid( "PlayerSpawn", 1, argv );
--
-- JS_FreeValue( ctx, ply );
--}
--
--void OpenVibeJS_Server_PlayerDeath( CHL2MP_Player *victim, CBaseEntity *attacker, CBaseEntity *inflictor )
--{
-- OpenVibeJS_EnsureStarted();
--
-- if ( !OpenVibeJS_IsRunning() || !victim )
-- return;
--
-- JSContext *ctx = g_OVServerJS.Context();
--
-- JSValue jsVictim = OVJS_NewPlayer( ctx, victim );
-- JSValue jsAttacker = JS_NULL;
--
-- if ( attacker && attacker->IsPlayer() )
-- jsAttacker = OVJS_NewPlayer( ctx, ToHL2MPPlayer( static_cast<CBasePlayer *>( attacker ) ) );
--
-- JSValueConst argv[] = { jsVictim, jsAttacker };
-- g_OVServerJS.CallHookVoid( "PlayerDeath", 2, argv );
--
-- JS_FreeValue( ctx, jsVictim );
-- JS_FreeValue( ctx, jsAttacker );
--}
--
--void OpenVibeJS_Server_PlayerDisconnected( CHL2MP_Player *player )
--{
-- OpenVibeJS_EnsureStarted();
--
-- if ( !OpenVibeJS_IsRunning() || !player )
-- return;
--
-- JSContext *ctx = g_OVServerJS.Context();
-- JSValue ply = OVJS_NewPlayer( ctx, player );
--
-- JSValueConst argv[] = { ply };
-- g_OVServerJS.CallHookVoid( "PlayerDisconnected", 1, argv );
--
-- JS_FreeValue( ctx, ply );
--}
--
--bool OpenVibeJS_Server_PlayerSay( CHL2MP_Player *player, const char *text )
--{
-- OpenVibeJS_EnsureStarted();
--
-- if ( !OpenVibeJS_IsRunning() || !player || !text )
-- return false;
--
-- JSContext *ctx = g_OVServerJS.Context();
--
-- JSValue ply = OVJS_NewPlayer( ctx, player );
-- JSValue msg = JS_NewString( ctx, text );
--
-- JSValueConst argv[] = { ply, msg };
--
-- bool value = false;
-- bool returned = g_OVServerJS.CallHookBool( "PlayerSay", &value, 2, argv );
--
-- JS_FreeValue( ctx, ply );
-- JS_FreeValue( ctx, msg );
--
-- return returned && value == false;
--}
--
--static void OV_JSReload_f()
--{
-- OpenVibeJS_ServerShutdown();
-- OpenVibeJS_ServerInit();
-- Msg( "[OV JS] reloaded\n" );
--}
--
--static ConCommand ov_js_reload(
-- "ov_js_reload",
-- OV_JSReload_f,
-- "Reload OpenVibe JavaScript runtime.",
-- FCVAR_GAMEDLL
--);
--
--static void OV_JSStatus_f()
--{
-- Msg( "[OV JS] enabled=%d started=%d running=%d mode=%s\n",
-- ov_js_enabled.GetBool() ? 1 : 0,
-- g_OVServerJSStarted ? 1 : 0,
-- OpenVibeJS_IsRunning() ? 1 : 0,
-- ov_mode.GetString() );
--}
--
--static ConCommand ov_js_status(
-- "ov_js_status",
-- OV_JSStatus_f,
-- "Print OpenVibe JavaScript runtime status.",
-- FCVAR_GAMEDLL
--);
--
--static void OV_JSFire_f( const CCommand &args )
--{
-- if ( args.ArgC() < 2 )
-- {
-- Msg( "Usage: ov_js_fire <HookName>\n" );
-- return;
-- }
--
-- OpenVibeJS_EnsureStarted();
--
-- if ( !OpenVibeJS_IsRunning() )
-- {
-- Warning( "[OV JS] runtime is not running\n" );
-- return;
-- }
--
-- g_OVServerJS.CallHookVoid( args[1] );
--}
--
--static ConCommand ov_js_fire(
-- "ov_js_fire",
-- OV_JSFire_f,
-- "Fire an OpenVibe JavaScript hook with no arguments.",
-- FCVAR_GAMEDLL
--);
--
--static void OV_JSCmd_f( const CCommand &args )
--{
-- if ( args.ArgC() < 2 )
-- {
-- Msg( "Usage: ov_js_cmd <text>\n" );
-- return;
-- }
--
-- OpenVibeJS_CallConsoleCommand( args.ArgS() );
--}
--
--static ConCommand ov_js_cmd(
-- "ov_js_cmd",
-- OV_JSCmd_f,
-- "Send a ConsoleCommand event into the embedded OpenVibe JavaScript runtime.",
-- FCVAR_GAMEDLL
--);
--CPP
--
--echo "[openvibe] canonical openvibe_js_server.cpp written"
--
--echo "[openvibe] removing stale legacy Node bridge remnants from openvibe_server.cpp"
--
--python3 <<'PY'
--from pathlib import Path
--import re
--
--p = Path("sdk/openvibe/server/hl2mp/openvibe_server.cpp")
--s = p.read_text()
--
--# Remove legacy COpenVibeJsBridge class, if present.
--s = re.sub(
-- r'\n// ={10,}\n// OpenVibe\.JS - Scripting Engine C\+\+ Bridge\n// ={10,}\n\nclass COpenVibeJsBridge\b.*?\nstatic COpenVibeJsBridge g_OpenVibeJsBridge;\n',
-- '\n',
-- s,
-- flags=re.S,
--)
--
--# More tolerant fallback if banner text differs.
--s = re.sub(
-- r'\nclass COpenVibeJsBridge\b.*?\nstatic COpenVibeJsBridge g_OpenVibeJsBridge;\n',
-- '\n',
-- s,
-- flags=re.S,
--)
--
--# Remove old Node-bridge ov_js_cmd command if it survived.
--s = re.sub(
-- r'\nstatic void OV_JsCmd_f\s*\([^)]*\)\s*\{.*?\n\}\n\nstatic ConCommand ov_js_cmd\s*\(.*?\);\n',
-- '\n',
-- s,
-- flags=re.S,
--)
--
--# Remove obvious unused Node/socket includes from our tracked patch file.
--for inc in [
-- '#include <thread>\n',
-- '#include <mutex>\n',
-- '#include <queue>\n',
-- '#include <string>\n',
-- '#include <sstream>\n',
-- '#include <sys/socket.h>\n',
-- '#include <sys/un.h>\n',
-- '#include <unistd.h>\n',
--]:
-- s = s.replace(inc, '')
--
--# Ensure embedded JS include exists.
--if '#include "openvibe_js_server.h"' not in s:
-- s = re.sub(
-- r'(#include\s+"hl2mp_player\.h"\s*\n)',
-- r'\1#include "openvibe_js_server.h"\n',
-- s,
-- count=1,
-- )
--
--p.write_text(s)
--PY
--
--echo "[openvibe] removing duplicate ov_js_cmd from already-applied SDK file if present"
--
--SDK_SERVER_JS="$SDK/src/game/server/hl2mp/openvibe_js_server.cpp"
--if [[ -f "$SDK_SERVER_JS" ]]; then
-- cp "$SDK_SERVER_JS" "$SDK_SERVER_JS.bak.$STAMP"
-- cp sdk/openvibe/server/hl2mp/openvibe_js_server.cpp "$SDK_SERVER_JS"
--fi
--
--echo "[openvibe] ensure JS ConsoleCommand handlers are present"
--
--mkdir -p game/openvibe.games/js/gamemodes/base game/openvibe.games/js/gamemodes/hub
--
--python3 <<'PY'
--from pathlib import Path
--
--def ensure_console_handler(path: str, marker: str, handler_src: str):
-- p = Path(path)
-- s = p.read_text() if p.exists() else "const GM = {\n};\n\ngamemode.set(GM);\n"
-- if marker in s:
-- return
-- idx = s.rfind("};")
-- if idx == -1:
-- s += "\n" + handler_src + "\n"
-- else:
-- before = s[:idx].rstrip()
-- after = s[idx:]
-- if before.endswith("{"):
-- before = before + "\n" + handler_src.strip()
-- else:
-- before = before + ",\n\n" + handler_src.strip()
-- s = before + "\n" + after
-- p.write_text(s)
--
--ensure_console_handler(
-- "game/openvibe.games/js/gamemodes/base/server.js",
-- "Base ConsoleCommand",
-- '''ConsoleCommand(text) {
-- OV.log(`Base ConsoleCommand: ${text}`);
-- }'''
--)
--
--ensure_console_handler(
-- "game/openvibe.games/js/gamemodes/hub/server.js",
-- "Hub ConsoleCommand",
-- '''ConsoleCommand(text) {
-- OV.log(`Hub ConsoleCommand: ${text}`);
-- if (text === "smoke") {
-- OV.broadcast("Embedded JS smoke command worked.");
-- }
-- }'''
--)
--PY
--
--echo "[openvibe] apply SDK patch"
--tools/apply-openvibe-sdk.sh
--
--echo "[openvibe] verify only one ov_js_cmd exists in SDK copied source"
--grep -n "ConCommand ov_js_cmd" "$SDK/src/game/server/hl2mp/openvibe_js_server.cpp" || true
--count="$(grep -c "ConCommand ov_js_cmd" "$SDK/src/game/server/hl2mp/openvibe_js_server.cpp" || true)"
--if [[ "$count" != "1" ]]; then
-- echo "[openvibe] ERROR: expected exactly one ov_js_cmd, got $count" >&2
-- exit 1
--fi
--
--echo "[openvibe] build SDK"
--tools/build-sdk-linux.sh 2>&1 | tee "$HOME/ov-build.log"
--
--echo "[openvibe] setup OpenVibe bin"
--if [[ -x tools/setup-openvibe-bin.sh ]]; then
-- tools/setup-openvibe-bin.sh
--else
-- echo "[openvibe] WARNING: tools/setup-openvibe-bin.sh not found/executable; skipping"
--fi
--
--echo
--echo "[openvibe] phase build complete."
--echo
--echo "Next runtime test:"
--echo " OPENVIBE_SRCDS_MAP_DELAY=3 tools/dev-up.sh"
--echo
--echo "Server console tests:"
--echo " ov_js_status"
--echo " ov_js_fire Initialize"
--echo " ov_js_cmd smoke"
--echo
--echo "Client tests:"
--echo " connect 127.0.0.1:27015"
--echo " say !js"
--echo " say !hp"
--echo " say !players"
ddiff --git a/tools/fix-gh-repo-slug-clean-artifacts-and-rerun.sh b/tools/fix-gh-repo-slug-clean-artifacts-and-rerun.sh
ddeleted file mode 100755
iindex 6684e1f..0000000
---- a/tools/fix-gh-repo-slug-clean-artifacts-and-rerun.sh
++++ /dev/null
@@@ -1,245 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--say() { echo "[openvibe] $*"; }
--warn() { echo "[openvibe warn] $*" >&2; }
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--cd "$ROOT"
--BRANCH="$(git rev-parse --abbrev-ref HEAD)"
--WORKFLOW="windows-source-sdk-dlls.yml"
--
--say "fix GitHub repo slug + clean committed diagnostics + rerun Windows DLL build"
--say "root=$ROOT"
--say "branch=$BRANCH"
--
--mkdir -p tools game/openvibe.games/bin
--
--cat > tools/openvibe-gh-repo.sh <<'EOS'
--#!/usr/bin/env bash
--set -euo pipefail
--url="${1:-$(git config --get remote.origin.url)}"
--url="${url#[email protected]:}"
--url="${url#ssh://[email protected]/}"
--url="${url#https://github.com/}"
--url="${url#http://github.com/}"
--url="${url%.git}"
--url="${url%/}"
--if [[ ! "$url" =~ ^[^/]+/[^/]+$ ]]; then
-- echo "Could not normalize GitHub repo from remote.origin.url: ${url}" >&2
-- exit 1
--fi
--echo "$url"
--EOS
--chmod +x tools/openvibe-gh-repo.sh
--
--REPO="$(tools/openvibe-gh-repo.sh)"
--say "repo=$REPO"
--
--# Make sure debug outputs never get committed again.
--touch .gitignore
--python3 - <<'PY'
--from pathlib import Path
--p = Path('.gitignore')
--lines = p.read_text().splitlines() if p.exists() else []
--want = [
-- '',
-- '# OpenVibe local/generated build diagnostics',
-- 'artifacts/',
-- '_deps/',
-- '*.tmp',
-- '*.log',
--]
--existing = set(lines)
--changed = False
--for line in want:
-- if line and line not in existing:
-- lines.append(line)
-- existing.add(line)
-- changed = True
-- elif line == '' and (not lines or lines[-1] != ''):
-- lines.append(line)
-- changed = True
--if changed:
-- p.write_text('\n'.join(lines).rstrip() + '\n')
--PY
--
--# Remove previously committed workflow debug artifacts from git, but keep local files if present.
--if git ls-files artifacts | grep -q .; then
-- say "removing committed artifacts/ from git index"
-- git rm -r --cached artifacts >/dev/null || true
--fi
--
--cat > tools/windows-workflow-debug-and-install.sh <<'EOS'
--#!/usr/bin/env bash
--set -euo pipefail
--say() { echo "[openvibe] $*"; }
--warn() { echo "[openvibe warn] $*" >&2; }
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--cd "$ROOT"
--WORKFLOW="${OPENVIBE_WORKFLOW:-windows-source-sdk-dlls.yml}"
--BRANCH="${OPENVIBE_BRANCH:-$(git rev-parse --abbrev-ref HEAD)}"
--REPO="${OPENVIBE_REPO:-$(tools/openvibe-gh-repo.sh)}"
--RUN_ID="${1:-}"
--
--if ! command -v gh >/dev/null 2>&1; then
-- echo "GitHub CLI gh is missing. Install/auth it first: sudo apt install gh && gh auth login" >&2
-- exit 1
--fi
--
--if [[ -z "$RUN_ID" ]]; then
-- RUN_ID="$(gh run list --repo "$REPO" --workflow "$WORKFLOW" --branch "$BRANCH" --limit 1 --json databaseId --jq '.[0].databaseId')"
--fi
--if [[ -z "$RUN_ID" || "$RUN_ID" == "null" ]]; then
-- echo "Could not find a workflow run for $REPO $WORKFLOW on $BRANCH" >&2
-- exit 1
--fi
--
--OUT="$ROOT/artifacts/windows-workflow-debug/run-${RUN_ID}-artifacts"
--rm -rf "$OUT"
--mkdir -p "$OUT"
--
--say "repo=$REPO"
--say "run=$RUN_ID"
--say "downloading logs/artifacts into $OUT"
--
--gh run view "$RUN_ID" --repo "$REPO" --log > "$OUT/full-run.log" 2>&1 || true
--gh run download "$RUN_ID" --repo "$REPO" --dir "$OUT" >/dev/null 2>&1 || true
--find "$OUT" -type f | sort > "$OUT/file-list.txt"
--
--say "likely errors:"
--if ! grep -RInE "error |error:|fatal:|failed|Exception|No patched|MSB[0-9]+|LNK[0-9]+|C[0-9]{4}" "$OUT" | head -n 80; then
-- warn "no obvious errors found in downloaded logs"
--fi
--
--say "artifact files:"
--cat "$OUT/file-list.txt"
--
--CLIENT=""
--SERVER=""
--while IFS= read -r f; do
-- case "$(basename "$f")" in
-- client.dll) CLIENT="$f" ;;
-- server.dll) SERVER="$f" ;;
-- esac
--done < <(find "$OUT" -type f \( -iname 'client.dll' -o -iname 'server.dll' \) | sort)
--
--if [[ -n "$CLIENT" && -n "$SERVER" ]]; then
-- say "candidate client=$CLIENT"
-- say "candidate server=$SERVER"
-- if strings "$CLIENT" | grep -Eq 'ov_join|ov_menu|OpenVibe'; then
-- mkdir -p game/openvibe.games/bin
-- cp -f "$CLIENT" game/openvibe.games/bin/client.dll
-- cp -f "$SERVER" game/openvibe.games/bin/server.dll
-- say "installed patched Windows DLLs"
-- if [[ -x tools/verify-openvibe-dll-content.sh ]]; then
-- tools/verify-openvibe-dll-content.sh || true
-- else
-- strings game/openvibe.games/bin/client.dll | grep -E 'ov_join|ov_menu|OpenVibe' | head || true
-- fi
-- exit 0
-- else
-- warn "DLLs were present but do not contain OpenVibe strings; not installing stale/stock DLLs"
-- fi
--else
-- warn "no client.dll/server.dll artifact available yet"
--fi
--
--say "next useful tails:"
--for f in \
-- "$OUT/full-run.log" \
-- "$OUT/openvibe-windows-build-debug/bootstrap-source-sdk-2013.log" \
-- "$OUT/openvibe-windows-build-debug/bootstrap-curl-master-zip.log" \
-- "$OUT/openvibe-windows-build-debug/bootstrap-git-clone-master.log" \
-- "$OUT/openvibe-windows-build-debug/build-sdk-windows.log"; do
-- if [[ -f "$f" ]]; then
-- echo "----- $f -----"
-- tail -n 80 "$f" || true
-- fi
--done
--exit 2
--EOS
--chmod +x tools/windows-workflow-debug-and-install.sh
--
--cat > tools/trigger-windows-dll-build-clean.sh <<'EOS'
--#!/usr/bin/env bash
--set -euo pipefail
--say() { echo "[openvibe] $*"; }
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--cd "$ROOT"
--WORKFLOW="${OPENVIBE_WORKFLOW:-windows-source-sdk-dlls.yml}"
--BRANCH="${OPENVIBE_BRANCH:-$(git rev-parse --abbrev-ref HEAD)}"
--REPO="${OPENVIBE_REPO:-$(tools/openvibe-gh-repo.sh)}"
--
--if ! command -v gh >/dev/null 2>&1; then
-- echo "GitHub CLI gh is missing. Install/auth it first: sudo apt install gh && gh auth login" >&2
-- exit 1
--fi
--
--gh auth status >/dev/null
--say "repo=$REPO"
--say "workflow=$WORKFLOW"
--say "branch=$BRANCH"
--
--git push
--say "triggering workflow"
--gh workflow run "$WORKFLOW" --repo "$REPO" --ref "$BRANCH" -f diagnostics=1
--
--say "waiting for new run to appear"
--sleep 8
--RUN_ID="$(gh run list --repo "$REPO" --workflow "$WORKFLOW" --branch "$BRANCH" --limit 1 --json databaseId --jq '.[0].databaseId')"
--say "watching run $RUN_ID"
--if gh run watch "$RUN_ID" --repo "$REPO" --exit-status; then
-- say "workflow passed"
--else
-- warn_msg="workflow failed; downloading diagnostics anyway"
-- echo "[openvibe warn] $warn_msg" >&2
--fi
--
--tools/windows-workflow-debug-and-install.sh "$RUN_ID" || true
--EOS
--chmod +x tools/trigger-windows-dll-build-clean.sh
--
--# Try to patch older helper scripts so the .git suffix never breaks gh API calls again.
--for f in \
-- tools/request-windows-dll-build.sh \
-- tools/gh-windows-build-and-install.sh \
-- tools/next-phase-platform-build-and-fetch.sh \
-- tools/fix-windows-bootstrap-download-master-and-rerun.sh \
-- tools/fix-windows-bootstrap-mp-branch-and-rerun.sh \
-- tools/fix-windows-sdk-bootstrap-and-real-dlls.sh \
-- tools/fix-windows-workflow-diagnostics-and-rerun.sh; do
-- [[ -f "$f" ]] || continue
-- python3 - "$f" <<'PY'
--from pathlib import Path
--import re, sys
--p = Path(sys.argv[1])
--s = p.read_text()
--# Add a harmless normalization immediately after simple REPO assignments derived from remote.origin.url.
--s = re.sub(r'(REPO="\$\([^\n]*remote\.origin\.url[^\n]*\)"\n)', r'\1REPO="${REPO%.git}"\n', s)
--s = re.sub(r"(REPO='\$\([^\n]*remote\.origin\.url[^\n]*\)'\n)", r"\1REPO=\"${REPO%.git}\"\n", s)
--# Also strip .git after any existing REPO assignment block if not already nearby.
--if 'REPO="${REPO%.git}"' not in s and 'gh workflow' in s:
-- s = s.replace('WORKFLOW=', 'REPO="${REPO%.git}"\nWORKFLOW=', 1)
--p.write_text(s)
--PY
-- chmod +x "$f" 2>/dev/null || true
--done
--
--say "git status after cleanup/patch"
--git status --short
--
--if ! git diff --quiet || ! git diff --cached --quiet; then
-- git add .gitignore tools/openvibe-gh-repo.sh tools/windows-workflow-debug-and-install.sh tools/trigger-windows-dll-build-clean.sh tools/*.sh 2>/dev/null || true
-- if git diff --cached --quiet; then
-- say "nothing staged to commit"
-- else
-- git commit -m "Fix GitHub workflow repo slug and ignore diagnostics"
-- fi
--else
-- say "no cleanup changes to commit"
--fi
--
--git push
--say "running clean Windows DLL workflow trigger"
--tools/trigger-windows-dll-build-clean.sh
ddiff --git a/tools/fix-js-gamemode-scope-and-smoke.sh b/tools/fix-js-gamemode-scope-and-smoke.sh
ddeleted file mode 100755
iindex e3c3256..0000000
---- a/tools/fix-js-gamemode-scope-and-smoke.sh
++++ /dev/null
@@@ -1,713 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--SDK="${OPENVIBE_SDK:-$ROOT/engine/source-sdk-2013}"
--STAMP="$(date +%Y%m%d-%H%M%S)"
--
--cd "$ROOT"
--
--echo "[openvibe] fix JS global-scope redeclare and continue command/timer phase"
--echo "[openvibe] root=$ROOT"
--echo "[openvibe] sdk=$SDK"
--
--backup_file() {
-- local file="$1"
-- [[ -f "$file" ]] && cp "$file" "$file.bak.$STAMP"
--}
--
--mkdir -p \
-- game/openvibe.games/js/core \
-- game/openvibe.games/js/gamemodes/base \
-- game/openvibe.games/js/gamemodes/hub \
-- game/openvibe.games/js/gamemodes/prophunt \
-- game/openvibe.games/js/gamemodes/deathrun \
-- game/openvibe.games/js/gamemodes/fortwars \
-- game/openvibe.games/js/gamemodes/traitortown \
-- tools
--
--echo "[openvibe] writing canonical command.js"
--backup_file game/openvibe.games/js/core/command.js
--cat > game/openvibe.games/js/core/command.js <<'JS'
--(function () {
-- const commands = new Map();
--
-- function say(target, message) {
-- const text = String(message);
-- if (target && typeof target.chat === "function") {
-- target.chat(text);
-- } else {
-- OV.log(text);
-- }
-- }
--
-- function normalizeName(name) {
-- return String(name || "").trim().replace(/^[!/]+/, "").toLowerCase();
-- }
--
-- function parse(text) {
-- const raw = String(text || "").trim();
-- const withoutPrefix = raw.replace(/^[!/]+/, "");
-- const parts = withoutPrefix.length ? withoutPrefix.split(/\s+/) : [];
-- const name = normalizeName(parts.shift() || "");
-- return { raw, name, args: parts };
-- }
--
-- globalThis.command = {
-- add(name, descriptionOrFn, maybeFn) {
-- const key = normalizeName(name);
-- if (!key) throw new Error("command.add requires command name");
--
-- let description = "";
-- let fn = maybeFn;
--
-- if (typeof descriptionOrFn === "function") {
-- fn = descriptionOrFn;
-- } else {
-- description = String(descriptionOrFn || "");
-- }
--
-- if (typeof fn !== "function") throw new Error(`command '${key}' requires function`);
-- commands.set(key, { name: key, description, fn });
-- },
--
-- remove(name) {
-- return commands.delete(normalizeName(name));
-- },
--
-- has(name) {
-- return commands.has(normalizeName(name));
-- },
--
-- list() {
-- return Array.from(commands.values()).map((entry) => ({
-- name: entry.name,
-- description: entry.description
-- }));
-- },
--
-- run(source, text, ...extra) {
-- const parsed = parse(text);
-- if (!parsed.name) return undefined;
--
-- const entry = commands.get(parsed.name);
-- if (!entry) {
-- say(source, `Unknown command: ${parsed.name}. Try !help.`);
-- return source ? false : undefined;
-- }
--
-- try {
-- const result = entry.fn(source, parsed.args, parsed.raw, ...extra);
-- return result === undefined ? false : result;
-- } catch (err) {
-- OV.error(`[command:${parsed.name}] ${err && err.stack ? err.stack : err}`);
-- say(source, `Command failed: ${parsed.name}`);
-- return false;
-- }
-- }
-- };
--
-- hook.add("ConsoleCommand", "OpenVibeCommandConsole", function (text) {
-- return command.run(null, text);
-- });
--
-- hook.add("PlayerSay", "OpenVibeCommandChat", function (ply, text) {
-- const msg = String(text || "").trim();
-- if (!msg.startsWith("!")) return undefined;
-- return command.run(ply, msg.slice(1));
-- });
--})();
--JS
--
--echo "[openvibe] writing canonical timer.js"
--backup_file game/openvibe.games/js/core/timer.js
--cat > game/openvibe.games/js/core/timer.js <<'JS'
--(function () {
-- const timers = new Map();
-- let nextId = 1;
--
-- function now() {
-- if (typeof OV !== "undefined" && typeof OV.time === "function") return Number(OV.time()) || 0;
-- return Date.now() / 1000;
-- }
--
-- function makeId() {
-- return `timer_${nextId++}`;
-- }
--
-- globalThis.timer = {
-- create(id, delay, repetitions, fn) {
-- if (typeof id !== "string" || !id) throw new Error("timer.create requires id");
-- if (typeof fn !== "function") throw new Error("timer.create requires callback");
--
-- const safeDelay = Math.max(0, Number(delay) || 0);
-- const reps = repetitions === undefined ? 1 : Number(repetitions);
--
-- timers.set(id, {
-- id,
-- delay: safeDelay,
-- repetitions: reps,
-- fn,
-- next: now() + safeDelay,
-- running: true
-- });
--
-- return id;
-- },
--
-- simple(delay, fn) {
-- return timer.create(makeId(), delay, 1, fn);
-- },
--
-- remove(id) {
-- return timers.delete(String(id));
-- },
--
-- exists(id) {
-- return timers.has(String(id));
-- },
--
-- count() {
-- return timers.size;
-- },
--
-- tick() {
-- const t = now();
--
-- for (const [id, item] of Array.from(timers.entries())) {
-- if (!item.running || t < item.next) continue;
--
-- try {
-- item.fn();
-- } catch (err) {
-- OV.error(`[timer:${id}] ${err && err.stack ? err.stack : err}`);
-- }
--
-- if (item.repetitions > 0) item.repetitions -= 1;
--
-- if (item.repetitions === 0) {
-- timers.delete(id);
-- } else {
-- item.next = t + item.delay;
-- }
-- }
-- },
--
-- clear() {
-- timers.clear();
-- }
-- };
--
-- hook.add("Think", "OpenVibeTimerThink", function () {
-- timer.tick();
-- return undefined;
-- });
--})();
--JS
--
--echo "[openvibe] writing scoped base gamemode"
--backup_file game/openvibe.games/js/gamemodes/base/server.js
--cat > game/openvibe.games/js/gamemodes/base/server.js <<'JS'
--(function () {
-- function say(target, message) {
-- if (target && typeof target.chat === "function") target.chat(String(message));
-- else OV.log(String(message));
-- }
--
-- command.add("help", "List available OpenVibe commands.", function (source) {
-- const names = command.list().map((entry) => `!${entry.name}`).sort().join(", ");
-- say(source, `Commands: ${names}`);
-- return false;
-- });
--
-- command.add("smoke", "Run embedded JS smoke test.", function (source) {
-- const message = "OpenVibe embedded JS smoke test passed.";
-- if (source) say(source, message);
-- OV.broadcast(message);
-- return false;
-- });
--
-- command.add("timer_smoke", "Run timer smoke test.", function (source) {
-- timer.simple(0, function () {
-- OV.broadcast("OpenVibe timer smoke fired.");
-- });
-- timer.tick();
-- say(source, "Timer smoke scheduled.");
-- return false;
-- });
--
-- const GM = {
-- mode: "base",
-- name: "OpenVibe Base",
--
-- Initialize() {
-- OV.log("Base Initialize fired");
-- },
--
-- MapInitialize(mapName) {
-- OV.log(`Base MapInitialize: ${mapName}`);
-- },
--
-- PlayerInitialSpawn(ply) {
-- ply.chat("Welcome to OpenVibe: Source.");
-- },
--
-- PlayerSpawn(_ply) {},
--
-- PlayerDeath(_victim, _attacker) {},
--
-- PlayerDisconnected(_ply) {},
--
-- PlayerSay(ply, text) {
-- const msg = String(text || "").trim();
-- if (msg.startsWith("!")) return command.run(ply, msg.slice(1));
-- return undefined;
-- },
--
-- ConsoleCommand(text) {
-- return command.run(null, text);
-- },
--
-- Think() {
-- timer.tick();
-- }
-- };
--
-- gamemode.set(GM);
--})();
--JS
--
--echo "[openvibe] writing scoped hub gamemode"
--backup_file game/openvibe.games/js/gamemodes/hub/server.js
--cat > game/openvibe.games/js/gamemodes/hub/server.js <<'JS'
--(function () {
-- command.add("hub_status", "Print hub runtime status.", function (source) {
-- const message = `Hub mode online. Map=${OV.getMapName()} Players=${OV.players().length}`;
-- if (source && typeof source.chat === "function") source.chat(message);
-- else OV.log(message);
-- return false;
-- });
--
-- command.add("where", "Show current map/mode.", function (source) {
-- const message = `You are on ${OV.getMapName()} in ${OV.getMode()} mode.`;
-- if (source && typeof source.chat === "function") source.chat(message);
-- else OV.log(message);
-- return false;
-- });
--
-- const GM = {
-- mode: "hub",
-- name: "OpenVibe Hub",
--
-- Initialize() {
-- OV.log("Hub Initialize fired");
-- },
--
-- MapInitialize(mapName) {
-- OV.log(`Map initialized: ${mapName}`);
-- },
--
-- PlayerInitialSpawn(ply) {
-- ply.chat("Welcome to OpenVibe: Source JS runtime.");
-- OV.broadcast(`${ply.name()} joined the hub.`);
-- },
--
-- PlayerSpawn(_ply) {},
--
-- PlayerSay(ply, text) {
-- const msg = String(text || "").trim();
--
-- if (msg === "!js") {
-- ply.chat("JavaScript hooks are working.");
-- return false;
-- }
--
-- if (msg === "!hp") {
-- ply.chat(`Health: ${ply.health()}`);
-- return false;
-- }
--
-- if (msg === "!players") {
-- ply.chat(`Players online: ${OV.players().length}`);
-- return false;
-- }
--
-- if (msg.startsWith("!")) return command.run(ply, msg.slice(1));
-- return undefined;
-- },
--
-- ConsoleCommand(text) {
-- OV.log(`Hub ConsoleCommand: ${text}`);
-- return command.run(null, text);
-- },
--
-- Think() {
-- timer.tick();
-- }
-- };
--
-- gamemode.set(GM);
--})();
--JS
--
--echo "[openvibe] writing scoped prototype gamemodes"
--cat > game/openvibe.games/js/gamemodes/prophunt/server.js <<'JS'
--(function () {
-- const props = ["crate", "barrel", "chair", "bucket"];
--
-- function randomProp() {
-- return props[Math.floor(Math.random() * props.length)];
-- }
--
-- command.add("prop", "Prop Hunt disguise command.", function (ply, args) {
-- if (!ply) {
-- OV.log("prop command requires player");
-- return false;
-- }
--
-- const prop = args[0] || randomProp();
-- ply.runCommand(`ov_prophunt_disguise ${prop}`);
-- return false;
-- });
--
-- const GM = {
-- mode: "prophunt",
-- name: "OpenVibe Prop Hunt",
--
-- Initialize() {
-- OV.log("Prop Hunt Initialize fired");
-- },
--
-- PlayerInitialSpawn(ply) {
-- ply.chat("Prop Hunt: hide as props or hunt them down.");
-- },
--
-- PlayerSay(ply, text) {
-- const msg = String(text || "").trim();
-- if (msg.startsWith("!")) return command.run(ply, msg.slice(1));
-- return undefined;
-- },
--
-- Think() {
-- timer.tick();
-- }
-- };
--
-- gamemode.set(GM);
--})();
--JS
--
--cat > game/openvibe.games/js/gamemodes/deathrun/server.js <<'JS'
--(function () {
-- command.add("finish", "Deathrun finish smoke command.", function (ply) {
-- if (ply) ply.chat("Deathrun finish test.");
-- return false;
-- });
--
-- const GM = {
-- mode: "deathrun",
-- name: "OpenVibe Deathrun",
--
-- Initialize() {
-- OV.log("Deathrun Initialize fired");
-- },
--
-- PlayerInitialSpawn(ply) {
-- ply.chat("Deathrun: survive the traps and reach the finish.");
-- },
--
-- PlayerSay(ply, text) {
-- const msg = String(text || "").trim();
-- if (msg.startsWith("!")) return command.run(ply, msg.slice(1));
-- return undefined;
-- },
--
-- Think() {
-- timer.tick();
-- }
-- };
--
-- gamemode.set(GM);
--})();
--JS
--
--cat > game/openvibe.games/js/gamemodes/fortwars/server.js <<'JS'
--(function () {
-- const allowed = new Set(["crate", "barrel", "pallet", "fence", "sheet"]);
--
-- command.add("build", "Fort Wars build command.", function (ply, args) {
-- if (!ply) {
-- OV.log("build command requires player");
-- return false;
-- }
--
-- const part = args[0] || "crate";
-- if (!allowed.has(part)) {
-- ply.chat("Allowed: crate, barrel, pallet, fence, sheet");
-- return false;
-- }
--
-- ply.runCommand(`ov_fortwars_spawn ${part}`);
-- return false;
-- });
--
-- const GM = {
-- mode: "fortwars",
-- name: "OpenVibe Fort Wars",
--
-- Initialize() {
-- OV.log("Fort Wars Initialize fired");
-- },
--
-- PlayerInitialSpawn(ply) {
-- ply.chat("Fort Wars: build first, fight second.");
-- },
--
-- PlayerSay(ply, text) {
-- const msg = String(text || "").trim();
-- if (msg.startsWith("!")) return command.run(ply, msg.slice(1));
-- return undefined;
-- },
--
-- Think() {
-- timer.tick();
-- }
-- };
--
-- gamemode.set(GM);
--})();
--JS
--
--cat > game/openvibe.games/js/gamemodes/traitortown/server.js <<'JS'
--(function () {
-- const roles = new Map();
--
-- command.add("role", "Show your Traitor Town role.", function (ply) {
-- if (!ply) {
-- OV.log("role command requires player");
-- return false;
-- }
--
-- ply.chat(`Your role: ${roles.get(ply.userId()) || "innocent"}`);
-- return false;
-- });
--
-- const GM = {
-- mode: "traitortown",
-- name: "OpenVibe Traitor Town",
--
-- Initialize() {
-- OV.log("Traitor Town Initialize fired");
-- },
--
-- PlayerInitialSpawn(ply) {
-- ply.chat("Traitor Town: find the traitors before they find you.");
-- roles.set(ply.userId(), "innocent");
-- },
--
-- PlayerSay(ply, text) {
-- const msg = String(text || "").trim();
-- if (msg.startsWith("!")) return command.run(ply, msg.slice(1));
-- return undefined;
-- },
--
-- PlayerDeath(victim, _attacker) {
-- OV.broadcast(`${victim.name()} died.`);
-- },
--
-- Think() {
-- timer.tick();
-- }
-- };
--
-- gamemode.set(GM);
--})();
--JS
--
--echo "[openvibe] patching runtime core load order"
--backup_file sdk/openvibe/shared/ov_js_runtime.cpp
--python3 <<'PY'
--from pathlib import Path
--import re
--
--p = Path("sdk/openvibe/shared/ov_js_runtime.cpp")
--s = p.read_text()
--
--new_func = r'''bool COpenVibeJSRuntime::LoadCoreFiles()
--{
-- if (!LoadFile("js/core/hook.js"))
-- return false;
--
-- if (!LoadFile("js/core/gamemode.js"))
-- return false;
--
-- if (!LoadFile("js/bridge.js"))
-- return false;
--
-- if (!LoadFile("js/core/command.js"))
-- return false;
--
-- if (!LoadFile("js/core/timer.js"))
-- return false;
--
-- return true;
--}'''
--
--s2 = re.sub(
-- r'bool\s+COpenVibeJSRuntime::LoadCoreFiles\s*\(\s*\)\s*\{.*?\n\}',
-- new_func,
-- s,
-- count=1,
-- flags=re.S,
--)
--
--if s2 == s:
-- raise SystemExit("Could not patch COpenVibeJSRuntime::LoadCoreFiles")
--
--p.write_text(s2)
--PY
--
--echo "[openvibe] writing robust Node JS core smoke test"
--backup_file tools/smoke-js-core-node.mjs
--cat > tools/smoke-js-core-node.mjs <<'MJS'
--import fs from "node:fs";
--import path from "node:path";
--import vm from "node:vm";
--
--const root = process.env.OPENVIBE_ROOT || process.cwd();
--let fakeTime = 0;
--const logs = [];
--const broadcasts = [];
--const chats = [];
--const serverCommands = [];
--
--const context = {
-- console,
-- globalThis: null,
-- OV: {
-- log: (msg) => logs.push(String(msg)),
-- warn: (msg) => logs.push(`WARN ${String(msg)}`),
-- error: (msg) => logs.push(`ERROR ${String(msg)}`),
-- getMode: () => "hub",
-- getMapName: () => "ov_hub",
-- time: () => fakeTime,
-- broadcast: (msg) => broadcasts.push(String(msg)),
-- players: () => [mockPlayer],
-- playerByUserId: (id) => Number(id) === 1 ? mockPlayer : null,
-- serverCommand: (cmd) => serverCommands.push(String(cmd)),
-- fireHook: (...args) => context.gamemode.call(...args),
-- reward: () => undefined,
-- endMatch: () => undefined
-- }
--};
--
--const mockPlayer = {
-- userId: () => 1,
-- entIndex: () => 1,
-- steamId: () => "STEAM_0:1:1",
-- name: () => "SmokePlayer",
-- health: () => 100,
-- setHealth: () => undefined,
-- team: () => 0,
-- setTeam: () => undefined,
-- chat: (msg) => chats.push(String(msg)),
-- runCommand: (cmd) => serverCommands.push(String(cmd))
--};
--
--context.globalThis = context;
--vm.createContext(context);
--
--function load(relativePath) {
-- const full = path.join(root, relativePath);
-- const source = fs.readFileSync(full, "utf8");
-- vm.runInContext(source, context, { filename: relativePath });
--}
--
--for (const file of [
-- "game/openvibe.games/js/core/hook.js",
-- "game/openvibe.games/js/core/gamemode.js",
-- "game/openvibe.games/js/bridge.js",
-- "game/openvibe.games/js/core/command.js",
-- "game/openvibe.games/js/core/timer.js",
-- "game/openvibe.games/js/gamemodes/base/server.js",
-- "game/openvibe.games/js/gamemodes/hub/server.js"
--]) {
-- load(file);
--}
--
--if (!context.hook) throw new Error("hook global missing");
--if (!context.gamemode) throw new Error("gamemode global missing");
--if (!context.command) throw new Error("command global missing");
--if (!context.timer) throw new Error("timer global missing");
--if (!context.gamemode.get()) throw new Error("gamemode was not set");
--
--context.gamemode.call("Initialize");
--context.gamemode.call("MapInitialize", "ov_hub");
--context.gamemode.call("ConsoleCommand", "help");
--context.gamemode.call("ConsoleCommand", "smoke");
--context.gamemode.call("ConsoleCommand", "timer_smoke");
--context.gamemode.call("ConsoleCommand", "hub_status");
--context.gamemode.call("PlayerInitialSpawn", mockPlayer);
--context.gamemode.call("PlayerSay", mockPlayer, "!help");
--context.gamemode.call("PlayerSay", mockPlayer, "!where");
--context.gamemode.call("PlayerSay", mockPlayer, "!js");
--context.gamemode.call("PlayerSay", mockPlayer, "!hp");
--context.gamemode.call("PlayerSay", mockPlayer, "!players");
--fakeTime += 1;
--context.gamemode.call("Think");
--context.timer.tick();
--
--const combined = [...logs, ...broadcasts, ...chats, ...serverCommands].join("\n");
--
--for (const expected of [
-- "Hub Initialize fired",
-- "OpenVibe embedded JS smoke test passed.",
-- "OpenVibe timer smoke fired.",
-- "JavaScript hooks are working.",
-- "Health: 100",
-- "Players online: 1",
-- "You are on ov_hub in hub mode."
--]) {
-- if (!combined.includes(expected)) {
-- console.error(combined);
-- throw new Error(`missing expected smoke output: ${expected}`);
-- }
--}
--
--console.log("[openvibe-smoke] JS core command/timer smoke passed");
--console.log(`[openvibe-smoke] logs=${logs.length} broadcasts=${broadcasts.length} chats=${chats.length}`);
--MJS
--
--echo "[openvibe] run JS core smoke test"
--node tools/smoke-js-core-node.mjs
--
--echo "[openvibe] apply SDK patch"
--tools/apply-openvibe-sdk.sh
--
--echo "[openvibe] build SDK"
--tools/build-sdk-linux.sh 2>&1 | tee "$HOME/ov-build.log"
--
--echo "[openvibe] setup OpenVibe bin"
--tools/setup-openvibe-bin.sh
--
--echo
--printf '%s\n' "[openvibe] JS command/timer phase complete."
--printf '%s\n' ""
--printf '%s\n' "Runtime test:"
--printf '%s\n' " OPENVIBE_SRCDS_MAP_DELAY=3 tools/dev-up.sh"
--printf '%s\n' ""
--printf '%s\n' "Server console tests:"
--printf '%s\n' " ov_js_status"
--printf '%s\n' " ov_js_fire Initialize"
--printf '%s\n' " ov_js_cmd help"
--printf '%s\n' " ov_js_cmd smoke"
--printf '%s\n' " ov_js_cmd timer_smoke"
--printf '%s\n' " ov_js_cmd hub_status"
--printf '%s\n' ""
--printf '%s\n' "Client tests:"
--printf '%s\n' " connect 127.0.0.1:27015"
--printf '%s\n' " say !help"
--printf '%s\n' " say !js"
--printf '%s\n' " say !hp"
--printf '%s\n' " say !players"
--printf '%s\n' " say !where"
ddiff --git a/tools/fix-launch-loading-and-explain-clientdll.sh b/tools/fix-launch-loading-and-explain-clientdll.sh
ddeleted file mode 100755
iindex a4db536..0000000
---- a/tools/fix-launch-loading-and-explain-clientdll.sh
++++ /dev/null
@@@ -1,344 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--cd "$ROOT"
--
--echo "[openvibe] next phase: launcher-aware Source loading + client DLL reality guard"
--echo "[openvibe] root=$ROOT"
--
--backup() {
-- local f="$1"
-- [[ -f "$f" ]] && cp "$f" "$f.bak.$(date +%Y%m%d-%H%M%S)"
--}
--
--backup launcher/main.js
--backup launcher/preload.js
--backup launcher/renderer.js
--backup launcher/index.html
--backup launcher/styles.css
--backup tools/run-client-proton.sh
--
--mkdir -p docs game/openvibe.games/resource game/openvibe.games/cfg launcher/assets
--
--cat > docs/CLIENT_DLL_AND_LOADING.md <<'DOC'
--# OpenVibe client DLL + loading screen notes
--
--The Proton launcher runs Windows `hl2.exe`. That Windows executable will not load the Linux
--`game/openvibe.games/bin/linux64/client.so` produced by the current Source SDK Linux build.
--
--That means native client commands such as `ov_join`, `ov_menu`, `ov_ui`, and the in-game
--CEF/HTML menu only exist when a matching client DLL is actually loaded by the game client.
--Until we build a Windows `client.dll` or switch to a native Linux client path, the Electron
--launcher is the reliable custom Chromium shell.
--
--This phase keeps the Electron launcher visible during Source startup, shows a clear launch
--state, and focuses the Source window only after it has appeared and stayed stable for a few
--seconds. It also installs best-effort Source resource overrides for the classic loading dialog.
--DOC
--
--cat > game/openvibe.games/cfg/autoexec.cfg <<'CFG'
--// OpenVibe: Source client defaults
--con_enable 1
--developer 1
--con_timestamp 1
--cl_showfps 0
--
--// These are server-side commands unless a matching client DLL is loaded.
--echo "[OpenVibe] autoexec loaded. If ov_join/ov_menu are unknown, the custom client DLL is not loaded."
--CFG
--
--cat > game/openvibe.games/resource/LoadingDialog.res <<'RES'
--"Resource/LoadingDialog.res"
--{
-- "LoadingDialog"
-- {
-- "ControlName" "Frame"
-- "fieldName" "LoadingDialog"
-- "xpos" "0"
-- "ypos" "0"
-- "wide" "f0"
-- "tall" "f0"
-- "autoResize" "0"
-- "pinCorner" "0"
-- "visible" "1"
-- "enabled" "1"
-- "tabPosition" "0"
-- "settitlebarvisible" "0"
-- "PaintBackgroundType" "2"
-- "bgcolor_override" "7 8 14 255"
-- }
--
-- "OpenVibeTitle"
-- {
-- "ControlName" "Label"
-- "fieldName" "OpenVibeTitle"
-- "xpos" "c-260"
-- "ypos" "c-72"
-- "wide" "520"
-- "tall" "42"
-- "visible" "1"
-- "enabled" "1"
-- "labelText" "OPENVIBE: SOURCE"
-- "textAlignment" "center"
-- "font" "DefaultVeryLarge"
-- "fgcolor_override" "0 220 255 255"
-- }
--
-- "OpenVibeStatus"
-- {
-- "ControlName" "Label"
-- "fieldName" "OpenVibeStatus"
-- "xpos" "c-300"
-- "ypos" "c-20"
-- "wide" "600"
-- "tall" "36"
-- "visible" "1"
-- "enabled" "1"
-- "labelText" "Loading OpenVibe shell, servers, and scripts..."
-- "textAlignment" "center"
-- "font" "DefaultLarge"
-- "fgcolor_override" "220 235 255 255"
-- }
--}
--RES
--
--cat > game/openvibe.games/resource/LoadingProgress.res <<'RES'
--"Resource/LoadingProgress.res"
--{
-- "LoadingProgress"
-- {
-- "ControlName" "Frame"
-- "fieldName" "LoadingProgress"
-- "xpos" "0"
-- "ypos" "0"
-- "wide" "f0"
-- "tall" "f0"
-- "visible" "1"
-- "enabled" "1"
-- "settitlebarvisible" "0"
-- "PaintBackgroundType" "2"
-- "bgcolor_override" "7 8 14 255"
-- }
--}
--RES
--
--python3 <<'PY'
--from pathlib import Path
--import re
--
--p = Path('launcher/main.js')
--s = p.read_text()
--
--if 'OPENVIBE_GAME_WINDOW_READY_TIMEOUT_MS' not in s:
-- marker = "let appIsQuitting = false;\n"
-- helpers = r'''
--
--const GAME_WINDOW_TITLE = process.env.OPENVIBE_GAME_WINDOW_TITLE || 'OpenVibe: Source';
--const GAME_READY_TIMEOUT_MS = Number(process.env.OPENVIBE_GAME_WINDOW_READY_TIMEOUT_MS || 45000);
--const GAME_STABLE_MS = Number(process.env.OPENVIBE_GAME_WINDOW_STABLE_MS || 8000);
--const HIDE_LAUNCHER_ON_GAME_READY = process.env.OPENVIBE_HIDE_LAUNCHER_ON_GAME_READY === '1';
--
--function sleep(ms) {
-- return new Promise((resolve) => setTimeout(resolve, ms));
--}
--
--function emitLaunchPhase(phase, message, extra = {}) {
-- if (mainWindow && !mainWindow.isDestroyed()) {
-- mainWindow.webContents.send('game-launch-phase', { phase, message, ...extra });
-- }
--}
--
--function safeExec(command) {
-- try {
-- return execSync(command, { stdio: ['ignore', 'pipe', 'ignore'] }).toString().trim();
-- } catch {
-- return '';
-- }
--}
--
--function findGameWindowId() {
-- // Linux desktop helper. If xdotool is not installed, the launcher simply stays visible.
-- const escaped = GAME_WINDOW_TITLE.replace(/"/g, '\\"');
-- const out = safeExec(`xdotool search --name "${escaped}" 2>/dev/null | tail -n 1`);
-- return out || null;
--}
--
--function focusGameWindow() {
-- const win = findGameWindowId();
-- if (!win) return false;
-- safeExec(`xdotool windowactivate ${win} 2>/dev/null || true`);
-- return true;
--}
--
--async function waitForGameWindowReady(timeoutMs = GAME_READY_TIMEOUT_MS) {
-- const started = Date.now();
-- let firstSeen = 0;
--
-- while (Date.now() - started < timeoutMs) {
-- const win = findGameWindowId();
-- if (win) {
-- if (!firstSeen) {
-- firstSeen = Date.now();
-- emitLaunchPhase('window-found', 'Source window found. Waiting for it to stop thrashing before switching focus...', { windowId: win });
-- }
--
-- if (Date.now() - firstSeen >= GAME_STABLE_MS) {
-- return { ready: true, windowId: win, reason: 'window-stable' };
-- }
-- }
--
-- await sleep(500);
-- }
--
-- return { ready: false, windowId: findGameWindowId(), reason: 'timeout' };
--}
--'''
-- s = s.replace(marker, marker + helpers)
--
--old = """ mainWindow?.webContents.send('game-started', gameProcess.pid);\n setTimeout(() => {\n if (gameProcess && mainWindow && !mainWindow.isDestroyed()) mainWindow.hide();\n }, 750);\n"""
--new = """ emitLaunchPhase(\n 'spawned',\n 'Source has started. OpenVibe will keep this launcher visible until the game window is detected and stable.'\n );\n mainWindow?.webContents.send('game-started', gameProcess.pid);\n\n waitForGameWindowReady().then((state) => {\n if (!gameProcess) return;\n\n if (state.ready) {\n emitLaunchPhase(\n 'ready',\n HIDE_LAUNCHER_ON_GAME_READY\n ? 'Source window looks ready. Focusing game and hiding launcher.'\n : 'Source window looks ready. Launcher will stay open so you are not stuck behind a frozen loading screen.',\n state\n );\n focusGameWindow();\n\n if (HIDE_LAUNCHER_ON_GAME_READY) {\n if (mainWindow && !mainWindow.isDestroyed()) mainWindow.hide();\n } else {\n if (mainWindow && !mainWindow.isDestroyed()) {\n mainWindow.show();\n mainWindow.focus();\n }\n }\n } else {\n emitLaunchPhase(\n 'slow',\n 'Source is taking longer than expected. The launcher is staying visible; use Focus Game when the window appears.',\n state\n );\n if (mainWindow && !mainWindow.isDestroyed()) {\n mainWindow.show();\n mainWindow.focus();\n }\n }\n });\n"""
--if old in s:
-- s = s.replace(old, new)
--elif "setTimeout(() =>" in s and "mainWindow.hide" in s:
-- s = re.sub(r"\s*mainWindow\?\.webContents\.send\('game-started', gameProcess\.pid\);\s*setTimeout\(\(\) => \{\s*if \(gameProcess && mainWindow && !mainWindow\.isDestroyed\(\)\) mainWindow\.hide\(\);\s*\}, 750\);", "\n" + new, s, count=1, flags=re.S)
--
--if "ipcMain.handle('game:focus'" not in s:
-- insert_after = "ipcMain.handle('game:status', () => ({\n running: gameProcess !== null,\n pid: gameProcess?.pid ?? null,\n}));\n"
-- addition = """
--
--ipcMain.handle('game:focus', async () => {
-- return focusGameWindow();
--});
--
--ipcMain.handle('launcher:show', async () => {
-- if (mainWindow && !mainWindow.isDestroyed()) {
-- mainWindow.show();
-- mainWindow.focus();
-- }
-- return true;
--});
--"""
-- if insert_after in s:
-- s = s.replace(insert_after, insert_after + addition)
-- else:
-- s += addition
--
--p.write_text(s)
--
--p = Path('launcher/preload.js')
--s = p.read_text()
--if 'focusGame:' not in s:
-- s = s.replace(" gameStatus: () => ipcRenderer.invoke('game:status'),", " gameStatus: () => ipcRenderer.invoke('game:status'),\n focusGame: () => ipcRenderer.invoke('game:focus'),")
--if 'showLauncher:' not in s:
-- s = s.replace(" focusGame: () => ipcRenderer.invoke('game:focus'),", " focusGame: () => ipcRenderer.invoke('game:focus'),\n showLauncher: () => ipcRenderer.invoke('launcher:show'),")
--if 'onLaunchPhase:' not in s:
-- s = s.replace(" onGameStart: (cb) => ipcRenderer.on('game-started', (_e, pid) => cb(pid)),", " onGameStart: (cb) => ipcRenderer.on('game-started', (_e, pid) => cb(pid)),\n onLaunchPhase: (cb) => ipcRenderer.on('game-launch-phase', (_e, info) => cb(info)),")
--p.write_text(s)
--
--p = Path('launcher/index.html')
--s = p.read_text()
--if 'launch-help' not in s:
-- s = s.replace(
-- '<div class="launch-label" id="launch-label">Launching…</div>\n <button class="launch-cancel" id="launch-cancel">Cancel</button>',
-- '<div class="launch-label" id="launch-label">Launching…</div>\n <div class="launch-help" id="launch-help">OpenVibe will keep this launcher visible until Source is actually ready.</div>\n <div class="launch-actions">\n <button class="launch-cancel" id="launch-focus-game">Focus Game Window</button>\n <button class="launch-cancel" id="launch-cancel">Dismiss</button>\n </div>'
-- )
--p.write_text(s)
--
--p = Path('launcher/styles.css')
--s = p.read_text()
--if '.launch-help' not in s:
-- s += r'''
--
--/* OpenVibe launch guard */
--.launch-help {
-- margin-top: 10px;
-- max-width: 620px;
-- color: rgba(220, 235, 255, 0.78);
-- font-size: 14px;
-- line-height: 1.35;
-- text-align: center;
--}
--.launch-actions {
-- display: flex;
-- gap: 10px;
-- justify-content: center;
-- flex-wrap: wrap;
-- margin-top: 18px;
--}
--'''
--p.write_text(s)
--
--p = Path('launcher/renderer.js')
--s = p.read_text()
--if 'focusGame: () =>' not in s:
-- s = s.replace(" gameStatus: () => isElectron ? window.OV.gameStatus() : Promise.resolve({ running: true, pid: null }),", " gameStatus: () => isElectron ? window.OV.gameStatus() : Promise.resolve({ running: true, pid: null }),\n focusGame: () => isElectron ? window.OV.focusGame() : Promise.resolve(false),")
--
--if "launch-focus-game" not in s:
-- s = s.replace("document.getElementById('launch-cancel')?.addEventListener('click', () => {\n launchOverlay.classList.remove('show');\n});", "document.getElementById('launch-cancel')?.addEventListener('click', () => {\n launchOverlay.classList.remove('show');\n});\n\ndocument.getElementById('launch-focus-game')?.addEventListener('click', async () => {\n const ok = await Bridge.focusGame();\n toast(ok ? 'Focused Source window.' : 'Source window not found yet.', !ok);\n});")
--
--if 'onLaunchPhase' not in s:
-- s = s.replace(" window.OV.onGameStart?.(() => {\n updateGameStatus(true);\n });", " window.OV.onGameStart?.(() => {\n updateGameStatus(true);\n });\n window.OV.onLaunchPhase?.((info) => {\n const label = document.getElementById('launch-label');\n const help = document.getElementById('launch-help');\n if (label && info?.message) label.textContent = info.message;\n if (help) {\n if (info?.phase === 'ready') {\n help.textContent = 'The game window is available. Because the Proton client may not load the custom client DLL, keep this launcher open as the reliable OpenVibe UI.';\n } else if (info?.phase === 'slow') {\n help.textContent = 'The game may still be loading or frozen. The launcher is staying visible so you can retry, focus the game, or inspect logs.';\n } else {\n help.textContent = 'OpenVibe is waiting for the Source window to appear and stabilize before switching focus.';\n }\n }\n launchOverlay?.classList.add('show');\n });")
--p.write_text(s)
--PY
--
--python3 <<'PY'
--from pathlib import Path
--p = Path('tools/run-client-proton.sh')
--s = p.read_text()
--if '-condebug' not in s:
-- s = s.replace('-console -dev -novid -sw -w 1280 -h 720 \\\n', '-console -condebug -dev -novid -sw -w 1280 -h 720 \\\n')
--if '+con_enable 1' not in s:
-- s = s.replace('-port 27115 -clientport 27105 \\\n', '-port 27115 -clientport 27105 +con_enable 1 +developer 1 \\\n')
--if 'client.so' not in s and 'Windows hl2.exe will not load Linux client.so' not in s:
-- s = s.replace('echo "Launching OpenVibe: Source..."', 'echo "Launching OpenVibe: Source..."\necho "NOTE: Proton Windows hl2.exe will not load Linux client.so; Electron launcher remains the reliable custom UI."')
--p.write_text(s)
--PY
--
--cat > tools/check-openvibe-client-mode.sh <<'CHK'
--#!/usr/bin/env bash
--set -euo pipefail
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--
--echo "[openvibe] client mode diagnostic"
--echo "Windows/Proton launcher: $ROOT/tools/run-client-proton.sh"
--echo "Linux client module expected at: $ROOT/game/openvibe.games/bin/linux64/client.so"
--
--if [[ -f "$ROOT/game/openvibe.games/bin/linux64/client.so" ]]; then
-- echo "[ok] Linux client.so exists"
--else
-- echo "[warn] Linux client.so missing; run tools/build-sdk-linux.sh and tools/setup-openvibe-bin.sh"
--fi
--
--cat <<'MSG'
--
--If the in-game console says unknown command "ov_join" or "ov_menu", the OpenVibe client DLL is not loaded.
--That is expected when launching Windows hl2.exe through Proton while only Linux client.so exists.
--
--Current reliable UI path:
-- - Electron launcher / local Chromium UI
-- - server-side GameDLL commands
--
--Full in-game HTML menu requires one of:
-- 1. Run a native Linux Source client that loads bin/linux64/client.so, or
-- 2. Build a Windows client.dll/server.dll and place them in game/openvibe.games/bin/ for Proton hl2.exe.
--MSG
--CHK
--chmod +x tools/check-openvibe-client-mode.sh
--
--if [[ -x tools/setup-openvibe-bin.sh ]]; then
-- tools/setup-openvibe-bin.sh || true
--fi
--
--echo
--node -c launcher/main.js
--node -c launcher/preload.js
--node -c launcher/renderer.js
--
--echo
--echo "[openvibe] done. Run diagnostics:"
--echo " tools/check-openvibe-client-mode.sh"
--echo
--echo "[openvibe] restart Electron/game. By default the launcher will stay visible."
--echo "[openvibe] To auto-hide only after Source window is stable:"
--echo " OPENVIBE_HIDE_LAUNCHER_ON_GAME_READY=1 npm run dev"
ddiff --git a/tools/fix-loadcorefiles-js-phase.sh b/tools/fix-loadcorefiles-js-phase.sh
ddeleted file mode 100755
iindex dcaa5fe..0000000
---- a/tools/fix-loadcorefiles-js-phase.sh
++++ /dev/null
@@@ -1,676 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--SDK="${OPENVIBE_SDK:-$ROOT/engine/source-sdk-2013}"
--STAMP="$(date +%Y%m%d-%H%M%S)"
--
--cd "$ROOT"
--
--echo "[openvibe] fix LoadCoreFiles patch + finish JS command/timer phase"
--echo "[openvibe] root=$ROOT"
--echo "[openvibe] sdk=$SDK"
--
--backup_file() {
-- local f="$1"
-- [[ -f "$f" ]] && cp "$f" "$f.bak.$STAMP"
--}
--
--mkdir -p game/openvibe.games/js/core
--mkdir -p game/openvibe.games/js/gamemodes/base
--mkdir -p game/openvibe.games/js/gamemodes/hub
--mkdir -p game/openvibe.games/js/gamemodes/prophunt
--mkdir -p game/openvibe.games/js/gamemodes/deathrun
--mkdir -p game/openvibe.games/js/gamemodes/fortwars
--mkdir -p game/openvibe.games/js/gamemodes/traitortown
--mkdir -p tools
--
--echo "[openvibe] writing canonical JS core files"
--
--cat > game/openvibe.games/js/core/hook.js <<'JS'
--(function () {
-- const buckets = Object.create(null);
--
-- function ensure(name) {
-- name = String(name || "");
-- if (!buckets[name]) buckets[name] = [];
-- return buckets[name];
-- }
--
-- globalThis.hook = {
-- add(name, id, fn) {
-- if (!name || !id || typeof fn !== "function") {
-- throw new Error("hook.add(name, id, fn) requires a hook name, id, and function");
-- }
--
-- const list = ensure(name);
-- const sid = String(id);
-- for (let i = list.length - 1; i >= 0; --i) {
-- if (list[i].id === sid) list.splice(i, 1);
-- }
-- list.push({ id: sid, fn });
-- },
--
-- remove(name, id) {
-- const list = buckets[String(name || "")];
-- if (!list) return;
-- const sid = String(id || "");
-- for (let i = list.length - 1; i >= 0; --i) {
-- if (list[i].id === sid) list.splice(i, 1);
-- }
-- },
--
-- run(name, ...args) {
-- const list = buckets[String(name || "")];
-- if (!list) return undefined;
--
-- const snapshot = list.slice();
-- for (const item of snapshot) {
-- const result = item.fn(...args);
-- if (result !== undefined) return result;
-- }
--
-- return undefined;
-- },
--
-- list(name) {
-- if (name) return (buckets[String(name)] || []).map((item) => item.id);
-- const out = Object.create(null);
-- for (const key of Object.keys(buckets)) out[key] = buckets[key].map((item) => item.id);
-- return out;
-- }
-- };
--})();
--JS
--
--cat > game/openvibe.games/js/core/gamemode.js <<'JS'
--(function () {
-- let current = null;
--
-- globalThis.gamemode = {
-- set(gm) {
-- if (!gm || typeof gm !== "object") throw new Error("gamemode.set(gm) requires an object");
-- current = gm;
-- return gm;
-- },
--
-- get() {
-- return current;
-- },
--
-- call(name, ...args) {
-- const hookResult = hook.run(name, ...args);
-- if (hookResult !== undefined) return hookResult;
--
-- if (current && typeof current[name] === "function") {
-- return current[name](...args);
-- }
--
-- return undefined;
-- }
-- };
--})();
--JS
--
--cat > game/openvibe.games/js/bridge.js <<'JS'
--(function () {
-- if (globalThis.OV && typeof OV.log === "function") {
-- OV.log("bridge.js loaded");
-- }
--})();
--JS
--
--cat > game/openvibe.games/js/core/command.js <<'JS'
--(function () {
-- const commands = Object.create(null);
--
-- function normalize(name) {
-- return String(name || "").trim().replace(/^!+/, "").toLowerCase();
-- }
--
-- function split(text) {
-- const raw = String(text || "").trim();
-- if (!raw) return { name: "", args: [], raw: "" };
-- const parts = raw.split(/\s+/g);
-- const name = normalize(parts.shift());
-- return { name, args: parts, raw };
-- }
--
-- function reply(ply, msg) {
-- if (ply && typeof ply.chat === "function") ply.chat(String(msg));
-- else if (globalThis.OV && typeof OV.broadcast === "function") OV.broadcast(String(msg));
-- else if (globalThis.OV && typeof OV.log === "function") OV.log(String(msg));
-- }
--
-- function listNames() {
-- return Object.keys(commands).sort();
-- }
--
-- globalThis.command = {
-- add(name, helpOrFn, maybeFn) {
-- const key = normalize(name);
-- if (!key) throw new Error("command.add requires a command name");
--
-- let help = "";
-- let fn = helpOrFn;
-- if (typeof helpOrFn === "string") {
-- help = helpOrFn;
-- fn = maybeFn;
-- }
--
-- if (typeof fn !== "function") throw new Error("command.add requires a function");
-- commands[key] = { help, fn };
-- },
--
-- remove(name) {
-- delete commands[normalize(name)];
-- },
--
-- has(name) {
-- return !!commands[normalize(name)];
-- },
--
-- list: listNames,
--
-- run(ply, text) {
-- const parsed = split(text);
-- if (!parsed.name) return undefined;
--
-- const entry = commands[parsed.name];
-- if (!entry) {
-- reply(ply, `Unknown command: ${parsed.name}. Try !help`);
-- return false;
-- }
--
-- const result = entry.fn({ ply, name: parsed.name, args: parsed.args, raw: parsed.raw, reply });
-- return result === undefined ? false : result;
-- },
--
-- dispatchConsole(text) {
-- return this.run(null, text);
-- }
-- };
--
-- hook.add("PlayerSay", "OpenVibeCommandRegistry", function (ply, text) {
-- const msg = String(text || "").trim();
-- if (!msg.startsWith("!")) return undefined;
-- return command.run(ply, msg.slice(1));
-- });
--
-- hook.add("ConsoleCommand", "OpenVibeConsoleCommandRegistry", function (text) {
-- return command.dispatchConsole(text);
-- });
--
-- command.add("help", "List OpenVibe JS commands", function ({ reply, ply }) {
-- reply(ply, `Commands: ${listNames().map((name) => `!${name}`).join(", ")}`);
-- return false;
-- });
--
-- command.add("smoke", "Run the embedded JS smoke test", function ({ reply, ply }) {
-- reply(ply, "OpenVibe embedded JS smoke test passed.");
-- return false;
-- });
--
-- command.add("timer_smoke", "Run the JS timer smoke test", function ({ reply, ply }) {
-- reply(ply, "Timer smoke scheduled.");
-- if (globalThis.timer && typeof timer.simple === "function") {
-- timer.simple(0.05, function () {
-- if (globalThis.OV && typeof OV.broadcast === "function") {
-- OV.broadcast("OpenVibe JS timer smoke fired.");
-- }
-- });
-- }
-- return false;
-- });
--})();
--JS
--
--cat > game/openvibe.games/js/core/timer.js <<'JS'
--(function () {
-- const timers = Object.create(null);
-- let nextAutoId = 1;
--
-- function now() {
-- if (globalThis.OV && typeof OV.time === "function") return Number(OV.time()) || 0;
-- return Date.now() / 1000;
-- }
--
-- function create(id, delay, reps, fn) {
-- if (!id) id = `timer_${nextAutoId++}`;
-- if (typeof fn !== "function") throw new Error("timer.create requires a callback");
--
-- delay = Math.max(0, Number(delay) || 0);
-- reps = Number(reps);
-- if (!Number.isFinite(reps)) reps = 1;
--
-- timers[String(id)] = {
-- id: String(id),
-- delay,
-- reps,
-- count: 0,
-- next: now() + delay,
-- fn
-- };
--
-- return String(id);
-- }
--
-- function remove(id) {
-- delete timers[String(id || "")];
-- }
--
-- function tick() {
-- const t = now();
-- for (const id of Object.keys(timers)) {
-- const item = timers[id];
-- if (!item || t < item.next) continue;
--
-- item.count += 1;
-- try {
-- item.fn(item.count, id);
-- } catch (err) {
-- if (globalThis.OV && typeof OV.error === "function") OV.error(`timer ${id}: ${err && err.message ? err.message : err}`);
-- delete timers[id];
-- continue;
-- }
--
-- if (item.reps > 0 && item.count >= item.reps) {
-- delete timers[id];
-- } else {
-- item.next = t + item.delay;
-- }
-- }
-- }
--
-- globalThis.timer = {
-- create,
-- remove,
-- exists(id) {
-- return !!timers[String(id || "")];
-- },
-- simple(delay, fn) {
-- return create(`simple_${nextAutoId++}`, delay, 1, fn);
-- },
-- tick,
-- list() {
-- return Object.keys(timers).sort();
-- }
-- };
--
-- hook.add("Think", "OpenVibeTimerThink", tick);
--})();
--JS
--
--echo "[openvibe] writing scoped gamemode scripts"
--
--cat > game/openvibe.games/js/gamemodes/base/server.js <<'JS'
--(function () {
-- const BaseServerGM = {
-- mode: "base",
-- name: "OpenVibe Base",
--
-- Initialize() {
-- OV.log("Base Initialize fired");
-- },
--
-- MapInitialize(mapName) {
-- OV.log(`Base MapInitialize: ${mapName}`);
-- },
--
-- PlayerInitialSpawn(ply) {
-- ply.chat("Welcome to OpenVibe: Source.");
-- },
--
-- PlayerSpawn(_ply) {},
-- PlayerDeath(_victim, _attacker) {},
-- PlayerDisconnected(_ply) {},
-- Think() {}
-- };
--
-- gamemode.set(BaseServerGM);
--})();
--JS
--
--cat > game/openvibe.games/js/gamemodes/hub/server.js <<'JS'
--(function () {
-- function registerHubCommands() {
-- if (!globalThis.command) return;
--
-- command.add("js", "Confirm JavaScript hooks are working", function ({ ply, reply }) {
-- reply(ply, "JavaScript hooks are working.");
-- return false;
-- });
--
-- command.add("hp", "Show current health", function ({ ply, reply }) {
-- if (!ply) return false;
-- reply(ply, `Health: ${ply.health()}`);
-- return false;
-- });
--
-- command.add("players", "Show player count", function ({ ply, reply }) {
-- reply(ply, `Players online: ${OV.players().length}`);
-- return false;
-- });
--
-- command.add("where", "Show current mode and map", function ({ ply, reply }) {
-- reply(ply, `Mode=${OV.getMode()} map=${OV.getMapName()}`);
-- return false;
-- });
--
-- command.add("hub_status", "Broadcast hub status", function ({ ply, reply }) {
-- reply(ply, `Hub OK. mode=${OV.getMode()} map=${OV.getMapName()} players=${OV.players().length}`);
-- return false;
-- });
-- }
--
-- const HubServerGM = {
-- mode: "hub",
-- name: "OpenVibe Hub",
--
-- Initialize() {
-- OV.log("Hub Initialize fired");
-- registerHubCommands();
-- },
--
-- MapInitialize(mapName) {
-- OV.log(`Hub MapInitialize: ${mapName}`);
-- },
--
-- PlayerInitialSpawn(ply) {
-- ply.chat("Welcome to OpenVibe: Source JS runtime.");
-- OV.broadcast(`${ply.name()} joined the hub.`);
-- },
--
-- PlayerSpawn(ply) {
-- ply.chat("PlayerSpawn hook fired.");
-- },
--
-- PlayerSay(_ply, _text) {
-- return undefined;
-- },
--
-- ConsoleCommand(text) {
-- OV.log(`Hub ConsoleCommand fallback: ${text}`);
-- return undefined;
-- },
--
-- Think() {}
-- };
--
-- gamemode.set(HubServerGM);
--})();
--JS
--
--cat > game/openvibe.games/js/gamemodes/prophunt/server.js <<'JS'
--(function () {
-- function registerCommands() {
-- if (!globalThis.command) return;
-- command.add("ph_status", "Show Prop Hunt status", function ({ ply, reply }) {
-- reply(ply, `Prop Hunt JS online. map=${OV.getMapName()} players=${OV.players().length}`);
-- return false;
-- });
-- command.add("disguise", "Disguise as an allowlisted prop", function ({ args, ply, reply }) {
-- const choice = args[0] || "crate";
-- if (!ply) return false;
-- ply.runCommand(`ov_prophunt_disguise ${choice}`);
-- reply(ply, `Trying prop disguise: ${choice}`);
-- return false;
-- });
-- }
--
-- const PropHuntGM = {
-- mode: "prophunt",
-- name: "OpenVibe Prop Hunt",
-- Initialize() { OV.log("Prop Hunt Initialize fired"); registerCommands(); },
-- PlayerInitialSpawn(ply) { ply.chat("Prop Hunt JS loaded. Try !ph_status or !disguise crate"); },
-- Think() {}
-- };
-- gamemode.set(PropHuntGM);
--})();
--JS
--
--cat > game/openvibe.games/js/gamemodes/deathrun/server.js <<'JS'
--(function () {
-- function registerCommands() {
-- if (!globalThis.command) return;
-- command.add("dr_status", "Show Deathrun status", function ({ ply, reply }) {
-- reply(ply, `Deathrun JS online. map=${OV.getMapName()} players=${OV.players().length}`);
-- return false;
-- });
-- }
--
-- const DeathrunGM = {
-- mode: "deathrun",
-- name: "OpenVibe Deathrun",
-- Initialize() { OV.log("Deathrun Initialize fired"); registerCommands(); },
-- PlayerInitialSpawn(ply) { ply.chat("Deathrun JS loaded. Try !dr_status"); },
-- Think() {}
-- };
-- gamemode.set(DeathrunGM);
--})();
--JS
--
--cat > game/openvibe.games/js/gamemodes/fortwars/server.js <<'JS'
--(function () {
-- function registerCommands() {
-- if (!globalThis.command) return;
-- command.add("fw_status", "Show Fort Wars status", function ({ ply, reply }) {
-- reply(ply, `Fort Wars JS online. map=${OV.getMapName()} players=${OV.players().length}`);
-- return false;
-- });
-- command.add("build", "Spawn an allowlisted Fort Wars prop", function ({ args, ply, reply }) {
-- const choice = args[0] || "crate";
-- if (!ply) return false;
-- ply.runCommand(`ov_fortwars_spawn ${choice}`);
-- reply(ply, `Trying Fort Wars prop: ${choice}`);
-- return false;
-- });
-- }
--
-- const FortWarsGM = {
-- mode: "fortwars",
-- name: "OpenVibe Fort Wars",
-- Initialize() { OV.log("Fort Wars Initialize fired"); registerCommands(); },
-- PlayerInitialSpawn(ply) { ply.chat("Fort Wars JS loaded. Try !fw_status or !build crate"); },
-- Think() {}
-- };
-- gamemode.set(FortWarsGM);
--})();
--JS
--
--cat > game/openvibe.games/js/gamemodes/traitortown/server.js <<'JS'
--(function () {
-- function registerCommands() {
-- if (!globalThis.command) return;
-- command.add("ttt_status", "Show Traitor Town status", function ({ ply, reply }) {
-- reply(ply, `Traitor Town JS online. map=${OV.getMapName()} players=${OV.players().length}`);
-- return false;
-- });
-- }
--
-- const TraitorTownGM = {
-- mode: "traitortown",
-- name: "OpenVibe Traitor Town",
-- Initialize() { OV.log("Traitor Town Initialize fired"); registerCommands(); },
-- PlayerInitialSpawn(ply) { ply.chat("Traitor Town JS loaded. Try !ttt_status"); },
-- Think() {}
-- };
-- gamemode.set(TraitorTownGM);
--})();
--JS
--
--echo "[openvibe] robustly replacing COpenVibeJSRuntime::LoadCoreFiles"
--
--backup_file sdk/openvibe/shared/ov_js_runtime.cpp
--
--python3 <<'PY'
--from pathlib import Path
--
--p = Path("sdk/openvibe/shared/ov_js_runtime.cpp")
--s = p.read_text()
--
--sig = "bool COpenVibeJSRuntime::LoadCoreFiles()"
--idx = s.find(sig)
--if idx < 0:
-- raise SystemExit("Could not find COpenVibeJSRuntime::LoadCoreFiles signature")
--
--brace = s.find("{", idx)
--if brace < 0:
-- raise SystemExit("Could not find COpenVibeJSRuntime::LoadCoreFiles opening brace")
--
--depth = 0
--end = None
--for i in range(brace, len(s)):
-- ch = s[i]
-- if ch == "{":
-- depth += 1
-- elif ch == "}":
-- depth -= 1
-- if depth == 0:
-- end = i + 1
-- break
--
--if end is None:
-- raise SystemExit("Could not find COpenVibeJSRuntime::LoadCoreFiles closing brace")
--
--replacement = '''bool COpenVibeJSRuntime::LoadCoreFiles()
--{
-- if (!LoadFile("js/core/hook.js"))
-- return false;
--
-- if (!LoadFile("js/core/gamemode.js"))
-- return false;
--
-- if (!LoadFile("js/bridge.js"))
-- return false;
--
-- if (!LoadFile("js/core/command.js"))
-- return false;
--
-- if (!LoadFile("js/core/timer.js"))
-- return false;
--
-- return true;
--}'''
--
--s = s[:idx] + replacement + s[end:]
--p.write_text(s)
--print("[openvibe] LoadCoreFiles replaced")
--PY
--
--echo "[openvibe] writing Node JS smoke test"
--
--cat > tools/smoke-js-core-node.mjs <<'JS'
--import fs from "node:fs";
--import vm from "node:vm";
--
--let fakeTime = 0;
--const messages = [];
--
--function player() {
-- return {
-- userId: 1,
-- entIndex: 1,
-- steamId: () => "STEAM_0:1:1",
-- name: () => "SmokePlayer",
-- health: () => 87,
-- setHealth: () => undefined,
-- chat: (msg) => messages.push(`[chat] ${msg}`),
-- team: () => 0,
-- setTeam: () => undefined,
-- runCommand: (cmd) => messages.push(`[runCommand] ${cmd}`)
-- };
--}
--
--const context = vm.createContext({
-- console,
-- globalThis: {},
-- OV: {
-- log: (msg) => messages.push(`[log] ${msg}`),
-- warn: (msg) => messages.push(`[warn] ${msg}`),
-- error: (msg) => messages.push(`[error] ${msg}`),
-- getMode: () => "hub",
-- getMapName: () => "ov_hub",
-- time: () => fakeTime,
-- broadcast: (msg) => messages.push(`[broadcast] ${msg}`),
-- players: () => [player()],
-- playerByUserId: () => player(),
-- serverCommand: (cmd) => messages.push(`[serverCommand] ${cmd}`),
-- reward: () => undefined,
-- endMatch: () => undefined
-- }
--});
--context.globalThis = context;
--
--const files = [
-- "game/openvibe.games/js/core/hook.js",
-- "game/openvibe.games/js/core/gamemode.js",
-- "game/openvibe.games/js/bridge.js",
-- "game/openvibe.games/js/core/command.js",
-- "game/openvibe.games/js/core/timer.js",
-- "game/openvibe.games/js/gamemodes/base/server.js",
-- "game/openvibe.games/js/gamemodes/hub/server.js"
--];
--
--for (const file of files) {
-- const code = fs.readFileSync(file, "utf8");
-- vm.runInContext(code, context, { filename: file });
--}
--
--context.gamemode.call("Initialize");
--context.gamemode.call("MapInitialize", "ov_hub");
--context.gamemode.call("PlayerInitialSpawn", player());
--
--const blocked = context.gamemode.call("PlayerSay", player(), "!js");
--if (blocked !== false) throw new Error("!js did not block default chat");
--
--context.gamemode.call("ConsoleCommand", "smoke");
--context.gamemode.call("ConsoleCommand", "hub_status");
--context.gamemode.call("ConsoleCommand", "timer_smoke");
--fakeTime += 1;
--context.gamemode.call("Think");
--
--if (!messages.some((line) => line.includes("JavaScript hooks are working"))) throw new Error("missing !js response");
--if (!messages.some((line) => line.includes("OpenVibe embedded JS smoke test passed"))) throw new Error("missing smoke response");
--if (!messages.some((line) => line.includes("OpenVibe JS timer smoke fired"))) throw new Error("missing timer smoke response");
--
--console.log(messages.join("\n"));
--console.log("[openvibe-smoke] JS command/timer smoke passed");
--JS
--
--echo "[openvibe] run JS smoke test"
--node tools/smoke-js-core-node.mjs
--
--echo "[openvibe] apply SDK patch"
--tools/apply-openvibe-sdk.sh
--
--echo "[openvibe] build SDK"
--if [[ "${RUN_BUILD:-1}" = "1" ]]; then
-- tools/build-sdk-linux.sh 2>&1 | tee "$HOME/ov-build.log"
--fi
--
--echo "[openvibe] setup OpenVibe bin"
--tools/setup-openvibe-bin.sh
--
--echo
--
--echo "[openvibe] JS command/timer phase complete."
--echo
--
--echo "Runtime test:"
--echo " OPENVIBE_SRCDS_MAP_DELAY=3 tools/dev-up.sh"
--echo
--echo "Server console tests:"
--echo " ov_js_status"
--echo " ov_js_cmd help"
--echo " ov_js_cmd smoke"
--echo " ov_js_cmd timer_smoke"
--echo " ov_js_cmd hub_status"
--echo
--echo "Client tests:"
--echo " connect 127.0.0.1:27015"
--echo " say !help"
--echo " say !js"
--echo " say !hp"
--echo " say !players"
--echo " say !where"
ddiff --git a/tools/fix-proton-loading-and-menu-fallback.sh b/tools/fix-proton-loading-and-menu-fallback.sh
ddeleted file mode 100755
iindex 8e4a7f5..0000000
---- a/tools/fix-proton-loading-and-menu-fallback.sh
++++ /dev/null
@@@ -1,396 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--cd "$ROOT"
--
--echo "[openvibe] next phase: Proton-aware loading UX + fallback menu aliases"
--echo "[openvibe] root=$ROOT"
--
--mkdir -p game/openvibe.games/cfg game/openvibe.games/resource game/openvibe.games/materialsrc/console game/openvibe.games/materials/console launcher
--
--# -----------------------------------------------------------------------------
--# 1) Proton/client fallback aliases.
--# Windows hl2.exe cannot load the Linux client.so, so these aliases prevent
--# confusing Unknown command spam and keep raw connect flows usable.
--# -----------------------------------------------------------------------------
--cat > game/openvibe.games/cfg/openvibe_proton_client.cfg <<'CFG'
--// OpenVibe Proton client fallback aliases.
--// This file is executed by tools/run-client-proton.sh.
--// Proton Windows hl2.exe does not load Linux bin/linux64/client.so, so the
--// native OpenVibe client DLL commands are not available in this mode.
--
--echo "[OpenVibe] Proton fallback cfg loaded. Electron is the custom menu in Proton mode."
--
--alias ov_help "echo OpenVibe Proton fallback: use ov_join_hub / ov_join_prophunt / ov_join_deathrun / ov_join_fortwars / ov_join_traitortown. Electron remains the full HTML/CSS/JS menu."
--alias ov_menu "echo OpenVibe HTML menu is hosted by Electron in Proton mode. Alt-tab to the launcher or run npm run dev."
--alias ov_ui "ov_menu"
--alias ov_main_menu "ov_menu"
--alias ov_menu_main "ov_menu"
--alias ov_menu_servers "echo Server browser is in Electron. Quick connects: ov_join_hub, ov_join_prophunt, ov_join_deathrun, ov_join_fortwars, ov_join_traitortown."
--alias ov_menu_leaderboard "echo Leaderboard UI is in Electron in Proton mode."
--alias ov_menu_inventory "echo Inventory UI is in Electron in Proton mode."
--alias ov_menu_shop "echo Shop UI is in Electron in Proton mode."
--alias ov_menu_settings "echo Settings UI is in Electron in Proton mode."
--alias ov_menu_reload "echo Reload the Electron window with Ctrl+R or restart npm run dev."
--alias ov_menu_close "echo Close/minimize the Electron launcher window."
--
--alias ov_join "echo Usage in Proton fallback: ov_join_hub / ov_join_prophunt / ov_join_deathrun / ov_join_fortwars / ov_join_traitortown. The argument-taking ov_join command needs OpenVibe client.dll/client.so."
--alias ov_join_hub "connect 127.0.0.1:27015"
--alias ov_join_prophunt "connect 127.0.0.1:27016"
--alias ov_join_deathrun "connect 127.0.0.1:27017"
--alias ov_join_fortwars "connect 127.0.0.1:27018"
--alias ov_join_traitortown "connect 127.0.0.1:27019"
--
--alias ov_js_status "cmd ov_js_status"
--alias ov_js_reload "cmd ov_js_reload"
--alias ov_js_cmd "cmd ov_js_cmd"
--alias ov_js_fire "cmd ov_js_fire"
--
--alias ov_prophunt_disguise "cmd ov_prophunt_disguise"
--alias ov_prophunt_reset_disguise "cmd ov_prophunt_reset_disguise"
--alias ov_fortwars_spawn "cmd ov_fortwars_spawn"
--CFG
--
--cat > game/openvibe.games/cfg/autoexec.cfg <<'CFG'
--// OpenVibe autoexec. Safe in both native and Proton modes.
--exec openvibe_proton_client.cfg
--CFG
--
--# Patch Proton launcher to exec aliases early.
--python3 <<'PY'
--from pathlib import Path
--p = Path('tools/run-client-proton.sh')
--s = p.read_text()
--if '+exec openvibe_proton_client.cfg' not in s:
-- s = s.replace(
-- '-console -dev -novid -sw -w 1280 -h 720 \\\n',
-- '-console -dev -novid -sw -w 1280 -h 720 +exec openvibe_proton_client.cfg \\\n'
-- )
--if 'OPENVIBE_PROTON_FALLBACK' not in s:
-- s = s.replace('export PROTON_LOG=0\n', 'export PROTON_LOG=0\nexport OPENVIBE_PROTON_FALLBACK=1\n')
--p.write_text(s)
--PY
--chmod +x tools/run-client-proton.sh
--
--# -----------------------------------------------------------------------------
--# 2) Best-effort loading dialog override resources.
--# This won't replace the HL2 background material until VTF files are supplied,
--# but it customizes text/layout and adds docs + source art placeholders.
--# -----------------------------------------------------------------------------
--cat > game/openvibe.games/resource/LoadingDialog.res <<'RES'
--"Resource/LoadingDialog.res"
--{
-- "LoadingDialog"
-- {
-- "ControlName" "Frame"
-- "fieldName" "LoadingDialog"
-- "xpos" "0"
-- "ypos" "0"
-- "wide" "f0"
-- "tall" "f0"
-- "visible" "1"
-- "enabled" "1"
-- "title" "OpenVibe: Source"
-- "proportionaltoparent" "1"
-- "PaintBackgroundType" "2"
-- "bgcolor_override" "7 8 14 245"
-- }
-- "LoadingLabel"
-- {
-- "ControlName" "Label"
-- "fieldName" "LoadingLabel"
-- "xpos" "cs-260"
-- "ypos" "rs1-92"
-- "wide" "520"
-- "tall" "28"
-- "visible" "1"
-- "enabled" "1"
-- "labelText" "OPENVIBE: SOURCE"
-- "textAlignment" "center"
-- "font" "DefaultLarge"
-- "fgcolor_override" "0 216 255 255"
-- }
-- "ProgressLabel"
-- {
-- "ControlName" "Label"
-- "fieldName" "ProgressLabel"
-- "xpos" "cs-260"
-- "ypos" "rs1-58"
-- "wide" "520"
-- "tall" "24"
-- "visible" "1"
-- "enabled" "1"
-- "labelText" "Loading OpenVibe session..."
-- "textAlignment" "center"
-- "font" "Default"
-- "fgcolor_override" "230 236 255 255"
-- }
--}
--RES
--
--cat > game/openvibe.games/materialsrc/console/openvibe-loading.svg <<'SVG'
--<svg xmlns="http://www.w3.org/2000/svg" width="1920" height="1080" viewBox="0 0 1920 1080">
-- <defs>
-- <radialGradient id="g" cx="50%" cy="45%" r="75%">
-- <stop offset="0" stop-color="#16334a"/>
-- <stop offset="0.45" stop-color="#0b1020"/>
-- <stop offset="1" stop-color="#05060b"/>
-- </radialGradient>
-- <linearGradient id="line" x1="0" x2="1">
-- <stop offset="0" stop-color="#00d8ff" stop-opacity="0"/>
-- <stop offset="0.5" stop-color="#00d8ff" stop-opacity="1"/>
-- <stop offset="1" stop-color="#a855f7" stop-opacity="0"/>
-- </linearGradient>
-- </defs>
-- <rect width="1920" height="1080" fill="url(#g)"/>
-- <g opacity="0.18" stroke="#00d8ff" fill="none">
-- <path d="M0 770 C300 630 520 890 850 730 C1190 560 1390 810 1920 610"/>
-- <path d="M0 830 C330 700 560 960 910 780 C1240 610 1480 860 1920 680"/>
-- </g>
-- <circle cx="960" cy="450" r="155" fill="none" stroke="#00d8ff" stroke-width="8" opacity="0.8"/>
-- <circle cx="960" cy="450" r="112" fill="none" stroke="#a855f7" stroke-width="4" opacity="0.7"/>
-- <text x="960" y="465" text-anchor="middle" font-family="Orbitron, Rajdhani, sans-serif" font-size="84" fill="#eaf8ff" font-weight="900">OPENVIBE</text>
-- <text x="960" y="530" text-anchor="middle" font-family="Rajdhani, sans-serif" font-size="38" fill="#00d8ff" font-weight="700">SOURCE</text>
-- <rect x="660" y="620" width="600" height="6" rx="3" fill="url(#line)"/>
-- <text x="960" y="680" text-anchor="middle" font-family="Inter, sans-serif" font-size="28" fill="#b9c7e8">Loading connected game session...</text>
--</svg>
--SVG
--
--cat > game/openvibe.games/materials/console/README.md <<'MD'
--# OpenVibe loading background
--
--Source's full-screen loading background is a VTF material, normally:
--
--- `materials/console/background01.vtf`
--- `materials/console/background01_widescreen.vtf`
--
--This repo includes source art at:
--
--- `materialsrc/console/openvibe-loading.svg`
--
--To fully replace the HL2 loading image, convert it to the two VTF filenames above using VTFEdit/VTFCmd or Valve's `vtex`, then place the VTFs in this folder.
--
--Until those VTF files exist, `resource/LoadingDialog.res` can restyle text/layout, but the old HL2 image may still appear.
--MD
--
--# -----------------------------------------------------------------------------
--# 3) Electron launch UX: keep launcher visible, expose focus button, and only
--# hide when Source window is stable if env var opts in.
--# -----------------------------------------------------------------------------
--python3 <<'PY'
--from pathlib import Path
--p = Path('launcher/main.js')
--s = p.read_text()
--
--if 'function sourceWindowVisible' not in s:
-- insert_after = "let appIsQuitting = false;\n"
-- block = r'''
--let lastLaunchState = { phase: 'idle', message: 'Ready', pid: null };
--
--function broadcastLaunchState(state) {
-- lastLaunchState = { ...lastLaunchState, ...state, at: Date.now() };
-- if (mainWindow && !mainWindow.isDestroyed()) {
-- mainWindow.webContents.send('launch-state', lastLaunchState);
-- }
--}
--
--function sourceWindowVisible() {
-- try {
-- const out = execSync('wmctrl -lx 2>/dev/null || true', { encoding: 'utf8' });
-- return /OpenVibe: Source|Source SDK Base 2013|hl2\.exe|hl2/i.test(out);
-- } catch {
-- return false;
-- }
--}
--
--function focusSourceWindow() {
-- const commands = [
-- "wmctrl -a 'OpenVibe: Source'",
-- "wmctrl -a 'Source SDK Base 2013'",
-- "wmctrl -a 'hl2.exe'",
-- ];
-- for (const cmd of commands) {
-- try {
-- execSync(cmd, { stdio: 'ignore' });
-- return true;
-- } catch {}
-- }
-- return false;
--}
--
--function waitForStableSourceWindow(timeoutMs = 60000) {
-- const started = Date.now();
-- let stableTicks = 0;
-- return new Promise((resolve) => {
-- const tick = () => {
-- if (sourceWindowVisible()) stableTicks += 1;
-- else stableTicks = 0;
--
-- broadcastLaunchState({
-- phase: stableTicks > 0 ? 'window-detected' : 'starting',
-- message: stableTicks > 0
-- ? `Source window detected (${stableTicks}/5 stable checks)...`
-- : 'Starting Source through Proton...',
-- });
--
-- if (stableTicks >= 5) return resolve(true);
-- if (Date.now() - started >= timeoutMs) return resolve(false);
-- setTimeout(tick, 1000);
-- };
-- tick();
-- });
--}
--'''
-- s = s.replace(insert_after, insert_after + block + "\n")
--
--old = """ mainWindow?.webContents.send('game-started', gameProcess.pid);\n setTimeout(() => {\n if (gameProcess && mainWindow && !mainWindow.isDestroyed()) mainWindow.hide();\n }, 750);\n"""
--new = """ mainWindow?.webContents.send('game-started', gameProcess.pid);\n broadcastLaunchState({\n phase: 'spawned',\n pid: gameProcess.pid,\n message: 'Source spawned. Keeping launcher visible until the game window is stable.',\n });\n\n waitForStableSourceWindow().then((ready) => {\n broadcastLaunchState({\n phase: ready ? 'ready' : 'timeout',\n pid: gameProcess ? gameProcess.pid : null,\n message: ready\n ? 'Source window appears stable. You can focus the game now.'\n : 'Source is still not confirmed stable. Launcher stayed visible to avoid confusion.',\n });\n\n const shouldAutoHide = process.env.OPENVIBE_HIDE_LAUNCHER_ON_GAME_READY === '1';\n if (ready && shouldAutoHide && gameProcess && mainWindow && !mainWindow.isDestroyed()) {\n mainWindow.hide();\n focusSourceWindow();\n }\n });\n"""
--if old in s:
-- s = s.replace(old, new)
--
--if "ipcMain.handle('game:focus'" not in s:
-- s = s.replace("ipcMain.handle('game:status', () => ({\n running: gameProcess !== null,\n pid: gameProcess?.pid ?? null,\n}));\n",
-- "ipcMain.handle('game:status', () => ({\n running: gameProcess !== null,\n pid: gameProcess?.pid ?? null,\n launchState: lastLaunchState,\n}));\n\nipcMain.handle('game:focus', async () => focusSourceWindow());\n")
--
--p.write_text(s)
--PY
--
--python3 <<'PY'
--from pathlib import Path
--p = Path('launcher/preload.js')
--s = p.read_text()
--if "focusGame" not in s:
-- s = s.replace("gameStatus: () => ipcRenderer.invoke('game:status'),",
-- "gameStatus: () => ipcRenderer.invoke('game:status'),\n focusGame: () => ipcRenderer.invoke('game:focus'),")
--if "onLaunchState" not in s:
-- s = s.replace("onGameStart: (cb) => ipcRenderer.on('game-started', (_e, pid) => cb(pid)),",
-- "onGameStart: (cb) => ipcRenderer.on('game-started', (_e, pid) => cb(pid)),\n onLaunchState: (cb) => ipcRenderer.on('launch-state', (_e, state) => cb(state)),")
--p.write_text(s)
--PY
--
--python3 <<'PY'
--from pathlib import Path
--p = Path('launcher/renderer.js')
--s = p.read_text()
--if 'focusGame:' not in s:
-- s = s.replace("gameStatus: () => isElectron ? window.OV.gameStatus() : Promise.resolve({ running: true, pid: null }),",
-- "gameStatus: () => isElectron ? window.OV.gameStatus() : Promise.resolve({ running: true, pid: null }),\n focusGame: () => isElectron ? window.OV.focusGame() : Promise.resolve(false),")
--if 'launch-focus' not in s:
-- s = s.replace("document.getElementById('launch-cancel')?.addEventListener('click', () => {\n launchOverlay.classList.remove('show');\n});",
-- "document.getElementById('launch-cancel')?.addEventListener('click', () => {\n launchOverlay.classList.remove('show');\n});\n\ndocument.getElementById('launch-focus')?.addEventListener('click', async () => {\n const ok = await Bridge.focusGame();\n toast(ok ? 'Focused Source window.' : 'Could not find Source window yet.', !ok);\n});")
--if 'onLaunchState' not in s:
-- s += r'''
--
--// Launcher-aware Source startup status. This keeps Electron useful while Proton
--// shows the default Source loading window and before a native client DLL exists.
--if (isElectron && window.OV.onLaunchState) {
-- window.OV.onLaunchState((state) => {
-- const label = document.getElementById('launch-label');
-- const sub = document.getElementById('launch-sub');
-- if (label && state?.message) label.textContent = state.message;
-- if (sub) {
-- sub.textContent = state?.phase === 'ready'
-- ? 'Game window is ready. Use Focus Game Window, or keep Electron open as the custom menu.'
-- : 'Electron remains open so you are not left staring at a frozen/default Source loading screen.';
-- }
-- if (state?.phase && state.phase !== 'idle') launchOverlay?.classList.add('show');
-- });
--}
--'''
--p.write_text(s)
--PY
--
--python3 <<'PY'
--from pathlib import Path
--p = Path('launcher/index.html')
--s = p.read_text()
--if 'id="launch-focus"' not in s:
-- s = s.replace(' <button class="launch-cancel" id="launch-cancel">Cancel</button>\n',
-- ' <button class="launch-cancel" id="launch-cancel">Cancel</button>\n <button class="launch-focus" id="launch-focus">Focus Game Window</button>\n <div class="launch-sub" id="launch-sub">Electron stays visible until Source is stable.</div>\n')
--p.write_text(s)
--PY
--
--cat >> launcher/styles.css <<'CSS'
--
--/* OpenVibe launcher-aware Source loading UX */
--.launch-focus {
-- margin-top: 10px;
-- border: 1px solid rgba(0, 216, 255, 0.55);
-- background: rgba(0, 216, 255, 0.14);
-- color: #eaf8ff;
-- border-radius: 10px;
-- padding: 9px 16px;
-- font-weight: 700;
-- cursor: pointer;
--}
--.launch-focus:hover {
-- background: rgba(0, 216, 255, 0.22);
--}
--.launch-sub {
-- margin-top: 10px;
-- max-width: 560px;
-- color: rgba(230, 236, 255, 0.76);
-- font-size: 13px;
-- line-height: 1.35;
-- text-align: center;
--}
--CSS
--
--# -----------------------------------------------------------------------------
--# 4) Diagnostic.
--# -----------------------------------------------------------------------------
--cat > tools/check-openvibe-client-mode.sh <<'SH'
--#!/usr/bin/env bash
--set -euo pipefail
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--cd "$ROOT"
--
--echo "[openvibe] client mode diagnostic"
--echo "Windows/Proton launcher: $ROOT/tools/run-client-proton.sh"
--echo "Linux client module: $ROOT/game/openvibe.games/bin/linux64/client.so"
--echo "Fallback aliases: $ROOT/game/openvibe.games/cfg/openvibe_proton_client.cfg"
--echo
--[[ -f game/openvibe.games/bin/linux64/client.so ]] && echo "[ok] Linux client.so exists" || echo "[missing] Linux client.so missing"
--[[ -f game/openvibe.games/cfg/openvibe_proton_client.cfg ]] && echo "[ok] Proton fallback aliases exist" || echo "[missing] Proton fallback aliases missing"
--
--echo
--cat <<'TXT'
--Important:
-- Proton Windows hl2.exe loads Windows client.dll, not Linux bin/linux64/client.so.
-- If the console says unknown command ov_join/ov_menu, the OpenVibe client DLL is not loaded.
--
--Current reliable custom UI:
-- Electron launcher + local Chromium UI.
--
--Current in-game fallback:
-- autoexec.cfg loads openvibe_proton_client.cfg aliases.
-- Try: ov_help, ov_join_hub, ov_join_prophunt, ov_join_deathrun.
--
--Full in-game HTML/CSS/JS menu requires:
-- 1) native Linux Source client loading bin/linux64/client.so, or
-- 2) a Windows client.dll build for Proton hl2.exe.
--
--Full loading image replacement:
-- Convert materialsrc/console/openvibe-loading.svg to:
-- materials/console/background01.vtf
-- materials/console/background01_widescreen.vtf
--TXT
--SH
--chmod +x tools/check-openvibe-client-mode.sh
--
--# Keep setup links current, if build exists.
--if [[ -x tools/setup-openvibe-bin.sh ]]; then
-- tools/setup-openvibe-bin.sh || true
--fi
--
--echo
--echo "[openvibe] done. Re-test:"
--echo " tools/check-openvibe-client-mode.sh"
--echo " cd launcher && npm run dev"
--echo
--echo "In Proton client console, try:"
--echo " exec openvibe_proton_client.cfg"
--echo " ov_help"
--echo " ov_join_hub"
--echo " ov_menu_servers"
ddiff --git a/tools/fix-quickjs-build-dir.sh b/tools/fix-quickjs-build-dir.sh
ddeleted file mode 100755
iindex 2ecd08c..0000000
---- a/tools/fix-quickjs-build-dir.sh
++++ /dev/null
@@@ -1,146 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--SDK="${OPENVIBE_SDK:-$ROOT/engine/source-sdk-2013}"
--
--cd "$ROOT"
--
--echo "[openvibe] replacing tools/build-quickjs-lib.sh"
--
--cat > tools/build-quickjs-lib.sh <<'BUILDQJS'
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--SDK="${OPENVIBE_SDK:-$ROOT/engine/source-sdk-2013}"
--
--SRC_QJS="$ROOT/sdk/openvibe/third_party/quickjs"
--SDK_QJS="$SDK/src/game/shared/openvibe/third_party/quickjs"
--OUT="$SDK_QJS/build"
--
--if [[ ! -f "$SRC_QJS/quickjs.c" ]]; then
-- echo "Missing $SRC_QJS/quickjs.c" >&2
-- echo "Run: tools/vendor-quickjs.sh" >&2
-- exit 1
--fi
--
--mkdir -p "$SDK_QJS"
--
--# Important: exclude build/ so rsync does not delete our object output dir.
--rsync -a --delete --exclude='build/' "$SRC_QJS/" "$SDK_QJS/"
--
--mkdir -p "$OUT"
--
--sources=(
-- quickjs.c
-- libregexp.c
-- libunicode.c
-- cutils.c
--)
--
--[[ -f "$SDK_QJS/dtoa.c" ]] && sources+=(dtoa.c)
--[[ -f "$SDK_QJS/libbf.c" ]] && sources+=(libbf.c)
--
--rm -f "$OUT"/*.o "$OUT"/libquickjs_openvibe.a
--
--for src in "${sources[@]}"; do
-- obj="$OUT/${src%.c}.o"
-- echo "[openvibe-qjs] cc $src -> $obj"
--
-- cc \
-- -std=gnu11 \
-- -O2 \
-- -fPIC \
-- -D_GNU_SOURCE \
-- -DCONFIG_VERSION=\"openvibe\" \
-- -I"$SDK_QJS" \
-- -c "$SDK_QJS/$src" \
-- -o "$obj"
--done
--
--ar rcs "$OUT/libquickjs_openvibe.a" "$OUT"/*.o
--
--echo "[openvibe-qjs] built $OUT/libquickjs_openvibe.a"
--ls -lh "$OUT/libquickjs_openvibe.a"
--BUILDQJS
--
--chmod +x tools/build-quickjs-lib.sh
--
--echo "[openvibe] removing direct QuickJS .c files from server_hl2mp.vpc"
--
--SERVER_VPC="$SDK/src/game/server/server_hl2mp.vpc"
--
--if [[ -f "$SERVER_VPC" ]]; then
-- cp "$SERVER_VPC" "$SERVER_VPC.bak.$(date +%Y%m%d-%H%M%S)"
--
-- perl -0pi -e '
-- s/^.*quickjs\\quickjs\.c.*\n//mg;
-- s/^.*quickjs\\libregexp\.c.*\n//mg;
-- s/^.*quickjs\\libunicode\.c.*\n//mg;
-- s/^.*quickjs\\cutils\.c.*\n//mg;
-- s/^.*quickjs\\dtoa\.c.*\n//mg;
-- s/^.*quickjs\\libbf\.c.*\n//mg;
-- ' "$SERVER_VPC"
--
-- if ! grep -q 'libquickjs_openvibe.a' "$SERVER_VPC"; then
-- perl -0pi -e 's/(\$File\s+"hl2mp\\openvibe_js_server\.cpp"\n)/$1\t\t\t\$Lib\t"..\\shared\\openvibe\\third_party\\quickjs\\build\\libquickjs_openvibe.a"\n/s' "$SERVER_VPC"
-- fi
--fi
--
--echo "[openvibe] patching apply-openvibe-sdk.sh so future applies keep this fix"
--
--python3 <<'PY'
--from pathlib import Path
--import re
--
--p = Path("tools/apply-openvibe-sdk.sh")
--s = p.read_text()
--
--# Make sure apply script calls static library builder after copying QuickJS.
--if '"$ROOT/tools/build-quickjs-lib.sh"' not in s:
-- needle = 'copy_tree "$ROOT/sdk/openvibe/third_party/quickjs" \\\n "$SDK/src/game/shared/openvibe/third_party/quickjs"'
-- if needle in s:
-- s = s.replace(needle, needle + '\n\n"$ROOT/tools/build-quickjs-lib.sh"')
-- else:
-- s += '\n\n"$ROOT/tools/build-quickjs-lib.sh"\n'
--
--# Add a strong cleanup block near the end.
--marker = "OPENVIBE_REMOVE_DIRECT_QUICKJS_C"
--if marker not in s:
-- s += r'''
--
--# OPENVIBE_REMOVE_DIRECT_QUICKJS_C
--if [[ -f "$SERVER_VPC" ]]; then
-- perl -0pi -e '
-- s/^.*quickjs\\quickjs\.c.*\n//mg;
-- s/^.*quickjs\\libregexp\.c.*\n//mg;
-- s/^.*quickjs\\libunicode\.c.*\n//mg;
-- s/^.*quickjs\\cutils\.c.*\n//mg;
-- s/^.*quickjs\\dtoa\.c.*\n//mg;
-- s/^.*quickjs\\libbf\.c.*\n//mg;
-- ' "$SERVER_VPC"
--
-- if ! grep -q 'libquickjs_openvibe.a' "$SERVER_VPC"; then
-- perl -0pi -e 's/(\$File\s+"hl2mp\\openvibe_js_server\.cpp"\n)/$1\t\t\t\$Lib\t"..\\shared\\openvibe\\third_party\\quickjs\\build\\libquickjs_openvibe.a"\n/s' "$SERVER_VPC"
-- fi
--fi
--'''
--
--p.write_text(s)
--PY
--
--echo "[openvibe] building QuickJS static lib"
--tools/build-quickjs-lib.sh
--
--echo
--echo "[openvibe] checking VPC for bad direct QuickJS C entries"
--if [[ -f "$SERVER_VPC" ]]; then
-- grep -nE 'quickjs\\(quickjs|libregexp|libunicode|cutils|dtoa|libbf)\.c' "$SERVER_VPC" || true
-- grep -n 'libquickjs_openvibe.a' "$SERVER_VPC" || true
--fi
--
--echo
--echo "[openvibe] fixed. Now run:"
--echo " tools/apply-openvibe-sdk.sh"
--echo " tools/build-sdk-linux.sh 2>&1 | tee ~/ov-build.log"
ddiff --git a/tools/fix-quickjs-c-link.sh b/tools/fix-quickjs-c-link.sh
ddeleted file mode 100755
iindex a5d4608..0000000
---- a/tools/fix-quickjs-c-link.sh
++++ /dev/null
@@@ -1,174 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--SDK="${OPENVIBE_SDK:-$ROOT/engine/source-sdk-2013}"
--STAMP="$(date +%Y%m%d-%H%M%S)"
--
--cd "$ROOT"
--
--backup_file() {
-- local file="$1"
-- [[ -f "$file" ]] && cp "$file" "$file.bak.$STAMP"
--}
--
--echo "[openvibe] writing QuickJS static library builder"
--
--cat > tools/build-quickjs-lib.sh <<'BUILDQJS'
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--SDK="${OPENVIBE_SDK:-$ROOT/engine/source-sdk-2013}"
--
--SRC_QJS="$ROOT/sdk/openvibe/third_party/quickjs"
--SDK_QJS="$SDK/src/game/shared/openvibe/third_party/quickjs"
--OUT="$SDK_QJS/build"
--
--mkdir -p "$OUT"
--
--if [[ ! -f "$SRC_QJS/quickjs.c" ]]; then
-- echo "Missing $SRC_QJS/quickjs.c" >&2
-- echo "Run: tools/vendor-quickjs.sh" >&2
-- exit 1
--fi
--
--# Make sure SDK copy exists. apply-openvibe-sdk.sh also does this,
--# but this makes the script usable directly.
--mkdir -p "$SDK_QJS"
--rsync -a --delete "$SRC_QJS/" "$SDK_QJS/"
--
--sources=(
-- quickjs.c
-- libregexp.c
-- libunicode.c
-- cutils.c
--)
--
--[[ -f "$SDK_QJS/dtoa.c" ]] && sources+=(dtoa.c)
--[[ -f "$SDK_QJS/libbf.c" ]] && sources+=(libbf.c)
--
--rm -f "$OUT"/*.o "$OUT"/libquickjs_openvibe.a
--
--for src in "${sources[@]}"; do
-- obj="$OUT/${src%.c}.o"
-- echo "[openvibe-qjs] cc $src"
-- cc \
-- -std=gnu11 \
-- -O2 \
-- -fPIC \
-- -D_GNU_SOURCE \
-- -DCONFIG_VERSION=\"openvibe\" \
-- -I"$SDK_QJS" \
-- -c "$SDK_QJS/$src" \
-- -o "$obj"
--done
--
--ar rcs "$OUT/libquickjs_openvibe.a" "$OUT"/*.o
--
--echo "[openvibe-qjs] built $OUT/libquickjs_openvibe.a"
--ls -lh "$OUT/libquickjs_openvibe.a"
--BUILDQJS
--
--chmod +x tools/build-quickjs-lib.sh
--
--echo "[openvibe] patching apply-openvibe-sdk.sh to link libquickjs_openvibe.a instead of compiling QuickJS C files"
--
--backup_file tools/apply-openvibe-sdk.sh
--
--python3 <<'PY'
--from pathlib import Path
--import re
--
--p = Path("tools/apply-openvibe-sdk.sh")
--s = p.read_text()
--
--# Remove QuickJS .c cleanup patterns from perl patch block if they exist.
--for name in ["quickjs", "libregexp", "libunicode", "cutils", "dtoa", "libbf"]:
-- s = re.sub(
-- rf'\s*s/\^\.\*openvibe\\\\third_party\\\\quickjs\\\\{name}\\\.c\.\*\\n//mg;\n?',
-- '\n',
-- s,
-- )
-- s = re.sub(
-- rf'\s*s/\^\.\*openvibe\\\\\\\\third_party\\\\\\\\quickjs\\\\\\\\{name}\\\\\.c\.\*\\n//mg;\n?',
-- '\n',
-- s,
-- )
--
--# Remove inserted QuickJS .c $File entries from replacement text.
--for name in ["quickjs", "libregexp", "libunicode", "cutils", "dtoa", "libbf"]:
-- s = s.replace(
-- f'\\n\\t\\t\\t\\$File\\t"..\\\\shared\\\\openvibe\\\\third_party\\\\quickjs\\\\{name}.c"',
-- ''
-- )
-- s = s.replace(
-- f'\n\t\t\t\\$File\t"..\\\\shared\\\\openvibe\\\\third_party\\\\quickjs\\\\{name}.c"',
-- ''
-- )
-- s = s.replace(
-- f'\n\t\t\t$File\t"..\\\\shared\\\\openvibe\\\\third_party\\\\quickjs\\\\{name}.c"',
-- ''
-- )
--
--# Remove any actual already-written lines from the script text.
--s = re.sub(r'^.*quickjs\\\\(quickjs|libregexp|libunicode|cutils|dtoa|libbf)\.c.*\n', '', s, flags=re.M)
--s = re.sub(r'^.*quickjs\\(quickjs|libregexp|libunicode|cutils|dtoa|libbf)\.c.*\n', '', s, flags=re.M)
--
--# Ensure build-quickjs-lib is called after copy_tree.
--if "tools/build-quickjs-lib.sh" not in s:
-- needle = 'copy_tree "$ROOT/sdk/openvibe/third_party/quickjs" \\\n "$SDK/src/game/shared/openvibe/third_party/quickjs"'
-- replacement = needle + '\n\n"$ROOT/tools/build-quickjs-lib.sh"'
-- if needle in s:
-- s = s.replace(needle, replacement)
-- else:
-- s += '\n\n"$ROOT/tools/build-quickjs-lib.sh"\n'
--
--# Add static lib to server_hl2mp.vpc patch.
--# Try to insert a $Lib line near the openvibe source files, only once.
--lib_line = '\t\t\t$Lib\t"..\\\\shared\\\\openvibe\\\\third_party\\\\quickjs\\\\build\\\\libquickjs_openvibe.a"'
--if 'libquickjs_openvibe.a' not in s:
-- # Add after openvibe_js_server.cpp if present in replacement text.
-- s = s.replace(
-- '\t\t\t\\$File\t"hl2mp\\\\openvibe_js_server.cpp"',
-- '\t\t\t\\$File\t"hl2mp\\\\openvibe_js_server.cpp"\\n' + lib_line.replace('$', '\\$')
-- )
-- # If that escaped replacement did not hit, add a direct post-patch insertion block.
-- if 'libquickjs_openvibe.a' not in s:
-- s += r'''
--
--# Link prebuilt QuickJS C static library. QuickJS must be compiled as C, not C++.
--if ! grep -q 'libquickjs_openvibe.a' "$SERVER_VPC"; then
-- perl -0pi -e 's/(\$File\s+"hl2mp\\openvibe_js_server\.cpp"\n)/$1\t\t\t\$Lib\t"..\\shared\\openvibe\\third_party\\quickjs\\build\\libquickjs_openvibe.a"\n/s' "$SERVER_VPC"
-- echo "[openvibe-sdk] linked QuickJS static library"
--fi
--'''
--
--p.write_text(s)
--PY
--
--echo "[openvibe] also patching already-applied SDK VPC now, if it exists"
--
--SERVER_VPC="$SDK/src/game/server/server_hl2mp.vpc"
--
--if [[ -f "$SERVER_VPC" ]]; then
-- backup_file "$SERVER_VPC"
--
-- # Remove direct QuickJS C files from VPC.
-- perl -0pi -e 's/^.*openvibe\\\\third_party\\\\quickjs\\\\(quickjs|libregexp|libunicode|cutils|dtoa|libbf)\.c.*\n//mg; s/^.*openvibe\\third_party\\quickjs\\(quickjs|libregexp|libunicode|cutils|dtoa|libbf)\.c.*\n//mg' "$SERVER_VPC"
--
-- if ! grep -q 'libquickjs_openvibe.a' "$SERVER_VPC"; then
-- perl -0pi -e 's/(\$File\s+"hl2mp\\openvibe_js_server\.cpp"\n)/$1\t\t\t\$Lib\t"..\\shared\\openvibe\\third_party\\quickjs\\build\\libquickjs_openvibe.a"\n/s' "$SERVER_VPC"
-- fi
--fi
--
--echo "[openvibe] building QuickJS C static library"
--tools/build-quickjs-lib.sh
--
--echo
--echo "[openvibe] fixed QuickJS C/C++ build split."
--echo
--echo "Now run:"
--echo " tools/apply-openvibe-sdk.sh"
--echo " tools/build-sdk-linux.sh 2>&1 | tee ~/ov-build.log"
--echo " tail -160 ~/ov-build.log"
ddiff --git a/tools/fix-quickjs-msvc-gnu-pthread-and-rerun.sh b/tools/fix-quickjs-msvc-gnu-pthread-and-rerun.sh
ddeleted file mode 100755
iindex b65a3bc..0000000
---- a/tools/fix-quickjs-msvc-gnu-pthread-and-rerun.sh
++++ /dev/null
@@@ -1,327 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--cd "$ROOT"
--BRANCH="$(git branch --show-current 2>/dev/null || echo codex/openvibe-next-steps)"
--
--say(){ echo "[openvibe] $*"; }
--
--say "fix QuickJS MSVC GNU builtins/attribute/pthread compat + rerun Windows DLL workflow"
--say "root=$ROOT"
--say "branch=$BRANCH"
--
--mkdir -p tools docs
--
--cat > tools/build-quickjs-lib-windows.ps1 <<'PS1'
--$ErrorActionPreference = 'Stop'
--
--$Root = if ($env:OPENVIBE_ROOT) { (Resolve-Path $env:OPENVIBE_ROOT).Path } else { (Resolve-Path (Join-Path $PSScriptRoot '..')).Path }
--$Sdk = if ($env:OPENVIBE_SDK) { $env:OPENVIBE_SDK } else { Join-Path $Root 'engine/source-sdk-2013' }
--
--$SrcQjs = Join-Path $Root 'sdk/openvibe/third_party/quickjs'
--$SdkQjs = Join-Path $Sdk 'src/game/shared/openvibe/third_party/quickjs'
--$Out = Join-Path $SdkQjs 'build'
--$LogDir = Join-Path $Root 'artifacts/windows-build-debug'
--
--function Say($m) { Write-Host "[openvibe-qjs-win] $m" }
--
--if (!(Test-Path (Join-Path $SrcQjs 'quickjs.c'))) {
-- throw "Missing $SrcQjs/quickjs.c. Run tools/vendor-quickjs.sh first."
--}
--if (!(Get-Command cl.exe -ErrorAction SilentlyContinue)) {
-- throw "cl.exe not found. Run this from a Visual Studio Developer PowerShell or Developer Command Prompt."
--}
--if (!(Get-Command lib.exe -ErrorAction SilentlyContinue)) {
-- throw "lib.exe not found. Run this from a Visual Studio Developer PowerShell or Developer Command Prompt."
--}
--
--New-Item -ItemType Directory -Force -Path $SdkQjs, $Out, $LogDir | Out-Null
--Copy-Item -Path (Join-Path $SrcQjs '*') -Destination $SdkQjs -Recurse -Force
--
--$CompatInclude = Join-Path $Out 'compat/include'
--$CompatSys = Join-Path $CompatInclude 'sys'
--New-Item -ItemType Directory -Force -Path $CompatInclude, $CompatSys | Out-Null
--
--# Upstream QuickJS is mostly GCC/clang oriented. MSVC needs a forced include for:
--# - GCC __attribute__((...)) syntax used in cutils.h
--# - GCC __builtin_* helpers used in cutils.h
--# - a few libc aliases
--$MsvcCompat = Join-Path $CompatInclude 'openvibe_qjs_msvc_compat.h'
--@'
--#ifndef OPENVIBE_QJS_MSVC_COMPAT_H
--#define OPENVIBE_QJS_MSVC_COMPAT_H
--
--#if defined(_MSC_VER)
--#ifndef WIN32_LEAN_AND_MEAN
--#define WIN32_LEAN_AND_MEAN
--#endif
--#ifndef NOMINMAX
--#define NOMINMAX
--#endif
--#include <stdint.h>
--#include <stdlib.h>
--#include <intrin.h>
--
--#ifndef __attribute__
--#define __attribute__(x)
--#endif
--#ifndef __builtin_expect
--#define __builtin_expect(x, y) (x)
--#endif
--
--static __inline int openvibe_qjs_clz32(unsigned int v)
--{
-- unsigned long idx;
-- if (_BitScanReverse(&idx, v)) return 31 - (int)idx;
-- return 32;
--}
--
--static __inline int openvibe_qjs_ctz32(unsigned int v)
--{
-- unsigned long idx;
-- if (_BitScanForward(&idx, v)) return (int)idx;
-- return 32;
--}
--
--static __inline int openvibe_qjs_clz64(uint64_t v)
--{
-- uint32_t hi = (uint32_t)(v >> 32);
-- uint32_t lo = (uint32_t)v;
-- if (hi) return openvibe_qjs_clz32(hi);
-- return 32 + openvibe_qjs_clz32(lo);
--}
--
--static __inline int openvibe_qjs_ctz64(uint64_t v)
--{
-- uint32_t hi = (uint32_t)(v >> 32);
-- uint32_t lo = (uint32_t)v;
-- if (lo) return openvibe_qjs_ctz32(lo);
-- return 32 + openvibe_qjs_ctz32(hi);
--}
--
--#ifndef __builtin_clz
--#define __builtin_clz(x) openvibe_qjs_clz32((unsigned int)(x))
--#endif
--#ifndef __builtin_ctz
--#define __builtin_ctz(x) openvibe_qjs_ctz32((unsigned int)(x))
--#endif
--#ifndef __builtin_clzll
--#define __builtin_clzll(x) openvibe_qjs_clz64((uint64_t)(x))
--#endif
--#ifndef __builtin_ctzll
--#define __builtin_ctzll(x) openvibe_qjs_ctz64((uint64_t)(x))
--#endif
--
--#ifndef alloca
--#define alloca _alloca
--#endif
--#ifndef strdup
--#define strdup _strdup
--#endif
--
--#endif /* _MSC_VER */
--#endif /* OPENVIBE_QJS_MSVC_COMPAT_H */
--'@ | Set-Content -Encoding ascii $MsvcCompat
--
--# Upstream QuickJS includes <sys/time.h>. MSVC does not provide it.
--$TimeHeader = Join-Path $CompatSys 'time.h'
--@'
--#ifndef OPENVIBE_QUICKJS_COMPAT_SYS_TIME_H
--#define OPENVIBE_QUICKJS_COMPAT_SYS_TIME_H
--
--#if defined(_WIN32)
--#ifndef WIN32_LEAN_AND_MEAN
--#define WIN32_LEAN_AND_MEAN
--#endif
--#ifndef NOMINMAX
--#define NOMINMAX
--#endif
--#include <windows.h>
--
--#ifdef __cplusplus
--extern "C" {
--#endif
--
--#ifndef _TIMEVAL_DEFINED
--#define _TIMEVAL_DEFINED
--typedef long suseconds_t;
--struct timeval {
-- long tv_sec;
-- long tv_usec;
--};
--#endif
--
--static __inline int gettimeofday(struct timeval *tv, void *tz)
--{
-- FILETIME ft;
-- ULARGE_INTEGER uli;
-- unsigned long long usec;
-- const unsigned long long unix_epoch = 116444736000000000ULL;
-- (void)tz;
-- if (!tv) return -1;
-- GetSystemTimeAsFileTime(&ft);
-- uli.LowPart = ft.dwLowDateTime;
-- uli.HighPart = ft.dwHighDateTime;
-- usec = (uli.QuadPart - unix_epoch) / 10ULL;
-- tv->tv_sec = (long)(usec / 1000000ULL);
-- tv->tv_usec = (long)(usec % 1000000ULL);
-- return 0;
--}
--
--#ifdef __cplusplus
--}
--#endif
--#else
--#include_next <sys/time.h>
--#endif
--
--#endif /* OPENVIBE_QUICKJS_COMPAT_SYS_TIME_H */
--'@ | Set-Content -Encoding ascii $TimeHeader
--
--# Stub enough pthread API for accidental includes. CONFIG_ATOMICS is disabled below,
--# so this should not be exercised, but the header keeps MSVC moving if upstream includes it.
--$PThreadHeader = Join-Path $CompatInclude 'pthread.h'
--@'
--#ifndef OPENVIBE_QUICKJS_COMPAT_PTHREAD_H
--#define OPENVIBE_QUICKJS_COMPAT_PTHREAD_H
--#if defined(_WIN32)
--typedef int pthread_mutex_t;
--typedef int pthread_t;
--typedef int pthread_attr_t;
--#define PTHREAD_MUTEX_INITIALIZER 0
--static __inline int pthread_mutex_init(pthread_mutex_t *m, const void *a) { (void)a; if (m) *m = 0; return 0; }
--static __inline int pthread_mutex_destroy(pthread_mutex_t *m) { (void)m; return 0; }
--static __inline int pthread_mutex_lock(pthread_mutex_t *m) { (void)m; return 0; }
--static __inline int pthread_mutex_unlock(pthread_mutex_t *m) { (void)m; return 0; }
--static __inline int pthread_create(pthread_t *t, const pthread_attr_t *a, void *(*fn)(void *), void *arg) { (void)t; (void)a; (void)fn; (void)arg; return -1; }
--static __inline int pthread_join(pthread_t t, void **ret) { (void)t; if (ret) *ret = 0; return 0; }
--#else
--#include_next <pthread.h>
--#endif
--#endif /* OPENVIBE_QUICKJS_COMPAT_PTHREAD_H */
--'@ | Set-Content -Encoding ascii $PThreadHeader
--
--# Disable QuickJS atomics/workers for this embedded game runtime on MSVC.
--# That avoids needing a real pthread implementation and is fine for our scripts.
--$QuickJsC = Join-Path $SdkQjs 'quickjs.c'
--if (Test-Path $QuickJsC) {
-- $q = Get-Content -Raw $QuickJsC
-- $q2 = [regex]::Replace($q, '(?m)^\s*#\s*define\s+CONFIG_ATOMICS\s*$', '/* OpenVibe MSVC build: CONFIG_ATOMICS disabled */')
-- if ($q2 -ne $q) {
-- Set-Content -Encoding ascii $QuickJsC $q2
-- Say 'disabled CONFIG_ATOMICS in SDK quickjs.c copy'
-- } else {
-- Say 'CONFIG_ATOMICS define not found in quickjs.c copy; pthread shim remains available'
-- }
--}
--
--Say "compat include=$CompatInclude"
--Say "compat forced include=$MsvcCompat"
--Say "compat sys/time.h=$TimeHeader"
--Say "compat pthread.h=$PThreadHeader"
--
--$sources = @('quickjs.c','libregexp.c','libunicode.c','cutils.c')
--if (Test-Path (Join-Path $SdkQjs 'dtoa.c')) { $sources += 'dtoa.c' }
--if (Test-Path (Join-Path $SdkQjs 'libbf.c')) { $sources += 'libbf.c' }
--
--Remove-Item (Join-Path $Out '*.obj') -Force -ErrorAction SilentlyContinue
--Remove-Item (Join-Path $Out 'libquickjs_openvibe.lib') -Force -ErrorAction SilentlyContinue
--
--$common = @(
-- '/nologo',
-- '/TC',
-- '/O2',
-- '/MT',
-- '/W3',
-- '/D_CRT_SECURE_NO_WARNINGS',
-- '/D_CRT_NONSTDC_NO_WARNINGS',
-- '/DWIN32',
-- '/D_WINDOWS',
-- '/DNOMINMAX',
-- '/DWIN32_LEAN_AND_MEAN',
-- "/DCONFIG_VERSION=`\`"openvibe`\`"",
-- "/FI$MsvcCompat",
-- "/I$CompatInclude",
-- "/I$SdkQjs"
--)
--
--$objects = @()
--foreach ($src in $sources) {
-- $srcPath = Join-Path $SdkQjs $src
-- $obj = Join-Path $Out ([IO.Path]::GetFileNameWithoutExtension($src) + '.obj')
-- Say "cl $src -> $obj"
-- & cl.exe @common /c "$srcPath" "/Fo$obj"
-- if ($LASTEXITCODE -ne 0) { throw "cl failed for $src" }
-- if (!(Test-Path $obj)) { throw "cl reported success but did not produce $obj" }
-- $objects += $obj
--}
--
--$outLib = Join-Path $Out 'libquickjs_openvibe.lib'
--Say "lib -> $outLib"
--& lib.exe /nologo "/OUT:$outLib" $objects
--if ($LASTEXITCODE -ne 0) { throw "lib.exe failed" }
--if (!(Test-Path $outLib)) { throw "lib.exe did not produce $outLib" }
--
--Say "built $outLib"
--PS1
--
--cat > docs/WINDOWS_QUICKJS_MSVC.md <<'MD'
--# Windows QuickJS MSVC Build Note
--
--The vendored QuickJS source is GCC/clang oriented. On Windows/MSVC it needs compatibility for:
--
--- POSIX `sys/time.h`
--- accidental `pthread.h` includes
--- GNU `__attribute__((...))`
--- GNU `__builtin_clz`, `__builtin_ctz`, and `__builtin_expect`
--
--`tools/build-quickjs-lib-windows.ps1` now generates build-local compatibility headers under:
--
--```text
--engine/source-sdk-2013/src/game/shared/openvibe/third_party/quickjs/build/compat/include
--```
--
--It also force-includes `openvibe_qjs_msvc_compat.h` for every QuickJS source file and disables `CONFIG_ATOMICS` in the copied SDK-side `quickjs.c` before compiling. Linux builds are unchanged.
--MD
--
--# Keep generated diagnostics/build deps out of git.
--if [[ -f .gitignore ]]; then
-- grep -qxF 'artifacts/' .gitignore || echo 'artifacts/' >> .gitignore
-- grep -qxF '_deps/' .gitignore || echo '_deps/' >> .gitignore
-- grep -qxF 'engine/source-sdk-2013/' .gitignore || echo 'engine/source-sdk-2013/' >> .gitignore
--else
-- cat > .gitignore <<'GI'
--artifacts/
--_deps/
--engine/source-sdk-2013/
--GI
--fi
--
--git rm -r --cached artifacts >/dev/null 2>&1 || true
--
--git add .gitignore docs/WINDOWS_QUICKJS_MSVC.md tools/build-quickjs-lib-windows.ps1 "$0" 2>/dev/null || \
-- git add .gitignore docs/WINDOWS_QUICKJS_MSVC.md tools/build-quickjs-lib-windows.ps1
--
--say "git diff summary"
--git diff --cached --stat || true
--
--if ! git diff --cached --quiet; then
-- git commit -m "Fix QuickJS MSVC GNU compatibility"
-- git push
--else
-- say "no changes to commit"
--fi
--
--if [[ -x tools/trigger-windows-dll-build-clean.sh ]]; then
-- say "triggering clean Windows DLL build"
-- tools/trigger-windows-dll-build-clean.sh
--else
-- REPO="$(git remote get-url origin | sed -E 's#^[email protected]:##; s#^https://github.com/##; s#\.git$##')"
-- say "triggering workflow repo=$REPO"
-- gh workflow run windows-source-sdk-dlls.yml --repo "$REPO" --ref "$BRANCH"
-- sleep 5
-- gh run list --repo "$REPO" --workflow windows-source-sdk-dlls.yml --limit 3
--fi
--
--say "done"
ddiff --git a/tools/fix-quickjs-msvc-systime-and-rerun.sh b/tools/fix-quickjs-msvc-systime-and-rerun.sh
ddeleted file mode 100755
iindex f58f6ab..0000000
---- a/tools/fix-quickjs-msvc-systime-and-rerun.sh
++++ /dev/null
@@@ -1,199 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--BRANCH="$(git -C "$ROOT" branch --show-current 2>/dev/null || true)"
--cd "$ROOT"
--
--echo "[openvibe] fix QuickJS MSVC sys/time.h shim + rerun Windows DLL workflow"
--echo "[openvibe] root=$ROOT"
--echo "[openvibe] branch=${BRANCH:-unknown}"
--
--mkdir -p tools docs
--
--cat > tools/build-quickjs-lib-windows.ps1 <<'PS1'
--$ErrorActionPreference = 'Stop'
--
--$Root = if ($env:OPENVIBE_ROOT) { (Resolve-Path $env:OPENVIBE_ROOT).Path } else { (Resolve-Path (Join-Path $PSScriptRoot '..')).Path }
--$Sdk = if ($env:OPENVIBE_SDK) { $env:OPENVIBE_SDK } else { Join-Path $Root 'engine/source-sdk-2013' }
--
--$SrcQjs = Join-Path $Root 'sdk/openvibe/third_party/quickjs'
--$SdkQjs = Join-Path $Sdk 'src/game/shared/openvibe/third_party/quickjs'
--$Out = Join-Path $SdkQjs 'build'
--$LogDir = Join-Path $Root 'artifacts/windows-build-debug'
--
--function Say($m) { Write-Host "[openvibe-qjs-win] $m" }
--
--if (!(Test-Path (Join-Path $SrcQjs 'quickjs.c'))) {
-- throw "Missing $SrcQjs/quickjs.c. Run tools/vendor-quickjs.sh first."
--}
--if (!(Get-Command cl.exe -ErrorAction SilentlyContinue)) {
-- throw "cl.exe not found. Run this from a Visual Studio Developer PowerShell or Developer Command Prompt."
--}
--if (!(Get-Command lib.exe -ErrorAction SilentlyContinue)) {
-- throw "lib.exe not found. Run this from a Visual Studio Developer PowerShell or Developer Command Prompt."
--}
--
--New-Item -ItemType Directory -Force -Path $SdkQjs, $Out, $LogDir | Out-Null
--Copy-Item -Path (Join-Path $SrcQjs '*') -Destination $SdkQjs -Recurse -Force
--
--# Upstream QuickJS includes <sys/time.h>. MSVC does not ship that POSIX header.
--# Provide a tiny compatibility header that exposes timeval + gettimeofday for quickjs.c.
--$CompatInclude = Join-Path $Out 'compat/include'
--$CompatSys = Join-Path $CompatInclude 'sys'
--New-Item -ItemType Directory -Force -Path $CompatSys | Out-Null
--$TimeHeader = Join-Path $CompatSys 'time.h'
--@'
--#ifndef OPENVIBE_QUICKJS_COMPAT_SYS_TIME_H
--#define OPENVIBE_QUICKJS_COMPAT_SYS_TIME_H
--
--#if defined(_WIN32)
--#ifndef WIN32_LEAN_AND_MEAN
--#define WIN32_LEAN_AND_MEAN
--#endif
--#ifndef NOMINMAX
--#define NOMINMAX
--#endif
--#include <windows.h>
--
--#ifdef __cplusplus
--extern "C" {
--#endif
--
--#ifndef _TIMEVAL_DEFINED
--#define _TIMEVAL_DEFINED
--typedef long suseconds_t;
--struct timeval {
-- long tv_sec;
-- long tv_usec;
--};
--#endif
--
--static __inline int gettimeofday(struct timeval *tv, void *tz)
--{
-- FILETIME ft;
-- ULARGE_INTEGER uli;
-- unsigned long long usec;
-- const unsigned long long unix_epoch = 116444736000000000ULL;
-- (void)tz;
-- if (!tv) return -1;
-- GetSystemTimeAsFileTime(&ft);
-- uli.LowPart = ft.dwLowDateTime;
-- uli.HighPart = ft.dwHighDateTime;
-- usec = (uli.QuadPart - unix_epoch) / 10ULL;
-- tv->tv_sec = (long)(usec / 1000000ULL);
-- tv->tv_usec = (long)(usec % 1000000ULL);
-- return 0;
--}
--
--#ifdef __cplusplus
--}
--#endif
--#else
--#include_next <sys/time.h>
--#endif
--
--#endif /* OPENVIBE_QUICKJS_COMPAT_SYS_TIME_H */
--'@ | Set-Content -Encoding ascii $TimeHeader
--
--Say "compat include=$CompatInclude"
--Say "compat header=$TimeHeader"
--
--$sources = @('quickjs.c','libregexp.c','libunicode.c','cutils.c')
--if (Test-Path (Join-Path $SdkQjs 'dtoa.c')) { $sources += 'dtoa.c' }
--if (Test-Path (Join-Path $SdkQjs 'libbf.c')) { $sources += 'libbf.c' }
--
--Remove-Item (Join-Path $Out '*.obj') -Force -ErrorAction SilentlyContinue
--Remove-Item (Join-Path $Out 'libquickjs_openvibe.lib') -Force -ErrorAction SilentlyContinue
--
--$common = @(
-- '/nologo',
-- '/TC',
-- '/O2',
-- '/MT',
-- '/W3',
-- '/D_CRT_SECURE_NO_WARNINGS',
-- '/DWIN32',
-- '/D_WINDOWS',
-- '/DNOMINMAX',
-- '/DWIN32_LEAN_AND_MEAN',
-- "/DCONFIG_VERSION=`\`"openvibe`\`"",
-- "/I$CompatInclude",
-- "/I$SdkQjs"
--)
--
--$objects = @()
--foreach ($src in $sources) {
-- $srcPath = Join-Path $SdkQjs $src
-- $obj = Join-Path $Out ([IO.Path]::GetFileNameWithoutExtension($src) + '.obj')
-- Say "cl $src -> $obj"
-- & cl.exe @common /c "$srcPath" "/Fo$obj"
-- if ($LASTEXITCODE -ne 0) { throw "cl failed for $src" }
-- if (!(Test-Path $obj)) { throw "cl reported success but did not produce $obj" }
-- $objects += $obj
--}
--
--$outLib = Join-Path $Out 'libquickjs_openvibe.lib'
--Say "lib -> $outLib"
--& lib.exe /nologo "/OUT:$outLib" $objects
--if ($LASTEXITCODE -ne 0) { throw "lib.exe failed" }
--if (!(Test-Path $outLib)) { throw "lib.exe did not produce $outLib" }
--
--Say "built $outLib"
--PS1
--
--cat > docs/WINDOWS_QUICKJS_MSVC.md <<'MD'
--# Windows QuickJS MSVC Build Note
--
--The vendored QuickJS C source includes POSIX `<sys/time.h>`, which MSVC does not provide. The Windows QuickJS build helper now generates a tiny compatibility include directory at build time:
--
--```text
--engine/source-sdk-2013/src/game/shared/openvibe/third_party/quickjs/build/compat/include/sys/time.h
--```
--
--That shim defines `struct timeval` and `gettimeofday()` using `GetSystemTimeAsFileTime()`, then passes the compatibility include directory to `cl.exe` before compiling QuickJS.
--
--This keeps Linux builds unchanged while allowing the Windows GitHub Actions runner to produce `libquickjs_openvibe.lib` for `client.dll` / `server.dll` builds.
--MD
--
--# Keep local generated diagnostics out of future commits.
--if [[ -f .gitignore ]]; then
-- grep -qxF 'artifacts/' .gitignore || echo 'artifacts/' >> .gitignore
-- grep -qxF '_deps/' .gitignore || echo '_deps/' >> .gitignore
-- grep -qxF 'engine/source-sdk-2013/' .gitignore || echo 'engine/source-sdk-2013/' >> .gitignore
--else
-- cat > .gitignore <<'GI'
--artifacts/
--_deps/
--engine/source-sdk-2013/
--GI
--fi
--
--# Avoid staging downloaded workflow diagnostics again.
--git rm -r --cached artifacts >/dev/null 2>&1 || true
--
--git add .gitignore docs/WINDOWS_QUICKJS_MSVC.md tools/build-quickjs-lib-windows.ps1 "$0" 2>/dev/null || \
-- git add .gitignore docs/WINDOWS_QUICKJS_MSVC.md tools/build-quickjs-lib-windows.ps1
--
--echo "[openvibe] git diff summary"
--git diff --cached --stat || true
--
--if ! git diff --cached --quiet; then
-- git commit -m "Fix QuickJS MSVC compatibility build"
-- git push
--else
-- echo "[openvibe] no changes to commit"
--fi
--
--if [[ -x tools/trigger-windows-dll-build-clean.sh ]]; then
-- echo "[openvibe] triggering clean Windows DLL build"
-- tools/trigger-windows-dll-build-clean.sh
--else
-- REPO="$(git remote get-url origin | sed -E 's#^[email protected]:##; s#^https://github.com/##; s#\.git$##')"
-- echo "[openvibe] triggering workflow repo=$REPO"
-- gh workflow run windows-source-sdk-dlls.yml --repo "$REPO" --ref "${BRANCH:-codex/openvibe-next-steps}"
-- sleep 5
-- gh run list --repo "$REPO" --workflow windows-source-sdk-dlls.yml --limit 3
--fi
--
--echo "[openvibe] done"
ddiff --git a/tools/fix-quickjs-use-clangcl-and-rerun.sh b/tools/fix-quickjs-use-clangcl-and-rerun.sh
ddeleted file mode 100755
iindex 17935aa..0000000
---- a/tools/fix-quickjs-use-clangcl-and-rerun.sh
++++ /dev/null
@@@ -1,342 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
--cd "$ROOT"
--BRANCH="$(git branch --show-current)"
--
--say() { echo "[openvibe] $*"; }
--
--say "fix QuickJS Windows build: use clang-cl for GNU/C99 QuickJS objects"
--say "root=$ROOT"
--say "branch=$BRANCH"
--
--mkdir -p tools docs
--
--cat > tools/build-quickjs-lib-windows.ps1 <<'PS1'
--$ErrorActionPreference = 'Stop'
--
--$Root = if ($env:OPENVIBE_ROOT) { (Resolve-Path $env:OPENVIBE_ROOT).Path } else { (Resolve-Path (Join-Path $PSScriptRoot '..')).Path }
--$Sdk = if ($env:OPENVIBE_SDK) { $env:OPENVIBE_SDK } else { Join-Path $Root 'engine/source-sdk-2013' }
--
--$SrcQjs = Join-Path $Root 'sdk/openvibe/third_party/quickjs'
--$SdkQjs = Join-Path $Sdk 'src/game/shared/openvibe/third_party/quickjs'
--$Out = Join-Path $SdkQjs 'build'
--$LogDir = Join-Path $Root 'artifacts/windows-build-debug'
--
--function Say($m) { Write-Host "[openvibe-qjs-win] $m" }
--function FirstExistingCommand([string[]]$names) {
-- foreach ($name in $names) {
-- $cmd = Get-Command $name -ErrorAction SilentlyContinue
-- if ($cmd) { return $cmd.Source }
-- }
-- return $null
--}
--function FirstExistingPath([string[]]$paths) {
-- foreach ($p in $paths) {
-- if ($p -and (Test-Path $p)) { return (Resolve-Path $p).Path }
-- }
-- return $null
--}
--
--if (!(Test-Path (Join-Path $SrcQjs 'quickjs.c'))) {
-- throw "Missing $SrcQjs/quickjs.c. Run tools/vendor-quickjs.sh first."
--}
--if (!(Get-Command lib.exe -ErrorAction SilentlyContinue)) {
-- throw "lib.exe not found. Run this from a Visual Studio Developer PowerShell or Developer Command Prompt."
--}
--
--New-Item -ItemType Directory -Force -Path $SdkQjs, $Out, $LogDir | Out-Null
--Copy-Item -Path (Join-Path $SrcQjs '*') -Destination $SdkQjs -Recurse -Force
--
--$CompatInclude = Join-Path $Out 'compat/include'
--$CompatSys = Join-Path $CompatInclude 'sys'
--New-Item -ItemType Directory -Force -Path $CompatInclude, $CompatSys | Out-Null
--
--# QuickJS is written for GCC/clang C, not plain MSVC C. Prefer clang-cl because it
--# emits MSVC-compatible COFF .obj files while accepting GNU/C99 constructs that
--# QuickJS uses heavily. Keep cl.exe only as a last-resort fallback.
--$clangCandidates = @(
-- (FirstExistingCommand @('clang-cl.exe','clang-cl')),
-- (Join-Path ${env:ProgramFiles} 'LLVM/bin/clang-cl.exe'),
-- (Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio/2022/Enterprise/VC/Tools/Llvm/x86/bin/clang-cl.exe'),
-- (Join-Path ${env:ProgramFiles} 'Microsoft Visual Studio/2022/Enterprise/VC/Tools/Llvm/x86/bin/clang-cl.exe'),
-- (Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio/2022/BuildTools/VC/Tools/Llvm/x86/bin/clang-cl.exe'),
-- (Join-Path ${env:ProgramFiles} 'Microsoft Visual Studio/2022/BuildTools/VC/Tools/Llvm/x86/bin/clang-cl.exe')
--)
--$ClangCl = FirstExistingPath $clangCandidates
--$ClExe = FirstExistingCommand @('cl.exe')
--if ($ClangCl) { Say "using clang-cl=$ClangCl" }
--elseif ($ClExe) { Say "clang-cl not found; falling back to cl.exe=$ClExe" }
--else { throw "Neither clang-cl.exe nor cl.exe was found." }
--
--# Windows sys/time.h shim for upstream QuickJS.
--$TimeHeader = Join-Path $CompatSys 'time.h'
--@'
--#ifndef OPENVIBE_QUICKJS_COMPAT_SYS_TIME_H
--#define OPENVIBE_QUICKJS_COMPAT_SYS_TIME_H
--#if defined(_WIN32)
--#ifndef WIN32_LEAN_AND_MEAN
--#define WIN32_LEAN_AND_MEAN
--#endif
--#ifndef NOMINMAX
--#define NOMINMAX
--#endif
--#include <windows.h>
--#ifdef __cplusplus
--extern "C" {
--#endif
--#ifndef _TIMEVAL_DEFINED
--#define _TIMEVAL_DEFINED
--typedef long suseconds_t;
--struct timeval {
-- long tv_sec;
-- long tv_usec;
--};
--#endif
--static __inline int gettimeofday(struct timeval *tv, void *tz)
--{
-- FILETIME ft;
-- ULARGE_INTEGER uli;
-- unsigned long long usec;
-- const unsigned long long unix_epoch = 116444736000000000ULL;
-- (void)tz;
-- if (!tv) return -1;
-- GetSystemTimeAsFileTime(&ft);
-- uli.LowPart = ft.dwLowDateTime;
-- uli.HighPart = ft.dwHighDateTime;
-- usec = (uli.QuadPart - unix_epoch) / 10ULL;
-- tv->tv_sec = (long)(usec / 1000000ULL);
-- tv->tv_usec = (long)(usec % 1000000ULL);
-- return 0;
--}
--#ifdef __cplusplus
--}
--#endif
--#else
--#include_next <sys/time.h>
--#endif
--#endif
--'@ | Set-Content -Encoding ascii $TimeHeader
--
--# pthread.h shim. We also disable CONFIG_ATOMICS below, so this is only to satisfy
--# accidental includes from vendored QuickJS variants.
--$PThreadHeader = Join-Path $CompatInclude 'pthread.h'
--@'
--#ifndef OPENVIBE_QUICKJS_COMPAT_PTHREAD_H
--#define OPENVIBE_QUICKJS_COMPAT_PTHREAD_H
--#if defined(_WIN32)
--typedef int pthread_mutex_t;
--typedef int pthread_t;
--typedef int pthread_attr_t;
--#define PTHREAD_MUTEX_INITIALIZER 0
--static __inline int pthread_mutex_init(pthread_mutex_t *m, const void *a) { (void)a; if (m) *m = 0; return 0; }
--static __inline int pthread_mutex_destroy(pthread_mutex_t *m) { (void)m; return 0; }
--static __inline int pthread_mutex_lock(pthread_mutex_t *m) { (void)m; return 0; }
--static __inline int pthread_mutex_unlock(pthread_mutex_t *m) { (void)m; return 0; }
--static __inline int pthread_create(pthread_t *t, const pthread_attr_t *a, void *(*fn)(void *), void *arg) { (void)t; (void)a; (void)fn; (void)arg; return -1; }
--static __inline int pthread_join(pthread_t t, void **ret) { (void)t; if (ret) *ret = 0; return 0; }
--#else
--#include_next <pthread.h>
--#endif
--#endif
--'@ | Set-Content -Encoding ascii $PThreadHeader
--
--# Forced include for clang-cl. Do NOT redefine __attribute__ or __builtin_* here;
--# clang handles those and QuickJS needs them.
--$ClangCompat = Join-Path $CompatInclude 'openvibe_qjs_clangcl_compat.h'
--@'
--#ifndef OPENVIBE_QJS_CLANGCL_COMPAT_H
--#define OPENVIBE_QJS_CLANGCL_COMPAT_H
--#if defined(_WIN32)
--#ifndef WIN32_LEAN_AND_MEAN
--#define WIN32_LEAN_AND_MEAN
--#endif
--#ifndef NOMINMAX
--#define NOMINMAX
--#endif
--#include <stdint.h>
--#include <stdlib.h>
--#include <malloc.h>
--#ifndef alloca
--#define alloca _alloca
--#endif
--#ifndef strdup
--#define strdup _strdup
--#endif
--#endif
--#endif
--'@ | Set-Content -Encoding ascii $ClangCompat
--
--# Forced include for emergency cl.exe fallback only.
--$MsvcCompat = Join-Path $CompatInclude 'openvibe_qjs_msvc_compat.h'
--@'
--#ifndef OPENVIBE_QJS_MSVC_COMPAT_H
--#define OPENVIBE_QJS_MSVC_COMPAT_H
--#if defined(_MSC_VER) && !defined(__clang__)
--#ifndef WIN32_LEAN_AND_MEAN
--#define WIN32_LEAN_AND_MEAN
--#endif
--#ifndef NOMINMAX
--#define NOMINMAX
--#endif
--#include <stdint.h>
--#include <stdlib.h>
--#include <malloc.h>
--#include <intrin.h>
--#ifndef __attribute__
--#define __attribute__(x)
--#endif
--#ifndef __builtin_expect
--#define __builtin_expect(x, y) (x)
--#endif
--#ifndef __builtin_frame_address
--#define __builtin_frame_address(x) ((void*)0)
--#endif
--static __inline int openvibe_qjs_clz32(unsigned int v) { unsigned long idx; if (_BitScanReverse(&idx, v)) return 31 - (int)idx; return 32; }
--static __inline int openvibe_qjs_ctz32(unsigned int v) { unsigned long idx; if (_BitScanForward(&idx, v)) return (int)idx; return 32; }
--static __inline int openvibe_qjs_clz64(uint64_t v) { uint32_t hi = (uint32_t)(v >> 32); uint32_t lo = (uint32_t)v; if (hi) return openvibe_qjs_clz32(hi); return 32 + openvibe_qjs_clz32(lo); }
--static __inline int openvibe_qjs_ctz64(uint64_t v) { uint32_t hi = (uint32_t)(v >> 32); uint32_t lo = (uint32_t)v; if (lo) return openvibe_qjs_ctz32(lo); return 32 + openvibe_qjs_ctz32(hi); }
--#ifndef __builtin_clz
--#define __builtin_clz(x) openvibe_qjs_clz32((unsigned int)(x))
--#endif
--#ifndef __builtin_ctz
--#define __builtin_ctz(x) openvibe_qjs_ctz32((unsigned int)(x))
--#endif
--#ifndef __builtin_clzll
--#define __builtin_clzll(x) openvibe_qjs_clz64((uint64_t)(x))
--#endif
--#ifndef __builtin_ctzll
--#define __builtin_ctzll(x) openvibe_qjs_ctz64((uint64_t)(x))
--#endif
--#ifndef alloca
--#define alloca _alloca
--#endif
--#ifndef strdup
--#define strdup _strdup
--#endif
--#endif
--#endif
--'@ | Set-Content -Encoding ascii $MsvcCompat
--
--# Disable QuickJS atomics/workers for the embedded game runtime.
--$QuickJsC = Join-Path $SdkQjs 'quickjs.c'
--if (Test-Path $QuickJsC) {
-- $q = Get-Content -Raw $QuickJsC
-- $q2 = [regex]::Replace($q, '(?m)^\s*#\s*define\s+CONFIG_ATOMICS\s*$', '/* OpenVibe Windows embedded build: CONFIG_ATOMICS disabled */')
-- if ($q2 -ne $q) {
-- Set-Content -Encoding ascii $QuickJsC $q2
-- Say 'disabled CONFIG_ATOMICS in SDK quickjs.c copy'
-- } else {
-- Say 'CONFIG_ATOMICS define not found in quickjs.c copy; pthread shim remains available'
-- }
--}
--
--Say "compat include=$CompatInclude"
--Say "compat sys/time.h=$TimeHeader"
--Say "compat pthread.h=$PThreadHeader"
--Say "clang forced include=$ClangCompat"
--Say "msvc forced include=$MsvcCompat"
--
--$sources = @('quickjs.c','libregexp.c','libunicode.c','cutils.c')
--if (Test-Path (Join-Path $SdkQjs 'dtoa.c')) { $sources += 'dtoa.c' }
--if (Test-Path (Join-Path $SdkQjs 'libbf.c')) { $sources += 'libbf.c' }
--
--Remove-Item (Join-Path $Out '*.obj') -Force -ErrorAction SilentlyContinue
--Remove-Item (Join-Path $Out 'libquickjs_openvibe.lib') -Force -ErrorAction SilentlyContinue
--
--if ($ClangCl) {
-- $Compiler = $ClangCl
-- $Common = @(
-- '/nologo',
-- '/TC',
-- '/O2',
-- '/MT',
-- '/W3',
-- '/D_CRT_SECURE_NO_WARNINGS',
-- '/D_CRT_NONSTDC_NO_WARNINGS',
-- '/DWIN32',
-- '/D_WINDOWS',
-- '/DNOMINMAX',
-- '/DWIN32_LEAN_AND_MEAN',
-- "/DCONFIG_VERSION=`\`"openvibe`\`"",
-- "/FI$ClangCompat",
-- "/I$CompatInclude",
-- "/I$SdkQjs",
-- '/clang:-std=gnu11',
-- '/clang:-Wno-everything'
-- )
--} else {
-- $Compiler = $ClExe
-- $Common = @(
-- '/nologo',
-- '/TC',
-- '/O2',
-- '/MT',
-- '/W3',
-- '/D_CRT_SECURE_NO_WARNINGS',
-- '/D_CRT_NONSTDC_NO_WARNINGS',
-- '/DWIN32',
-- '/D_WINDOWS',
-- '/DNOMINMAX',
-- '/DWIN32_LEAN_AND_MEAN',
-- "/DCONFIG_VERSION=`\`"openvibe`\`"",
-- "/FI$MsvcCompat",
-- "/I$CompatInclude",
-- "/I$SdkQjs"
-- )
--}
--
--$objects = @()
--foreach ($src in $sources) {
-- $srcPath = Join-Path $SdkQjs $src
-- $obj = Join-Path $Out ([IO.Path]::GetFileNameWithoutExtension($src) + '.obj')
-- Say "$([IO.Path]::GetFileName($Compiler)) $src -> $obj"
-- & $Compiler @Common /c "$srcPath" "/Fo$obj"
-- if ($LASTEXITCODE -ne 0) { throw "$Compiler failed for $src" }
-- if (!(Test-Path $obj)) { throw "$Compiler reported success but did not produce $obj" }
-- $objects += $obj
--}
--
--$outLib = Join-Path $Out 'libquickjs_openvibe.lib'
--Say "lib -> $outLib"
--& lib.exe /nologo "/OUT:$outLib" $objects
--if ($LASTEXITCODE -ne 0) { throw "lib.exe failed" }
--if (!(Test-Path $outLib)) { throw "lib.exe did not produce $outLib" }
--Say "built $outLib"
--PS1
--
--cat > docs/WINDOWS_QUICKJS_CLANGCL.md <<'MD'
--# Windows QuickJS build uses clang-cl
--
--QuickJS uses GNU/C99 compiler features that plain `cl.exe` does not parse cleanly, including GNU attributes, GCC/Clang builtins, and C initializer patterns. The Windows workflow now prefers `clang-cl.exe` for QuickJS only. `clang-cl` emits MSVC-compatible COFF `.obj` files, and `lib.exe` packs them into `libquickjs_openvibe.lib` for the regular Source SDK MSVC link.
--
--The rest of the Source SDK build still uses the Visual Studio/MSBuild toolchain.
--MD
--
--chmod +x tools/trigger-windows-dll-build-clean.sh 2>/dev/null || true
--
--git add tools/build-quickjs-lib-windows.ps1 docs/WINDOWS_QUICKJS_CLANGCL.md "$0" 2>/dev/null || git add tools/build-quickjs-lib-windows.ps1 docs/WINDOWS_QUICKJS_CLANGCL.md
--
--say "git diff summary"
--git diff --cached --stat || true
--
--if git diff --cached --quiet; then
-- say "no changes to commit"
--else
-- git commit -m "Build QuickJS with clang-cl on Windows"
-- git push
--fi
--
--say "triggering clean Windows DLL build"
--if [[ -x tools/trigger-windows-dll-build-clean.sh ]]; then
-- tools/trigger-windows-dll-build-clean.sh
--else
-- gh workflow run windows-source-sdk-dlls.yml --ref "$BRANCH"
-- sleep 10
-- RUN_ID="$(gh run list --workflow windows-source-sdk-dlls.yml --branch "$BRANCH" --limit 1 --json databaseId --jq '.[0].databaseId')"
-- gh run watch "$RUN_ID" --exit-status || true
-- tools/windows-workflow-debug-and-install.sh "$RUN_ID" || true
--fi
--
--say "done"
ddiff --git a/tools/fix-quickjs-vendor-smoke.sh b/tools/fix-quickjs-vendor-smoke.sh
ddeleted file mode 100755
iindex cc3491c..0000000
---- a/tools/fix-quickjs-vendor-smoke.sh
++++ /dev/null
@@@ -1,104 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--QJS="$ROOT/sdk/openvibe/third_party/quickjs"
--
--cd "$ROOT"
--
--echo "[openvibe] forcing clean QuickJS re-vendor"
--rm -rf "$QJS"
--tools/vendor-quickjs.sh
--
--echo "[openvibe] replacing smoke-quickjs.sh with robust version"
--
--cat > tools/smoke-quickjs.sh <<'SMOKE'
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--QJS="$ROOT/sdk/openvibe/third_party/quickjs"
--BUILD="$ROOT/.tmp/quickjs-smoke"
--
--rm -rf "$BUILD"
--mkdir -p "$BUILD"
--
--cat > "$BUILD/smoke.c" <<'C'
--#include <stdio.h>
--#include "quickjs.h"
--
--int main(void) {
-- JSRuntime *rt = JS_NewRuntime();
-- JSContext *ctx = JS_NewContext(rt);
--
-- JSValue value = JS_Eval(ctx, "1 + 2 + 3", 9, "<smoke>", JS_EVAL_TYPE_GLOBAL);
-- int32_t out = 0;
-- JS_ToInt32(ctx, &out, value);
--
-- JS_FreeValue(ctx, value);
-- JS_FreeContext(ctx);
-- JS_FreeRuntime(rt);
--
-- printf("quickjs result=%d\n", out);
-- return out == 6 ? 0 : 1;
--}
--C
--
--sources=(
-- "$QJS/quickjs.c"
-- "$QJS/libregexp.c"
-- "$QJS/libunicode.c"
-- "$QJS/cutils.c"
--)
--
--[[ -f "$QJS/dtoa.c" ]] && sources+=("$QJS/dtoa.c")
--[[ -f "$QJS/libbf.c" ]] && sources+=("$QJS/libbf.c")
--
--cc \
-- -std=gnu11 \
-- -D_GNU_SOURCE \
-- -DCONFIG_VERSION=\"openvibe\" \
-- -I"$QJS" \
-- "$BUILD/smoke.c" \
-- "${sources[@]}" \
-- -lm \
-- -ldl \
-- -lpthread \
-- -o "$BUILD/smoke-quickjs"
--
--"$BUILD/smoke-quickjs"
--SMOKE
--
--chmod +x tools/smoke-quickjs.sh
--
--echo "[openvibe] patching apply-openvibe-sdk.sh to not require libbf.c blindly"
--
--python3 <<'PY'
--from pathlib import Path
--
--p = Path("tools/apply-openvibe-sdk.sh")
--s = p.read_text()
--
--# Remove hardcoded libbf VPC line insertion if present.
--s = s.replace(
-- '\\n\\t\\t\\t\\$File\\t"..\\\\shared\\\\openvibe\\\\third_party\\\\quickjs\\\\libbf.c"',
-- ''
--)
--
--# Also remove the cleanup regex for libbf so repeated runs don't matter.
--s = s.replace(
-- ' s/^.*openvibe\\\\\\\\third_party\\\\\\\\quickjs\\\\\\\\libbf\\\\.c.*\\n//mg;\\n',
-- ''
--)
--
--p.write_text(s)
--PY
--
--echo "[openvibe] quickjs folder now contains:"
--find "$QJS" -maxdepth 1 -type f -printf " - %f\n" | sort
--
--echo "[openvibe] running smoke test"
--tools/smoke-quickjs.sh
--
--echo
--echo "[openvibe] QuickJS vendor/smoke fixed."
ddiff --git a/tools/fix-vpc-libquickjs-name.sh b/tools/fix-vpc-libquickjs-name.sh
ddeleted file mode 100755
iindex e735cd8..0000000
---- a/tools/fix-vpc-libquickjs-name.sh
++++ /dev/null
@@@ -1,215 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--SDK="${OPENVIBE_SDK:-$ROOT/engine/source-sdk-2013}"
--RUN_BUILD="${RUN_BUILD:-1}"
--STAMP="$(date +%Y%m%d-%H%M%S)"
--
--cd "$ROOT"
--
--backup_file() {
-- local file="$1"
-- [[ -f "$file" ]] && cp "$file" "$file.bak.$STAMP"
--}
--
--say() {
-- echo "[openvibe] $*"
--}
--
--say "fixing QuickJS static-library VPC link issue"
--
--mkdir -p tools
--
--# Clean accidentally tracked local/generated junk if it exists.
--say "cleaning accidental tracked junk if present"
--git rm -f .tmp/quickjs-smoke/smoke-quickjs 2>/dev/null || true
--git rm -f tools/apply-openvibe-sdk.sh.bak.* 2>/dev/null || true
--
--touch .gitignore
--if ! grep -qxF ".tmp/" .gitignore; then
-- cat >> .gitignore <<'GITIGNORE_ADD'
--
--# OpenVibe local/generated build junk
--.tmp/
--*.bak.*
--tools/*.bak.*
--engine/source-sdk-2013/src/game/shared/openvibe/third_party/quickjs/build/
--GITIGNORE_ADD
--fi
--
--say "writing tools/build-quickjs-lib.sh"
--
--cat > tools/build-quickjs-lib.sh <<'BUILDQJS'
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--SDK="${OPENVIBE_SDK:-$ROOT/engine/source-sdk-2013}"
--
--SRC_QJS="$ROOT/sdk/openvibe/third_party/quickjs"
--SDK_QJS="$SDK/src/game/shared/openvibe/third_party/quickjs"
--OUT="$SDK_QJS/build"
--
--if [[ ! -f "$SRC_QJS/quickjs.c" ]]; then
-- echo "Missing $SRC_QJS/quickjs.c" >&2
-- echo "Run: tools/vendor-quickjs.sh" >&2
-- exit 1
--fi
--
--mkdir -p "$SDK_QJS"
--
--# Exclude build/ so rsync does not delete our .o/.a output dir.
--rsync -a --delete --exclude='build/' "$SRC_QJS/" "$SDK_QJS/"
--
--mkdir -p "$OUT"
--
--sources=(
-- quickjs.c
-- libregexp.c
-- libunicode.c
-- cutils.c
--)
--
--[[ -f "$SDK_QJS/dtoa.c" ]] && sources+=(dtoa.c)
--[[ -f "$SDK_QJS/libbf.c" ]] && sources+=(libbf.c)
--
--rm -f "$OUT"/*.o "$OUT"/libquickjs_openvibe.a
--
--for src in "${sources[@]}"; do
-- obj="$OUT/${src%.c}.o"
-- echo "[openvibe-qjs] cc $src -> $obj"
--
-- cc \
-- -std=gnu11 \
-- -O2 \
-- -fPIC \
-- -D_GNU_SOURCE \
-- -DCONFIG_VERSION=\"openvibe\" \
-- -I"$SDK_QJS" \
-- -c "$SDK_QJS/$src" \
-- -o "$obj"
--done
--
--ar rcs "$OUT/libquickjs_openvibe.a" "$OUT"/*.o
--
--echo "[openvibe-qjs] built $OUT/libquickjs_openvibe.a"
--ls -lh "$OUT/libquickjs_openvibe.a"
--BUILDQJS
--
--chmod +x tools/build-quickjs-lib.sh
--
--say "patching tools/apply-openvibe-sdk.sh"
--
--if [[ ! -f tools/apply-openvibe-sdk.sh ]]; then
-- echo "Missing tools/apply-openvibe-sdk.sh" >&2
-- exit 1
--fi
--
--backup_file tools/apply-openvibe-sdk.sh
--
--python3 <<'PY'
--from pathlib import Path
--import re
--
--p = Path("tools/apply-openvibe-sdk.sh")
--s = p.read_text()
--
--# Remove older injected QuickJS final blocks so this script stays idempotent.
--patterns = [
-- r'\n# OPENVIBE_REMOVE_DIRECT_QUICKJS_C\n.*?(?=\n# OPENVIBE|\necho "\[openvibe-sdk\] Source SDK OpenVibe patch applied"|\Z)',
-- r'\n# OPENVIBE_LINK_QUICKJS_STATIC_LIB\n.*?(?=\n# OPENVIBE|\necho "\[openvibe-sdk\] Source SDK OpenVibe patch applied"|\Z)',
-- r'\n# OPENVIBE_FIX_QUICKJS_LINK_FINAL_V2\n.*?(?=\n# OPENVIBE|\necho "\[openvibe-sdk\] Source SDK OpenVibe patch applied"|\Z)',
--]
--for pat in patterns:
-- s = re.sub(pat, "\n", s, flags=re.S)
--
--# Do not allow a literal libquickjs_openvibe.a in any VPC $Lib line.
--# VPC appends .a itself, so ".a" here becomes ".a.a".
--s = s.replace("libquickjs_openvibe.a", "libquickjs_openvibe")
--
--# Ensure apply script calls the QuickJS static library builder.
--if '"$ROOT/tools/build-quickjs-lib.sh"' not in s:
-- needle = 'copy_tree "$ROOT/sdk/openvibe/third_party/quickjs" \\\n "$SDK/src/game/shared/openvibe/third_party/quickjs"'
-- if needle in s:
-- s = s.replace(needle, needle + '\n\n"$ROOT/tools/build-quickjs-lib.sh"')
-- else:
-- s += '\n\n"$ROOT/tools/build-quickjs-lib.sh"\n'
--
--final_block = r'''
--# OPENVIBE_FIX_QUICKJS_LINK_FINAL_V2
--# QuickJS is C, so do not compile its .c files through Source SDK/VPC C++.
--# We build libquickjs_openvibe.a with cc and link it as a library.
--if [[ -f "$SERVER_VPC" ]]; then
-- perl -0pi -e '
-- s/^.*quickjs\\quickjs\.c.*\n//mg;
-- s/^.*quickjs\\libregexp\.c.*\n//mg;
-- s/^.*quickjs\\libunicode\.c.*\n//mg;
-- s/^.*quickjs\\cutils\.c.*\n//mg;
-- s/^.*quickjs\\dtoa\.c.*\n//mg;
-- s/^.*quickjs\\libbf\.c.*\n//mg;
-- ' "$SERVER_VPC"
--
-- perl -0pi -e 's/^.*libquickjs_openvibe(?:\.a)?".*\n//mg' "$SERVER_VPC"
--
-- perl -0pi -e 's/(\$File\s+"hl2mp\\openvibe_js_server\.cpp"\n)/$1\t\t\t\$Lib\t"..\\shared\\openvibe\\third_party\\quickjs\\build\\libquickjs_openvibe"\n/s' "$SERVER_VPC"
--
-- echo "[openvibe-sdk] linked QuickJS static library"
--fi
--'''
--
--final_echo = 'echo "[openvibe-sdk] Source SDK OpenVibe patch applied"'
--if final_echo in s:
-- s = s.replace(final_echo, final_block + "\n" + final_echo)
--else:
-- s += "\n" + final_block + "\n"
--
--p.write_text(s)
--PY
--
--chmod +x tools/apply-openvibe-sdk.sh
--
--say "applying SDK patch"
--tools/apply-openvibe-sdk.sh
--
--SERVER_VPC="$SDK/src/game/server/server_hl2mp.vpc"
--
--say "force-fixing already-applied SDK server_hl2mp.vpc"
--
--if [[ -f "$SERVER_VPC" ]]; then
-- backup_file "$SERVER_VPC"
--
-- perl -0pi -e '
-- s/^.*quickjs\\quickjs\.c.*\n//mg;
-- s/^.*quickjs\\libregexp\.c.*\n//mg;
-- s/^.*quickjs\\libunicode\.c.*\n//mg;
-- s/^.*quickjs\\cutils\.c.*\n//mg;
-- s/^.*quickjs\\dtoa\.c.*\n//mg;
-- s/^.*quickjs\\libbf\.c.*\n//mg;
-- ' "$SERVER_VPC"
--
-- perl -0pi -e 's/^.*libquickjs_openvibe(?:\.a)?".*\n//mg' "$SERVER_VPC"
-- perl -0pi -e 's/(\$File\s+"hl2mp\\openvibe_js_server\.cpp"\n)/$1\t\t\t\$Lib\t"..\\shared\\openvibe\\third_party\\quickjs\\build\\libquickjs_openvibe"\n/s' "$SERVER_VPC"
--fi
--
--say "building QuickJS static lib"
--tools/build-quickjs-lib.sh
--
--say "VPC quickjs check"
--if [[ -f "$SERVER_VPC" ]]; then
-- grep -nE 'quickjs\\(quickjs|libregexp|libunicode|cutils|dtoa|libbf)\.c|libquickjs_openvibe' "$SERVER_VPC" || true
--else
-- echo "Missing $SERVER_VPC" >&2
--fi
--
--if [[ "$RUN_BUILD" = "1" ]]; then
-- say "running SDK build"
-- tools/build-sdk-linux.sh 2>&1 | tee "$HOME/ov-build.log"
--
-- say "build finished. Last lines:"
-- tail -80 "$HOME/ov-build.log"
--else
-- say "skipping build because RUN_BUILD=0"
-- echo "Run manually:"
-- echo " tools/build-sdk-linux.sh 2>&1 | tee ~/ov-build.log"
--fi
ddiff --git a/tools/fix-win32-build-missing-bitmap-lib-and-install.sh b/tools/fix-win32-build-missing-bitmap-lib-and-install.sh
ddeleted file mode 100755
iindex 29e6e3e..0000000
---- a/tools/fix-win32-build-missing-bitmap-lib-and-install.sh
++++ /dev/null
@@@ -1,127 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--say() { echo "[openvibe] $*"; }
--warn() { echo "[openvibe warn] $*" >&2; }
--fail() { echo "[openvibe error] $*" >&2; exit 1; }
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--cd "$ROOT"
--BRANCH="${OPENVIBE_BRANCH:-$(git rev-parse --abbrev-ref HEAD)}"
--REPO="${OPENVIBE_REPO:-$(tools/openvibe-gh-repo.sh)}"
--WORKFLOW="${OPENVIBE_WORKFLOW:-windows-source-sdk-dlls.yml}"
--BUILD_PS1="tools/build-sdk-windows.ps1"
--
--say "fix Win32 build: build/copy bitmap.lib before client link"
--say "root=$ROOT"
--say "branch=$BRANCH"
--say "repo=$REPO"
--
--command -v python3 >/dev/null 2>&1 || fail "python3 is required"
--command -v gh >/dev/null 2>&1 || fail "GitHub CLI gh is required"
--[[ -f "$BUILD_PS1" ]] || fail "missing $BUILD_PS1"
--
--python3 - <<'PY'
--from pathlib import Path
--p = Path('tools/build-sdk-windows.ps1')
--s = p.read_text()
--orig = s
--
--# Add bitmap project to the dependency build list. Client links ..\..\lib\public\bitmap.lib.
--if '"bitmap*.vcxproj"' not in s:
-- anchor = ' "mathlib*.vcxproj",\n'
-- if anchor not in s:
-- raise SystemExit('could not find dependency patterns anchor near mathlib*.vcxproj')
-- s = s.replace(anchor, anchor + ' "bitmap*.vcxproj",\n', 1)
--
--# Ensure bitmap.lib is copied into the public lib dir after dependency builds.
--if 'Copy-LibIfNeeded "bitmap.lib"' not in s:
-- anchor = ' Copy-LibIfNeeded "mathlib.lib" $publicLibDir\n'
-- if anchor not in s:
-- raise SystemExit('could not find Copy-LibIfNeeded anchor near mathlib.lib')
-- s = s.replace(anchor, anchor + ' Copy-LibIfNeeded "bitmap.lib" $publicLibDir\n', 1)
--
--# Improve fallback search ordering for libs: prefer target arch output dirs over stale x64.
--old = ''' $found = Get-ChildItem -Path $Sdk -Recurse -File -Filter $libName -ErrorAction SilentlyContinue |
-- Where-Object { $_.FullName -notmatch "artifacts|windows-build-debug|_deps" } |
-- Sort-Object LastWriteTime -Descending |
-- Select-Object -First 1
--'''
--new = ''' $found = Get-ChildItem -Path $Sdk -Recurse -File -Filter $libName -ErrorAction SilentlyContinue |
-- Where-Object { $_.FullName -notmatch "artifacts|windows-build-debug|_deps" } |
-- Sort-Object @{ Expression = {
-- if ($script:OpenVibeTargetArch -eq "x86") {
-- if ($_.FullName -match "\\x64\\|/x64/|win64|x64") { 1 } else { 0 }
-- } else {
-- if ($_.FullName -match "\\x64\\|/x64/|win64|x64") { 0 } else { 1 }
-- }
-- } }, LastWriteTime -Descending |
-- Select-Object -First 1
--'''
--if old in s and new not in s:
-- s = s.replace(old, new, 1)
--
--# Add an explicit diagnostic line so we can tell this patch ran.
--marker = '# OPENVIBE_WIN32_BITMAP_LIB_DEP_PATCH'
--if marker not in s:
-- s = s.replace('function Build-SourceSdkDependencyProjects {\n', f'{marker}\nfunction Build-SourceSdkDependencyProjects {{\n', 1)
--
--if s != orig:
-- p.write_text(s)
-- print('[patched] tools/build-sdk-windows.ps1')
--else:
-- print('[openvibe] tools/build-sdk-windows.ps1 already patched')
--PY
--
--mkdir -p docs
--cat > docs/WINDOWS_WIN32_BITMAP_LIB_FIX.md <<'DOC'
--# Windows Win32 bitmap.lib fix
--
--The Win32/Proton DLL build converts VPC's generated win64 projects to Win32 before MSBuild.
--After conversion, the HL2MP client links against `..\..\lib\public\bitmap.lib`.
--
--This patch adds the generated bitmap project to the dependency build list and copies
--`bitmap.lib` into the target public library directory before building `client.dll`.
--DOC
--
--say "git diff summary"
--git diff --stat
--
--git add tools/build-sdk-windows.ps1 docs/WINDOWS_WIN32_BITMAP_LIB_FIX.md tools/fix-win32-build-missing-bitmap-lib-and-install.sh 2>/dev/null || true
--if ! git diff --cached --quiet; then
-- git commit -m "Build bitmap lib for Win32 Proton DLLs"
-- git push origin "$BRANCH"
--else
-- say "no commit needed"
--fi
--
--HEAD="$(git rev-parse HEAD)"
--say "head=$HEAD"
--say "triggering Win32 workflow"
--gh workflow run "$WORKFLOW" --repo "$REPO" --ref "$BRANCH" -f diagnostics=1 || true
--
--RUN_ID=""
--for _ in $(seq 1 60); do
-- RUN_ID="$(gh run list --repo "$REPO" --workflow "$WORKFLOW" --branch "$BRANCH" --commit "$HEAD" --limit 10 --json databaseId,createdAt,status --jq 'sort_by(.createdAt) | last | .databaseId' 2>/dev/null || true)"
-- if [[ -n "$RUN_ID" && "$RUN_ID" != "null" ]]; then
-- break
-- fi
-- sleep 5
--done
--[[ -n "$RUN_ID" && "$RUN_ID" != "null" ]] || fail "could not find workflow run for commit $HEAD"
--
--say "watching run $RUN_ID"
--if ! gh run watch "$RUN_ID" --repo "$REPO" --exit-status; then
-- warn "workflow failed; downloading diagnostics"
-- tools/windows-workflow-debug-and-install.sh "$RUN_ID" || true
-- fail "Win32 DLL workflow failed"
--fi
--
--say "workflow passed; installing PE32 OpenVibe DLLs"
--OPENVIBE_EXPECT_DLL_ARCH=x86 tools/install-latest-openvibe-windows-dlls.sh "$RUN_ID"
--
--say "done. Now fully close hl2.exe/Proton, relaunch, and test:"
--echo 'OPENVIBE_PROTON_LOG=1 OPENVIBE_CLIENT_MODE=proton tools/run-client-auto.sh 127.0.0.1 27015'
--echo 'ov_help'
--echo 'ov_join hub'
--echo 'ov_menu'
ddiff --git a/tools/fix-windows-bootstrap-actions-checkout-and-rerun.sh b/tools/fix-windows-bootstrap-actions-checkout-and-rerun.sh
ddeleted file mode 100755
iindex 3a2e9ad..0000000
---- a/tools/fix-windows-bootstrap-actions-checkout-and-rerun.sh
++++ /dev/null
@@@ -1,195 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--cd "$ROOT"
--
--say() { printf '[openvibe] %s\n' "$*"; }
--
--BRANCH="$(git rev-parse --abbrev-ref HEAD)"
--REPO=""
--if [[ -x tools/openvibe-gh-repo.sh ]]; then
-- REPO="$(tools/openvibe-gh-repo.sh || true)"
--fi
--if [[ -z "$REPO" ]]; then
-- REPO="$(git remote get-url origin | sed -E 's#^[email protected]:##; s#^https://github.com/##; s#\.git$##')"
--else
-- REPO="$(printf '%s' "$REPO" | sed -E 's#^[email protected]:##; s#^https://github.com/##; s#\.git$##')"
--fi
--WORKFLOW="windows-source-sdk-dlls.yml"
--WORKFLOW_PATH=".github/workflows/$WORKFLOW"
--BOOT="tools/bootstrap-source-sdk-2013-windows.ps1"
--
--say "fix Windows bootstrap by using actions/checkout for Valve SDK"
--say "root=$ROOT"
--say "branch=$BRANCH"
--say "repo=$REPO"
--
--mkdir -p .github/workflows tools docs
--
--# Keep downloaded diagnostics and bootstrapped SDK out of git from now on.
--touch .gitignore
--for line in 'artifacts/' '_deps/' 'engine/source-sdk-2013/'; do
-- grep -qxF "$line" .gitignore || echo "$line" >> .gitignore
--done
--
--cat > "$BOOT" <<'PS1'
--$ErrorActionPreference = 'Stop'
--
--$Root = if ($env:OPENVIBE_ROOT) { (Resolve-Path $env:OPENVIBE_ROOT).Path } else { (Resolve-Path (Join-Path $PSScriptRoot '..')).Path }
--$Sdk = if ($env:OPENVIBE_SDK) { $env:OPENVIBE_SDK } else { Join-Path $Root 'engine/source-sdk-2013' }
--$Src = Join-Path $Sdk 'src'
--$Deps = Join-Path $Root '_deps'
--$Checkout = Join-Path $Deps 'source-sdk-2013-upstream'
--$LogDir = Join-Path $Root 'artifacts/windows-build-debug'
--New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
--
--function Say($m) { Write-Host "[openvibe-sdk-bootstrap] $m" }
--function WriteTree($path, $name) {
-- try {
-- if (Test-Path $path) {
-- "=== $path ===" | Out-File (Join-Path $LogDir $name)
-- Get-ChildItem -Force $path | Select-Object Mode,Length,Name,FullName | Format-Table -AutoSize | Out-File (Join-Path $LogDir $name) -Append
-- } else {
-- "[missing] $path" | Out-File (Join-Path $LogDir $name)
-- }
-- } catch {
-- "[tree failed] $path $($_.Exception.Message)" | Out-File (Join-Path $LogDir $name)
-- }
--}
--function HasHl2mp($root) {
-- $src = Join-Path $root 'src'
-- return ((Test-Path (Join-Path $src 'game/client/hl2mp')) -and (Test-Path (Join-Path $src 'game/server/hl2mp')))
--}
--function CopyLayout($fromRoot, $label) {
-- Say "using Valve SDK layout: $label -> $Sdk"
-- if (Test-Path $Sdk) { Remove-Item -Recurse -Force $Sdk }
-- New-Item -ItemType Directory -Force -Path $Sdk | Out-Null
-- Get-ChildItem -Force $fromRoot | Where-Object { $_.Name -ne '.git' } | ForEach-Object {
-- Copy-Item $_.FullName -Destination $Sdk -Recurse -Force
-- }
-- if (-not (HasHl2mp $Sdk)) {
-- WriteTree $Sdk 'sdk-root-after-copy.txt'
-- throw "Copied SDK layout is missing expected src/game/client/hl2mp and src/game/server/hl2mp folders"
-- }
-- Say "SDK bootstrapped successfully"
--}
--
--Say "root=$Root"
--Say "sdk=$Sdk"
--Say "deps=$Deps"
--Say "checkout=$Checkout"
--WriteTree $Root 'repo-root-before-bootstrap.txt'
--WriteTree $Deps 'deps-before-bootstrap.txt'
--
--if (HasHl2mp $Sdk) {
-- Say "existing SDK tree looks usable"
-- exit 0
--}
--
--# Preferred path: GitHub Actions checks out ValveSoftware/source-sdk-2013 into _deps/source-sdk-2013-upstream.
--# This avoids flaky codeload/curl behavior and avoids git credential leakage from the OpenVibe checkout.
--$candidates = @(
-- @{ Root = $Checkout; Label = 'upstream root/src' },
-- @{ Root = (Join-Path $Checkout 'mp'); Label = 'upstream mp/src' },
-- @{ Root = (Join-Path $Checkout 'sp'); Label = 'upstream sp/src' }
--)
--foreach ($c in $candidates) {
-- if ((Test-Path $c.Root) -and (HasHl2mp $c.Root)) {
-- CopyLayout $c.Root $c.Label
-- exit 0
-- }
--}
--
--# Emergency fallback only: try a clean public clone with credentials explicitly disabled.
--# The workflow should normally never reach this if the actions/checkout step worked.
--Say "actions checkout layout was not found; trying emergency public git clone fallback"
--$Fallback = Join-Path $Deps 'source-sdk-2013-git-fallback'
--if (Test-Path $Fallback) { Remove-Item -Recurse -Force $Fallback }
--New-Item -ItemType Directory -Force -Path $Deps | Out-Null
--$cloneLog = Join-Path $LogDir 'bootstrap-git-clone-fallback.log'
--& git -c http.https://github.com/.extraheader= clone --depth 1 https://github.com/ValveSoftware/source-sdk-2013.git $Fallback 2>&1 | Tee-Object -FilePath $cloneLog
--if ($LASTEXITCODE -ne 0) {
-- WriteTree $Deps 'deps-after-bootstrap-failure.txt'
-- throw "Could not clone ValveSoftware/source-sdk-2013. Check $cloneLog"
--}
--
--foreach ($root in @($Fallback, (Join-Path $Fallback 'mp'), (Join-Path $Fallback 'sp'))) {
-- if ((Test-Path $root) -and (HasHl2mp $root)) {
-- CopyLayout $root "git fallback $root"
-- exit 0
-- }
--}
--
--WriteTree $Fallback 'fallback-root-after-clone.txt'
--throw "ValveSoftware/source-sdk-2013 was fetched, but no usable HL2MP src layout was found."
--PS1
--
--python3 - <<'PY'
--from pathlib import Path
--p = Path('.github/workflows/windows-source-sdk-dlls.yml')
--s = p.read_text()
--insert = '''\n - name: Checkout Valve Source SDK 2013\n uses: actions/checkout@v4\n with:\n repository: ValveSoftware/source-sdk-2013\n path: _deps/source-sdk-2013-upstream\n fetch-depth: 1\n persist-credentials: false\n\n'''
--if 'Checkout Valve Source SDK 2013' not in s:
-- marker = ' - name: Bootstrap Source SDK 2013\n'
-- if marker not in s:
-- raise SystemExit('Could not find Bootstrap Source SDK 2013 step in workflow')
-- s = s.replace(marker, insert + marker, 1)
--# Make the tree/debug step aware of the second checkout path if it exists later in the job.
--old = "if (Test-Path engine/source-sdk-2013/src) { Get-ChildItem -Force engine/source-sdk-2013/src | Select-Object Mode,Length,Name } else { Write-Host \"[miss] engine/source-sdk-2013/src\" }"
--new = old + "\n if (Test-Path _deps/source-sdk-2013-upstream) { Get-ChildItem -Force _deps/source-sdk-2013-upstream | Select-Object Mode,Length,Name } else { Write-Host \"[miss] _deps/source-sdk-2013-upstream before checkout step\" }"
--if old in s and '_deps/source-sdk-2013-upstream before checkout step' not in s:
-- s = s.replace(old, new, 1)
--p.write_text(s)
--PY
--
--cat > docs/WINDOWS_WORKFLOW_SDK_BOOTSTRAP.md <<'MD'
--# Windows Source SDK bootstrap
--
--The Windows DLL workflow uses a two-checkout model:
--
--1. Checkout this OpenVibe repository.
--2. Checkout `ValveSoftware/source-sdk-2013` into `_deps/source-sdk-2013-upstream`.
--3. Normalize the Valve SDK layout into `engine/source-sdk-2013`.
--4. Apply OpenVibe SDK patches.
--5. Build `client.dll` and `server.dll` with MSBuild.
--
--The bootstrap script accepts these upstream layouts:
--
--- `_deps/source-sdk-2013-upstream/src/...`
--- `_deps/source-sdk-2013-upstream/mp/src/...`
--- `_deps/source-sdk-2013-upstream/sp/src/...`
--
--Diagnostics are uploaded under the `openvibe-windows-build-debug` artifact.
--MD
--
--say "git diff summary"
--git diff --stat -- .gitignore "$WORKFLOW_PATH" "$BOOT" docs/WINDOWS_WORKFLOW_SDK_BOOTSTRAP.md || true
--
--git add .gitignore "$WORKFLOW_PATH" "$BOOT" docs/WINDOWS_WORKFLOW_SDK_BOOTSTRAP.md "$0" 2>/dev/null || \
-- git add .gitignore "$WORKFLOW_PATH" "$BOOT" docs/WINDOWS_WORKFLOW_SDK_BOOTSTRAP.md tools/fix-windows-bootstrap-actions-checkout-and-rerun.sh
--
--git commit -m "Use actions checkout for Windows Source SDK bootstrap" || say "nothing to commit"
--git push
--
--say "triggering workflow"
--gh workflow run "$WORKFLOW" --repo "$REPO" --ref "$BRANCH"
--sleep 8
--RUN_ID="$(gh run list --repo "$REPO" --workflow "$WORKFLOW" --branch "$BRANCH" --limit 1 --json databaseId --jq '.[0].databaseId')"
--say "watching run $RUN_ID"
--if gh run watch "$RUN_ID" --repo "$REPO" --exit-status; then
-- say "workflow passed"
--else
-- say "workflow failed; downloading diagnostics anyway"
--fi
--
--if [[ -x tools/windows-workflow-debug-and-install.sh ]]; then
-- tools/windows-workflow-debug-and-install.sh || true
--fi
--
--say "verify DLL content"
--if [[ -x tools/verify-openvibe-dll-content.sh ]]; then
-- tools/verify-openvibe-dll-content.sh || true
--fi
--
--say "done"
ddiff --git a/tools/fix-windows-bootstrap-download-master-and-rerun.sh b/tools/fix-windows-bootstrap-download-master-and-rerun.sh
ddeleted file mode 100755
iindex a68c45c..0000000
---- a/tools/fix-windows-bootstrap-download-master-and-rerun.sh
++++ /dev/null
@@@ -1,248 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--cd "$ROOT"
--
--BRANCH="$(git rev-parse --abbrev-ref HEAD)"
--REPO="${OPENVIBE_REPO:-$(git remote get-url origin | sed -E 's#.*github.com[:/]([^/]+/[^/.]+)(\.git)?#\1#')}"
--REPO="${REPO%.git}"
--WORKFLOW="windows-source-sdk-dlls.yml"
--
--echo "[openvibe] fix Windows bootstrap: use Valve master zip/download fallback"
--echo "[openvibe] root=$ROOT"
--echo "[openvibe] branch=$BRANCH"
--echo "[openvibe] repo=$REPO"
--
--mkdir -p tools docs .github/workflows
--
--cat > tools/bootstrap-source-sdk-2013-windows.ps1 <<'PS'
--$ErrorActionPreference = 'Stop'
--
--$Root = if ($env:OPENVIBE_ROOT) { (Resolve-Path $env:OPENVIBE_ROOT).Path } else { (Resolve-Path (Join-Path $PSScriptRoot '..')).Path }
--$Sdk = if ($env:OPENVIBE_SDK) { $env:OPENVIBE_SDK } else { Join-Path $Root 'engine/source-sdk-2013' }
--$Src = Join-Path $Sdk 'src'
--$Deps = Join-Path $Root '_deps'
--$LogDir = Join-Path $Root 'artifacts/windows-build-debug'
--New-Item -ItemType Directory -Force -Path $Deps, $LogDir | Out-Null
--
--function Say($m) { Write-Host "[openvibe-sdk-bootstrap] $m" }
--function Dump-Dir($path, $outName) {
-- try {
-- if (Test-Path $path) {
-- Get-ChildItem -Force $path | Select-Object Mode,Length,Name | Format-Table -AutoSize | Out-File (Join-Path $LogDir $outName)
-- } else {
-- "[missing] $path" | Out-File (Join-Path $LogDir $outName)
-- }
-- } catch {
-- "dump failed: $($_.Exception.Message)" | Out-File (Join-Path $LogDir $outName)
-- }
--}
--function Assert-UsableSdk($prefix) {
-- if ((Test-Path (Join-Path $Src 'game/client/hl2mp')) -and (Test-Path (Join-Path $Src 'game/server/hl2mp'))) {
-- Say "$prefix SDK tree looks usable"
-- return $true
-- }
-- return $false
--}
--function Copy-SdkRoot($sourceRoot) {
-- Say "copying SDK root $sourceRoot -> $Sdk"
-- if (Test-Path $Sdk) { Remove-Item -Recurse -Force $Sdk }
-- New-Item -ItemType Directory -Force -Path $Sdk | Out-Null
-- Copy-Item -Path (Join-Path $sourceRoot '*') -Destination $Sdk -Recurse -Force
-- Dump-Dir $Sdk 'sdk-root-after-copy.txt'
-- if (!(Assert-UsableSdk 'copied')) {
-- throw "Copied SDK is missing expected HL2MP folders under $Src"
-- }
--}
--
--Say "root=$Root"
--Say "sdk=$Sdk"
--Say "deps=$Deps"
--
--if (Assert-UsableSdk 'existing') { exit 0 }
--
--# ValveSoftware/source-sdk-2013 currently uses the default/master layout with src/ at the repository root.
--# Do not assume an mp branch or mp/src folder. Downloading the zip avoids git/extraheader/auth weirdness on Actions runners.
--$zip = Join-Path $Deps 'source-sdk-2013-master.zip'
--$extractParent = Join-Path $Deps 'source-sdk-2013-zip'
--$zipRoot = Join-Path $extractParent 'source-sdk-2013-master'
--$zipUrl = 'https://codeload.github.com/ValveSoftware/source-sdk-2013/zip/refs/heads/master'
--
--$zipOk = $false
--try {
-- Say "downloading Valve SDK master zip from $zipUrl"
-- if (Test-Path $zip) { Remove-Item -Force $zip }
-- if (Test-Path $extractParent) { Remove-Item -Recurse -Force $extractParent }
-- New-Item -ItemType Directory -Force -Path $extractParent | Out-Null
--
-- if (Get-Command curl.exe -ErrorAction SilentlyContinue) {
-- & curl.exe -L --retry 4 --retry-delay 3 --connect-timeout 30 -o $zip $zipUrl 2>&1 | Tee-Object -FilePath (Join-Path $LogDir 'bootstrap-curl-master-zip.log')
-- if ($LASTEXITCODE -ne 0) { throw "curl.exe failed with exit code $LASTEXITCODE" }
-- } else {
-- Invoke-WebRequest -Uri $zipUrl -OutFile $zip -UseBasicParsing 2>&1 | Tee-Object -FilePath (Join-Path $LogDir 'bootstrap-iwr-master-zip.log')
-- }
--
-- $zipInfo = Get-Item $zip
-- Say "zip bytes=$($zipInfo.Length)"
-- if ($zipInfo.Length -lt 1000000) { throw "downloaded zip is suspiciously small" }
--
-- Expand-Archive -Path $zip -DestinationPath $extractParent -Force
-- Dump-Dir $extractParent 'zip-extract-parent.txt'
-- Dump-Dir $zipRoot 'zip-root.txt'
--
-- if (Test-Path (Join-Path $zipRoot 'src/game/client/hl2mp')) {
-- Copy-SdkRoot $zipRoot
-- $zipOk = $true
-- } else {
-- throw "zip did not contain expected src/game/client/hl2mp at $zipRoot"
-- }
--} catch {
-- Say "zip bootstrap failed: $($_.Exception.Message)"
-- $_ | Out-String | Out-File (Join-Path $LogDir 'bootstrap-zip-exception.txt')
--}
--
--if ($zipOk) { Say 'SDK bootstrapped successfully from zip'; exit 0 }
--
--# Last-resort git fallback. Clear possible GitHub Actions extraheaders and force public HTTPS.
--$gitRepo = Join-Path $Deps 'source-sdk-2013-git'
--try {
-- Say "trying git clone fallback"
-- git config --global --unset-all http.https://github.com/.extraheader 2>$null
-- git config --global --unset-all http.https://github.com/ValveSoftware/source-sdk-2013.extraheader 2>$null
-- if (Test-Path $gitRepo) { Remove-Item -Recurse -Force $gitRepo }
-- & git -c http.https://github.com/.extraheader= clone --depth 1 --single-branch https://github.com/ValveSoftware/source-sdk-2013.git $gitRepo 2>&1 | Tee-Object -FilePath (Join-Path $LogDir 'bootstrap-git-clone-master.log')
-- if ($LASTEXITCODE -ne 0) { throw "git clone failed with exit code $LASTEXITCODE" }
-- Dump-Dir $gitRepo 'git-root.txt'
-- if (Test-Path (Join-Path $gitRepo 'src/game/client/hl2mp')) {
-- Copy-SdkRoot $gitRepo
-- Say 'SDK bootstrapped successfully from git'
-- exit 0
-- }
-- throw "git clone did not contain src/game/client/hl2mp"
--} catch {
-- Say "git bootstrap failed: $($_.Exception.Message)"
-- $_ | Out-String | Out-File (Join-Path $LogDir 'bootstrap-git-exception.txt')
--}
--
--Dump-Dir $Deps 'deps-after-bootstrap-failure.txt'
--throw "Could not bootstrap ValveSoftware/source-sdk-2013. Check openvibe-windows-build-debug artifact, especially bootstrap-curl-master-zip.log, bootstrap-zip-exception.txt, and bootstrap-git-clone-master.log."
--PS
--
--cat > docs/WINDOWS_WORKFLOW_SDK_BOOTSTRAP.md <<'MD'
--# Windows Source SDK bootstrap
--
--The Windows GitHub Actions runner is fresh and does not have Alex's local `engine/source-sdk-2013` checkout.
--
--The workflow bootstraps Valve's public Source SDK 2013 repository into:
--
--```text
--engine/source-sdk-2013
--```
--
--The expected SDK layout is:
--
--```text
--engine/source-sdk-2013/src/game/client/hl2mp
--engine/source-sdk-2013/src/game/server/hl2mp
--```
--
--The bootstrap intentionally downloads the default/master zip first:
--
--```text
--https://codeload.github.com/ValveSoftware/source-sdk-2013/zip/refs/heads/master
--```
--
--This avoids brittle branch assumptions and avoids GitHub Actions `git` auth/header issues. A git clone fallback is still present for debugging.
--MD
--
--# Make sure the workflow has the bootstrap step before build, without rewriting the whole file if it already does.
--python3 - <<'PY'
--from pathlib import Path
--p = Path('.github/workflows/windows-source-sdk-dlls.yml')
--s = p.read_text()
--if 'Bootstrap Source SDK 2013 MP' not in s and 'Bootstrap Source SDK 2013' not in s:
-- marker = ' - name: Build Windows Source SDK DLLs\n'
-- insert = ''' - name: Bootstrap Source SDK 2013\n run: |\n $ErrorActionPreference = 'Stop'\n powershell -NoProfile -ExecutionPolicy Bypass -File tools/bootstrap-source-sdk-2013-windows.ps1 *>&1 | Tee-Object -FilePath artifacts/windows-build-debug/bootstrap-source-sdk-2013.log\n\n'''
-- if marker not in s:
-- raise SystemExit('could not find Build Windows Source SDK DLLs step marker')
-- s = s.replace(marker, insert + marker, 1)
--# Normalize the old step label if present.
--s = s.replace('Bootstrap Source SDK 2013 MP', 'Bootstrap Source SDK 2013')
--p.write_text(s)
--PY
--
--# Improve debug helper to print the new bootstrap files if the run fails.
--python3 - <<'PY'
--from pathlib import Path
--p = Path('tools/windows-workflow-debug-and-install.sh')
--if not p.exists():
-- raise SystemExit(0)
--s = p.read_text()
--needle = 'grep -RInE "(error|failed|fatal|C[0-9]{4}|LNK[0-9]{4}|MSB[0-9]{4}|No patched OpenVibe DLLs)" "$OUT" 2>/dev/null | head -n 80 || true'
--replacement = '''grep -RInE "(error|failed|fatal|C[0-9]{4}|LNK[0-9]{4}|MSB[0-9]{4}|No patched OpenVibe DLLs|Could not bootstrap|suspiciously small)" "$OUT" 2>/dev/null | head -n 120 || true
--
--for f in \
-- "$OUT"/openvibe-windows-build-debug/bootstrap-source-sdk-2013.log \
-- "$OUT"/openvibe-windows-build-debug/bootstrap-curl-master-zip.log \
-- "$OUT"/openvibe-windows-build-debug/bootstrap-zip-exception.txt \
-- "$OUT"/openvibe-windows-build-debug/bootstrap-git-clone-master.log \
-- "$OUT"/openvibe-windows-build-debug/bootstrap-git-exception.txt \
-- "$OUT"/openvibe-windows-build-debug/build-sdk-windows.log; do
-- if [[ -f "$f" ]]; then
-- echo
-- echo "[openvibe] tail: ${f#$ROOT/}"
-- tail -n 80 "$f" || true
-- fi
--done'''
--if needle in s:
-- s = s.replace(needle, replacement)
--elif 'bootstrap-curl-master-zip.log' not in s:
-- s += '\n\n# Extra bootstrap tails for current Windows workflow debugging.\n'
--p.write_text(s)
--PY
--
--echo "[openvibe] git diff summary"
--git diff --stat
--
--if ! git diff --quiet; then
-- git add tools/bootstrap-source-sdk-2013-windows.ps1 docs/WINDOWS_WORKFLOW_SDK_BOOTSTRAP.md .github/workflows/windows-source-sdk-dlls.yml tools/windows-workflow-debug-and-install.sh
-- git commit -m "Fix Windows SDK bootstrap download path"
-- git push origin "$BRANCH"
--else
-- echo "[openvibe] no changes to commit"
--fi
--
--if ! command -v gh >/dev/null 2>&1; then
-- echo "[openvibe] gh missing; install/authenticate GitHub CLI, then run tools/windows-workflow-debug-and-install.sh after triggering workflow."
-- exit 0
--fi
--
--echo "[openvibe] triggering workflow"
--gh workflow run "$WORKFLOW" --repo "$REPO" --ref "$BRANCH" || true
--sleep 8
--RUN_ID="$(gh run list --repo "$REPO" --workflow "$WORKFLOW" --branch "$BRANCH" --limit 1 --json databaseId --jq '.[0].databaseId')"
--echo "[openvibe] watching run $RUN_ID"
--if gh run watch "$RUN_ID" --repo "$REPO" --exit-status; then
-- echo "[openvibe] workflow passed"
--else
-- echo "[openvibe warn] workflow failed; downloading diagnostics"
--fi
--
--tools/windows-workflow-debug-and-install.sh || true
--
--echo
--if [[ -x tools/verify-openvibe-dll-content.sh ]]; then
-- tools/verify-openvibe-dll-content.sh || true
--fi
--
--echo
--cat <<'EOF'
--[openvibe] next if patched DLLs are installed:
-- OPENVIBE_PROTON_LOG=1 OPENVIBE_CLIENT_MODE=proton tools/run-client-auto.sh 127.0.0.1 27015
--
--In game console:
-- ov_join hub
-- ov_menu
-- ov_menu_servers
--EOF
ddiff --git a/tools/fix-windows-bootstrap-mp-branch-and-rerun.sh b/tools/fix-windows-bootstrap-mp-branch-and-rerun.sh
ddeleted file mode 100755
iindex 9601b08..0000000
---- a/tools/fix-windows-bootstrap-mp-branch-and-rerun.sh
++++ /dev/null
@@@ -1,206 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--cd "$ROOT"
--
--BRANCH="$(git rev-parse --abbrev-ref HEAD)"
--REPO="${OPENVIBE_GITHUB_REPO:-$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || true)}"
--REPO="${REPO:-OpenVibers/OpenVibe.Games}"
--REPO="${REPO%.git}"
--WORKFLOW="windows-source-sdk-dlls.yml"
--
--say(){ printf '[openvibe] %s\n' "$*"; }
--
--say "fix Windows bootstrap to clone the real Source SDK 2013 MP branch"
--say "root=$ROOT"
--say "branch=$BRANCH"
--say "repo=$REPO"
--
--if ! command -v gh >/dev/null 2>&1; then
-- echo "Missing gh. Run: sudo apt install gh && gh auth login" >&2
-- exit 1
--fi
--
--if ! gh auth status >/dev/null 2>&1; then
-- echo "gh is not authenticated. Run: gh auth login" >&2
-- exit 1
--fi
--
--mkdir -p tools docs .github/workflows
--
--cat > tools/bootstrap-source-sdk-2013-windows.ps1 <<'PS1'
--$ErrorActionPreference = 'Stop'
--
--$Root = if ($env:OPENVIBE_ROOT) { (Resolve-Path $env:OPENVIBE_ROOT).Path } else { (Resolve-Path (Join-Path $PSScriptRoot '..')).Path }
--$Sdk = if ($env:OPENVIBE_SDK) { $env:OPENVIBE_SDK } else { Join-Path $Root 'engine/source-sdk-2013' }
--$Src = Join-Path $Sdk 'src'
--$Deps = Join-Path $Root '_deps'
--$LogDir = Join-Path $Root 'artifacts/windows-build-debug'
--New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
--
--function Say($m) { Write-Host "[openvibe-sdk-bootstrap] $m" }
--function Has-HL2MP($base) {
-- return ((Test-Path (Join-Path $base 'src/game/client/hl2mp')) -and (Test-Path (Join-Path $base 'src/game/server/hl2mp')))
--}
--function Run-Git($args, $logName) {
-- $log = Join-Path $LogDir $logName
-- Say "git $args"
-- & git @args 2>&1 | Tee-Object -FilePath $log
-- if ($LASTEXITCODE -ne 0) { throw "git $args failed with exit code $LASTEXITCODE. See $log" }
--}
--
--Say "root=$Root"
--Say "sdk=$Sdk"
--Say "logdir=$LogDir"
--
--if (Has-HL2MP $Sdk) {
-- Say "existing SDK tree already contains HL2MP client/server"
-- exit 0
--}
--
--New-Item -ItemType Directory -Force -Path $Deps | Out-Null
--
--# Valve's source-sdk-2013 repository is normally organized by branches: mp and sp.
--# Some forks/mirrors use root folders like mp/src. Support both layouts.
--$RepoUrl = if ($env:OPENVIBE_SOURCE_SDK_REPO) { $env:OPENVIBE_SOURCE_SDK_REPO } else { 'https://github.com/ValveSoftware/source-sdk-2013.git' }
--$ValveRepo = Join-Path $Deps 'source-sdk-2013-mp'
--
--if (Test-Path $ValveRepo) { Remove-Item -Recurse -Force $ValveRepo }
--
--$cloned = $false
--try {
-- Say "cloning Valve Source SDK 2013 branch mp"
-- Run-Git @('clone','--depth','1','--branch','mp',$RepoUrl,$ValveRepo) 'bootstrap-git-clone-mp.log'
-- $cloned = $true
--} catch {
-- Say "mp branch clone failed: $($_.Exception.Message)"
-- if (Test-Path $ValveRepo) { Remove-Item -Recurse -Force $ValveRepo }
-- Say "falling back to default branch clone"
-- Run-Git @('clone','--depth','1',$RepoUrl,$ValveRepo) 'bootstrap-git-clone-default.log'
-- $cloned = $true
--}
--
--if (-not $cloned) { throw "could not clone Source SDK repo" }
--
--"=== Valve repo root ===" | Out-File (Join-Path $LogDir 'valve-repo-root.txt')
--Get-ChildItem -Force $ValveRepo | Select-Object Mode,Length,Name | Format-Table -AutoSize | Out-File (Join-Path $LogDir 'valve-repo-root.txt') -Append
--"=== candidate src folders ===" | Out-File (Join-Path $LogDir 'valve-candidate-src.txt')
--Get-ChildItem -Path $ValveRepo -Recurse -Directory -Filter src -ErrorAction SilentlyContinue |
-- Select-Object FullName | Format-List | Out-File (Join-Path $LogDir 'valve-candidate-src.txt') -Append
--
--$candidates = @(
-- $ValveRepo,
-- (Join-Path $ValveRepo 'mp'),
-- (Join-Path $ValveRepo 'srcsdk/mp'),
-- (Join-Path $ValveRepo 'source-sdk-2013/mp')
--)
--
--$SourceRoot = $null
--foreach ($candidate in $candidates) {
-- if (Has-HL2MP $candidate) {
-- $SourceRoot = $candidate
-- break
-- }
--}
--
--if (-not $SourceRoot) {
-- $matches = Get-ChildItem -Path $ValveRepo -Recurse -Directory -Filter hl2mp -ErrorAction SilentlyContinue |
-- Where-Object { $_.FullName -match '[\\/]src[\\/]game[\\/](client|server)[\\/]hl2mp$' }
-- $matches | Select-Object FullName | Format-List | Out-File (Join-Path $LogDir 'hl2mp-folder-search.txt')
-- throw "Could not find Source SDK 2013 MP HL2MP layout after clone. Check valve-repo-root.txt, valve-candidate-src.txt, and hl2mp-folder-search.txt."
--}
--
--Say "source root=$SourceRoot"
--Say "copying Source SDK MP layout into $Sdk"
--if (Test-Path $Sdk) { Remove-Item -Recurse -Force $Sdk }
--New-Item -ItemType Directory -Force -Path $Sdk | Out-Null
--Copy-Item -Path (Join-Path $SourceRoot '*') -Destination $Sdk -Recurse -Force
--
--if (!(Has-HL2MP $Sdk)) {
-- Get-ChildItem -Force $Sdk | Select-Object Mode,Length,Name | Format-Table -AutoSize | Out-File (Join-Path $LogDir 'sdk-root-after-copy.txt')
-- throw "Bootstrapped SDK is missing expected HL2MP folders after copy"
--}
--
--"=== SDK root after copy ===" | Out-File (Join-Path $LogDir 'sdk-root-after-copy.txt')
--Get-ChildItem -Force $Sdk | Select-Object Mode,Length,Name | Format-Table -AutoSize | Out-File (Join-Path $LogDir 'sdk-root-after-copy.txt') -Append
--"=== SDK src root after copy ===" | Out-File (Join-Path $LogDir 'sdk-src-root-after-copy.txt')
--Get-ChildItem -Force (Join-Path $Sdk 'src') | Select-Object Mode,Length,Name | Format-Table -AutoSize | Out-File (Join-Path $LogDir 'sdk-src-root-after-copy.txt') -Append
--
--Say "SDK bootstrapped successfully"
--PS1
--
--cat > docs/WINDOWS_WORKFLOW_SDK_BOOTSTRAP.md <<'MD'
--# Windows Source SDK bootstrap
--
--The GitHub Actions Windows runner does not have Alex's local `engine/source-sdk-2013` checkout. The workflow bootstraps it by cloning ValveSoftware/source-sdk-2013.
--
--Important detail: the Source SDK 2013 multiplayer code is normally on the `mp` branch, not necessarily in a `mp/` directory on the default branch. The bootstrap script now supports both layouts:
--
--- `source-sdk-2013` checked out directly on the `mp` branch, with `src/...` at repo root
--- older/mirrored layout with `mp/src/...`
--
--The workflow should not upload or install stock/stale DLLs. A DLL artifact is considered useful only when `client.dll` contains OpenVibe command strings such as `ov_join`, `ov_menu`, or `OpenVibe`.
--MD
--
--# Make workflow rerun on bootstrap changes and make bootstrap log more obvious.
--python3 - <<'PY'
--from pathlib import Path
--p = Path('.github/workflows/windows-source-sdk-dlls.yml')
--s = p.read_text()
--if 'tools/bootstrap-source-sdk-2013-windows.ps1' not in s:
-- s = s.replace(' - "tools/build-sdk-windows.ps1"\n', ' - "tools/build-sdk-windows.ps1"\n - "tools/bootstrap-source-sdk-2013-windows.ps1"\n')
--# Ensure bootstrap step captures a specific log and prints it on failure before exiting.
--old = ''' - name: Bootstrap Source SDK 2013 MP\n run: |\n powershell -NoProfile -ExecutionPolicy Bypass -File tools/bootstrap-source-sdk-2013-windows.ps1 *>&1 | Tee-Object -FilePath artifacts/windows-build-debug/bootstrap-source-sdk-2013.log\n'''
--new = ''' - name: Bootstrap Source SDK 2013 MP\n run: |\n $ErrorActionPreference = 'Continue'\n New-Item -ItemType Directory -Force -Path artifacts/windows-build-debug | Out-Null\n powershell -NoProfile -ExecutionPolicy Bypass -File tools/bootstrap-source-sdk-2013-windows.ps1 *>&1 | Tee-Object -FilePath artifacts/windows-build-debug/bootstrap-source-sdk-2013.log\n $code = $LASTEXITCODE\n if ($code -ne 0) {\n Write-Host \"[openvibe] bootstrap failed; tail follows\"\n Get-Content artifacts/windows-build-debug/bootstrap-source-sdk-2013.log -Tail 120\n exit $code\n }\n'''
--if old in s:
-- s = s.replace(old, new)
--p.write_text(s)
--PY
--
--# Improve debug downloader so it actually prints the useful logs next time.
--if [[ -f tools/windows-workflow-debug-and-install.sh ]]; then
--python3 - <<'PY'
--from pathlib import Path
--p = Path('tools/windows-workflow-debug-and-install.sh')
--s = p.read_text()
--needle = 'say "artifact files:"\nfind "$OUT" -type f | sort || true\n'
--insert = '''say "artifact files:"\nfind "$OUT" -type f | sort || true\n\nfor f in \\\n "$OUT"/openvibe-windows-build-debug/bootstrap-source-sdk-2013.log \\\n "$OUT"/openvibe-windows-build-debug/build-sdk-windows.log \\\n "$OUT"/openvibe-windows-build-debug/bootstrap-git-clone-mp.log \\\n "$OUT"/openvibe-windows-build-debug/valve-repo-root.txt \\\n "$OUT"/openvibe-windows-build-debug/valve-candidate-src.txt \\\n "$OUT"/openvibe-windows-build-debug/hl2mp-folder-search.txt; do\n if [[ -f "$f" ]]; then\n echo\n say "tail: ${f#$OUT/}"\n tail -n 120 "$f" || true\n fi\ndone\n'''
--if needle in s and 'bootstrap-git-clone-mp.log' not in s:
-- s = s.replace(needle, insert)
--p.write_text(s)
--PY
--fi
--
--say "git diff summary"
--git diff --stat
--
--git add .github/workflows/windows-source-sdk-dlls.yml tools/bootstrap-source-sdk-2013-windows.ps1 docs/WINDOWS_WORKFLOW_SDK_BOOTSTRAP.md tools/windows-workflow-debug-and-install.sh
--
--if git diff --cached --quiet; then
-- say "nothing to commit"
--else
-- git commit -m "Fix Windows Source SDK MP branch bootstrap"
-- git push origin "$BRANCH"
--fi
--
--say "triggering workflow"
--gh workflow run "$WORKFLOW" --ref "$BRANCH"
--sleep 6
--RUN_ID="$(gh run list --workflow "$WORKFLOW" --branch "$BRANCH" --limit 1 --json databaseId -q '.[0].databaseId')"
--say "watching run $RUN_ID"
--set +e
--gh run watch "$RUN_ID" --exit-status
--STATUS=$?
--set -e
--
--say "downloading diagnostics/artifacts"
--tools/windows-workflow-debug-and-install.sh || true
--
--if [[ $STATUS -eq 0 ]]; then
-- say "workflow succeeded"
--else
-- say "workflow failed again; paste the tail printed above if it does not clearly show the next compile error"
--fi
--
--exit $STATUS
ddiff --git a/tools/fix-windows-build-deps-python-and-rerun.sh b/tools/fix-windows-build-deps-python-and-rerun.sh
ddeleted file mode 100755
iindex 28ec0ab..0000000
---- a/tools/fix-windows-build-deps-python-and-rerun.sh
++++ /dev/null
@@@ -1,183 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$(pwd)}"
--if [[ ! -d "$ROOT/.git" ]]; then
-- ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--fi
--cd "$ROOT"
--
--say(){ printf '[openvibe] %s\n' "$*"; }
--
--say "fix Windows build: build Source SDK deps + make python available"
--say "root=$ROOT"
--say "branch=$(git branch --show-current)"
--
--python3 - <<'PY'
--from pathlib import Path
--import re
--
--root = Path.cwd()
--workflow = root / '.github/workflows/windows-source-sdk-dlls.yml'
--build = root / 'tools/build-sdk-windows.ps1'
--
--# ---------------------------------------------------------------------------
--# 1) Workflow: GitHub Windows runner has Python installed in some contexts, but
--# Source SDK custom build steps call literal "python" from inside vcvars/msbuild.
--# setup-python makes that deterministic.
--# ---------------------------------------------------------------------------
--y = workflow.read_text()
--if 'actions/setup-python@v5' not in y:
-- needle = ''' - name: Setup MSBuild\n uses: microsoft/setup-msbuild@v2\n'''
-- repl = needle + '''\n - name: Setup Python\n uses: actions/setup-python@v5\n with:\n python-version: "3.x"\n'''
-- if needle not in y:
-- raise SystemExit('Could not find Setup MSBuild block in workflow')
-- y = y.replace(needle, repl)
--workflow.write_text(y)
--
--# ---------------------------------------------------------------------------
--# 2) Build script: install a python command/shim onto PATH after vcvars64.
--# ---------------------------------------------------------------------------
--s = build.read_text()
--helper = r'''
--function Ensure-PythonOnPath {
-- $pyCmd = Get-Command python.exe -ErrorAction SilentlyContinue
-- if (-not $pyCmd) { $pyCmd = Get-Command python -ErrorAction SilentlyContinue }
-- if ($pyCmd) {
-- Say "python=$($pyCmd.Source)"
-- try { & $pyCmd.Source --version 2>&1 | Tee-Object -FilePath (Join-Path $LogDir "python-version.txt") | Out-Null } catch {}
-- return
-- }
--
-- $pyLauncher = Get-Command py.exe -ErrorAction SilentlyContinue
-- if ($pyLauncher) {
-- $shimDir = Join-Path $Root "_tools/python-shim"
-- New-Item -ItemType Directory -Force -Path $shimDir | Out-Null
-- $shim = Join-Path $shimDir "python.bat"
-- "@echo off`r`npy -3 %*`r`n" | Set-Content -Encoding ascii $shim
-- $env:PATH = "$shimDir;$env:PATH"
-- Say "python shim=$shim"
-- & python --version 2>&1 | Tee-Object -FilePath (Join-Path $LogDir "python-version.txt") | Out-Null
-- return
-- }
--
-- throw "python was not found on PATH and py.exe was not found. Add actions/setup-python before the build step."
--}
--'''
--if 'function Ensure-PythonOnPath' not in s:
-- marker = 'function Require($p, $msg) {\n if (!(Test-Path $p)) { throw "$msg`nMissing: $p" }\n}\n'
-- if marker not in s:
-- raise SystemExit('Could not find Require function marker')
-- s = s.replace(marker, marker + helper + '\n')
--
--call_marker = 'where.exe lib | Out-File (Join-Path $LogDir "where-lib.txt")\n'
--if 'Ensure-PythonOnPath' in s and 'Ensure-PythonOnPath\n\n# Apply OpenVibe' not in s:
-- if call_marker not in s:
-- raise SystemExit('Could not find where-lib marker')
-- s = s.replace(call_marker, call_marker + 'Ensure-PythonOnPath\n\n')
--
--# ---------------------------------------------------------------------------
--# 3) Build dependency libs before client/server projects.
--# Direct .vcxproj builds do not necessarily build VPC's implicit lib deps.
--# Current failure was missing src/lib/public/x64/mathlib.lib.
--# ---------------------------------------------------------------------------
--deps_block = r'''
--function Find-ProjectByPattern([string]$pattern) {
-- return @(Get-ChildItem -Path $Src -Recurse -File -Filter $pattern -ErrorAction SilentlyContinue |
-- Sort-Object @{ Expression = { if ($_.FullName -match "\\(mathlib|tier1|raytrace|vgui2|fgdlib)\\") { 0 } else { 1 } } }, FullName)
--}
--
--function Copy-LibIfNeeded([string]$libName, [string]$destDir) {
-- New-Item -ItemType Directory -Force -Path $destDir | Out-Null
-- $dest = Join-Path $destDir $libName
-- if (Test-Path $dest) { return }
-- $found = Get-ChildItem -Path $Sdk -Recurse -File -Filter $libName -ErrorAction SilentlyContinue |
-- Where-Object { $_.FullName -notmatch "artifacts|windows-build-debug|_deps" } |
-- Sort-Object LastWriteTime -Descending |
-- Select-Object -First 1
-- if ($found) {
-- Say "copying fallback lib $($found.FullName) -> $dest"
-- Copy-Item $found.FullName $dest -Force
-- }
--}
--
--function Build-SourceSdkDependencyProjects {
-- Say "building Source SDK dependency libraries before HL2MP client/server"
--
-- $patterns = @(
-- "tier1*_win64.vcxproj",
-- "mathlib*_win64.vcxproj",
-- "raytrace*_win64.vcxproj",
-- "vgui_controls*_win64.vcxproj",
-- "matsys_controls*_win64.vcxproj",
-- "fgdlib*_win64.vcxproj"
-- )
--
-- $projects = @()
-- foreach ($pat in $patterns) {
-- $projects += Find-ProjectByPattern $pat
-- }
-- $projects = @($projects | Sort-Object FullName -Unique)
--
-- "=== dependency projects ===" | Out-File (Join-Path $LogDir "dependency-projects.txt")
-- $projects | Select-Object FullName,Length,LastWriteTime | Format-List | Out-File (Join-Path $LogDir "dependency-projects.txt") -Append
--
-- foreach ($dep in $projects) {
-- $label = "dep-$($dep.BaseName -replace '[^A-Za-z0-9]+','_')"
-- [void](Invoke-MSBuildProject $dep $label)
-- }
--
-- $publicX64 = Join-Path $Src "lib/public/x64"
-- Copy-LibIfNeeded "mathlib.lib" $publicX64
-- Copy-LibIfNeeded "tier1.lib" $publicX64
-- Copy-LibIfNeeded "raytrace.lib" $publicX64
-- Copy-LibIfNeeded "vgui_controls.lib" $publicX64
-- Copy-LibIfNeeded "matsys_controls.lib" $publicX64
--
-- "=== public x64 libs after deps ===" | Out-File (Join-Path $LogDir "public-x64-libs-after-deps.txt")
-- if (Test-Path $publicX64) {
-- Get-ChildItem -Path $publicX64 -File -ErrorAction SilentlyContinue |
-- Select-Object FullName,Length,LastWriteTime |
-- Format-List | Out-File (Join-Path $LogDir "public-x64-libs-after-deps.txt") -Append
-- }
--}
--'''
--if 'function Build-SourceSdkDependencyProjects' not in s:
-- marker = 'function Invoke-MSBuildProject([System.IO.FileInfo]$proj, [string]$label) {'
-- idx = s.find(marker)
-- if idx == -1:
-- raise SystemExit('Could not find Invoke-MSBuildProject function marker')
-- # place deps block after Invoke-MSBuildProject function by locating the next Generate projects comment
-- gen_marker = '# Generate projects if no relevant vcxproj files exist yet.'
-- gen_idx = s.find(gen_marker)
-- if gen_idx == -1:
-- raise SystemExit('Could not find Generate projects marker')
-- s = s[:gen_idx] + deps_block + '\n' + s[gen_idx:]
--
--build_call_marker = '$beforeBuild = Get-Date\n\nif (-not (Invoke-MSBuildProject $clientProject "client")) {'
--if 'Build-SourceSdkDependencyProjects\n\n$beforeBuild = Get-Date' not in s:
-- if build_call_marker not in s:
-- raise SystemExit('Could not find beforeBuild/client build marker')
-- s = s.replace(build_call_marker, 'Build-SourceSdkDependencyProjects\n\n$beforeBuild = Get-Date\n\nif (-not (Invoke-MSBuildProject $clientProject "client")) {')
--
--build.write_text(s)
--PY
--
--say "git diff summary"
--git diff --stat
--
--git add .github/workflows/windows-source-sdk-dlls.yml tools/build-sdk-windows.ps1
--if git diff --cached --quiet; then
-- say "nothing to commit"
--else
-- git commit -m "Build Windows Source SDK dependencies before HL2MP DLLs"
--fi
--
--git push
--
--say "triggering clean Windows DLL build"
--if [[ -x tools/trigger-windows-dll-build-clean.sh ]]; then
-- tools/trigger-windows-dll-build-clean.sh
--else
-- tools/gh-windows-build-and-install.sh
--fi
ddiff --git a/tools/fix-windows-python-shim-for-msbuild-and-rerun.sh b/tools/fix-windows-python-shim-for-msbuild-and-rerun.sh
ddeleted file mode 100755
iindex ac47e9a..0000000
---- a/tools/fix-windows-python-shim-for-msbuild-and-rerun.sh
++++ /dev/null
@@@ -1,168 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
--cd "$ROOT"
--BRANCH="$(git rev-parse --abbrev-ref HEAD)"
--WORKFLOW="windows-source-sdk-dlls.yml"
--
--if [[ -x tools/openvibe-gh-repo.sh ]]; then
-- # shellcheck disable=SC1091
-- REPO="$(tools/openvibe-gh-repo.sh)"
--else
-- REPO="$(git remote get-url origin | sed -E 's#^[email protected]:##; s#^https://github.com/##; s#\.git$##')"
--fi
--REPO="${REPO%.git}"
--
--echo "[openvibe] fix Windows MSBuild Python shim + rerun"
--echo "[openvibe] root=$ROOT"
--echo "[openvibe] branch=$BRANCH"
--echo "[openvibe] repo=$REPO"
--
--# Keep setup-python in the workflow. This is still useful, but vcvars/msbuild can lose PATH,
--# so the PowerShell build script will also create its own python.bat shim.
--python3 - <<'PY'
--from pathlib import Path
--p = Path('.github/workflows/windows-source-sdk-dlls.yml')
--if p.exists():
-- s = p.read_text()
-- if 'actions/setup-python@v5' not in s:
-- marker = ' - name: Setup MSBuild\n'
-- block = """ - name: Setup Python\n uses: actions/setup-python@v5\n with:\n python-version: '3.x'\n\n"""
-- if marker in s:
-- s = s.replace(marker, block + marker, 1)
-- else:
-- s += "\n" + block
-- p.write_text(s)
--PY
--
--python3 - <<'PY'
--from pathlib import Path
--p = Path('tools/build-sdk-windows.ps1')
--s = p.read_text()
--marker = 'OPENVIBE_PYTHON_SHIM_FOR_MSBUILD'
--func = r'''
--# OPENVIBE_PYTHON_SHIM_FOR_MSBUILD
--function Ensure-PythonCommandForMSBuild {
-- $shimDir = Join-Path $Root "artifacts/python-shim"
-- New-Item -ItemType Directory -Force -Path $shimDir | Out-Null
-- $log = Join-Path $LogDir "python-version.txt"
-- "=== Ensure-PythonCommandForMSBuild ===" | Out-File $log
-- "initial PATH=$env:PATH" | Out-File $log -Append
-- "pythonLocation=$env:pythonLocation" | Out-File $log -Append
-- "Python_ROOT_DIR=$env:Python_ROOT_DIR" | Out-File $log -Append
--
-- $candidates = New-Object System.Collections.Generic.List[string]
-- foreach ($base in @($env:pythonLocation, $env:Python_ROOT_DIR, $env:Python3_ROOT_DIR)) {
-- if ($base) {
-- [void]$candidates.Add((Join-Path $base "python.exe"))
-- [void]$candidates.Add((Join-Path $base "python3.exe"))
-- }
-- }
--
-- foreach ($name in @("python.exe", "python3.exe", "py.exe")) {
-- $cmd = Get-Command $name -ErrorAction SilentlyContinue
-- if ($cmd -and $cmd.Source) { [void]$candidates.Add($cmd.Source) }
-- }
--
-- foreach ($rootCandidate in @("C:\hostedtoolcache\windows\Python", "C:\hostedtoolcache\windows\PyPy")) {
-- if (Test-Path $rootCandidate) {
-- Get-ChildItem -Path $rootCandidate -Recurse -File -Filter "python.exe" -ErrorAction SilentlyContinue |
-- Sort-Object LastWriteTime -Descending |
-- ForEach-Object { [void]$candidates.Add($_.FullName) }
-- }
-- }
--
-- $py = $null
-- foreach ($cand in ($candidates | Select-Object -Unique)) {
-- if ($cand -and (Test-Path $cand)) {
-- $py = (Resolve-Path $cand).Path
-- break
-- }
-- }
--
-- if (-not $py) {
-- "candidate list:" | Out-File $log -Append
-- $candidates | Out-File $log -Append
-- throw "Python executable not found after setup-python/vcvars. Check python-version.txt in the debug artifact."
-- }
--
-- $pyDir = Split-Path -Parent $py
-- $bat = Join-Path $shimDir "python.bat"
-- $cmdFile = Join-Path $shimDir "python.cmd"
-- $batContent = @"
--@echo off
--"$py" %*
--exit /b %ERRORLEVEL%
--"@
-- Set-Content -Encoding ascii -Path $bat -Value $batContent
-- Set-Content -Encoding ascii -Path $cmdFile -Value $batContent
--
-- # Put the shim first so custom build steps that literally run `python` find it.
-- $env:PATH = "$shimDir;$pyDir;$env:PATH"
-- "selected python=$py" | Out-File $log -Append
-- "shimDir=$shimDir" | Out-File $log -Append
-- "updated PATH=$env:PATH" | Out-File $log -Append
-- & $py --version 2>&1 | Tee-Object -FilePath $log -Append
-- & cmd.exe /d /s /c "where python" 2>&1 | Tee-Object -FilePath $log -Append
--}
--'''
--
--if marker not in s:
-- # Insert after Require function, before Find-VcVars64.
-- needle = 'function Find-VcVars64 {'
-- if needle not in s:
-- raise SystemExit('Could not find Find-VcVars64 insertion point in tools/build-sdk-windows.ps1')
-- s = s.replace(needle, func + '\n' + needle, 1)
--
--# Ensure it is called after tool where dumps in the x64 dev shell, before apply-openvibe-sdk/build.
--call = 'Ensure-PythonCommandForMSBuild'
--if call not in s.split('# Apply OpenVibe source files', 1)[0]:
-- needle = 'where.exe lib | Out-File (Join-Path $LogDir "where-lib.txt")\n'
-- if needle in s:
-- s = s.replace(needle, needle + call + '\n', 1)
-- else:
-- needle2 = 'Say "cl.exe already available"\n'
-- if needle2 not in s:
-- raise SystemExit('Could not find tool setup insertion point in tools/build-sdk-windows.ps1')
-- s = s.replace(needle2, needle2 + call + '\n', 1)
--
--p.write_text(s)
--PY
--
--# Commit patch.
--echo "[openvibe] git diff summary"
--git diff --stat .github/workflows/windows-source-sdk-dlls.yml tools/build-sdk-windows.ps1 || true
--
--git add .github/workflows/windows-source-sdk-dlls.yml tools/build-sdk-windows.ps1
--if git diff --cached --quiet; then
-- echo "[openvibe] no code changes to commit"
--else
-- git commit -m "Make python available to Windows MSBuild custom steps"
--fi
--
--echo "[openvibe] pushing $BRANCH"
--git push origin "$BRANCH"
--
--# Trigger and watch/download using the repo helper if available.
--if [[ -x tools/trigger-windows-dll-build-clean.sh ]]; then
-- echo "[openvibe] triggering clean Windows DLL build"
-- tools/trigger-windows-dll-build-clean.sh
--else
-- echo "[openvibe] triggering workflow"
-- gh workflow run "$WORKFLOW" --repo "$REPO" --ref "$BRANCH"
-- sleep 8
-- RUN_ID="$(gh run list --repo "$REPO" --workflow "$WORKFLOW" --branch "$BRANCH" --limit 1 --json databaseId --jq '.[0].databaseId')"
-- echo "[openvibe] watching run $RUN_ID"
-- gh run watch --repo "$REPO" "$RUN_ID" --exit-status || true
-- if [[ -x tools/windows-workflow-debug-and-install.sh ]]; then
-- tools/windows-workflow-debug-and-install.sh || true
-- fi
--fi
--
--if [[ -x tools/verify-openvibe-dll-content.sh ]]; then
-- echo "[openvibe] verify DLL content"
-- tools/verify-openvibe-dll-content.sh || true
--fi
--
--echo "[openvibe] done"
ddiff --git a/tools/fix-windows-sdk-bootstrap-and-real-dlls.sh b/tools/fix-windows-sdk-bootstrap-and-real-dlls.sh
ddeleted file mode 100755
iindex 6a25d5d..0000000
---- a/tools/fix-windows-sdk-bootstrap-and-real-dlls.sh
++++ /dev/null
@@@ -1,521 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--cd "$ROOT"
--BRANCH="$(git rev-parse --abbrev-ref HEAD)"
--REPO="${OPENVIBE_GITHUB_REPO:-OpenVibers/OpenVibe.Games}"
--REPO="${REPO%.git}"
--WORKFLOW="windows-source-sdk-dlls.yml"
--
--echo "[openvibe] fix Windows SDK bootstrap + real DLL artifact validation"
--echo "[openvibe] root=$ROOT"
--echo "[openvibe] branch=$BRANCH"
--echo "[openvibe] repo=$REPO"
--
--mkdir -p tools .github/workflows docs
--
--cat > tools/bootstrap-source-sdk-2013-windows.ps1 <<'PS1'
--$ErrorActionPreference = 'Stop'
--
--$Root = if ($env:OPENVIBE_ROOT) { (Resolve-Path $env:OPENVIBE_ROOT).Path } else { (Resolve-Path (Join-Path $PSScriptRoot '..')).Path }
--$Sdk = if ($env:OPENVIBE_SDK) { $env:OPENVIBE_SDK } else { Join-Path $Root 'engine/source-sdk-2013' }
--$Src = Join-Path $Sdk 'src'
--$Deps = Join-Path $Root '_deps'
--$ValveRepo = Join-Path $Deps 'source-sdk-2013'
--$LogDir = Join-Path $Root 'artifacts/windows-build-debug'
--New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
--
--function Say($m) { Write-Host "[openvibe-sdk-bootstrap] $m" }
--
--Say "root=$Root"
--Say "sdk=$Sdk"
--
--if ((Test-Path (Join-Path $Src 'game/client/hl2mp')) -and (Test-Path (Join-Path $Src 'game/server/hl2mp'))) {
-- Say "existing SDK tree looks usable"
-- exit 0
--}
--
--New-Item -ItemType Directory -Force -Path $Deps | Out-Null
--
--if (!(Test-Path (Join-Path $ValveRepo '.git'))) {
-- Say "cloning ValveSoftware/source-sdk-2013"
-- if (Test-Path $ValveRepo) { Remove-Item -Recurse -Force $ValveRepo }
-- git clone --depth 1 https://github.com/ValveSoftware/source-sdk-2013.git $ValveRepo 2>&1 | Tee-Object -FilePath (Join-Path $LogDir 'bootstrap-git-clone.log')
--} else {
-- Say "updating cached Valve source-sdk-2013"
-- Push-Location $ValveRepo
-- git fetch --depth 1 origin 2>&1 | Tee-Object -FilePath (Join-Path $LogDir 'bootstrap-git-fetch.log')
-- git reset --hard origin/master 2>&1 | Tee-Object -FilePath (Join-Path $LogDir 'bootstrap-git-reset.log')
-- Pop-Location
--}
--
--$Mp = Join-Path $ValveRepo 'mp'
--if (!(Test-Path (Join-Path $Mp 'src'))) {
-- Get-ChildItem -Force $ValveRepo | Select-Object Mode,Length,Name | Format-Table -AutoSize | Out-File (Join-Path $LogDir 'valve-repo-root.txt')
-- throw "Valve source-sdk-2013 clone does not contain mp/src at $Mp/src"
--}
--
--Say "copying mp branch layout into $Sdk"
--if (Test-Path $Sdk) { Remove-Item -Recurse -Force $Sdk }
--New-Item -ItemType Directory -Force -Path $Sdk | Out-Null
--Copy-Item -Path (Join-Path $Mp '*') -Destination $Sdk -Recurse -Force
--
--if (!(Test-Path (Join-Path $Src 'game/client/hl2mp')) -or !(Test-Path (Join-Path $Src 'game/server/hl2mp'))) {
-- Get-ChildItem -Force $Sdk | Select-Object Mode,Length,Name | Format-Table -AutoSize | Out-File (Join-Path $LogDir 'sdk-root-after-copy.txt')
-- throw "Bootstrapped SDK is missing expected HL2MP folders"
--}
--
--Say "SDK bootstrapped successfully"
--PS1
--
--cat > tools/build-sdk-windows.ps1 <<'PS1'
--param(
-- [switch]$InDevShell
--)
--
--$ErrorActionPreference = "Stop"
--
--$Root = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
--$Sdk = if ($env:OPENVIBE_SDK) { $env:OPENVIBE_SDK } else { Join-Path $Root "engine/source-sdk-2013" }
--$Src = Join-Path $Sdk "src"
--$Mod = Join-Path $Root "game/openvibe.games"
--$OutBin = Join-Path $Mod "bin"
--$LogDir = Join-Path $Root "artifacts/windows-build-debug"
--New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
--
--function Say($m) { Write-Host "[openvibe-win] $m" }
--function Require($p, $msg) { if (!(Test-Path $p)) { throw "$msg`nMissing: $p" } }
--
--function Enter-MsvcDevShellIfNeeded {
-- if (Get-Command cl.exe -ErrorAction SilentlyContinue) {
-- Say "cl.exe already available"
-- return
-- }
-- if ($InDevShell) { throw "cl.exe still not found after entering MSVC dev shell" }
--
-- $vswhere = Join-Path ${env:ProgramFiles(x86)} "Microsoft Visual Studio/Installer/vswhere.exe"
-- if (!(Test-Path $vswhere)) { throw "vswhere.exe not found; Visual Studio Build Tools are not installed" }
--
-- $install = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
-- if (!$install) { throw "Could not find Visual Studio installation with VC x86/x64 tools" }
--
-- $vcvars = Join-Path $install "VC/Auxiliary/Build/vcvars32.bat"
-- if (!(Test-Path $vcvars)) { throw "vcvars32.bat not found at $vcvars" }
--
-- Say "relaunching through MSVC x86 dev shell: $vcvars"
-- $self = $PSCommandPath
-- $cmd = "call `"$vcvars`" && powershell -NoProfile -ExecutionPolicy Bypass -File `"$self`" -InDevShell"
-- & cmd.exe /d /s /c $cmd
-- exit $LASTEXITCODE
--}
--
--Say "root=$Root"
--Say "sdk=$Sdk"
--Say "src=$Src"
--
--$env:OPENVIBE_ROOT = $Root
--$env:OPENVIBE_SDK = $Sdk
--
--if (!(Test-Path $Src)) {
-- Say "SDK src missing; bootstrapping Source SDK 2013 MP"
-- & powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $Root "tools/bootstrap-source-sdk-2013-windows.ps1")
-- if ($LASTEXITCODE -ne 0) { throw "bootstrap-source-sdk-2013-windows.ps1 failed with exit code $LASTEXITCODE" }
--}
--
--Require $Src "Source SDK checkout is missing from this runner."
--Require (Join-Path $Src "game/client/hl2mp") "Source SDK HL2MP client tree missing."
--Require (Join-Path $Src "game/server/hl2mp") "Source SDK HL2MP server tree missing."
--
--Enter-MsvcDevShellIfNeeded
--
--# Do not let old/stock repo DLLs get mistaken for a successful new build.
--New-Item -ItemType Directory -Force -Path $OutBin | Out-Null
--Remove-Item (Join-Path $OutBin "client.dll") -Force -ErrorAction SilentlyContinue
--Remove-Item (Join-Path $OutBin "server.dll") -Force -ErrorAction SilentlyContinue
--Remove-Item -Recurse -Force (Join-Path $Sdk "game/mod_hl2mp/bin") -ErrorAction SilentlyContinue
--
--# Apply OpenVibe source files using Git Bash. Skip the Linux QuickJS build inside apply-openvibe-sdk.sh.
--$bash = (Get-Command bash -ErrorAction SilentlyContinue)
--if ($bash) {
-- Say "applying OpenVibe SDK patch through bash"
-- $env:OPENVIBE_SKIP_QJS_BUILD = "1"
-- & bash "$Root/tools/apply-openvibe-sdk.sh" 2>&1 | Tee-Object -FilePath (Join-Path $LogDir "apply-openvibe-sdk.log")
-- if ($LASTEXITCODE -ne 0) { throw "apply-openvibe-sdk.sh failed with exit code $LASTEXITCODE" }
--} else {
-- throw "Git Bash is required on the Windows runner to apply the OpenVibe SDK patch."
--}
--
--Say "building QuickJS Windows static library"
--& powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $Root "tools/build-quickjs-lib-windows.ps1") 2>&1 | Tee-Object -FilePath (Join-Path $LogDir "quickjs-windows.log")
--if ($LASTEXITCODE -ne 0) { throw "build-quickjs-lib-windows.ps1 failed with exit code $LASTEXITCODE" }
--
--Set-Location $Src
--
--# Generate Visual Studio project files if needed.
--$solutions = @(Get-ChildItem -Path $Src -Recurse -Filter "*.sln" -ErrorAction SilentlyContinue)
--if ($solutions.Count -eq 0) {
-- Say "no .sln files found; trying Source SDK project generators"
-- $generators = @(
-- (Join-Path $Src "creategameprojects.bat"),
-- (Join-Path $Src "createallprojects.bat"),
-- (Join-Path $Src "createprojects.bat")
-- )
-- $ran = $false
-- foreach ($gen in $generators) {
-- if (Test-Path $gen) {
-- Say "running $gen"
-- & cmd /d /s /c "`"$gen`"" 2>&1 | Tee-Object -FilePath (Join-Path $LogDir "project-generation.log")
-- $ran = $true
-- break
-- }
-- }
--
-- if (-not $ran) {
-- $vpc = Join-Path $Src "devtools/bin/vpc.exe"
-- if (Test-Path $vpc) {
-- Say "running vpc.exe fallback"
-- & $vpc /hl2mp +game /mksln OpenVibe_HL2MP.sln 2>&1 | Tee-Object -FilePath (Join-Path $LogDir "vpc.log")
-- $ran = $true
-- }
-- }
--
-- $solutions = @(Get-ChildItem -Path $Src -Recurse -Filter "*.sln" -ErrorAction SilentlyContinue)
-- if ($solutions.Count -eq 0) {
-- Get-ChildItem -Path $Src -Force | Select-Object Mode,Length,Name | Format-Table -AutoSize | Out-File (Join-Path $LogDir "src-root-after-generator.txt")
-- throw "No Visual Studio solution was generated. Check openvibe-windows-build-debug artifact."
-- }
--}
--
--Say "solutions found:"
--$solutions | ForEach-Object { Say " $($_.FullName)" }
--
--$solution = $solutions |
-- Where-Object { $_.Name -match "hl2mp|game|sdk|everything|OpenVibe" } |
-- Select-Object -First 1
--if (-not $solution) { $solution = $solutions | Select-Object -First 1 }
--Say "selected solution=$($solution.FullName)"
--
--$configs = @("Release", "Release_HL2MP", "Release HL2MP")
--$platforms = @("Win32", "x86")
--$targets = @("client_hl2mp", "server_hl2mp", "client", "server", "Build")
--
--$builtAny = $false
--foreach ($cfg in $configs) {
-- foreach ($plat in $platforms) {
-- foreach ($target in $targets) {
-- Say "msbuild cfg=$cfg platform=$plat target=$target"
-- $log = Join-Path $LogDir "msbuild-$($cfg -replace '[^A-Za-z0-9]','_')-$plat-$target.log"
-- & msbuild $solution.FullName /m /p:Configuration=$cfg /p:Platform=$plat /p:PlatformToolset=v143 /t:$target /v:minimal /nologo 2>&1 | Tee-Object -FilePath $log
-- if ($LASTEXITCODE -eq 0) { $builtAny = $true; break }
-- }
-- if ($builtAny) { break }
-- }
-- if ($builtAny) { break }
--}
--
--if (-not $builtAny) { throw "MSBuild could not build any known Source SDK HL2MP target. Check uploaded msbuild logs." }
--
--New-Item -ItemType Directory -Force -Path $OutBin | Out-Null
--
--$client = Get-ChildItem -Path $Sdk -Recurse -Filter client.dll -ErrorAction SilentlyContinue |
-- Where-Object { $_.FullName -match "hl2mp|mod_hl2mp|client" } |
-- Sort-Object LastWriteTime -Descending |
-- Select-Object -First 1
--$server = Get-ChildItem -Path $Sdk -Recurse -Filter server.dll -ErrorAction SilentlyContinue |
-- Where-Object { $_.FullName -match "hl2mp|mod_hl2mp|server" } |
-- Sort-Object LastWriteTime -Descending |
-- Select-Object -First 1
--
--if (-not $client) { throw "client.dll was not produced" }
--if (-not $server) { throw "server.dll was not produced" }
--
--Say "copy client=$($client.FullName)"
--Say "copy server=$($server.FullName)"
--Copy-Item $client.FullName (Join-Path $OutBin "client.dll") -Force
--Copy-Item $server.FullName (Join-Path $OutBin "server.dll") -Force
--
--$clientText = & strings.exe (Join-Path $OutBin "client.dll") 2>$null | Select-String -Pattern "ov_join|ov_menu|OpenVibe" -Quiet
--if (-not $clientText) { throw "Built client.dll does not contain OpenVibe strings; refusing to publish stale/stock DLL" }
--
--Say "done"
--PS1
--
--python3 - <<'PY'
--from pathlib import Path
--p = Path('tools/apply-openvibe-sdk.sh')
--s = p.read_text()
--old = '''copy_tree() {\n local src="$1"\n local dst="$2"\n mkdir -p "$dst"\n rsync -a --delete "$src/" "$dst/"\n echo "[openvibe-sdk] copied tree ${dst#$SDK/}"\n}\n'''
--new = '''copy_tree() {\n local src="$1"\n local dst="$2"\n mkdir -p "$dst"\n if command -v rsync >/dev/null 2>&1; then\n rsync -a --delete "$src/" "$dst/"\n else\n rm -rf "$dst"\n mkdir -p "$dst"\n cp -R "$src"/. "$dst"/\n fi\n echo "[openvibe-sdk] copied tree ${dst#$SDK/}"\n}\n'''
--if old not in s:
-- raise SystemExit('Could not patch copy_tree() in tools/apply-openvibe-sdk.sh')
--p.write_text(s.replace(old, new))
--PY
--
--cat > .github/workflows/windows-source-sdk-dlls.yml <<'YAML'
--name: Build Windows Source DLLs
--
--on:
-- workflow_dispatch:
-- inputs:
-- diagnostics:
-- description: "Upload verbose build diagnostics"
-- required: false
-- default: "1"
-- push:
-- branches:
-- - codex/openvibe-next-steps
-- paths:
-- - ".github/workflows/windows-source-sdk-dlls.yml"
-- - "tools/bootstrap-source-sdk-2013-windows.ps1"
-- - "tools/build-sdk-windows.ps1"
-- - "tools/build-quickjs-lib-windows.ps1"
-- - "tools/apply-openvibe-sdk.sh"
-- - "sdk/openvibe/**"
-- - "game/openvibe.games/js/**"
--
--permissions:
-- contents: read
-- actions: read
--
--jobs:
-- windows-dlls:
-- name: Windows client.dll/server.dll
-- runs-on: windows-2022
-- defaults:
-- run:
-- shell: pwsh
--
-- steps:
-- - name: Checkout OpenVibe
-- uses: actions/checkout@v4
-- with:
-- fetch-depth: 1
--
-- - name: Configure git line endings
-- run: |
-- git config --global core.autocrlf false
-- git config --global core.eol lf
-- git status --short
--
-- - name: Setup MSBuild
-- uses: microsoft/setup-msbuild@v2
--
-- - name: Show toolchain and tree
-- run: |
-- $ErrorActionPreference = 'Continue'
-- New-Item -ItemType Directory -Force -Path artifacts/windows-build-debug | Out-Null
-- Write-Host "pwd=$(Get-Location)"
-- git rev-parse --short HEAD
-- where.exe msbuild
-- where.exe cl
-- where.exe link
-- where.exe bash
-- where.exe git
-- Get-ChildItem -Force | Select-Object Mode,Length,Name | Tee-Object -FilePath artifacts/windows-build-debug/repo-root-before-build.txt
-- if (Test-Path engine) { Get-ChildItem -Force engine | Select-Object Mode,Length,Name | Tee-Object -FilePath artifacts/windows-build-debug/engine-before-build.txt } else { Write-Host "[miss] engine directory" }
--
-- - name: Bootstrap Source SDK 2013 MP
-- run: |
-- $ErrorActionPreference = 'Stop'
-- powershell -NoProfile -ExecutionPolicy Bypass -File tools/bootstrap-source-sdk-2013-windows.ps1 *>&1 | Tee-Object -FilePath artifacts/windows-build-debug/bootstrap-source-sdk-2013.log
--
-- - name: Build Windows Source SDK DLLs
-- id: build
-- run: |
-- $ErrorActionPreference = 'Stop'
-- New-Item -ItemType Directory -Force -Path artifacts/windows-build-debug | Out-Null
-- powershell -NoProfile -ExecutionPolicy Bypass -File tools/build-sdk-windows.ps1 *>&1 | Tee-Object -FilePath artifacts/windows-build-debug/build-sdk-windows.log
--
-- - name: Stage build outputs and diagnostics
-- if: always()
-- run: |
-- $ErrorActionPreference = 'Continue'
-- New-Item -ItemType Directory -Force -Path artifacts/windows-dlls | Out-Null
-- New-Item -ItemType Directory -Force -Path artifacts/windows-build-debug | Out-Null
--
-- "=== repo ===" | Out-File artifacts/windows-build-debug/tree.txt
-- Get-ChildItem -Force | Format-Table -AutoSize | Out-File artifacts/windows-build-debug/tree.txt -Append
-- "=== dll search ===" | Out-File artifacts/windows-build-debug/dll-search.txt
-- Get-ChildItem -Recurse -Force -Include client.dll,server.dll -ErrorAction SilentlyContinue |
-- Select-Object FullName,Length,LastWriteTime |
-- Format-List | Out-File artifacts/windows-build-debug/dll-search.txt -Append
-- "=== project/log search ===" | Out-File artifacts/windows-build-debug/project-search.txt
-- Get-ChildItem -Recurse -Force -Include *.sln,*.vcxproj,*.vcproj,*.log -ErrorAction SilentlyContinue |
-- Select-Object FullName,Length,LastWriteTime |
-- Format-List | Out-File artifacts/windows-build-debug/project-search.txt -Append
--
-- $client = "game/openvibe.games/bin/client.dll"
-- $server = "game/openvibe.games/bin/server.dll"
-- $clientOk = $false
-- if (Test-Path $client) {
-- $text = strings.exe $client 2>$null | Select-String -Pattern "ov_join|ov_menu|OpenVibe" -Quiet
-- if ($text) { $clientOk = $true }
-- }
-- if ($clientOk -and (Test-Path $server)) {
-- Copy-Item $client artifacts/windows-dlls/client.dll -Force
-- Copy-Item $server artifacts/windows-dlls/server.dll -Force
-- Write-Host "[ok] staged patched OpenVibe Windows DLLs"
-- } else {
-- Write-Host "[miss] not staging DLLs because patched OpenVibe client.dll was not produced"
-- "No patched OpenVibe DLLs staged. Check build logs." | Out-File artifacts/windows-build-debug/no-patched-dlls.txt
-- }
--
-- - name: Upload Windows DLL artifact
-- if: always()
-- uses: actions/upload-artifact@v4
-- with:
-- name: openvibe-windows-dlls
-- path: artifacts/windows-dlls/**
-- if-no-files-found: warn
--
-- - name: Upload Windows build diagnostics
-- if: always()
-- uses: actions/upload-artifact@v4
-- with:
-- name: openvibe-windows-build-debug
-- path: artifacts/windows-build-debug/**
-- if-no-files-found: warn
--YAML
--
--cat > tools/windows-workflow-debug-and-install.sh <<'BASH2'
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--cd "$ROOT"
--REPO="${OPENVIBE_GITHUB_REPO:-OpenVibers/OpenVibe.Games}"
--WORKFLOW="${OPENVIBE_WINDOWS_WORKFLOW:-windows-source-sdk-dlls.yml}"
--RUN_ID="${1:-}"
--
--if ! command -v gh >/dev/null 2>&1; then
-- echo "[openvibe] missing gh. Install with: sudo apt install gh" >&2
-- exit 1
--fi
--
--if [[ -z "$RUN_ID" ]]; then
-- RUN_ID="$(gh run list --repo "$REPO" --workflow "$WORKFLOW" --branch "$(git rev-parse --abbrev-ref HEAD)" --limit 1 --json databaseId --jq '.[0].databaseId')"
--fi
--
--OUT="$ROOT/artifacts/windows-workflow-debug/run-$RUN_ID-artifacts"
--rm -rf "$OUT"
--mkdir -p "$OUT"
--
--echo "[openvibe] run=$RUN_ID"
--echo "[openvibe] downloading logs/artifacts into $OUT"
--
--gh run view "$RUN_ID" --repo "$REPO" --log > "$OUT/full-run.log" || true
--gh run download "$RUN_ID" --repo "$REPO" --dir "$OUT" || true
--
--find "$OUT" -maxdepth 3 -type f | sort > "$OUT/file-list.txt"
--
--echo
--echo "[openvibe] likely errors:"
--grep -RniE "error MSB|fatal error|undefined reference|unresolved external|LNK[0-9]+|C[0-9]{4}:|throw |Missing:|not found|could not|failed|No Visual Studio solution|No patched" "$OUT" | tail -n 120 || true
--
--echo
--echo "[openvibe] artifact files:"
--sed -n '1,160p' "$OUT/file-list.txt"
--
--DLLDIR="$OUT/openvibe-windows-dlls"
--if [[ -f "$DLLDIR/client.dll" && -f "$DLLDIR/server.dll" ]]; then
-- if strings "$DLLDIR/client.dll" | grep -Eq 'ov_join|ov_menu|OpenVibe'; then
-- mkdir -p game/openvibe.games/bin
-- cp -f "$DLLDIR/client.dll" game/openvibe.games/bin/client.dll
-- cp -f "$DLLDIR/server.dll" game/openvibe.games/bin/server.dll
-- echo "[openvibe] installed patched Windows DLLs"
-- tools/verify-openvibe-dll-content.sh || true
-- else
-- echo "[openvibe] refused to install DLL artifact because client.dll lacks OpenVibe strings" >&2
-- exit 2
-- fi
--else
-- echo "[openvibe] no DLL artifact available yet" >&2
-- exit 3
--fi
--BASH2
--chmod +x tools/windows-workflow-debug-and-install.sh
--
--cat > docs/WINDOWS_WORKFLOW_SDK_BOOTSTRAP.md <<'MD'
--# Windows DLL workflow bootstrap
--
--The Windows GitHub Actions runner does not have the local `engine/source-sdk-2013` checkout that exists on the Linux workstation.
--
--The workflow now bootstraps Valve's Source SDK 2013 multiplayer tree into:
--
--```text
--engine/source-sdk-2013
--```
--
--Then it applies the OpenVibe SDK patch, skips the Linux QuickJS static-library build, builds QuickJS with MSVC, generates Visual Studio projects, builds `client.dll/server.dll`, and only uploads DLLs when `client.dll` contains OpenVibe command strings such as `ov_join`, `ov_menu`, or `OpenVibe`.
--
--Use:
--
--```bash
--tools/gh-windows-build-and-install.sh
--```
--
--or debug the latest run with:
--
--```bash
--tools/windows-workflow-debug-and-install.sh
--```
--MD
--
--# Keep gh helper stricter if present: do not install stale DLLs.
--if [[ -f tools/gh-windows-build-and-install.sh ]]; then
--python3 - <<'PY'
--from pathlib import Path
--p = Path('tools/gh-windows-build-and-install.sh')
--s = p.read_text()
--if 'strings "$ARTDIR/openvibe-windows-dlls/client.dll"' not in s and 'refused to install DLL artifact' not in s:
-- marker = 'if [[ -f "$ARTDIR/openvibe-windows-dlls/client.dll" && -f "$ARTDIR/openvibe-windows-dlls/server.dll" ]]; then'
-- if marker in s:
-- repl = marker + '''
-- if ! strings "$ARTDIR/openvibe-windows-dlls/client.dll" | grep -Eq 'ov_join|ov_menu|OpenVibe'; then
-- echo "[openvibe] refused to install DLL artifact because client.dll lacks OpenVibe strings" >&2
-- tools/windows-workflow-debug-and-install.sh "$RUN_ID" || true
-- exit 2
-- fi'''
-- s = s.replace(marker, repl)
-- p.write_text(s)
--PY
--fi
--
--# Commit/push and run workflow if gh is available.
--echo
--echo "[openvibe] git diff summary"
--git diff --stat
--
--git add \
-- .github/workflows/windows-source-sdk-dlls.yml \
-- tools/bootstrap-source-sdk-2013-windows.ps1 \
-- tools/build-sdk-windows.ps1 \
-- tools/apply-openvibe-sdk.sh \
-- tools/windows-workflow-debug-and-install.sh \
-- tools/gh-windows-build-and-install.sh \
-- docs/WINDOWS_WORKFLOW_SDK_BOOTSTRAP.md 2>/dev/null || true
--
--if ! git diff --cached --quiet; then
-- git commit -m "Bootstrap Source SDK for Windows DLL workflow"
-- git push origin "$BRANCH"
--else
-- echo "[openvibe] no changes to commit"
--fi
--
--if command -v gh >/dev/null 2>&1; then
-- echo "[openvibe] triggering workflow"
-- gh workflow run "$WORKFLOW" --repo "$REPO" --ref "$BRANCH" -f diagnostics=1
-- sleep 6
-- RUN_ID="$(gh run list --repo "$REPO" --workflow "$WORKFLOW" --branch "$BRANCH" --limit 1 --json databaseId --jq '.[0].databaseId')"
-- echo "[openvibe] watching run $RUN_ID"
-- gh run watch "$RUN_ID" --repo "$REPO" || true
-- tools/windows-workflow-debug-and-install.sh "$RUN_ID" || true
--else
-- echo "[openvibe] gh missing; run tools/gh-windows-build-and-install.sh after installing/authenticating gh"
--fi
--
--echo
--echo "[openvibe] next if DLLs installed:"
--echo " OPENVIBE_PROTON_LOG=1 OPENVIBE_CLIENT_MODE=proton tools/run-client-auto.sh 127.0.0.1 27015"
--echo " # in game console: ov_join hub ; ov_menu ; ov_menu_servers"
ddiff --git a/tools/fix-windows-server-python-project-patch-and-rerun.sh b/tools/fix-windows-server-python-project-patch-and-rerun.sh
ddeleted file mode 100755
iindex d1e81ef..0000000
---- a/tools/fix-windows-server-python-project-patch-and-rerun.sh
++++ /dev/null
@@@ -1,146 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
--cd "$ROOT"
--BRANCH="$(git branch --show-current)"
--REPO="$(git remote get-url origin | sed -E 's#^[email protected]:##; s#^https://github.com/##; s#\.git$##')"
--
--echo "[openvibe] fix Windows server vcxproj python custom build commands + rerun"
--echo "[openvibe] root=$ROOT"
--echo "[openvibe] branch=$BRANCH"
--echo "[openvibe] repo=$REPO"
--
--python3 - <<'PY'
--from pathlib import Path
--p = Path('tools/build-sdk-windows.ps1')
--s = p.read_text()
--orig = s
--
--# 1) Ensure the robust MSBuild python shim is actually called. The previous patch
--# defined Ensure-PythonCommandForMSBuild but only called Ensure-PythonOnPath.
--needle = 'Ensure-PythonOnPath\n'
--if needle in s and 'Ensure-PythonCommandForMSBuild\n' not in s.split(needle,1)[1][:120]:
-- s = s.replace(needle, 'Ensure-PythonOnPath\nEnsure-PythonCommandForMSBuild\n', 1)
--
--# 2) Make Ensure-PythonCommandForMSBuild publish the selected shim path for later project-file patching.
--needle2 = '$env:PATH = "$shimDir;$pyDir;$env:PATH"\n'
--if needle2 in s and '$script:OpenVibePythonCommand' not in s[s.find(needle2):s.find(needle2)+260]:
-- s = s.replace(needle2, needle2 + ' $script:OpenVibePythonCommand = $bat\n', 1)
--
--# 3) Add a generated vcxproj patcher. The Source SDK VPC emits custom build commands
--# that literally start with `python ...`. On hosted Windows, MSBuild sometimes does
--# not resolve batch shims through PATH for these custom steps. Rewriting generated
--# project files to call the absolute shim is deterministic.
--marker = 'function Patch-GeneratedPythonCustomBuildCommands'
--func = r'''
--function Patch-GeneratedPythonCustomBuildCommands {
-- if (-not $script:OpenVibePythonCommand -or !(Test-Path $script:OpenVibePythonCommand)) {
-- Ensure-PythonCommandForMSBuild
-- }
--
-- $py = $script:OpenVibePythonCommand
-- Say "patching generated vcxproj python custom build commands to $py"
-- $patchLog = Join-Path $LogDir "python-vcxproj-patches.txt"
-- "python command=$py" | Out-File $patchLog
--
-- $projects = @(Get-ChildItem -Path $Src -Recurse -File -Filter "*.vcxproj" -ErrorAction SilentlyContinue)
-- foreach ($proj in $projects) {
-- $text = Get-Content -Raw $proj.FullName
-- $before = $text
--
-- # XML command bodies can contain python at line start, after XML tags, or after &&.
-- # Replace only executable tokens, preserving surrounding whitespace/tags.
-- $text = [regex]::Replace($text, '(?im)(^|[>`r`n])([ `t]*)python(\.exe)?([ `t]+)', {
-- param($m)
-- return $m.Groups[1].Value + $m.Groups[2].Value + '"' + $py + '"' + $m.Groups[4].Value
-- })
-- $text = [regex]::Replace($text, '(?i)(&amp;&amp;[ `t]*)python(\.exe)?([ `t]+)', {
-- param($m)
-- return $m.Groups[1].Value + '"' + $py + '"' + $m.Groups[3].Value
-- })
-- $text = [regex]::Replace($text, '(?i)(&&[ `t]*)python(\.exe)?([ `t]+)', {
-- param($m)
-- return $m.Groups[1].Value + '"' + $py + '"' + $m.Groups[3].Value
-- })
--
-- if ($text -ne $before) {
-- Set-Content -Encoding utf8 -Path $proj.FullName -Value $text
-- "patched $($proj.FullName)" | Out-File $patchLog -Append
-- }
-- }
--}
--'''
--if marker not in s:
-- insert_after = 'function Invoke-MSBuildProject([System.IO.FileInfo]$proj, [string]$label) {'
-- idx = s.find(insert_after)
-- if idx == -1:
-- raise SystemExit('Could not find Invoke-MSBuildProject insertion point')
-- s = s[:idx] + func + '\n\n' + s[idx:]
--
--# 4) Call patcher after project generation/project discovery but before building dependencies/client/server.
--call = 'Patch-GeneratedPythonCustomBuildCommands\n\nBuild-SourceSdkDependencyProjects'
--if call not in s:
-- s = s.replace('Build-SourceSdkDependencyProjects\n\n$beforeBuild = Get-Date', call + '\n\n$beforeBuild = Get-Date', 1)
--
--if s == orig:
-- print('[openvibe] build-sdk-windows.ps1 already had python project patch')
--else:
-- p.write_text(s)
-- print('[openvibe] patched tools/build-sdk-windows.ps1')
--PY
--
--cat > docs/WINDOWS_SERVER_PYTHON_CUSTOM_BUILD.md <<'EOF_DOC'
--# Windows server.vcxproj Python custom build fix
--
--Valve Source SDK's generated server project uses custom build commands that run
--`python` while converting `.nut` script resources into generated headers. On
--GitHub-hosted Windows runners, `actions/setup-python` may succeed while the VPC
--emitted MSBuild custom step still cannot resolve the literal `python` command.
--
--The Windows build script now:
--
--1. creates a deterministic `python.bat`/`python.cmd` shim;
--2. prepends it to PATH;
--3. rewrites generated `.vcxproj` custom build command bodies so `python ...`
-- uses the absolute shim path.
--
--This keeps the generated Source SDK tree disposable while making CI builds
--reliable.
--EOF_DOC
--
--echo "[openvibe] git diff summary"
--git diff --stat
--
--git add tools/build-sdk-windows.ps1 docs/WINDOWS_SERVER_PYTHON_CUSTOM_BUILD.md
--if git diff --cached --quiet; then
-- echo "[openvibe] no changes to commit"
--else
-- git commit -m "Patch Windows server Python custom build commands"
--fi
--
--echo "[openvibe] pushing $BRANCH"
--git push origin "$BRANCH"
--
--if [[ -x tools/trigger-windows-dll-build-clean.sh ]]; then
-- echo "[openvibe] triggering clean Windows DLL build"
-- tools/trigger-windows-dll-build-clean.sh || true
--else
-- echo "[openvibe] triggering workflow via gh"
-- gh workflow run windows-source-sdk-dlls.yml --repo "$REPO" --ref "$BRANCH"
-- sleep 5
-- RUN_ID="$(gh run list --repo "$REPO" --workflow windows-source-sdk-dlls.yml --branch "$BRANCH" --limit 1 --json databaseId --jq '.[0].databaseId')"
-- echo "[openvibe] watching run $RUN_ID"
-- gh run watch "$RUN_ID" --repo "$REPO" --exit-status || true
--fi
--
--if [[ -x tools/windows-workflow-debug-and-install.sh ]]; then
-- echo "[openvibe] downloading diagnostics/artifacts"
-- tools/windows-workflow-debug-and-install.sh || true
--fi
--
--if [[ -x tools/verify-openvibe-dll-content.sh ]]; then
-- tools/verify-openvibe-dll-content.sh || true
--fi
--
--echo "[openvibe] done"
ddiff --git a/tools/fix-windows-server-quickjs-cpp-nutheaders-and-rerun.sh b/tools/fix-windows-server-quickjs-cpp-nutheaders-and-rerun.sh
ddeleted file mode 100755
iindex 8087eb0..0000000
---- a/tools/fix-windows-server-quickjs-cpp-nutheaders-and-rerun.sh
++++ /dev/null
@@@ -1,188 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--log(){ printf '[openvibe] %s\n' "$*"; }
--warn(){ printf '[openvibe warn] %s\n' "$*" >&2; }
--
--ROOT="${OPENVIBE_ROOT:-$(pwd)}"
--cd "$ROOT"
--BRANCH="$(git branch --show-current)"
--REPO="$(git config --get remote.origin.url | sed -E 's#^[email protected]:##; s#^https://github.com/##; s#\.git$##')"
--: "${REPO:=OpenVibers/OpenVibe.Games}"
--
--log "fix Windows server build: QuickJS C++ header + vscript nut headers + real msbuild failures"
--log "root=$ROOT"
--log "branch=$BRANCH"
--log "repo=$REPO"
--
--python3 - <<'PY'
--from pathlib import Path
--p = Path('tools/build-sdk-windows.ps1')
--text = p.read_text()
--
--# 1) Make Invoke-MSBuildProject stop leaking Tee-Object output into the function return value.
--old = '& msbuild $proj.FullName /m /p:Configuration="$($p.Configuration)" /p:Platform="$($p.Platform)" /t:Build /v:minimal /nologo 2>&1 | Tee-Object -FilePath $log'
--new = '& msbuild $proj.FullName /m /p:Configuration="$($p.Configuration)" /p:Platform="$($p.Platform)" /t:Build /v:minimal /nologo 2>&1 | Tee-Object -FilePath $log | Out-Host'
--if old in text:
-- text = text.replace(old, new)
--
--# 2) Ensure the stronger python shim is actually called and exposes paths for other helpers.
--if 'Ensure-PythonOnPath\nEnsure-PythonCommandForMSBuild' not in text and 'Ensure-PythonOnPath\r\nEnsure-PythonCommandForMSBuild' not in text:
-- text = text.replace('Ensure-PythonOnPath\n', 'Ensure-PythonOnPath\nEnsure-PythonCommandForMSBuild\n')
--
--# Add script-scoped python vars inside Ensure-PythonCommandForMSBuild if not present.
--if '$script:OpenVibePythonExe = $py' not in text:
-- text = text.replace(' $pyDir = Split-Path -Parent $py\n', ' $pyDir = Split-Path -Parent $py\n $script:OpenVibePythonExe = $py\n')
--if '$script:OpenVibePythonShim = $bat' not in text:
-- text = text.replace(' Set-Content -Encoding ascii -Path $cmdFile -Value $batContent\n', ' Set-Content -Encoding ascii -Path $cmdFile -Value $batContent\n $script:OpenVibePythonShim = $bat\n')
--
--helper = r'''
--
--# OPENVIBE_FIX_QJS_CPP_AND_NUT_HEADERS
--function Patch-QuickJsHeaderForMsvcCpp {
-- $header = Join-Path $Sdk "src/game/shared/openvibe/third_party/quickjs/quickjs.h"
-- if (!(Test-Path $header)) {
-- Say "QuickJS header not present yet, skipping C++ compatibility patch: $header"
-- return
-- }
--
-- $log = Join-Path $LogDir "quickjs-cpp-header-patch.txt"
-- "header=$header" | Out-File $log
-- $q = Get-Content -Raw $header
-- $orig = $q
--
-- $mkvalOld = '#define JS_MKVAL(tag, val) (JSValue){ (JSValueUnion){ .uint64 = (uint32_t)(val) }, tag }'
-- $mkvalNew = @'
--#if defined(__cplusplus)
--static inline JSValue JS_MKVAL_CPP(int tag, uint32_t val) { JSValue v; v.u.uint64 = (uint32_t)val; v.tag = tag; return v; }
--#define JS_MKVAL(tag, val) JS_MKVAL_CPP((tag), (uint32_t)(val))
--#else
--#define JS_MKVAL(tag, val) (JSValue){ (JSValueUnion){ .uint64 = (uint32_t)(val) }, tag }
--#endif
--'@
-- $q = $q.Replace($mkvalOld, $mkvalNew)
--
-- $mkptrOld = '#define JS_MKPTR(tag, p) (JSValue){ (JSValueUnion){ .ptr = p }, tag }'
-- $mkptrNew = @'
--#if defined(__cplusplus)
--static inline JSValue JS_MKPTR_CPP(int tag, void *p) { JSValue v; v.u.ptr = p; v.tag = tag; return v; }
--#define JS_MKPTR(tag, p) JS_MKPTR_CPP((tag), (void *)(p))
--#else
--#define JS_MKPTR(tag, p) (JSValue){ (JSValueUnion){ .ptr = p }, tag }
--#endif
--'@
-- $q = $q.Replace($mkptrOld, $mkptrNew)
--
-- $nanOld = '#define JS_NAN (JSValue){ .u.float64 = JS_FLOAT64_NAN, JS_TAG_FLOAT64 }'
-- $nanNew = @'
--#if defined(__cplusplus)
--static inline JSValue JS_NAN_CPP(void) { JSValue v; v.u.float64 = JS_FLOAT64_NAN; v.tag = JS_TAG_FLOAT64; return v; }
--#define JS_NAN JS_NAN_CPP()
--#else
--#define JS_NAN (JSValue){ .u.float64 = JS_FLOAT64_NAN, JS_TAG_FLOAT64 }
--#endif
--'@
-- $q = $q.Replace($nanOld, $nanNew)
--
-- $q = $q.Replace(' JSCFunctionType ft = { .generic_magic = func };', ' JSCFunctionType ft; memset(&ft, 0, sizeof(ft)); ft.generic_magic = func;')
--
-- if ($q -ne $orig) {
-- Set-Content -Encoding ascii -Path $header -Value $q
-- "patched=1" | Out-File $log -Append
-- Say "patched QuickJS header for MSVC C++ compound literal/designated initializer compatibility"
-- } else {
-- "patched=0" | Out-File $log -Append
-- Say "QuickJS header C++ patch made no changes"
-- }
--}
--
--function Ensure-ServerNutHeaders {
-- $serverDir = Join-Path $Src "game/server"
-- $textToArray = Join-Path $Src "devtools/bin/texttoarray.py"
-- $log = Join-Path $LogDir "server-nut-headers.txt"
-- "serverDir=$serverDir" | Out-File $log
-- "textToArray=$textToArray" | Out-File $log -Append
-- "pythonExe=$script:OpenVibePythonExe" | Out-File $log -Append
-- "pythonShim=$script:OpenVibePythonShim" | Out-File $log -Append
--
-- if (!(Test-Path $textToArray)) {
-- throw "Missing Source SDK texttoarray.py at $textToArray"
-- }
-- if (-not $script:OpenVibePythonExe -or !(Test-Path $script:OpenVibePythonExe)) {
-- throw "Python exe was not captured for nut header generation. Check python-version.txt."
-- }
--
-- foreach ($name in @("spawn_helper", "vscript_server")) {
-- $input = Join-Path $serverDir "$name.nut"
-- $out = Join-Path $serverDir "${name}_nut.h"
-- if (!(Test-Path $input)) {
-- "missing input $input" | Out-File $log -Append
-- continue
-- }
-- Say "generating $out from $input"
-- & $script:OpenVibePythonExe $textToArray $input "g_Script_$name" | Set-Content -Encoding ascii -Path $out
-- if ($LASTEXITCODE -ne 0) { throw "texttoarray.py failed for $input" }
-- if (!(Test-Path $out)) { throw "Expected generated nut header was not created: $out" }
-- "generated $out" | Out-File $log -Append
-- }
--}
--'''
--
--if 'function Patch-QuickJsHeaderForMsvcCpp' not in text:
-- marker = 'function Find-VcVars64 {'
-- text = text.replace(marker, helper + '\n' + marker)
--
--# Call QuickJS C++ header patch right after QuickJS lib build and before Set-Location.
--if 'Patch-QuickJsHeaderForMsvcCpp\n\nSet-Location $Src' not in text:
-- text = text.replace('\nSet-Location $Src\n', '\nPatch-QuickJsHeaderForMsvcCpp\n\nSet-Location $Src\n')
--
--# Generate vscript headers after project generation/patches and before dependency/client/server builds.
--if 'Ensure-ServerNutHeaders\nBuild-SourceSdkDependencyProjects' not in text:
-- text = text.replace('Build-SourceSdkDependencyProjects\n', 'Ensure-ServerNutHeaders\nBuild-SourceSdkDependencyProjects\n')
--
--p.write_text(text)
--PY
--
--cat > docs/WINDOWS_SERVER_QJS_CPP_NUT_HEADERS.md <<'EOF'
--# Windows server build: QuickJS C++ and vscript nut headers
--
--The Windows HL2MP server build needs two extra fixes on hosted Actions runners:
--
--- The Source SDK custom build step for `vscript_server.nut` can report success without producing `vscript_server_nut.h`, so the build script pre-generates `spawn_helper_nut.h` and `vscript_server_nut.h` with `devtools/bin/texttoarray.py`.
--- QuickJS is compiled as C with `clang-cl`, but its header is also included from C++ server files. The script patches the SDK copy of `quickjs.h` so MSVC C++ can compile QuickJS value macros without C compound literals/designated initializers.
--
--The patch is applied only to the generated SDK copy under `engine/source-sdk-2013`, not to the vendored source files.
--EOF
--
--log "git diff summary"
--git diff --stat
--
--git add tools/build-sdk-windows.ps1 docs/WINDOWS_SERVER_QJS_CPP_NUT_HEADERS.md
--if git diff --cached --quiet; then
-- log "nothing new to commit"
--else
-- git commit -m "Fix Windows server QuickJS C++ and nut headers"
--fi
--
--log "pushing $BRANCH"
--git push origin "$BRANCH"
--
--if [[ -x tools/trigger-windows-dll-build-clean.sh ]]; then
-- log "triggering clean Windows DLL build"
-- tools/trigger-windows-dll-build-clean.sh || true
--elif [[ -x tools/gh-windows-build-and-install.sh ]]; then
-- log "triggering Windows DLL build/install helper"
-- tools/gh-windows-build-and-install.sh || true
--else
-- log "triggering workflow directly"
-- gh workflow run windows-source-sdk-dlls.yml --repo "$REPO" --ref "$BRANCH"
-- sleep 5
-- run_id="$(gh run list --repo "$REPO" --workflow windows-source-sdk-dlls.yml --branch "$BRANCH" --limit 1 --json databaseId --jq '.[0].databaseId')"
-- log "watching run $run_id"
-- gh run watch "$run_id" --repo "$REPO" --exit-status || true
-- if [[ -x tools/windows-workflow-debug-and-install.sh ]]; then
-- tools/windows-workflow-debug-and-install.sh "$run_id" || true
-- fi
--fi
--
--log "done"
ddiff --git a/tools/fix-windows-server-stub-pch-order-and-rerun.sh b/tools/fix-windows-server-stub-pch-order-and-rerun.sh
ddeleted file mode 100755
iindex 4db5365..0000000
---- a/tools/fix-windows-server-stub-pch-order-and-rerun.sh
++++ /dev/null
@@@ -1,160 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--log(){ printf '[openvibe] %s\n' "$*"; }
--warn(){ printf '[openvibe warn] %s\n' "$*" >&2; }
--
--ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
--cd "$ROOT"
--BRANCH="$(git rev-parse --abbrev-ref HEAD)"
--if [[ -x tools/openvibe-gh-repo.sh ]]; then
-- REPO="$(tools/openvibe-gh-repo.sh)"
--else
-- remote="$(git config --get remote.origin.url || true)"
-- REPO="${remote#[email protected]:}"; REPO="${REPO#ssh://[email protected]/}"; REPO="${REPO#https://github.com/}"; REPO="${REPO%.git}"; REPO="${REPO%/}"
--fi
--WORKFLOW="windows-source-sdk-dlls.yml"
--
--log "fix Windows server stub PCH order + rerun"
--log "root=$ROOT"
--log "branch=$BRANCH"
--log "repo=$REPO"
--
--PY="$(command -v python3 || command -v python || true)"
--if [[ -z "$PY" ]]; then
-- echo "python3/python required for this patch script" >&2
-- exit 1
--fi
--
--"$PY" - <<'PY'
--from pathlib import Path
--import re
--
--root = Path.cwd()
--files = [
-- Path("sdk/openvibe/server/hl2mp/openvibe_js_server.cpp"),
-- Path("sdk/openvibe/shared/ov_js_runtime.cpp"),
-- Path("sdk/openvibe/shared/ov_js_bindings.cpp"),
-- Path("sdk/openvibe/shared/ov_js_player.cpp"),
--]
--marker = "// OPENVIBE_WINDOWS_SERVER_QUICKJS_STUB"
--cond = "#if defined(_WIN32) && defined(GAME_DLL) && !defined(OPENVIBE_WINDOWS_SERVER_REAL_QUICKJS)"
--
--for rel in files:
-- path = root / rel
-- if not path.exists():
-- raise SystemExit(f"missing expected file: {rel}")
-- text = path.read_text(encoding="utf-8")
-- original = text
-- if marker not in text:
-- print(f"[skip] no stub marker: {rel}")
-- continue
--
-- # MSVC /Yu cbase.h skips preprocessor/code before the PCH include. The earlier
-- # stub patch put #if before #include "cbase.h", so MSVC skipped the #if and then
-- # saw an unexpected #else. Make cbase.h the first real line.
-- bom = "\ufeff" if text.startswith("\ufeff") else ""
-- if bom:
-- text = text[1:]
--
-- start_pat = re.compile(
-- r"\A\s*// OPENVIBE_WINDOWS_SERVER_QUICKJS_STUB\s*\r?\n"
-- r"\s*#if defined\(_WIN32\) && defined\(GAME_DLL\) && !defined\(OPENVIBE_WINDOWS_SERVER_REAL_QUICKJS\)\s*\r?\n"
-- r"\s*(?:#include \"cbase\.h\"\s*\r?\n)?",
-- re.MULTILINE,
-- )
-- text2, n = start_pat.subn(f'#include "cbase.h"\n{marker}\n{cond}\n', text, count=1)
-- if n == 0:
-- # Idempotent path: already starts with cbase.h, but still normalize the next lines.
-- text2 = text
-- text2 = re.sub(
-- r"\A\s*#include \"cbase\.h\"\s*\r?\n\s*// OPENVIBE_WINDOWS_SERVER_QUICKJS_STUB\s*\r?\n\s*#if defined\(_WIN32\) && defined\(GAME_DLL\) && !defined\(OPENVIBE_WINDOWS_SERVER_REAL_QUICKJS\)\s*\r?\n",
-- f'#include "cbase.h"\n{marker}\n{cond}\n',
-- text2,
-- count=1,
-- )
--
-- # Remove the duplicate cbase.h include from the real/non-stub side. It is already
-- # first in the translation unit and must not be repeated immediately after #else.
-- text2 = re.sub(r"(#else\s*\r?\n)\s*#include \"cbase\.h\"\s*\r?\n", r"\1", text2, count=1)
--
-- # Ensure there is a newline before #endif at EOF.
-- text2 = text2.rstrip() + "\n"
--
-- # Cheap sanity checks for the stub wrapper.
-- if not text2.startswith('#include "cbase.h"\n'):
-- raise SystemExit(f"{rel}: cbase.h is not first after patch")
-- if text2.count(cond) != 1:
-- raise SystemExit(f"{rel}: expected exactly one stub #if, found {text2.count(cond)}")
-- if text2.count("#else") < 1:
-- raise SystemExit(f"{rel}: expected at least one #else")
-- if text2.count("#endif") < 1:
-- raise SystemExit(f"{rel}: expected at least one #endif")
--
-- if text2 != original:
-- path.write_text(bom + text2, encoding="utf-8", newline="\n")
-- print(f"[patched] {rel}")
-- else:
-- print(f"[ok] already normalized: {rel}")
--PY
--
--cat > docs/WINDOWS_SERVER_STUB_PCH_ORDER.md <<'EOF_DOC'
--# Windows server stub PCH order
--
--Source SDK server projects compile with MSVC precompiled headers using `/Yu cbase.h`.
--MSVC ignores/skips text before the configured PCH include, so the Windows server
--QuickJS stub wrapper cannot put `#if ...` before `#include "cbase.h"`.
--
--The stubbed OpenVibe server `.cpp` files must start with:
--
--```cpp
--#include "cbase.h"
--// OPENVIBE_WINDOWS_SERVER_QUICKJS_STUB
--#if defined(_WIN32) && defined(GAME_DLL) && !defined(OPENVIBE_WINDOWS_SERVER_REAL_QUICKJS)
--```
--
--The non-stub branch must not re-include `cbase.h` immediately after `#else`.
--EOF_DOC
--
--log "git diff summary"
--git diff --stat
--
--if [[ -n "$(git status --porcelain)" ]]; then
-- git add \
-- sdk/openvibe/server/hl2mp/openvibe_js_server.cpp \
-- sdk/openvibe/shared/ov_js_runtime.cpp \
-- sdk/openvibe/shared/ov_js_bindings.cpp \
-- sdk/openvibe/shared/ov_js_player.cpp \
-- docs/WINDOWS_SERVER_STUB_PCH_ORDER.md
-- git commit -m "Fix Windows server stub PCH include order"
--else
-- log "no source changes to commit"
--fi
--
--log "pushing $BRANCH"
--git push origin "$BRANCH"
--
--if [[ -x tools/trigger-windows-dll-build-clean.sh ]]; then
-- log "triggering clean Windows DLL build"
-- tools/trigger-windows-dll-build-clean.sh || true
--elif command -v gh >/dev/null 2>&1; then
-- log "triggering workflow"
-- gh workflow run "$WORKFLOW" --repo "$REPO" --ref "$BRANCH" || true
-- sleep 8
-- RUN_ID="$(gh run list --repo "$REPO" --workflow "$WORKFLOW" --branch "$BRANCH" --limit 1 --json databaseId --jq '.[0].databaseId' || true)"
-- if [[ -n "${RUN_ID:-}" && "$RUN_ID" != "null" ]]; then
-- log "watching run $RUN_ID"
-- gh run watch "$RUN_ID" --repo "$REPO" --exit-status || true
-- if [[ -x tools/windows-workflow-debug-and-install.sh ]]; then
-- tools/windows-workflow-debug-and-install.sh "$RUN_ID" || true
-- fi
-- fi
--else
-- warn "gh not found; pushed patch but did not trigger workflow"
--fi
--
--if [[ -x tools/verify-openvibe-dll-content.sh ]]; then
-- tools/verify-openvibe-dll-content.sh || true
--fi
--
--log "done"
ddiff --git a/tools/fix-windows-server-stub-quickjs-and-rerun.sh b/tools/fix-windows-server-stub-quickjs-and-rerun.sh
ddeleted file mode 100755
iindex 32f200d..0000000
---- a/tools/fix-windows-server-stub-quickjs-and-rerun.sh
++++ /dev/null
@@@ -1,330 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
--cd "$ROOT"
--BRANCH="$(git branch --show-current)"
--
--repo_slug() {
-- if [ -x tools/openvibe-gh-repo.sh ]; then
-- tools/openvibe-gh-repo.sh
-- return
-- fi
-- local url
-- url="$(git config --get remote.origin.url || true)"
-- url="${url#[email protected]:}"
-- url="${url#https://github.com/}"
-- url="${url%.git}"
-- printf '%s\n' "$url"
--}
--
--REPO="$(repo_slug)"
--WORKFLOW="windows-source-sdk-dlls.yml"
--
--echo "[openvibe] fix Windows server build: stub QuickJS runtime for MSVC server DLL + rerun"
--echo "[openvibe] root=$ROOT"
--echo "[openvibe] branch=$BRANCH"
--echo "[openvibe] repo=$REPO"
--
--python3 - <<'PY'
--from pathlib import Path
--
--root = Path.cwd()
--marker = "OPENVIBE_WINDOWS_SERVER_QUICKJS_STUB"
--cond = "defined(_WIN32) && defined(GAME_DLL) && !defined(OPENVIBE_WINDOWS_SERVER_REAL_QUICKJS)"
--
--def strip_pragma_once(text: str) -> str:
-- lines = text.splitlines()
-- if lines and lines[0].strip() == "#pragma once":
-- return "\n".join(lines[1:]).lstrip("\n") + ("\n" if text.endswith("\n") else "")
-- return text
--
--def write_if_changed(path: Path, text: str):
-- old = path.read_text()
-- if old != text:
-- path.write_text(text)
-- print(f"[openvibe] patched {path}")
-- else:
-- print(f"[openvibe] unchanged {path}")
--
--def wrap_cpp(path_rel: str, stub: str):
-- path = root / path_rel
-- text = path.read_text()
-- if marker in text:
-- print(f"[openvibe] already patched {path_rel}")
-- return
-- new = f"""// {marker}\n#if {cond}\n{stub.rstrip()}\n#else\n{text.rstrip()}\n#endif\n"""
-- write_if_changed(path, new)
--
--def wrap_header(path_rel: str, stub: str):
-- path = root / path_rel
-- text = path.read_text()
-- if marker in text:
-- print(f"[openvibe] already patched {path_rel}")
-- return
-- rest = strip_pragma_once(text).rstrip()
-- new = f"""#pragma once\n\n// {marker}\n#if {cond}\n{stub.rstrip()}\n#else\n{rest}\n#endif\n"""
-- write_if_changed(path, new)
--
--runtime_h_stub = r'''
--// Windows server CI uses a stub JS runtime so server.dll can be produced with
--// OpenVibe concommands while QuickJS C/C++ portability is handled separately.
--typedef void JSContext;
--typedef void JSRuntime;
--struct JSValue { void *opaque; };
--typedef JSValue JSValueConst;
--
--#define JS_UNDEFINED (JSValue{ nullptr })
--#define JS_NULL (JSValue{ nullptr })
--
--class COpenVibeJSRuntime
--{
--public:
-- COpenVibeJSRuntime();
-- ~COpenVibeJSRuntime();
--
-- bool Init(bool bServerRealm, const char *pszMode);
-- void Shutdown();
--
-- bool LoadFile(const char *pszPath);
-- bool Eval(const char *pszCode, const char *pszFilename);
--
-- JSValue CallHookRaw(const char *pszHookName, int argc = 0, JSValueConst *argv = nullptr);
-- void CallHookVoid(const char *pszHookName, int argc = 0, JSValueConst *argv = nullptr);
-- bool CallHookBool(const char *pszHookName, bool *pOut, int argc = 0, JSValueConst *argv = nullptr);
--
-- JSContext *Context() { return nullptr; }
-- const char *GetMode() const { return m_szMode; }
--
--private:
-- bool m_bServerRealm = false;
-- char m_szMode[64]{};
--};
--'''
--
--bindings_h_stub = r'''
--typedef void JSContext;
--class COpenVibeJSRuntime;
--void OVJS_RegisterNativeBindings(JSContext *ctx, COpenVibeJSRuntime *runtime);
--'''
--
--player_h_stub = r'''
--#include "openvibe/ov_js_runtime.h"
--class CHL2MP_Player;
--void OVJS_RegisterPlayerClass(JSContext *ctx);
--JSValue OVJS_NewPlayer(JSContext *ctx, CHL2MP_Player *player);
--CHL2MP_Player *OVJS_ResolvePlayerByUserId(int userId);
--CHL2MP_Player *OVJS_GetPlayerFromThis(JSContext *ctx, JSValueConst thisVal);
--'''
--
--runtime_cpp_stub = r'''
--#include "cbase.h"
--#include "ov_js_runtime.h"
--
--#include "tier0/memdbgon.h"
--
--COpenVibeJSRuntime::COpenVibeJSRuntime() {}
--COpenVibeJSRuntime::~COpenVibeJSRuntime() { Shutdown(); }
--
--bool COpenVibeJSRuntime::Init(bool bServerRealm, const char *pszMode)
--{
-- m_bServerRealm = bServerRealm;
-- Q_strncpy(m_szMode, pszMode && pszMode[0] ? pszMode : "hub", sizeof(m_szMode));
-- Msg("[OV JS] Windows server CI stub runtime active for mode '%s'\n", m_szMode);
-- return false;
--}
--
--void COpenVibeJSRuntime::Shutdown() {}
--bool COpenVibeJSRuntime::LoadFile(const char *pszPath) { return false; }
--bool COpenVibeJSRuntime::Eval(const char *pszCode, const char *pszFilename) { return false; }
--JSValue COpenVibeJSRuntime::CallHookRaw(const char *pszHookName, int argc, JSValueConst *argv) { return JS_UNDEFINED; }
--void COpenVibeJSRuntime::CallHookVoid(const char *pszHookName, int argc, JSValueConst *argv) {}
--bool COpenVibeJSRuntime::CallHookBool(const char *pszHookName, bool *pOut, int argc, JSValueConst *argv)
--{
-- if (pOut) *pOut = false;
-- return false;
--}
--'''
--
--bindings_cpp_stub = r'''
--#include "cbase.h"
--#include "ov_js_bindings.h"
--
--#include "tier0/memdbgon.h"
--
--void OVJS_RegisterNativeBindings(JSContext *ctx, COpenVibeJSRuntime *runtime) {}
--'''
--
--player_cpp_stub = r'''
--#include "cbase.h"
--#include "ov_js_player.h"
--#include "hl2mp_player.h"
--
--#include "tier0/memdbgon.h"
--
--void OVJS_RegisterPlayerClass(JSContext *ctx) {}
--JSValue OVJS_NewPlayer(JSContext *ctx, CHL2MP_Player *player) { return JS_NULL; }
--CHL2MP_Player *OVJS_ResolvePlayerByUserId(int userId) { return nullptr; }
--CHL2MP_Player *OVJS_GetPlayerFromThis(JSContext *ctx, JSValueConst thisVal) { return nullptr; }
--'''
--
--server_cpp_stub = r'''
--#include "cbase.h"
--#include "hl2mp_player.h"
--#include "openvibe_js_server.h"
--
--#include "tier0/memdbgon.h"
--
--static ConVar ov_mode(
-- "ov_mode",
-- "hub",
-- FCVAR_GAMEDLL,
-- "OpenVibe mode: hub, prophunt, deathrun, fortwars, traitortown." );
--
--static ConVar ov_js_enabled(
-- "ov_js_enabled",
-- "0",
-- FCVAR_GAMEDLL,
-- "Enable OpenVibe JavaScript runtime. Windows server CI uses a stub runtime." );
--
--void OpenVibeJS_ServerInit()
--{
-- Msg("[OV JS] Windows server CI stub active; runtime disabled, mode=%s\n", ov_mode.GetString());
--}
--
--void OpenVibeJS_ServerShutdown() {}
--void OpenVibeJS_ServerThink() {}
--void OpenVibeJS_Server_PlayerInitialSpawn(CHL2MP_Player *player) {}
--void OpenVibeJS_Server_PlayerSpawn(CHL2MP_Player *player) {}
--void OpenVibeJS_Server_PlayerDeath(CHL2MP_Player *victim, CBaseEntity *attacker, CBaseEntity *inflictor) {}
--void OpenVibeJS_Server_PlayerDisconnected(CHL2MP_Player *player) {}
--bool OpenVibeJS_Server_PlayerSay(CHL2MP_Player *player, const char *text) { return false; }
--
--static void OV_JSReload_f()
--{
-- Msg("[OV JS] Windows server CI stub: ov_js_reload ignored, mode=%s\n", ov_mode.GetString());
--}
--
--static ConCommand ov_js_reload(
-- "ov_js_reload",
-- OV_JSReload_f,
-- "Reload OpenVibe JavaScript runtime.",
-- FCVAR_GAMEDLL
--);
--
--static void OV_JSStatus_f()
--{
-- Msg("[OV JS] enabled=%d started=0 running=0 mode=%s backend=windows-server-stub\n",
-- ov_js_enabled.GetBool() ? 1 : 0,
-- ov_mode.GetString() );
--}
--
--static ConCommand ov_js_status(
-- "ov_js_status",
-- OV_JSStatus_f,
-- "Print OpenVibe JavaScript runtime status.",
-- FCVAR_GAMEDLL
--);
--
--static void OV_JSFire_f(const CCommand &args)
--{
-- Msg("[OV JS] Windows server CI stub: ov_js_fire ignored. Usage: ov_js_fire <HookName>\n");
--}
--
--static ConCommand ov_js_fire(
-- "ov_js_fire",
-- OV_JSFire_f,
-- "Fire an OpenVibe JavaScript hook with no arguments.",
-- FCVAR_GAMEDLL
--);
--
--static void OV_JSCmd_f(const CCommand &args)
--{
-- Msg("[OV JS] Windows server CI stub: ov_js_cmd %s\n", args.ArgS());
--}
--
--static ConCommand ov_js_cmd(
-- "ov_js_cmd",
-- OV_JSCmd_f,
-- "Send a ConsoleCommand event into the embedded OpenVibe JavaScript runtime.",
-- FCVAR_GAMEDLL
--);
--'''
--
--wrap_header("sdk/openvibe/shared/ov_js_runtime.h", runtime_h_stub)
--wrap_header("sdk/openvibe/shared/ov_js_bindings.h", bindings_h_stub)
--wrap_header("sdk/openvibe/shared/ov_js_player.h", player_h_stub)
--wrap_cpp("sdk/openvibe/shared/ov_js_runtime.cpp", runtime_cpp_stub)
--wrap_cpp("sdk/openvibe/shared/ov_js_bindings.cpp", bindings_cpp_stub)
--wrap_cpp("sdk/openvibe/shared/ov_js_player.cpp", player_cpp_stub)
--wrap_cpp("sdk/openvibe/server/hl2mp/openvibe_js_server.cpp", server_cpp_stub)
--
--# The old Windows build header patch is no longer needed for the server, but keep
--# it harmless for any future real QuickJS Windows work. Do not remove it here.
--PY
--
--mkdir -p docs
--cat > docs/WINDOWS_SERVER_QJS_STUB_BUILD.md <<'EOF'
--# Windows server QuickJS stub build
--
--The Windows GitHub Actions build is currently used to produce patched Windows
--`client.dll` and `server.dll` artifacts for Proton/runtime validation.
--
--The vendored QuickJS C runtime builds as a static library with `clang-cl`, but
--including QuickJS's C-facing headers from MSVC C++ inside the Source SDK server
--project still trips C++ parser issues around C compound literals and designated
--initializers.
--
--To keep the Windows DLL pipeline moving, the server build uses a Windows-only
--`GAME_DLL` stub for the OpenVibe JS runtime. The stub preserves the public
--OpenVibe server concommands and strings (`ov_js_status`, `ov_js_cmd`,
--`ov_js_reload`, `ov_js_fire`, `OpenVibe`) so the produced DLL is patched and
--usable for smoke tests. Native Linux builds can still use the real QuickJS path.
--
--Define `OPENVIBE_WINDOWS_SERVER_REAL_QUICKJS` later when the QuickJS C++ header
--compatibility work is complete.
--EOF
--
--# Do not add downloaded workflow artifacts.
--git add \
-- sdk/openvibe/shared/ov_js_runtime.h \
-- sdk/openvibe/shared/ov_js_bindings.h \
-- sdk/openvibe/shared/ov_js_player.h \
-- sdk/openvibe/shared/ov_js_runtime.cpp \
-- sdk/openvibe/shared/ov_js_bindings.cpp \
-- sdk/openvibe/shared/ov_js_player.cpp \
-- sdk/openvibe/server/hl2mp/openvibe_js_server.cpp \
-- docs/WINDOWS_SERVER_QJS_STUB_BUILD.md \
-- tools/fix-windows-server-stub-quickjs-and-rerun.sh
--
--echo "[openvibe] git diff summary"
--git diff --cached --stat
--
--if git diff --cached --quiet; then
-- echo "[openvibe] nothing to commit"
--else
-- git commit -m "Stub Windows server QuickJS runtime for DLL build"
--fi
--
--echo "[openvibe] pushing $BRANCH"
--git push origin "$BRANCH"
--
--echo "[openvibe] triggering clean Windows DLL build"
--if [ -x tools/trigger-windows-dll-build-clean.sh ]; then
-- tools/trigger-windows-dll-build-clean.sh
--else
-- gh workflow run "$WORKFLOW" --repo "$REPO" --ref "$BRANCH"
-- sleep 10
-- RUN_ID="$(gh run list --repo "$REPO" --workflow "$WORKFLOW" --branch "$BRANCH" --limit 1 --json databaseId --jq '.[0].databaseId')"
-- echo "[openvibe] watching run $RUN_ID"
-- gh run watch "$RUN_ID" --repo "$REPO" --interval 3 || true
-- if [ -x tools/windows-workflow-debug-and-install.sh ]; then
-- tools/windows-workflow-debug-and-install.sh "$RUN_ID" || true
-- fi
--fi
--
--if [ -x tools/verify-openvibe-dll-content.sh ]; then
-- tools/verify-openvibe-dll-content.sh || true
--fi
--
--echo "[openvibe] done"
ddiff --git a/tools/fix-windows-vpc-direct-vcxproj-build-and-rerun.sh b/tools/fix-windows-vpc-direct-vcxproj-build-and-rerun.sh
ddeleted file mode 100755
iindex 25d44e3..0000000
---- a/tools/fix-windows-vpc-direct-vcxproj-build-and-rerun.sh
++++ /dev/null
@@@ -1,335 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--cd "$ROOT"
--
--BRANCH="$(git branch --show-current)"
--echo "[openvibe] fix Windows build: VPC generated extensionless solution / direct vcxproj build"
--echo "[openvibe] root=$ROOT"
--echo "[openvibe] branch=$BRANCH"
--
--mkdir -p tools docs
--
--cat > tools/build-sdk-windows.ps1 <<'PS1'
--param(
-- [switch]$InDevShell
--)
--
--$ErrorActionPreference = "Stop"
--
--$Root = if ($env:OPENVIBE_ROOT) { (Resolve-Path $env:OPENVIBE_ROOT).Path } else { (Resolve-Path (Join-Path $PSScriptRoot "..")).Path }
--$Sdk = if ($env:OPENVIBE_SDK) { $env:OPENVIBE_SDK } else { Join-Path $Root "engine/source-sdk-2013" }
--$Src = Join-Path $Sdk "src"
--$Mod = Join-Path $Root "game/openvibe.games"
--$OutBin = Join-Path $Mod "bin"
--$LogDir = Join-Path $Root "artifacts/windows-build-debug"
--New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
--
--function Say($m) { Write-Host "[openvibe-win] $m" }
--function Require($p, $msg) {
-- if (!(Test-Path $p)) { throw "$msg`nMissing: $p" }
--}
--
--function Find-VcVars64 {
-- $candidates = @(
-- "${env:ProgramFiles}\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvars64.bat",
-- "${env:ProgramFiles}\Microsoft Visual Studio\2022\Professional\VC\Auxiliary\Build\vcvars64.bat",
-- "${env:ProgramFiles}\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat",
-- "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvars64.bat",
-- "${env:ProgramFiles}\Microsoft Visual Studio\2022\BuildTools\VC\Auxiliary\Build\vcvars64.bat"
-- )
-- foreach ($c in $candidates) {
-- if ($c -and (Test-Path $c)) { return $c }
-- }
-- return $null
--}
--
--Say "root=$Root"
--Say "sdk=$Sdk"
--Say "src=$Src"
--
--Require $Src "Source SDK checkout is missing from this runner. Bootstrap must create engine/source-sdk-2013 first."
--
--# VPC is generating *_win64_*.vcxproj on the hosted runner, so use an x64 VS shell.
--# clang-cl/lib/msbuild all remain MSVC ABI-compatible.
--if (-not $InDevShell -or !(Get-Command cl.exe -ErrorAction SilentlyContinue)) {
-- $vcvars = Find-VcVars64
-- if (-not $vcvars) { throw "Could not find Visual Studio vcvars64.bat" }
-- Say "relaunching through MSVC x64 dev shell: $vcvars"
-- $self = $PSCommandPath
-- $cmd = "`"$vcvars`" && powershell -NoProfile -ExecutionPolicy Bypass -File `"$self`" -InDevShell"
-- & cmd.exe /d /s /c $cmd
-- exit $LASTEXITCODE
--}
--
--Say "cl.exe already available"
--where.exe cl | Out-File (Join-Path $LogDir "where-cl.txt")
--where.exe msbuild | Out-File (Join-Path $LogDir "where-msbuild.txt")
--where.exe lib | Out-File (Join-Path $LogDir "where-lib.txt")
--
--# Apply OpenVibe source files using Git Bash when available. Skip Linux QuickJS build on Windows.
--$bash = (Get-Command bash -ErrorAction SilentlyContinue)
--if ($bash) {
-- Say "applying OpenVibe SDK patch through bash"
-- $env:OPENVIBE_ROOT = $Root
-- $env:OPENVIBE_SDK = $Sdk
-- $env:OPENVIBE_SKIP_QJS_BUILD = "1"
-- & bash "$Root/tools/apply-openvibe-sdk.sh" 2>&1 | Tee-Object -FilePath (Join-Path $LogDir "apply-openvibe-sdk.log")
-- if ($LASTEXITCODE -ne 0) { throw "apply-openvibe-sdk.sh failed with exit code $LASTEXITCODE" }
--} else {
-- throw "Git Bash is required on the Windows runner to apply the OpenVibe SDK patch."
--}
--
--# Build QuickJS as MSVC ABI objects/lib. quickjs itself uses clang-cl because the upstream C is GNU/C99-ish.
--if (Test-Path (Join-Path $Root "tools/build-quickjs-lib-windows.ps1")) {
-- Say "building QuickJS Windows static library"
-- & powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $Root "tools/build-quickjs-lib-windows.ps1") 2>&1 | Tee-Object -FilePath (Join-Path $LogDir "quickjs-windows.log")
-- if ($LASTEXITCODE -ne 0) { throw "build-quickjs-lib-windows.ps1 failed with exit code $LASTEXITCODE" }
--} else {
-- Say "no build-quickjs-lib-windows.ps1 found; continuing"
--}
--
--Set-Location $Src
--
--function Test-SlnContent([string]$path) {
-- if (!(Test-Path $path)) { return $false }
-- try {
-- $first = Get-Content -Path $path -TotalCount 3 -ErrorAction Stop
-- return (($first -join "`n") -match "Microsoft Visual Studio Solution File")
-- } catch {
-- return $false
-- }
--}
--
--function Normalize-Solutions {
-- $out = New-Object System.Collections.Generic.List[System.IO.FileInfo]
--
-- # Normal .sln files.
-- Get-ChildItem -Path $Src -Recurse -File -Filter "*.sln" -ErrorAction SilentlyContinue | ForEach-Object {
-- if (Test-SlnContent $_.FullName) { [void]$out.Add($_) }
-- }
--
-- # VPC sometimes logs "Writing solution file ...\everything." and produces an extensionless-ish file.
-- Get-ChildItem -Path $Src -Recurse -File -ErrorAction SilentlyContinue |
-- Where-Object { $_.Name -match '^(everything|games|OpenVibe_HL2MP)(\.?)$' } |
-- ForEach-Object {
-- if (Test-SlnContent $_.FullName) {
-- $fixed = Join-Path $_.DirectoryName ($_.BaseName.TrimEnd('.') + ".sln")
-- if ($fixed -ne $_.FullName) {
-- Copy-Item $_.FullName $fixed -Force
-- Say "normalized extensionless VPC solution $($_.FullName) -> $fixed"
-- [void]$out.Add((Get-Item $fixed))
-- } else {
-- [void]$out.Add($_)
-- }
-- }
-- }
--
-- return @($out | Sort-Object FullName -Unique)
--}
--
--function Run-ProjectGenerator {
-- Say "generating Source SDK Visual Studio projects"
-- $generators = @(
-- (Join-Path $Src "createallprojects.bat"),
-- (Join-Path $Src "creategameprojects.bat"),
-- (Join-Path $Src "createprojects.bat")
-- )
--
-- $ran = $false
-- foreach ($gen in $generators) {
-- if (Test-Path $gen) {
-- Say "running $gen"
-- & cmd /c "`"$gen`"" 2>&1 | Tee-Object -FilePath (Join-Path $LogDir "project-generation.log")
-- $ran = $true
-- break
-- }
-- }
--
-- if (-not $ran) {
-- $vpc = Join-Path $Src "devtools/bin/vpc.exe"
-- if (Test-Path $vpc) {
-- Say "running vpc.exe fallback"
-- & $vpc /hl2mp /define:SOURCESDK +game /mksln OpenVibe_HL2MP.sln 2>&1 | Tee-Object -FilePath (Join-Path $LogDir "vpc.log")
-- $ran = $true
-- }
-- }
--
-- if (-not $ran) {
-- throw "No Source SDK project generator found."
-- }
--}
--
--function Get-ProjectConfigs([string]$projPath) {
-- $text = Get-Content -Raw $projPath
-- $matches = [regex]::Matches($text, '<ProjectConfiguration\s+Include="([^|"]+)\|([^"]+)"')
-- $pairs = @()
-- foreach ($m in $matches) {
-- $pairs += [pscustomobject]@{ Configuration = $m.Groups[1].Value; Platform = $m.Groups[2].Value }
-- }
-- return @($pairs | Sort-Object Configuration,Platform -Unique)
--}
--
--function Sort-PreferredConfigs($pairs, [string]$projName) {
-- $preferred = @()
-- foreach ($cfg in @("Release", "Release_HL2MP", "Release HL2MP", "Debug")) {
-- foreach ($plat in @("x64", "Win64", "Win32", "x86")) {
-- $hit = $pairs | Where-Object { $_.Configuration -eq $cfg -and $_.Platform -eq $plat }
-- if ($hit) { $preferred += $hit }
-- }
-- }
-- # Add anything else not covered.
-- foreach ($p in $pairs) {
-- if (-not ($preferred | Where-Object { $_.Configuration -eq $p.Configuration -and $_.Platform -eq $p.Platform })) {
-- $preferred += $p
-- }
-- }
-- return @($preferred)
--}
--
--function Invoke-MSBuildProject([System.IO.FileInfo]$proj, [string]$label) {
-- Say "building $label project=$($proj.FullName)"
-- $pairs = Get-ProjectConfigs $proj.FullName
-- if ($pairs.Count -eq 0) {
-- $pairs = @([pscustomobject]@{ Configuration = "Release"; Platform = "x64" })
-- }
-- $pairs = Sort-PreferredConfigs $pairs $proj.Name
--
-- foreach ($p in $pairs) {
-- $cfgSafe = ($p.Configuration -replace '[^A-Za-z0-9]+','_')
-- $platSafe = ($p.Platform -replace '[^A-Za-z0-9]+','_')
-- $log = Join-Path $LogDir "msbuild-$label-$cfgSafe-$platSafe.log"
-- Say "msbuild $label cfg=$($p.Configuration) platform=$($p.Platform)"
-- & msbuild $proj.FullName /m /p:Configuration="$($p.Configuration)" /p:Platform="$($p.Platform)" /t:Build /v:minimal /nologo 2>&1 | Tee-Object -FilePath $log
-- if ($LASTEXITCODE -eq 0) {
-- Say "built $label with cfg=$($p.Configuration) platform=$($p.Platform)"
-- return $true
-- }
-- }
--
-- return $false
--}
--
--# Generate projects if no relevant vcxproj files exist yet.
--$clientProjects = @(Get-ChildItem -Path $Src -Recurse -File -Filter "*client*hl2mp*.vcxproj" -ErrorAction SilentlyContinue)
--$serverProjects = @(Get-ChildItem -Path $Src -Recurse -File -Filter "*server*hl2mp*.vcxproj" -ErrorAction SilentlyContinue)
--$solutions = Normalize-Solutions
--
--if ($clientProjects.Count -eq 0 -or $serverProjects.Count -eq 0) {
-- Run-ProjectGenerator
-- $solutions = Normalize-Solutions
-- $clientProjects = @(Get-ChildItem -Path $Src -Recurse -File -Filter "*client*hl2mp*.vcxproj" -ErrorAction SilentlyContinue)
-- $serverProjects = @(Get-ChildItem -Path $Src -Recurse -File -Filter "*server*hl2mp*.vcxproj" -ErrorAction SilentlyContinue)
--}
--
--"=== normalized solutions ===" | Out-File (Join-Path $LogDir "normalized-solutions.txt")
--$solutions | Select-Object FullName,Length,LastWriteTime | Format-List | Out-File (Join-Path $LogDir "normalized-solutions.txt") -Append
--"=== hl2mp projects ===" | Out-File (Join-Path $LogDir "hl2mp-projects.txt")
--@($clientProjects + $serverProjects) | Select-Object FullName,Length,LastWriteTime | Format-List | Out-File (Join-Path $LogDir "hl2mp-projects.txt") -Append
--
--if ($clientProjects.Count -eq 0) {
-- Get-ChildItem -Path $Src -Recurse -File -Include "*.vcxproj","*.sln","everything*" -ErrorAction SilentlyContinue |
-- Select-Object FullName,Length,LastWriteTime |
-- Format-List | Out-File (Join-Path $LogDir "project-search-after-generator.txt")
-- throw "No HL2MP client vcxproj was generated. Check openvibe-windows-build-debug artifact."
--}
--if ($serverProjects.Count -eq 0) {
-- Get-ChildItem -Path $Src -Recurse -File -Include "*.vcxproj","*.sln","everything*" -ErrorAction SilentlyContinue |
-- Select-Object FullName,Length,LastWriteTime |
-- Format-List | Out-File (Join-Path $LogDir "project-search-after-generator.txt")
-- throw "No HL2MP server vcxproj was generated. Check openvibe-windows-build-debug artifact."
--}
--
--# Prefer the actual HL2MP game projects and prefer win64 because current Valve VPC emitted *_win64_hl2mp.vcxproj on the runner.
--$clientProject = $clientProjects |
-- Sort-Object @{ Expression = { if ($_.Name -match 'win64') { 0 } else { 1 } } },
-- @{ Expression = { if ($_.Name -match '^client') { 0 } else { 1 } } },
-- FullName |
-- Select-Object -First 1
--$serverProject = $serverProjects |
-- Sort-Object @{ Expression = { if ($_.Name -match 'win64') { 0 } else { 1 } } },
-- @{ Expression = { if ($_.Name -match '^server') { 0 } else { 1 } } },
-- FullName |
-- Select-Object -First 1
--
--$beforeBuild = Get-Date
--
--if (-not (Invoke-MSBuildProject $clientProject "client")) {
-- throw "MSBuild failed all configurations for $($clientProject.FullName). Check msbuild-client-*.log."
--}
--if (-not (Invoke-MSBuildProject $serverProject "server")) {
-- throw "MSBuild failed all configurations for $($serverProject.FullName). Check msbuild-server-*.log."
--}
--
--New-Item -ItemType Directory -Force -Path $OutBin | Out-Null
--
--# Prefer recently built DLLs. Then prefer HL2MP paths.
--$allDlls = @(Get-ChildItem -Path $Sdk -Recurse -Filter "*.dll" -ErrorAction SilentlyContinue |
-- Where-Object { $_.LastWriteTime -ge $beforeBuild.AddMinutes(-2) } |
-- Sort-Object LastWriteTime -Descending)
--
--"=== dlls after build ===" | Out-File (Join-Path $LogDir "dlls-after-build.txt")
--$allDlls | Select-Object FullName,Length,LastWriteTime | Format-List | Out-File (Join-Path $LogDir "dlls-after-build.txt") -Append
--
--$client = $allDlls |
-- Where-Object { $_.Name -ieq "client.dll" -and $_.FullName -match "hl2mp|client" } |
-- Select-Object -First 1
--$server = $allDlls |
-- Where-Object { $_.Name -ieq "server.dll" -and $_.FullName -match "hl2mp|server" } |
-- Select-Object -First 1
--
--# Fallback: search all SDK DLLs if timestamps were weird.
--if (-not $client) {
-- $client = Get-ChildItem -Path $Sdk -Recurse -Filter client.dll -ErrorAction SilentlyContinue |
-- Where-Object { $_.FullName -match "hl2mp|client" } |
-- Sort-Object LastWriteTime -Descending |
-- Select-Object -First 1
--}
--if (-not $server) {
-- $server = Get-ChildItem -Path $Sdk -Recurse -Filter server.dll -ErrorAction SilentlyContinue |
-- Where-Object { $_.FullName -match "hl2mp|server" } |
-- Sort-Object LastWriteTime -Descending |
-- Select-Object -First 1
--}
--
--if (-not $client) { throw "client.dll was not produced" }
--if (-not $server) { throw "server.dll was not produced" }
--
--Say "copy client=$($client.FullName)"
--Say "copy server=$($server.FullName)"
--Copy-Item $client.FullName (Join-Path $OutBin "client.dll") -Force
--Copy-Item $server.FullName (Join-Path $OutBin "server.dll") -Force
--
--Say "done"
--PS1
--
--cat > docs/WINDOWS_VPC_DIRECT_PROJECT_BUILD.md <<'EOF'
--# Windows VPC direct project build
--
--Valve's `createallprojects.bat` runs VPC and is expected to emit `everything.sln`, but on the hosted runner VPC logged an extensionless `everything` solution while still generating the actual HL2MP `.vcxproj` files.
--
--The Windows build now treats the solution as optional and builds the generated HL2MP client/server projects directly:
--
--- `client*_hl2mp*.vcxproj`
--- `server*_hl2mp*.vcxproj`
--
--It also records generated projects and DLL locations in `artifacts/windows-build-debug`.
--EOF
--
--echo "[openvibe] git diff summary"
--git diff --stat -- tools/build-sdk-windows.ps1 docs/WINDOWS_VPC_DIRECT_PROJECT_BUILD.md || true
--
--git add tools/build-sdk-windows.ps1 docs/WINDOWS_VPC_DIRECT_PROJECT_BUILD.md "$0"
--if ! git diff --cached --quiet; then
-- git commit -m "Build Windows Source SDK HL2MP projects directly"
--fi
--
--git push origin "$BRANCH"
--
--echo "[openvibe] triggering clean Windows DLL build"
--if [[ -x tools/trigger-windows-dll-build-clean.sh ]]; then
-- tools/trigger-windows-dll-build-clean.sh
--else
-- tools/gh-windows-build-and-install.sh
--fi
ddiff --git a/tools/fix-windows-workflow-diagnostics-and-rerun.sh b/tools/fix-windows-workflow-diagnostics-and-rerun.sh
ddeleted file mode 100755
iindex 9baf359..0000000
---- a/tools/fix-windows-workflow-diagnostics-and-rerun.sh
++++ /dev/null
@@@ -1,392 +0,0 @@
--#!/usr/bin/env bash
--set -euo pipefail
--
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--REPO="${OPENVIBE_REPO:-OpenVibers/OpenVibe.Games}"
--REPO="${REPO%.git}"
--WORKFLOW="${OPENVIBE_WINDOWS_WORKFLOW:-windows-source-sdk-dlls.yml}"
--BRANCH="$(git -C "$ROOT" rev-parse --abbrev-ref HEAD)"
--LOG_DIR="$ROOT/artifacts/windows-workflow-debug"
--
--cd "$ROOT"
--
--echo "[openvibe] fix Windows workflow diagnostics + rerun"
--echo "[openvibe] root=$ROOT"
--echo "[openvibe] repo=$REPO"
--echo "[openvibe] workflow=$WORKFLOW"
--echo "[openvibe] branch=$BRANCH"
--
--if ! command -v gh >/dev/null 2>&1; then
-- echo "[openvibe error] gh is required. Install/auth first:" >&2
-- echo " sudo apt update && sudo apt install gh" >&2
-- echo " gh auth login" >&2
-- exit 1
--fi
--
--if ! gh auth status >/dev/null 2>&1; then
-- echo "[openvibe error] gh is installed but not authenticated. Run:" >&2
-- echo " gh auth login" >&2
-- exit 1
--fi
--
--mkdir -p "$LOG_DIR"
--
--latest_run="$(gh run list --repo "$REPO" --workflow "$WORKFLOW" --branch "$BRANCH" --limit 1 --json databaseId --jq '.[0].databaseId // empty')"
--if [[ -n "$latest_run" ]]; then
-- echo "[openvibe] latest workflow run=$latest_run"
-- echo "[openvibe] saving failed/full logs into $LOG_DIR"
-- gh run view "$latest_run" --repo "$REPO" --log-failed > "$LOG_DIR/run-${latest_run}-failed.log" 2>&1 || true
-- gh run view "$latest_run" --repo "$REPO" --log > "$LOG_DIR/run-${latest_run}-full.log" 2>&1 || true
--
-- echo "[openvibe] likely error snippets:"
-- grep -Eina "error|fatal|exception|not found|cannot find|MSB[0-9]+|LNK[0-9]+|C[0-9]{4}|No such|missing|failed" \
-- "$LOG_DIR/run-${latest_run}-failed.log" "$LOG_DIR/run-${latest_run}-full.log" \
-- | tail -n 80 || true
--else
-- echo "[openvibe warn] no existing workflow run found yet"
--fi
--
--echo
--
--echo "[openvibe] rewriting Windows workflow to always upload logs/artifacts"
--mkdir -p .github/workflows
--cat > .github/workflows/windows-source-sdk-dlls.yml <<'YAML'
--name: Build Windows Source DLLs
--
--on:
-- workflow_dispatch:
-- inputs:
-- diagnostics:
-- description: "Upload verbose build diagnostics"
-- required: false
-- default: "1"
-- push:
-- branches:
-- - codex/openvibe-next-steps
-- paths:
-- - ".github/workflows/windows-source-sdk-dlls.yml"
-- - "tools/build-sdk-windows.ps1"
-- - "tools/build-quickjs-lib-windows.ps1"
-- - "tools/apply-openvibe-sdk.sh"
-- - "sdk/openvibe/**"
-- - "game/openvibe.games/js/**"
--
--permissions:
-- contents: read
-- actions: read
--
--jobs:
-- windows-dlls:
-- name: Windows client.dll/server.dll
-- runs-on: windows-2022
-- defaults:
-- run:
-- shell: pwsh
--
-- steps:
-- - name: Checkout OpenVibe
-- uses: actions/checkout@v4
-- with:
-- fetch-depth: 1
--
-- - name: Configure git line endings
-- run: |
-- git config --global core.autocrlf false
-- git config --global core.eol lf
-- git status --short
--
-- - name: Setup MSBuild
-- uses: microsoft/setup-msbuild@v2
--
-- - name: Show toolchain and tree
-- run: |
-- $ErrorActionPreference = 'Continue'
-- Write-Host "pwd=$(Get-Location)"
-- git rev-parse --short HEAD
-- where.exe msbuild
-- where.exe cl
-- where.exe link
-- where.exe bash
-- Get-ChildItem -Force | Select-Object Mode,Length,Name
-- if (Test-Path engine) { Get-ChildItem -Force engine | Select-Object Mode,Length,Name } else { Write-Host "[miss] engine directory" }
-- if (Test-Path engine/source-sdk-2013/src) { Get-ChildItem -Force engine/source-sdk-2013/src | Select-Object Mode,Length,Name } else { Write-Host "[miss] engine/source-sdk-2013/src" }
--
-- - name: Build Windows Source SDK DLLs
-- id: build
-- run: |
-- $ErrorActionPreference = 'Stop'
-- New-Item -ItemType Directory -Force -Path artifacts/windows-build-debug | Out-Null
-- powershell -NoProfile -ExecutionPolicy Bypass -File tools/build-sdk-windows.ps1 *>&1 | Tee-Object -FilePath artifacts/windows-build-debug/build-sdk-windows.log
--
-- - name: Stage build outputs and diagnostics
-- if: always()
-- run: |
-- $ErrorActionPreference = 'Continue'
-- New-Item -ItemType Directory -Force -Path artifacts/windows-dlls | Out-Null
-- New-Item -ItemType Directory -Force -Path artifacts/windows-build-debug | Out-Null
--
-- "=== repo ===" | Out-File artifacts/windows-build-debug/tree.txt
-- Get-ChildItem -Force | Format-Table -AutoSize | Out-File artifacts/windows-build-debug/tree.txt -Append
-- "=== dll search ===" | Out-File artifacts/windows-build-debug/dll-search.txt
-- Get-ChildItem -Recurse -Force -Include client.dll,server.dll -ErrorAction SilentlyContinue |
-- Select-Object FullName,Length,LastWriteTime |
-- Format-List | Out-File artifacts/windows-build-debug/dll-search.txt -Append
-- "=== vc/sln search ===" | Out-File artifacts/windows-build-debug/project-search.txt
-- Get-ChildItem -Recurse -Force -Include *.sln,*.vcxproj,*.vcproj,*.log -ErrorAction SilentlyContinue |
-- Select-Object FullName,Length,LastWriteTime |
-- Format-List | Out-File artifacts/windows-build-debug/project-search.txt -Append
--
-- $clientCandidates = @(
-- "game/openvibe.games/bin/client.dll",
-- "engine/source-sdk-2013/game/mod_hl2mp/bin/client.dll",
-- "engine/source-sdk-2013/game/mod_hl2mp/bin/win64/client.dll",
-- "engine/source-sdk-2013/game/mod_hl2mp/bin/Release/client.dll",
-- "engine/source-sdk-2013/src/game/client/Release_hl2mp/client.dll",
-- "engine/source-sdk-2013/src/game/client/Release/client.dll"
-- )
-- $serverCandidates = @(
-- "game/openvibe.games/bin/server.dll",
-- "engine/source-sdk-2013/game/mod_hl2mp/bin/server.dll",
-- "engine/source-sdk-2013/game/mod_hl2mp/bin/win64/server.dll",
-- "engine/source-sdk-2013/game/mod_hl2mp/bin/Release/server.dll",
-- "engine/source-sdk-2013/src/game/server/Release_hl2mp/server.dll",
-- "engine/source-sdk-2013/src/game/server/Release/server.dll"
-- )
--
-- $client = $clientCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1
-- $server = $serverCandidates | Where-Object { Test-Path $_ } | Select-Object -First 1
-- if ($client) { Copy-Item $client artifacts/windows-dlls/client.dll -Force }
-- if ($server) { Copy-Item $server artifacts/windows-dlls/server.dll -Force }
--
-- if (Test-Path artifacts/windows-dlls/client.dll) {
-- $clientText = (Select-String -Path artifacts/windows-dlls/client.dll -Pattern "ov_join|ov_menu|OpenVibe" -Encoding Byte -SimpleMatch -Quiet)
-- Write-Host "client.dll staged"
-- } else {
-- Write-Host "[miss] no client.dll staged"
-- }
-- if (Test-Path artifacts/windows-dlls/server.dll) { Write-Host "server.dll staged" } else { Write-Host "[miss] no server.dll staged" }
--
-- - name: Upload Windows DLL artifact
-- if: always()
-- uses: actions/upload-artifact@v4
-- with:
-- name: openvibe-windows-dlls
-- path: artifacts/windows-dlls/**
-- if-no-files-found: warn
--
-- - name: Upload Windows build diagnostics
-- if: always()
-- uses: actions/upload-artifact@v4
-- with:
-- name: openvibe-windows-build-debug
-- path: artifacts/windows-build-debug/**
-- if-no-files-found: warn
--YAML
--
--cat > tools/build-sdk-windows.ps1 <<'PS1'
--$ErrorActionPreference = "Stop"
--
--$Root = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
--$Sdk = Join-Path $Root "engine/source-sdk-2013"
--$Src = Join-Path $Sdk "src"
--$Mod = Join-Path $Root "game/openvibe.games"
--$OutBin = Join-Path $Mod "bin"
--$Qjs = Join-Path $Src "game/shared/openvibe/third_party/quickjs"
--$LogDir = Join-Path $Root "artifacts/windows-build-debug"
--New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
--
--function Say($m) { Write-Host "[openvibe-win] $m" }
--function Require($p, $msg) {
-- if (!(Test-Path $p)) { throw "$msg`nMissing: $p" }
--}
--
--Say "root=$Root"
--Say "sdk=$Sdk"
--Say "src=$Src"
--Require $Src "Source SDK checkout is missing from this runner. The workflow must have engine/source-sdk-2013 available in the repository/submodule before Windows DLLs can build."
--
--# Apply OpenVibe source files using Git Bash when available. This reuses the Linux patcher on Windows runners.
--$bash = (Get-Command bash -ErrorAction SilentlyContinue)
--if ($bash) {
-- Say "applying OpenVibe SDK patch through bash"
-- & bash "$Root/tools/apply-openvibe-sdk.sh"
-- if ($LASTEXITCODE -ne 0) { throw "apply-openvibe-sdk.sh failed with exit code $LASTEXITCODE" }
--} else {
-- throw "Git Bash is required on the Windows runner to apply the OpenVibe SDK patch."
--}
--
--# Build QuickJS as MSVC objects/lib. The Linux .a cannot be linked into Windows DLLs.
--if (Test-Path (Join-Path $Root "tools/build-quickjs-lib-windows.ps1")) {
-- Say "building QuickJS Windows static library"
-- & powershell -NoProfile -ExecutionPolicy Bypass -File (Join-Path $Root "tools/build-quickjs-lib-windows.ps1")
-- if ($LASTEXITCODE -ne 0) { throw "build-quickjs-lib-windows.ps1 failed with exit code $LASTEXITCODE" }
--} else {
-- Say "no build-quickjs-lib-windows.ps1 found; continuing"
--}
--
--Set-Location $Src
--
--# Generate Visual Studio project files if needed.
--$solutions = @(Get-ChildItem -Path $Src -Recurse -Filter "*.sln" -ErrorAction SilentlyContinue)
--if ($solutions.Count -eq 0) {
-- Say "no .sln files found; trying Source SDK project generators"
-- $generators = @(
-- (Join-Path $Src "creategameprojects.bat"),
-- (Join-Path $Src "createallprojects.bat"),
-- (Join-Path $Src "createprojects.bat")
-- )
-- $ran = $false
-- foreach ($gen in $generators) {
-- if (Test-Path $gen) {
-- Say "running $gen"
-- & cmd /c "`"$gen`"" | Tee-Object -FilePath (Join-Path $LogDir "project-generation.log")
-- $ran = $true
-- break
-- }
-- }
--
-- if (-not $ran) {
-- $vpc = Join-Path $Src "devtools/bin/vpc.exe"
-- if (Test-Path $vpc) {
-- Say "running vpc.exe fallback"
-- & $vpc /hl2mp +game /mksln OpenVibe_HL2MP.sln | Tee-Object -FilePath (Join-Path $LogDir "vpc.log")
-- $ran = $true
-- }
-- }
--
-- $solutions = @(Get-ChildItem -Path $Src -Recurse -Filter "*.sln" -ErrorAction SilentlyContinue)
-- if ($solutions.Count -eq 0) {
-- Get-ChildItem -Path $Src -Force | Select-Object Mode,Length,Name | Format-Table -AutoSize | Out-File (Join-Path $LogDir "src-root-after-generator.txt")
-- throw "No Visual Studio solution was generated. Check openvibe-windows-build-debug artifact."
-- }
--}
--
--Say "solutions found:"
--$solutions | ForEach-Object { Say " $($_.FullName)" }
--
--# Pick the most likely HL2MP/game solution.
--$solution = $solutions |
-- Where-Object { $_.Name -match "hl2mp|game|sdk|everything|OpenVibe" } |
-- Select-Object -First 1
--if (-not $solution) { $solution = $solutions | Select-Object -First 1 }
--Say "selected solution=$($solution.FullName)"
--
--# Try common project targets/configurations. Source SDK projects vary by branch/version.
--$configs = @("Release", "Release_HL2MP", "Release HL2MP")
--$platforms = @("Win32", "x86")
--$targets = @("client_hl2mp", "server_hl2mp", "client", "server", "Build")
--
--$builtAny = $false
--foreach ($cfg in $configs) {
-- foreach ($plat in $platforms) {
-- foreach ($target in $targets) {
-- Say "msbuild cfg=$cfg platform=$plat target=$target"
-- & msbuild $solution.FullName /m /p:Configuration=$cfg /p:Platform=$plat /t:$target /v:minimal /nologo 2>&1 | Tee-Object -FilePath (Join-Path $LogDir "msbuild-$($cfg -replace '[^A-Za-z0-9]','_')-$plat-$target.log")
-- if ($LASTEXITCODE -eq 0) {
-- $builtAny = $true
-- break
-- }
-- }
-- if ($builtAny) { break }
-- }
-- if ($builtAny) { break }
--}
--
--if (-not $builtAny) {
-- throw "MSBuild could not build any known Source SDK HL2MP target. Check uploaded msbuild logs."
--}
--
--New-Item -ItemType Directory -Force -Path $OutBin | Out-Null
--
--$client = Get-ChildItem -Path $Sdk -Recurse -Filter client.dll -ErrorAction SilentlyContinue |
-- Where-Object { $_.FullName -match "hl2mp|mod_hl2mp|client" } |
-- Sort-Object LastWriteTime -Descending |
-- Select-Object -First 1
--$server = Get-ChildItem -Path $Sdk -Recurse -Filter server.dll -ErrorAction SilentlyContinue |
-- Where-Object { $_.FullName -match "hl2mp|mod_hl2mp|server" } |
-- Sort-Object LastWriteTime -Descending |
-- Select-Object -First 1
--
--if (-not $client) { throw "client.dll was not produced" }
--if (-not $server) { throw "server.dll was not produced" }
--
--Say "copy client=$($client.FullName)"
--Say "copy server=$($server.FullName)"
--Copy-Item $client.FullName (Join-Path $OutBin "client.dll") -Force
--Copy-Item $server.FullName (Join-Path $OutBin "server.dll") -Force
--
--Say "done"
--PS1
--
--# Add a local helper that downloads full logs/artifacts from the last run even when failed.
--cat > tools/windows-workflow-debug-and-install.sh <<'SH'
--#!/usr/bin/env bash
--set -euo pipefail
--ROOT="${OPENVIBE_ROOT:-$HOME/src/openvibe-source}"
--REPO="${OPENVIBE_REPO:-OpenVibers/OpenVibe.Games}"
--WORKFLOW="${OPENVIBE_WINDOWS_WORKFLOW:-windows-source-sdk-dlls.yml}"
--BRANCH="$(git -C "$ROOT" rev-parse --abbrev-ref HEAD)"
--OUT="$ROOT/artifacts/windows-workflow-debug"
--cd "$ROOT"
--mkdir -p "$OUT"
--run="$(gh run list --repo "$REPO" --workflow "$WORKFLOW" --branch "$BRANCH" --limit 1 --json databaseId --jq '.[0].databaseId')"
--echo "[openvibe] latest run=$run"
--gh run view "$run" --repo "$REPO" --log > "$OUT/run-${run}.log" 2>&1 || true
--gh run view "$run" --repo "$REPO" --log-failed > "$OUT/run-${run}-failed.log" 2>&1 || true
--gh run download "$run" --repo "$REPO" --dir "$OUT/run-${run}-artifacts" || true
--printf '\n[openvibe] tail of failed log:\n'
--tail -n 160 "$OUT/run-${run}-failed.log" || true
--printf '\n[openvibe] artifacts saved under: %s\n' "$OUT/run-${run}-artifacts"
--if [[ -f "$OUT/run-${run}-artifacts/openvibe-windows-dlls/client.dll" && -f "$OUT/run-${run}-artifacts/openvibe-windows-dlls/server.dll" ]]; then
-- mkdir -p game/openvibe.games/bin
-- cp -f "$OUT/run-${run}-artifacts/openvibe-windows-dlls/client.dll" game/openvibe.games/bin/client.dll
-- cp -f "$OUT/run-${run}-artifacts/openvibe-windows-dlls/server.dll" game/openvibe.games/bin/server.dll
-- echo "[openvibe] installed DLL artifacts"
-- tools/verify-openvibe-dll-content.sh || true
--else
-- echo "[openvibe] no DLL artifact available yet; check debug artifact/logs"
--fi
--SH
--chmod +x tools/windows-workflow-debug-and-install.sh
--
--git add .github/workflows/windows-source-sdk-dlls.yml tools/build-sdk-windows.ps1 tools/windows-workflow-debug-and-install.sh
--
--if ! git diff --cached --quiet; then
-- echo "[openvibe] committing workflow/build diagnostic patch"
-- git commit -m "Improve Windows DLL workflow diagnostics"
-- echo "[openvibe] pushing $BRANCH"
-- git push origin "$BRANCH"
--else
-- echo "[openvibe] no workflow/build changes to commit"
--fi
--
--echo "[openvibe] triggering updated workflow"
--gh workflow run "$WORKFLOW" --repo "$REPO" --ref "$BRANCH"
--
--echo "[openvibe] waiting for new workflow run to appear"
--sleep 5
--new_run="$(gh run list --repo "$REPO" --workflow "$WORKFLOW" --branch "$BRANCH" --limit 1 --json databaseId --jq '.[0].databaseId // empty')"
--if [[ -z "$new_run" ]]; then
-- echo "[openvibe error] could not find new workflow run" >&2
-- exit 1
--fi
--
--echo "[openvibe] watching run $new_run"
--set +e
--gh run watch "$new_run" --repo "$REPO" --exit-status
--status=$?
--set -e
--
--if [[ $status -eq 0 ]]; then
-- echo "[openvibe] workflow passed; downloading artifacts"
--else
-- echo "[openvibe warn] workflow still failed; downloading diagnostics anyway"
--fi
--
--tools/windows-workflow-debug-and-install.sh
--
--if [[ $status -ne 0 ]]; then
-- echo
-- echo "[openvibe] next: paste the tail printed above if you want me to patch the exact compile error."
-- exit $status
--fi