openvibers/source.openvibe.games Commits
Phase 6: client-side JS runtime + server->client net (both realms)
Changes
@@@ -124,7 +124,8 @@receivers[String(name)] = fn;},-- // server -> client++ // server -> client. ids are passed to C++ as a comma-separated list of++ // player userIds; -1 means "broadcast to everyone".Send: function (target) {if (!writeBuf) return;if (!isServer) { OV.warn("net.Send is server-only; use SendToServer on the client"); return; }@@@ -132,15 +133,15 @@if (target == null) { ids = [-1]; }else if (Array.isArray(target)) { ids = target.map(function (p) { return p && p.userId ? p.userId() : (p | 0); }); }else { ids = [target && target.userId ? target.userId() : (target | 0)]; }-- var payload = serialize();-- if (OV.netEmit) OV.netEmit(JSON.stringify(ids), writeBuf.name, payload);++ var name = writeBuf.name, payload = serialize();++ if (OV.netEmit) OV.netEmit(ids.join(","), name, payload);writeBuf = null;},Broadcast: function () {if (!writeBuf) return;if (!isServer) { OV.warn("net.Broadcast is server-only"); return; }-- var payload = serialize();-- if (OV.netEmit) OV.netEmit(JSON.stringify([-1]), writeBuf.name, payload);++ var name = writeBuf.name, payload = serialize();++ if (OV.netEmit) OV.netEmit("-1", name, payload);writeBuf = null;},nnew file mode 100644@@@ -0,0 +1,349 @@++#include "cbase.h"++#include "openvibe_js_client.h"++++// The client always uses the real QuickJS runtime (the Windows *server* stub++// guard keys on GAME_DLL, which is not defined in the client build).++#include "filesystem.h"++#include "openvibe/ov_js_runtime.h"++#include "openvibe/ov_js_bindings.h"++#include "usermessages.h"++#include <ctype.h>++++#include "tier0/memdbgon.h"++++// ---------------------------------------------------------------------------++// Client JS runtime instance + mode.++// ---------------------------------------------------------------------------++static COpenVibeJSRuntime g_OVClientJS;++static bool g_OVClientJSStarted = false;++++static ConVar ov_client_js_enabled(++ "ov_client_js_enabled", "1", FCVAR_CLIENTDLL,++ "Enable the OpenVibe client-side JavaScript runtime." );++++// Which gamemode's client.js to load. The server pushes its mode to the client++// (ov_client_mode <mode>; then the runtime reloads), defaulting to hub.++static ConVar ov_client_mode(++ "ov_client_mode", "hub", FCVAR_CLIENTDLL,++ "OpenVibe client gamemode realm: hub, prophunt, deathrun, fortwars, traitortown, sandbox." );++++static bool OpenVibeClientJS_IsRunning()++{++ return ov_client_js_enabled.GetBool() && g_OVClientJS.Context() != nullptr;++}++++// ---------------------------------------------------------------------------++// Client OV.* native bindings. Client-safe subset (no server player list /++// ServerCommand). File I/O + logging + net + realm info, matching the server++// bridge so the shared core JS (require/net/addon/timer/hook) runs unchanged.++// ---------------------------------------------------------------------------++static bool OVCJS_IsSafeModPath( const char *pszPath )++{++ if ( !pszPath || !pszPath[0] ) return false;++ if ( pszPath[0] == '/' || pszPath[0] == '\\' ) return false;++ if ( Q_strstr( pszPath, ".." ) ) return false;++ if ( Q_strstr( pszPath, ":" ) ) return false;++ return true;++}++++static JSValue OVCJS_log( JSContext *ctx, JSValueConst, int argc, JSValueConst *argv )++{++ if ( argc < 1 ) return JS_UNDEFINED;++ const char *msg = JS_ToCString( ctx, argv[0] );++ if ( msg ) { Msg( "[OV JS/client] %s\n", msg ); JS_FreeCString( ctx, msg ); }++ return JS_UNDEFINED;++}++++static JSValue OVCJS_warn( JSContext *ctx, JSValueConst, int argc, JSValueConst *argv )++{++ if ( argc < 1 ) return JS_UNDEFINED;++ const char *msg = JS_ToCString( ctx, argv[0] );++ if ( msg ) { Warning( "[OV JS/client] %s\n", msg ); JS_FreeCString( ctx, msg ); }++ return JS_UNDEFINED;++}++++static JSValue OVCJS_error( JSContext *ctx, JSValueConst, int argc, JSValueConst *argv )++{++ if ( argc < 1 ) return JS_UNDEFINED;++ const char *msg = JS_ToCString( ctx, argv[0] );++ if ( msg ) { Warning( "[OV JS/client ERROR] %s\n", msg ); JS_FreeCString( ctx, msg ); }++ return JS_UNDEFINED;++}++++static JSValue OVCJS_isServer( JSContext *ctx, JSValueConst, int, JSValueConst * )++{++ return JS_FALSE; // client realm++}++++static JSValue OVCJS_getMode( JSContext *ctx, JSValueConst, int, JSValueConst * )++{++ return JS_NewString( ctx, g_OVClientJS.GetMode() );++}++++static JSValue OVCJS_getMapName( JSContext *ctx, JSValueConst, int, JSValueConst * )++{++ return JS_NewString( ctx, engine ? engine->GetLevelName() : "" );++}++++static JSValue OVCJS_time( JSContext *ctx, JSValueConst, int, JSValueConst * )++{++ return JS_NewFloat64( ctx, gpGlobals ? gpGlobals->curtime : 0.0 );++}++++static JSValue OVCJS_readFile( JSContext *ctx, JSValueConst, int argc, JSValueConst *argv )++{++ if ( argc < 1 ) return JS_NULL;++ const char *path = JS_ToCString( ctx, argv[0] );++ if ( !path ) return JS_NULL;++++ if ( !OVCJS_IsSafeModPath( path ) )++ {++ Warning( "[OV JS/client] readFile refused unsafe path: %s\n", path );++ JS_FreeCString( ctx, path );++ return JS_NULL;++ }++++ FileHandle_t file = filesystem->Open( path, "rb", "MOD" );++ if ( !file ) { JS_FreeCString( ctx, path ); return JS_NULL; }++++ int size = filesystem->Size( file );++ if ( size < 0 || size > 8 * 1024 * 1024 )++ {++ filesystem->Close( file );++ JS_FreeCString( ctx, path );++ return JS_NULL;++ }++++ char *buffer = new char[size + 1];++ filesystem->Read( buffer, size, file );++ filesystem->Close( file );++ buffer[size] = '\0';++++ JSValue out = JS_NewStringLen( ctx, buffer, size );++ delete[] buffer;++ JS_FreeCString( ctx, path );++ return out;++}++++static JSValue OVCJS_fileExists( JSContext *ctx, JSValueConst, int argc, JSValueConst *argv )++{++ if ( argc < 1 ) return JS_FALSE;++ const char *path = JS_ToCString( ctx, argv[0] );++ if ( !path ) return JS_FALSE;++ bool exists = OVCJS_IsSafeModPath( path )++ && filesystem->FileExists( path, "MOD" )++ && !filesystem->IsDirectory( path, "MOD" );++ JS_FreeCString( ctx, path );++ return exists ? JS_TRUE : JS_FALSE;++}++++static JSValue OVCJS_listDir( JSContext *ctx, JSValueConst, int argc, JSValueConst *argv )++{++ JSValue arr = JS_NewArray( ctx );++ if ( argc < 1 ) return arr;++++ const char *dir = JS_ToCString( ctx, argv[0] );++ if ( !dir ) return arr;++++ const char *wildcardArg = ( argc >= 2 && JS_IsString( argv[1] ) ) ? JS_ToCString( ctx, argv[1] ) : nullptr;++ const char *wildcard = ( wildcardArg && wildcardArg[0] ) ? wildcardArg : "*";++++ if ( !OVCJS_IsSafeModPath( dir ) )++ {++ if ( wildcardArg ) JS_FreeCString( ctx, wildcardArg );++ JS_FreeCString( ctx, dir );++ return arr;++ }++++ char search[512];++ Q_snprintf( search, sizeof( search ), "%s/%s", dir, wildcard );++++ FileFindHandle_t findHandle;++ const char *name = filesystem->FindFirstEx( search, "MOD", &findHandle );++ uint32 index = 0;++ while ( name )++ {++ if ( Q_strcmp( name, "." ) != 0 && Q_strcmp( name, ".." ) != 0 )++ JS_SetPropertyUint32( ctx, arr, index++, JS_NewString( ctx, name ) );++ name = filesystem->FindNext( findHandle );++ }++ filesystem->FindClose( findHandle );++++ if ( wildcardArg ) JS_FreeCString( ctx, wildcardArg );++ JS_FreeCString( ctx, dir );++ return arr;++}++++// client -> server: forward as the ov_net command the server JS dispatches.++static JSValue OVCJS_netSendToServer( JSContext *ctx, JSValueConst, int argc, JSValueConst *argv )++{++ if ( argc < 2 ) return JS_UNDEFINED;++ const char *name = JS_ToCString( ctx, argv[0] );++ const char *payload = JS_ToCString( ctx, argv[1] );++ if ( name && payload && engine )++ {++ char cmd[16384];++ Q_snprintf( cmd, sizeof( cmd ), "ov_net %s %s\n", name, payload );++ engine->ClientCmd_Unrestricted( cmd );++ }++ if ( name ) JS_FreeCString( ctx, name );++ if ( payload ) JS_FreeCString( ctx, payload );++ return JS_UNDEFINED;++}++++// broadcast/serverCommand are server-only; provide inert client stubs so shared++// JS that references them doesn't throw on the client.++static JSValue OVCJS_noop( JSContext *ctx, JSValueConst, int, JSValueConst * )++{++ return JS_UNDEFINED;++}++++static const JSCFunctionListEntry OVClientFuncs[] =++{++ JS_CFUNC_DEF( "log", 1, OVCJS_log ),++ JS_CFUNC_DEF( "warn", 1, OVCJS_warn ),++ JS_CFUNC_DEF( "error", 1, OVCJS_error ),++ JS_CFUNC_DEF( "isServer", 0, OVCJS_isServer ),++ JS_CFUNC_DEF( "getMode", 0, OVCJS_getMode ),++ JS_CFUNC_DEF( "getMapName", 0, OVCJS_getMapName ),++ JS_CFUNC_DEF( "time", 0, OVCJS_time ),++ JS_CFUNC_DEF( "readFile", 1, OVCJS_readFile ),++ JS_CFUNC_DEF( "fileExists", 1, OVCJS_fileExists ),++ JS_CFUNC_DEF( "listDir", 2, OVCJS_listDir ),++ JS_CFUNC_DEF( "netSendToServer", 2, OVCJS_netSendToServer ),++ JS_CFUNC_DEF( "broadcast", 1, OVCJS_noop ),++ JS_CFUNC_DEF( "serverCommand", 1, OVCJS_noop ),++};++++// This is the symbol ov_js_runtime.cpp calls; the client project compiles this++// file instead of the server ov_js_bindings.cpp.++void OVJS_RegisterNativeBindings( JSContext *ctx, COpenVibeJSRuntime * )++{++ JSValue global = JS_GetGlobalObject( ctx );++ JSValue ov = JS_NewObject( ctx );++ JS_SetPropertyFunctionList( ctx, ov, OVClientFuncs, ARRAYSIZE( OVClientFuncs ) );++ JS_SetPropertyStr( ctx, global, "OV", ov );++ JS_FreeValue( ctx, global );++}++++// ---------------------------------------------------------------------------++// server -> client: OVNet usermessage -> OVNetReceive hook into client JS.++// ---------------------------------------------------------------------------++void __MsgFunc_OVNet( bf_read &msg )++{++ if ( !OpenVibeClientJS_IsRunning() )++ return;++++ char name[128];++ char payload[16384];++ msg.ReadString( name, sizeof( name ) );++ msg.ReadString( payload, sizeof( payload ) );++++ JSContext *ctx = g_OVClientJS.Context();++ JSValue jsName = JS_NewString( ctx, name );++ JSValue jsPayload = JS_NewString( ctx, payload );++ JSValue jsPly = JS_NULL; // server->client has no "sending player"++++ JSValueConst argv[] = { jsName, jsPayload, jsPly };++ g_OVClientJS.CallHookVoid( "OVNetReceive", 3, argv );++++ JS_FreeValue( ctx, jsName );++ JS_FreeValue( ctx, jsPayload );++}++++// ---------------------------------------------------------------------------++// Lifecycle.++// ---------------------------------------------------------------------------++void OpenVibeJS_Client_Init()++{++ if ( g_OVClientJSStarted )++ return;++ g_OVClientJSStarted = true;++++ if ( !ov_client_js_enabled.GetBool() )++ {++ Msg( "[OV JS/client] disabled by ov_client_js_enabled=0\n" );++ return;++ }++++ if ( g_OVClientJS.Init( false /*client realm*/, ov_client_mode.GetString() ) )++ {++ Msg( "[OV JS/client] client runtime initialized for mode '%s'\n", ov_client_mode.GetString() );++ g_OVClientJS.CallHookVoid( "Initialize" );++ }++ else++ {++ Warning( "[OV JS/client] client runtime failed to initialize\n" );++ }++}++++void OpenVibeJS_Client_Shutdown()++{++ if ( OpenVibeClientJS_IsRunning() )++ g_OVClientJS.CallHookVoid( "Shutdown" );++ g_OVClientJS.Shutdown();++ g_OVClientJSStarted = false;++}++++void OpenVibeJS_Client_Think()++{++ if ( OpenVibeClientJS_IsRunning() )++ g_OVClientJS.CallHookVoid( "Think" );++}++++// ---------------------------------------------------------------------------++// Client concommands (mirror the server ones for parity/debugging).++// ---------------------------------------------------------------------------++static void OV_ClientJSStatus_f()++{++ Msg( "[OV JS/client] enabled=%d started=%d running=%d mode=%s\n",++ ov_client_js_enabled.GetBool() ? 1 : 0,++ g_OVClientJSStarted ? 1 : 0,++ OpenVibeClientJS_IsRunning() ? 1 : 0,++ ov_client_mode.GetString() );++}++static ConCommand ov_client_js_status( "ov_client_js_status", OV_ClientJSStatus_f,++ "Print OpenVibe client JavaScript runtime status.", FCVAR_CLIENTDLL );++++static void OV_ClientJSReload_f()++{++ OpenVibeJS_Client_Shutdown();++ OpenVibeJS_Client_Init();++ Msg( "[OV JS/client] reloaded\n" );++}++static ConCommand ov_client_js_reload( "ov_client_js_reload", OV_ClientJSReload_f,++ "Reload the OpenVibe client JavaScript runtime.", FCVAR_CLIENTDLL );++++// Drives the client JS runtime: hooks the OVNet usermessage at load, starts the++// runtime once the level/systems are up, and pumps Think() each frame (for JS++// timers). Self-contained so no external init-call patching is needed.++class COpenVibeClientJSSystem : public CAutoGameSystemPerFrame++{++public:++ COpenVibeClientJSSystem() : CAutoGameSystemPerFrame( "COpenVibeClientJSSystem" ) {}++++ bool Init() OVERRIDE++ {++ usermessages->HookMessage( "OVNet", __MsgFunc_OVNet );++ return true;++ }++++ void LevelInitPostEntity() OVERRIDE++ {++ // (Re)start the runtime when a level is entered so <mode>/client.js and++ // client addons load fresh for the connected server.++ OpenVibeJS_Client_Shutdown();++ OpenVibeJS_Client_Init();++ }++++ void LevelShutdownPostEntity() OVERRIDE++ {++ OpenVibeJS_Client_Shutdown();++ }++++ void Update( float frametime ) OVERRIDE++ {++ OpenVibeJS_Client_Think();++ }++};++static COpenVibeClientJSSystem g_OpenVibeClientJSSystem;nnew file mode 100644@@@ -0,0 +1,16 @@++#ifndef OPENVIBE_JS_CLIENT_H++#define OPENVIBE_JS_CLIENT_H++#ifdef _WIN32++#pragma once++#endif++++// Client-realm OpenVibe JavaScript runtime lifecycle. Mirrors the server JS++// integration but for CLIENT_DLL: runs core JS + js/gamemodes/<mode>/client.js,++// gives client addons a place to run, and bridges the net library both ways++// (client->server via the ov_net forward, server->client via the OVNet++// usermessage).++void OpenVibeJS_Client_Init();++void OpenVibeJS_Client_Shutdown();++void OpenVibeJS_Client_Think();++++#endif // OPENVIBE_JS_CLIENT_H@@@ -13,6 +13,7 @@ void OVJS_RegisterNativeBindings(JSContext *ctx, COpenVibeJSRuntime *runtime) {}#include "hl2mp_player.h"#include "util.h"#include "filesystem.h"++#include "recipientfilter.h"#include "tier0/memdbgon.h"@@@ -263,6 +264,70 @@ static JSValue OVJS_listDir(JSContext *ctx, JSValueConst thisVal, int argc, JSVareturn arr;}++// OV.netEmit(idsCsv, name, payloadB64) — server->client transport for the net++// library. idsCsv is a comma-separated list of player userIds; -1 = broadcast.++// Sends the "OVNet" usermessage which the client hooks and dispatches to++// net.Receive. (Payload is bounded by the usermessage string size.)++static JSValue OVJS_netEmit(JSContext *ctx, JSValueConst thisVal, int argc, JSValueConst *argv)++{++ if (argc < 3) return JS_UNDEFINED;++++ const char *idsCsv = JS_ToCString(ctx, argv[0]);++ const char *name = JS_ToCString(ctx, argv[1]);++ const char *payload = JS_ToCString(ctx, argv[2]);++++ if (!idsCsv || !name || !payload)++ {++ if (idsCsv) JS_FreeCString(ctx, idsCsv);++ if (name) JS_FreeCString(ctx, name);++ if (payload) JS_FreeCString(ctx, payload);++ return JS_UNDEFINED;++ }++++ bool broadcast = false;++ CUtlVector<int> userIds;++ {++ // Portable comma-separated int parse (no strtok_s/strtok_r).++ const char *p = idsCsv;++ while (*p)++ {++ while (*p == ',' || *p == ' ') ++p;++ if (!*p) break;++ int id = atoi(p);++ if (id < 0) { broadcast = true; break; }++ userIds.AddToTail(id);++ while (*p && *p != ',') ++p;++ }++ }++++ if (broadcast)++ {++ CBroadcastRecipientFilter filter;++ UserMessageBegin(filter, "OVNet");++ WRITE_STRING(name);++ WRITE_STRING(payload);++ MessageEnd();++ }++ else++ {++ for (int i = 0; i < userIds.Count(); ++i)++ {++ CBasePlayer *base = UTIL_PlayerByUserId(userIds[i]);++ if (!base) continue;++ CSingleUserRecipientFilter filter(base);++ UserMessageBegin(filter, "OVNet");++ WRITE_STRING(name);++ WRITE_STRING(payload);++ MessageEnd();++ }++ }++++ JS_FreeCString(ctx, idsCsv);++ JS_FreeCString(ctx, name);++ JS_FreeCString(ctx, payload);++ return JS_UNDEFINED;++}++static const JSCFunctionListEntry OVFuncs[] ={JS_CFUNC_DEF("log", 1, OVJS_log),@@@ -282,6 +347,7 @@ static const JSCFunctionListEntry OVFuncs[] =JS_CFUNC_DEF("readFile", 1, OVJS_readFile),JS_CFUNC_DEF("fileExists", 1, OVJS_fileExists),JS_CFUNC_DEF("listDir", 2, OVJS_listDir),++ JS_CFUNC_DEF("netEmit", 3, OVJS_netEmit),};void OVJS_RegisterNativeBindings(JSContext *ctx, COpenVibeJSRuntime *runtime)@@@ -35,6 +35,10 @@ copy_file "$ROOT/sdk/openvibe/client/hl2mp/openvibe_client.cpp" \"$SDK/src/game/client/hl2mp/openvibe_client.cpp"copy_file "$ROOT/sdk/openvibe/client/hl2mp/vgui_openvibe_menu.cpp" \"$SDK/src/game/client/hl2mp/vgui_openvibe_menu.cpp"++copy_file "$ROOT/sdk/openvibe/client/hl2mp/openvibe_js_client.cpp" \++ "$SDK/src/game/client/hl2mp/openvibe_js_client.cpp"++copy_file "$ROOT/sdk/openvibe/client/hl2mp/openvibe_js_client.h" \++ "$SDK/src/game/client/hl2mp/openvibe_js_client.h"copy_file "$ROOT/sdk/openvibe/server/hl2mp/openvibe_server.cpp" \"$SDK/src/game/server/hl2mp/openvibe_server.cpp"@@@ -73,10 +77,21 @@ HL2MP_PLAYER="$SDK/src/game/server/hl2mp/hl2mp_player.cpp"perl -0pi -e 's/^.*hl2mp\\openvibe_client\.cpp.*\n//mg;s/^.*hl2mp\\vgui_openvibe_menu\.cpp.*\n//mg;-- s/(\$File\s+"hl2mp\\clientmode_hl2mpnormal\.h"\n)/$1\t\t\t\$File\t"hl2mp\\openvibe_client.cpp"\n\t\t\t\$File\t"hl2mp\\vgui_openvibe_menu.cpp"\n/s;++ s/^.*hl2mp\\openvibe_js_client\.cpp.*\n//mg;++ s/^.*openvibe\\ov_js_runtime\.cpp.*\n//mg;++ s/^.*libquickjs_openvibe(?:\.a)?".*\n//mg;++ s/(\$File\s+"hl2mp\\clientmode_hl2mpnormal\.h"\n)/$1\t\t\t\$File\t"hl2mp\\openvibe_client.cpp"\n\t\t\t\$File\t"hl2mp\\vgui_openvibe_menu.cpp"\n\t\t\t\$File\t"hl2mp\\openvibe_js_client.cpp"\n\t\t\t\$File\t"..\\shared\\openvibe\\ov_js_runtime.cpp"\n\t\t\t\$Lib\t"..\\shared\\openvibe\\third_party\\quickjs\\build\\libquickjs_openvibe"\n/s;' "$CLIENT_VPC"echo "[openvibe-sdk] patched client_hl2mp.vpc"++# Register the OVNet usermessage (server->client net library transport) in the++# shared HL2 usermessage table so both realms agree on it.++HL2_USERMSG="$SDK/src/game/shared/hl2/hl2_usermessages.cpp"++if [[ -f "$HL2_USERMSG" ]] && ! grep -q '"OVNet"' "$HL2_USERMSG"; then++ perl -0pi -e 's/(usermessages->Register\(\s*"AchievementEvent",\s*-1\s*\);\n)/$1\tusermessages->Register( "OVNet", -1 );\t\/\/ OpenVibe net library server->client\n/s;' "$HL2_USERMSG"++ echo "[openvibe-sdk] registered OVNet usermessage"++fi++perl -0pi -e 's/^.*hl2mp\\openvibe_server\.cpp.*\n//mg;s/^.*hl2mp\\openvibe_js_server\.cpp.*\n//mg;
@@ -124,7 +124,8 @@
@@ -124,7 +124,8 @@
receivers[String(name)] = fn;
receivers[String(name)] = fn;
},
},
- // server -> client
+ // server -> client. ids are passed to C++ as a comma-separated list of
+ // player userIds; -1 means "broadcast to everyone".
Send: function (target) {
Send: function (target) {
if (!writeBuf) return;
if (!writeBuf) return;
if (!isServer) { OV.warn("net.Send is server-only; use SendToServer on the client"); return; }
if (!isServer) { OV.warn("net.Send is server-only; use SendToServer on the client"); return; }
@@ -132,15 +133,15 @@
@@ -132,15 +133,15 @@
if (target == null) { ids = [-1]; }
if (target == null) { ids = [-1]; }
else if (Array.isArray(target)) { ids = target.map(function (p) { return p && p.userId ? p.userId() : (p | 0); }); }
else if (Array.isArray(target)) { ids = target.map(function (p) { return p && p.userId ? p.userId() : (p | 0); }); }
else { ids = [target && target.userId ? target.userId() : (target | 0)]; }
else { ids = [target && target.userId ? target.userId() : (target | 0)]; }
- var payload = serialize();
- if (OV.netEmit) OV.netEmit(JSON.stringify(ids), writeBuf.name, payload);
+ var name = writeBuf.name, payload = serialize();
+ if (OV.netEmit) OV.netEmit(ids.join(","), name, payload);
writeBuf = null;
writeBuf = null;
},
},
Broadcast: function () {
Broadcast: function () {
if (!writeBuf) return;
if (!writeBuf) return;
if (!isServer) { OV.warn("net.Broadcast is server-only"); return; }
if (!isServer) { OV.warn("net.Broadcast is server-only"); return; }
- var payload = serialize();
- if (OV.netEmit) OV.netEmit(JSON.stringify([-1]), writeBuf.name, payload);
+ var name = writeBuf.name, payload = serialize();
+ if (OV.netEmit) OV.netEmit("-1", name, payload);
writeBuf = null;
writeBuf = null;
},
},
new file mode 100644
new file mode 100644
@@ -0,0 +1,349 @@
@@ -0,0 +1,349 @@
+#include "cbase.h"
+#include "openvibe_js_client.h"
+
+// The client always uses the real QuickJS runtime (the Windows *server* stub
+// guard keys on GAME_DLL, which is not defined in the client build).
+#include "filesystem.h"
+#include "openvibe/ov_js_runtime.h"
+#include "openvibe/ov_js_bindings.h"
+#include "usermessages.h"
+#include <ctype.h>
+
+#include "tier0/memdbgon.h"
+
+// ---------------------------------------------------------------------------
+// Client JS runtime instance + mode.
+// ---------------------------------------------------------------------------
+static COpenVibeJSRuntime g_OVClientJS;
+static bool g_OVClientJSStarted = false;
+
+static ConVar ov_client_js_enabled(
+ "ov_client_js_enabled", "1", FCVAR_CLIENTDLL,
+ "Enable the OpenVibe client-side JavaScript runtime." );
+
+// Which gamemode's client.js to load. The server pushes its mode to the client
+// (ov_client_mode <mode>; then the runtime reloads), defaulting to hub.
+static ConVar ov_client_mode(
+ "ov_client_mode", "hub", FCVAR_CLIENTDLL,
+ "OpenVibe client gamemode realm: hub, prophunt, deathrun, fortwars, traitortown, sandbox." );
+
+static bool OpenVibeClientJS_IsRunning()
+{
+ return ov_client_js_enabled.GetBool() && g_OVClientJS.Context() != nullptr;
+}
+
+// ---------------------------------------------------------------------------
+// Client OV.* native bindings. Client-safe subset (no server player list /
+// ServerCommand). File I/O + logging + net + realm info, matching the server
+// bridge so the shared core JS (require/net/addon/timer/hook) runs unchanged.
+// ---------------------------------------------------------------------------
+static bool OVCJS_IsSafeModPath( const char *pszPath )
+{
+ if ( !pszPath || !pszPath[0] ) return false;
+ if ( pszPath[0] == '/' || pszPath[0] == '\\' ) return false;
+ if ( Q_strstr( pszPath, ".." ) ) return false;
+ if ( Q_strstr( pszPath, ":" ) ) return false;
+ return true;
+}
+
+static JSValue OVCJS_log( JSContext *ctx, JSValueConst, int argc, JSValueConst *argv )
+{
+ if ( argc < 1 ) return JS_UNDEFINED;
+ const char *msg = JS_ToCString( ctx, argv[0] );
+ if ( msg ) { Msg( "[OV JS/client] %s\n", msg ); JS_FreeCString( ctx, msg ); }
+ return JS_UNDEFINED;
+}
+
+static JSValue OVCJS_warn( JSContext *ctx, JSValueConst, int argc, JSValueConst *argv )
+{
+ if ( argc < 1 ) return JS_UNDEFINED;
+ const char *msg = JS_ToCString( ctx, argv[0] );
+ if ( msg ) { Warning( "[OV JS/client] %s\n", msg ); JS_FreeCString( ctx, msg ); }
+ return JS_UNDEFINED;
+}
+
+static JSValue OVCJS_error( JSContext *ctx, JSValueConst, int argc, JSValueConst *argv )
+{
+ if ( argc < 1 ) return JS_UNDEFINED;
+ const char *msg = JS_ToCString( ctx, argv[0] );
+ if ( msg ) { Warning( "[OV JS/client ERROR] %s\n", msg ); JS_FreeCString( ctx, msg ); }
+ return JS_UNDEFINED;
+}
+
+static JSValue OVCJS_isServer( JSContext *ctx, JSValueConst, int, JSValueConst * )
+{
+ return JS_FALSE; // client realm
+}
+
+static JSValue OVCJS_getMode( JSContext *ctx, JSValueConst, int, JSValueConst * )
+{
+ return JS_NewString( ctx, g_OVClientJS.GetMode() );
+}
+
+static JSValue OVCJS_getMapName( JSContext *ctx, JSValueConst, int, JSValueConst * )
+{
+ return JS_NewString( ctx, engine ? engine->GetLevelName() : "" );
+}
+
+static JSValue OVCJS_time( JSContext *ctx, JSValueConst, int, JSValueConst * )
+{
+ return JS_NewFloat64( ctx, gpGlobals ? gpGlobals->curtime : 0.0 );
+}
+
+static JSValue OVCJS_readFile( JSContext *ctx, JSValueConst, int argc, JSValueConst *argv )
+{
+ if ( argc < 1 ) return JS_NULL;
+ const char *path = JS_ToCString( ctx, argv[0] );
+ if ( !path ) return JS_NULL;
+
+ if ( !OVCJS_IsSafeModPath( path ) )
+ {
+ Warning( "[OV JS/client] readFile refused unsafe path: %s\n", path );
+ JS_FreeCString( ctx, path );
+ return JS_NULL;
+ }
+
+ FileHandle_t file = filesystem->Open( path, "rb", "MOD" );
+ if ( !file ) { JS_FreeCString( ctx, path ); return JS_NULL; }
+
+ int size = filesystem->Size( file );
+ if ( size < 0 || size > 8 * 1024 * 1024 )
+ {
+ filesystem->Close( file );
+ JS_FreeCString( ctx, path );
+ return JS_NULL;
+ }
+
+ char *buffer = new char[size + 1];
+ filesystem->Read( buffer, size, file );
+ filesystem->Close( file );
+ buffer[size] = '\0';
+
+ JSValue out = JS_NewStringLen( ctx, buffer, size );
+ delete[] buffer;
+ JS_FreeCString( ctx, path );
+ return out;
+}
+
+static JSValue OVCJS_fileExists( JSContext *ctx, JSValueConst, int argc, JSValueConst *argv )
+{
+ if ( argc < 1 ) return JS_FALSE;
+ const char *path = JS_ToCString( ctx, argv[0] );
+ if ( !path ) return JS_FALSE;
+ bool exists = OVCJS_IsSafeModPath( path )
+ && filesystem->FileExists( path, "MOD" )
+ && !filesystem->IsDirectory( path, "MOD" );
+ JS_FreeCString( ctx, path );
+ return exists ? JS_TRUE : JS_FALSE;
+}
+
+static JSValue OVCJS_listDir( JSContext *ctx, JSValueConst, int argc, JSValueConst *argv )
+{
+ JSValue arr = JS_NewArray( ctx );
+ if ( argc < 1 ) return arr;
+
+ const char *dir = JS_ToCString( ctx, argv[0] );
+ if ( !dir ) return arr;
+
+ const char *wildcardArg = ( argc >= 2 && JS_IsString( argv[1] ) ) ? JS_ToCString( ctx, argv[1] ) : nullptr;
+ const char *wildcard = ( wildcardArg && wildcardArg[0] ) ? wildcardArg : "*";
+
+ if ( !OVCJS_IsSafeModPath( dir ) )
+ {
+ if ( wildcardArg ) JS_FreeCString( ctx, wildcardArg );
+ JS_FreeCString( ctx, dir );
+ return arr;
+ }
+
+ char search[512];
+ Q_snprintf( search, sizeof( search ), "%s/%s", dir, wildcard );
+
+ FileFindHandle_t findHandle;
+ const char *name = filesystem->FindFirstEx( search, "MOD", &findHandle );
+ uint32 index = 0;
+ while ( name )
+ {
+ if ( Q_strcmp( name, "." ) != 0 && Q_strcmp( name, ".." ) != 0 )
+ JS_SetPropertyUint32( ctx, arr, index++, JS_NewString( ctx, name ) );
+ name = filesystem->FindNext( findHandle );
+ }
+ filesystem->FindClose( findHandle );
+
+ if ( wildcardArg ) JS_FreeCString( ctx, wildcardArg );
+ JS_FreeCString( ctx, dir );
+ return arr;
+}
+
+// client -> server: forward as the ov_net command the server JS dispatches.
+static JSValue OVCJS_netSendToServer( JSContext *ctx, JSValueConst, int argc, JSValueConst *argv )
+{
+ if ( argc < 2 ) return JS_UNDEFINED;
+ const char *name = JS_ToCString( ctx, argv[0] );
+ const char *payload = JS_ToCString( ctx, argv[1] );
+ if ( name && payload && engine )
+ {
+ char cmd[16384];
+ Q_snprintf( cmd, sizeof( cmd ), "ov_net %s %s\n", name, payload );
+ engine->ClientCmd_Unrestricted( cmd );
+ }
+ if ( name ) JS_FreeCString( ctx, name );
+ if ( payload ) JS_FreeCString( ctx, payload );
+ return JS_UNDEFINED;
+}
+
+// broadcast/serverCommand are server-only; provide inert client stubs so shared
+// JS that references them doesn't throw on the client.
+static JSValue OVCJS_noop( JSContext *ctx, JSValueConst, int, JSValueConst * )
+{
+ return JS_UNDEFINED;
+}
+
+static const JSCFunctionListEntry OVClientFuncs[] =
+{
+ JS_CFUNC_DEF( "log", 1, OVCJS_log ),
+ JS_CFUNC_DEF( "warn", 1, OVCJS_warn ),
+ JS_CFUNC_DEF( "error", 1, OVCJS_error ),
+ JS_CFUNC_DEF( "isServer", 0, OVCJS_isServer ),
+ JS_CFUNC_DEF( "getMode", 0, OVCJS_getMode ),
+ JS_CFUNC_DEF( "getMapName", 0, OVCJS_getMapName ),
+ JS_CFUNC_DEF( "time", 0, OVCJS_time ),
+ JS_CFUNC_DEF( "readFile", 1, OVCJS_readFile ),
+ JS_CFUNC_DEF( "fileExists", 1, OVCJS_fileExists ),
+ JS_CFUNC_DEF( "listDir", 2, OVCJS_listDir ),
+ JS_CFUNC_DEF( "netSendToServer", 2, OVCJS_netSendToServer ),
+ JS_CFUNC_DEF( "broadcast", 1, OVCJS_noop ),
+ JS_CFUNC_DEF( "serverCommand", 1, OVCJS_noop ),
+};
+
+// This is the symbol ov_js_runtime.cpp calls; the client project compiles this
+// file instead of the server ov_js_bindings.cpp.
+void OVJS_RegisterNativeBindings( JSContext *ctx, COpenVibeJSRuntime * )
+{
+ JSValue global = JS_GetGlobalObject( ctx );
+ JSValue ov = JS_NewObject( ctx );
+ JS_SetPropertyFunctionList( ctx, ov, OVClientFuncs, ARRAYSIZE( OVClientFuncs ) );
+ JS_SetPropertyStr( ctx, global, "OV", ov );
+ JS_FreeValue( ctx, global );
+}
+
+// ---------------------------------------------------------------------------
+// server -> client: OVNet usermessage -> OVNetReceive hook into client JS.
+// ---------------------------------------------------------------------------
+void __MsgFunc_OVNet( bf_read &msg )
+{
+ if ( !OpenVibeClientJS_IsRunning() )
+ return;
+
+ char name[128];
+ char payload[16384];
+ msg.ReadString( name, sizeof( name ) );
+ msg.ReadString( payload, sizeof( payload ) );
+
+ JSContext *ctx = g_OVClientJS.Context();
+ JSValue jsName = JS_NewString( ctx, name );
+ JSValue jsPayload = JS_NewString( ctx, payload );
+ JSValue jsPly = JS_NULL; // server->client has no "sending player"
+
+ JSValueConst argv[] = { jsName, jsPayload, jsPly };
+ g_OVClientJS.CallHookVoid( "OVNetReceive", 3, argv );
+
+ JS_FreeValue( ctx, jsName );
+ JS_FreeValue( ctx, jsPayload );
+}
+
+// ---------------------------------------------------------------------------
+// Lifecycle.
+// ---------------------------------------------------------------------------
+void OpenVibeJS_Client_Init()
+{
+ if ( g_OVClientJSStarted )
+ return;
+ g_OVClientJSStarted = true;
+
+ if ( !ov_client_js_enabled.GetBool() )
+ {
+ Msg( "[OV JS/client] disabled by ov_client_js_enabled=0\n" );
+ return;
+ }
+
+ if ( g_OVClientJS.Init( false /*client realm*/, ov_client_mode.GetString() ) )
+ {
+ Msg( "[OV JS/client] client runtime initialized for mode '%s'\n", ov_client_mode.GetString() );
+ g_OVClientJS.CallHookVoid( "Initialize" );
+ }
+ else
+ {
+ Warning( "[OV JS/client] client runtime failed to initialize\n" );
+ }
+}
+
+void OpenVibeJS_Client_Shutdown()
+{
+ if ( OpenVibeClientJS_IsRunning() )
+ g_OVClientJS.CallHookVoid( "Shutdown" );
+ g_OVClientJS.Shutdown();
+ g_OVClientJSStarted = false;
+}
+
+void OpenVibeJS_Client_Think()
+{
+ if ( OpenVibeClientJS_IsRunning() )
+ g_OVClientJS.CallHookVoid( "Think" );
+}
+
+// ---------------------------------------------------------------------------
+// Client concommands (mirror the server ones for parity/debugging).
+// ---------------------------------------------------------------------------
+static void OV_ClientJSStatus_f()
+{
+ Msg( "[OV JS/client] enabled=%d started=%d running=%d mode=%s\n",
+ ov_client_js_enabled.GetBool() ? 1 : 0,
+ g_OVClientJSStarted ? 1 : 0,
+ OpenVibeClientJS_IsRunning() ? 1 : 0,
+ ov_client_mode.GetString() );
+}
+static ConCommand ov_client_js_status( "ov_client_js_status", OV_ClientJSStatus_f,
+ "Print OpenVibe client JavaScript runtime status.", FCVAR_CLIENTDLL );
+
+static void OV_ClientJSReload_f()
+{
+ OpenVibeJS_Client_Shutdown();
+ OpenVibeJS_Client_Init();
+ Msg( "[OV JS/client] reloaded\n" );
+}
+static ConCommand ov_client_js_reload( "ov_client_js_reload", OV_ClientJSReload_f,
+ "Reload the OpenVibe client JavaScript runtime.", FCVAR_CLIENTDLL );
+
+// Drives the client JS runtime: hooks the OVNet usermessage at load, starts the
+// runtime once the level/systems are up, and pumps Think() each frame (for JS
+// timers). Self-contained so no external init-call patching is needed.
+class COpenVibeClientJSSystem : public CAutoGameSystemPerFrame
+{
+public:
+ COpenVibeClientJSSystem() : CAutoGameSystemPerFrame( "COpenVibeClientJSSystem" ) {}
+
+ bool Init() OVERRIDE
+ {
+ usermessages->HookMessage( "OVNet", __MsgFunc_OVNet );
+ return true;
+ }
+
+ void LevelInitPostEntity() OVERRIDE
+ {
+ // (Re)start the runtime when a level is entered so <mode>/client.js and
+ // client addons load fresh for the connected server.
+ OpenVibeJS_Client_Shutdown();
+ OpenVibeJS_Client_Init();
+ }
+
+ void LevelShutdownPostEntity() OVERRIDE
+ {
+ OpenVibeJS_Client_Shutdown();
+ }
+
+ void Update( float frametime ) OVERRIDE
+ {
+ OpenVibeJS_Client_Think();
+ }
+};
+static COpenVibeClientJSSystem g_OpenVibeClientJSSystem;
new file mode 100644
new file mode 100644
@@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
+#ifndef OPENVIBE_JS_CLIENT_H
+#define OPENVIBE_JS_CLIENT_H
+#ifdef _WIN32
+#pragma once
+#endif
+
+// Client-realm OpenVibe JavaScript runtime lifecycle. Mirrors the server JS
+// integration but for CLIENT_DLL: runs core JS + js/gamemodes/<mode>/client.js,
+// gives client addons a place to run, and bridges the net library both ways
+// (client->server via the ov_net forward, server->client via the OVNet
+// usermessage).
+void OpenVibeJS_Client_Init();
+void OpenVibeJS_Client_Shutdown();
+void OpenVibeJS_Client_Think();
+
+#endif // OPENVIBE_JS_CLIENT_H
@@ -13,6 +13,7 @@ void OVJS_RegisterNativeBindings(JSContext *ctx, COpenVibeJSRuntime *runtime) {}
@@ -13,6 +13,7 @@ void OVJS_RegisterNativeBindings(JSContext *ctx, COpenVibeJSRuntime *runtime) {}
#include "hl2mp_player.h"
#include "hl2mp_player.h"
#include "util.h"
#include "util.h"
#include "filesystem.h"
#include "filesystem.h"
+#include "recipientfilter.h"
#include "tier0/memdbgon.h"
#include "tier0/memdbgon.h"
@@ -263,6 +264,70 @@ static JSValue OVJS_listDir(JSContext *ctx, JSValueConst thisVal, int argc, JSVa
@@ -263,6 +264,70 @@ static JSValue OVJS_listDir(JSContext *ctx, JSValueConst thisVal, int argc, JSVa
return arr;
return arr;
}
}
+// OV.netEmit(idsCsv, name, payloadB64) — server->client transport for the net
+// library. idsCsv is a comma-separated list of player userIds; -1 = broadcast.
+// Sends the "OVNet" usermessage which the client hooks and dispatches to
+// net.Receive. (Payload is bounded by the usermessage string size.)
+static JSValue OVJS_netEmit(JSContext *ctx, JSValueConst thisVal, int argc, JSValueConst *argv)
+{
+ if (argc < 3) return JS_UNDEFINED;
+
+ const char *idsCsv = JS_ToCString(ctx, argv[0]);
+ const char *name = JS_ToCString(ctx, argv[1]);
+ const char *payload = JS_ToCString(ctx, argv[2]);
+
+ if (!idsCsv || !name || !payload)
+ {
+ if (idsCsv) JS_FreeCString(ctx, idsCsv);
+ if (name) JS_FreeCString(ctx, name);
+ if (payload) JS_FreeCString(ctx, payload);
+ return JS_UNDEFINED;
+ }
+
+ bool broadcast = false;
+ CUtlVector<int> userIds;
+ {
+ // Portable comma-separated int parse (no strtok_s/strtok_r).
+ const char *p = idsCsv;
+ while (*p)
+ {
+ while (*p == ',' || *p == ' ') ++p;
+ if (!*p) break;
+ int id = atoi(p);
+ if (id < 0) { broadcast = true; break; }
+ userIds.AddToTail(id);
+ while (*p && *p != ',') ++p;
+ }
+ }
+
+ if (broadcast)
+ {
+ CBroadcastRecipientFilter filter;
+ UserMessageBegin(filter, "OVNet");
+ WRITE_STRING(name);
+ WRITE_STRING(payload);
+ MessageEnd();
+ }
+ else
+ {
+ for (int i = 0; i < userIds.Count(); ++i)
+ {
+ CBasePlayer *base = UTIL_PlayerByUserId(userIds[i]);
+ if (!base) continue;
+ CSingleUserRecipientFilter filter(base);
+ UserMessageBegin(filter, "OVNet");
+ WRITE_STRING(name);
+ WRITE_STRING(payload);
+ MessageEnd();
+ }
+ }
+
+ JS_FreeCString(ctx, idsCsv);
+ JS_FreeCString(ctx, name);
+ JS_FreeCString(ctx, payload);
+ return JS_UNDEFINED;
+}
+
static const JSCFunctionListEntry OVFuncs[] =
static const JSCFunctionListEntry OVFuncs[] =
{
{
JS_CFUNC_DEF("log", 1, OVJS_log),
JS_CFUNC_DEF("log", 1, OVJS_log),
@@ -282,6 +347,7 @@ static const JSCFunctionListEntry OVFuncs[] =
@@ -282,6 +347,7 @@ static const JSCFunctionListEntry OVFuncs[] =
JS_CFUNC_DEF("readFile", 1, OVJS_readFile),
JS_CFUNC_DEF("readFile", 1, OVJS_readFile),
JS_CFUNC_DEF("fileExists", 1, OVJS_fileExists),
JS_CFUNC_DEF("fileExists", 1, OVJS_fileExists),
JS_CFUNC_DEF("listDir", 2, OVJS_listDir),
JS_CFUNC_DEF("listDir", 2, OVJS_listDir),
+ JS_CFUNC_DEF("netEmit", 3, OVJS_netEmit),
};
};
void OVJS_RegisterNativeBindings(JSContext *ctx, COpenVibeJSRuntime *runtime)
void OVJS_RegisterNativeBindings(JSContext *ctx, COpenVibeJSRuntime *runtime)
@@ -35,6 +35,10 @@ copy_file "$ROOT/sdk/openvibe/client/hl2mp/openvibe_client.cpp" \
@@ -35,6 +35,10 @@ copy_file "$ROOT/sdk/openvibe/client/hl2mp/openvibe_client.cpp" \
"$SDK/src/game/client/hl2mp/openvibe_client.cpp"
"$SDK/src/game/client/hl2mp/openvibe_client.cpp"
copy_file "$ROOT/sdk/openvibe/client/hl2mp/vgui_openvibe_menu.cpp" \
copy_file "$ROOT/sdk/openvibe/client/hl2mp/vgui_openvibe_menu.cpp" \
"$SDK/src/game/client/hl2mp/vgui_openvibe_menu.cpp"
"$SDK/src/game/client/hl2mp/vgui_openvibe_menu.cpp"
+copy_file "$ROOT/sdk/openvibe/client/hl2mp/openvibe_js_client.cpp" \
+ "$SDK/src/game/client/hl2mp/openvibe_js_client.cpp"
+copy_file "$ROOT/sdk/openvibe/client/hl2mp/openvibe_js_client.h" \
+ "$SDK/src/game/client/hl2mp/openvibe_js_client.h"
copy_file "$ROOT/sdk/openvibe/server/hl2mp/openvibe_server.cpp" \
copy_file "$ROOT/sdk/openvibe/server/hl2mp/openvibe_server.cpp" \
"$SDK/src/game/server/hl2mp/openvibe_server.cpp"
"$SDK/src/game/server/hl2mp/openvibe_server.cpp"
@@ -73,10 +77,21 @@ HL2MP_PLAYER="$SDK/src/game/server/hl2mp/hl2mp_player.cpp"
@@ -73,10 +77,21 @@ HL2MP_PLAYER="$SDK/src/game/server/hl2mp/hl2mp_player.cpp"
perl -0pi -e '
perl -0pi -e '
s/^.*hl2mp\\openvibe_client\.cpp.*\n//mg;
s/^.*hl2mp\\openvibe_client\.cpp.*\n//mg;
s/^.*hl2mp\\vgui_openvibe_menu\.cpp.*\n//mg;
s/^.*hl2mp\\vgui_openvibe_menu\.cpp.*\n//mg;
- s/(\$File\s+"hl2mp\\clientmode_hl2mpnormal\.h"\n)/$1\t\t\t\$File\t"hl2mp\\openvibe_client.cpp"\n\t\t\t\$File\t"hl2mp\\vgui_openvibe_menu.cpp"\n/s;
+ s/^.*hl2mp\\openvibe_js_client\.cpp.*\n//mg;
+ s/^.*openvibe\\ov_js_runtime\.cpp.*\n//mg;
+ s/^.*libquickjs_openvibe(?:\.a)?".*\n//mg;
+ s/(\$File\s+"hl2mp\\clientmode_hl2mpnormal\.h"\n)/$1\t\t\t\$File\t"hl2mp\\openvibe_client.cpp"\n\t\t\t\$File\t"hl2mp\\vgui_openvibe_menu.cpp"\n\t\t\t\$File\t"hl2mp\\openvibe_js_client.cpp"\n\t\t\t\$File\t"..\\shared\\openvibe\\ov_js_runtime.cpp"\n\t\t\t\$Lib\t"..\\shared\\openvibe\\third_party\\quickjs\\build\\libquickjs_openvibe"\n/s;
' "$CLIENT_VPC"
' "$CLIENT_VPC"
echo "[openvibe-sdk] patched client_hl2mp.vpc"
echo "[openvibe-sdk] patched client_hl2mp.vpc"
+# Register the OVNet usermessage (server->client net library transport) in the
+# shared HL2 usermessage table so both realms agree on it.
+HL2_USERMSG="$SDK/src/game/shared/hl2/hl2_usermessages.cpp"
+if [[ -f "$HL2_USERMSG" ]] && ! grep -q '"OVNet"' "$HL2_USERMSG"; then
+ perl -0pi -e 's/(usermessages->Register\(\s*"AchievementEvent",\s*-1\s*\);\n)/$1\tusermessages->Register( "OVNet", -1 );\t\/\/ OpenVibe net library server->client\n/s;' "$HL2_USERMSG"
+ echo "[openvibe-sdk] registered OVNet usermessage"
+fi
+
perl -0pi -e '
perl -0pi -e '
s/^.*hl2mp\\openvibe_server\.cpp.*\n//mg;
s/^.*hl2mp\\openvibe_server\.cpp.*\n//mg;
s/^.*hl2mp\\openvibe_js_server\.cpp.*\n//mg;
s/^.*hl2mp\\openvibe_js_server\.cpp.*\n//mg;