openvibers/source.openvibe.games Commits

Stub Windows server QuickJS runtime for DLL build

CO Copilot committed 29 Jun 2026 · 75904c0b8e · 9 files, +547 −0


Changes

ddiff --git a/docs/WINDOWS_SERVER_QJS_STUB_BUILD.md b/docs/WINDOWS_SERVER_QJS_STUB_BUILD.md
nnew file mode 100644
iindex 0000000..125a709
---- /dev/null
++++ b/docs/WINDOWS_SERVER_QJS_STUB_BUILD.md
@@@ -0,0 +1,18 @@
++# 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.
ddiff --git a/sdk/openvibe/server/hl2mp/openvibe_js_server.cpp b/sdk/openvibe/server/hl2mp/openvibe_js_server.cpp
iindex 46a6c1d..370532e 100644
---- a/sdk/openvibe/server/hl2mp/openvibe_js_server.cpp
++++ b/sdk/openvibe/server/hl2mp/openvibe_js_server.cpp
@@@ -1,3 +1,87 @@
++// OPENVIBE_WINDOWS_SERVER_QUICKJS_STUB
++#if defined(_WIN32) && defined(GAME_DLL) && !defined(OPENVIBE_WINDOWS_SERVER_REAL_QUICKJS)
++
++#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
++);
++#else
#include "cbase.h"
#include "hl2mp_player.h"
#include "openvibe_js_server.h"
@@@ -265,3 +349,4 @@ static ConCommand ov_js_cmd(
"Send a ConsoleCommand event into the embedded OpenVibe JavaScript runtime.",
FCVAR_GAMEDLL
);
++#endif
ddiff --git a/sdk/openvibe/shared/ov_js_bindings.cpp b/sdk/openvibe/shared/ov_js_bindings.cpp
iindex 7aaaf5b..d35a4fc 100644
---- a/sdk/openvibe/shared/ov_js_bindings.cpp
++++ b/sdk/openvibe/shared/ov_js_bindings.cpp
@@@ -1,3 +1,13 @@
++// OPENVIBE_WINDOWS_SERVER_QUICKJS_STUB
++#if defined(_WIN32) && defined(GAME_DLL) && !defined(OPENVIBE_WINDOWS_SERVER_REAL_QUICKJS)
++
++#include "cbase.h"
++#include "ov_js_bindings.h"
++
++#include "tier0/memdbgon.h"
++
++void OVJS_RegisterNativeBindings(JSContext *ctx, COpenVibeJSRuntime *runtime) {}
++#else
#include "cbase.h"
#include "ov_js_bindings.h"
#include "ov_js_runtime.h"
@@@ -168,3 +178,4 @@ void OVJS_RegisterNativeBindings(JSContext *ctx, COpenVibeJSRuntime *runtime)
JS_FreeValue(ctx, global);
}
++#endif
ddiff --git a/sdk/openvibe/shared/ov_js_bindings.h b/sdk/openvibe/shared/ov_js_bindings.h
iindex 1f3e51b..3014d73 100644
---- a/sdk/openvibe/shared/ov_js_bindings.h
++++ b/sdk/openvibe/shared/ov_js_bindings.h
@@@ -1,7 +1,15 @@
#pragma once
++// OPENVIBE_WINDOWS_SERVER_QUICKJS_STUB
++#if defined(_WIN32) && defined(GAME_DLL) && !defined(OPENVIBE_WINDOWS_SERVER_REAL_QUICKJS)
++
++typedef void JSContext;
++class COpenVibeJSRuntime;
++void OVJS_RegisterNativeBindings(JSContext *ctx, COpenVibeJSRuntime *runtime);
++#else
#include "openvibe/third_party/quickjs/quickjs.h"
class COpenVibeJSRuntime;
void OVJS_RegisterNativeBindings(JSContext *ctx, COpenVibeJSRuntime *runtime);
++#endif
ddiff --git a/sdk/openvibe/shared/ov_js_player.cpp b/sdk/openvibe/shared/ov_js_player.cpp
iindex 6e2a965..930e771 100644
---- a/sdk/openvibe/shared/ov_js_player.cpp
++++ b/sdk/openvibe/shared/ov_js_player.cpp
@@@ -1,3 +1,17 @@
++// OPENVIBE_WINDOWS_SERVER_QUICKJS_STUB
++#if defined(_WIN32) && defined(GAME_DLL) && !defined(OPENVIBE_WINDOWS_SERVER_REAL_QUICKJS)
++
++#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; }
++#else
#include "cbase.h"
#include "hl2mp_player.h"
#include "player.h"
@@@ -208,3 +222,4 @@ JSValue OVJS_NewPlayer(JSContext *ctx, CHL2MP_Player *player)
JS_SetOpaque(obj, handle);
return obj;
}
++#endif
ddiff --git a/sdk/openvibe/shared/ov_js_player.h b/sdk/openvibe/shared/ov_js_player.h
iindex 765e2da..e2e87ed 100644
---- a/sdk/openvibe/shared/ov_js_player.h
++++ b/sdk/openvibe/shared/ov_js_player.h
@@@ -1,5 +1,15 @@
#pragma once
++// OPENVIBE_WINDOWS_SERVER_QUICKJS_STUB
++#if defined(_WIN32) && defined(GAME_DLL) && !defined(OPENVIBE_WINDOWS_SERVER_REAL_QUICKJS)
++
++#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);
++#else
#include "openvibe/third_party/quickjs/quickjs.h"
class CHL2MP_Player;
@@@ -8,3 +18,4 @@ 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);
++#endif
ddiff --git a/sdk/openvibe/shared/ov_js_runtime.cpp b/sdk/openvibe/shared/ov_js_runtime.cpp
iindex 9bbb640..64b3484 100644
---- a/sdk/openvibe/shared/ov_js_runtime.cpp
++++ b/sdk/openvibe/shared/ov_js_runtime.cpp
@@@ -1,3 +1,33 @@
++// OPENVIBE_WINDOWS_SERVER_QUICKJS_STUB
++#if defined(_WIN32) && defined(GAME_DLL) && !defined(OPENVIBE_WINDOWS_SERVER_REAL_QUICKJS)
++
++#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;
++}
++#else
#include "cbase.h"
#include "filesystem.h"
#include "ov_js_runtime.h"
@@@ -232,3 +262,4 @@ void COpenVibeJSRuntime::PrintException(const char *pszWhere)
JS_FreeValue(m_pCtx, exception);
}
++#endif
ddiff --git a/sdk/openvibe/shared/ov_js_runtime.h b/sdk/openvibe/shared/ov_js_runtime.h
iindex 94fee5c..8b1cfe6 100644
---- a/sdk/openvibe/shared/ov_js_runtime.h
++++ b/sdk/openvibe/shared/ov_js_runtime.h
@@@ -1,5 +1,42 @@
#pragma once
++// OPENVIBE_WINDOWS_SERVER_QUICKJS_STUB
++#if defined(_WIN32) && defined(GAME_DLL) && !defined(OPENVIBE_WINDOWS_SERVER_REAL_QUICKJS)
++
++// 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]{};
++};
++#else
#include "openvibe/third_party/quickjs/quickjs.h"
class COpenVibeJSRuntime
@@@ -31,3 +68,4 @@ private:
bool m_bServerRealm = false;
char m_szMode[64]{};
};
++#endif
ddiff --git a/tools/fix-windows-server-stub-quickjs-and-rerun.sh b/tools/fix-windows-server-stub-quickjs-and-rerun.sh
nnew file mode 100755
iindex 0000000..32f200d
---- /dev/null
++++ b/tools/fix-windows-server-stub-quickjs-and-rerun.sh
@@@ -0,0 +1,330 @@
++#!/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"