diff options
Diffstat (limited to 'NorthstarDLL/plugins/plugins.cpp')
-rw-r--r-- | NorthstarDLL/plugins/plugins.cpp | 581 |
1 files changed, 355 insertions, 226 deletions
diff --git a/NorthstarDLL/plugins/plugins.cpp b/NorthstarDLL/plugins/plugins.cpp index 6e3ceb5a..47d27da1 100644 --- a/NorthstarDLL/plugins/plugins.cpp +++ b/NorthstarDLL/plugins/plugins.cpp @@ -1,292 +1,421 @@ -#include "plugins.h" -#include "config/profile.h" - #include "squirrel/squirrel.h" #include "plugins.h" #include "masterserver/masterserver.h" #include "core/convar/convar.h" #include "server/serverpresence.h" -#include <optional> - -#include "util/version.h" -#include "pluginbackend.h" -#include "util/wininfo.h" -#include "logging/logging.h" -#include "dedicated/dedicated.h" -PluginManager* g_pPluginManager; +#include <chrono> +#include <windows.h> -void freeLibrary(HMODULE hLib) +/// <summary> +/// The data is split into two different representations: one for internal, and one for plugins, for thread safety reasons +/// The struct exposed to plugins contains getter functions for the various data types. +/// We can safely use C++ types like std::string here since these are only ever handled by Northstar internally +/// </summary> +struct InternalGameState +{ + int ourScore; + int secondHighestScore; + int highestScore; + + bool connected; + bool loading; + std::string map; + std::string mapDisplayName; + std::string playlist; + std::string playlistDisplayName; + int players; +}; +struct InternalServerInfo +{ + std::string id; + std::string name; + std::string description; + std::string password; + int maxPlayers; + bool roundBased; + int scoreLimit; + int endTime; +}; +// TODO: need to extend this to include current player data like loadouts +struct InternalPlayerInfo +{ + int uid; +}; + +InternalGameState gameState; +InternalServerInfo serverInfo; +InternalPlayerInfo playerInfo; + +GameState gameStateExport; +ServerInfo serverInfoExport; +PlayerInfo playerInfoExport; + +/// <summary> +/// We use SRW Locks because plugins will often be running their own thread +/// To ensure thread safety, and to make it difficult to fuck up, we force them to use *our* functions to get data +/// </summary> +static SRWLOCK gameStateLock; +static SRWLOCK serverInfoLock; +static SRWLOCK playerInfoLock; + +void* getPluginObject(PluginObject var) { - if (!FreeLibrary(hLib)) + switch (var) { - spdlog::error("There was an error while trying to free library"); + case PluginObject::GAMESTATE: + return &gameStateExport; + case PluginObject::SERVERINFO: + return &serverInfoExport; + case PluginObject::PLAYERINFO: + return &playerInfoExport; + default: + return (void*)-1; } } -EXPORT void PLUGIN_LOG(LogMsg* msg) +void initGameState() { - spdlog::source_loc src {}; - src.filename = msg->source.file; - src.funcname = msg->source.func; - src.line = msg->source.line; - auto&& logger = g_pPluginManager->m_vLoadedPlugins[msg->pluginHandle].logger; - logger->log(src, (spdlog::level::level_enum)msg->level, msg->msg); + // Initalize the Slim Reader / Writer locks + InitializeSRWLock(&gameStateLock); + InitializeSRWLock(&serverInfoLock); + InitializeSRWLock(&playerInfoLock); + + gameStateExport.getGameStateChar = &getGameStateChar; + gameStateExport.getGameStateInt = &getGameStateInt; + gameStateExport.getGameStateBool = &getGameStateBool; + + serverInfoExport.getServerInfoChar = &getServerInfoChar; + serverInfoExport.getServerInfoInt = &getServerInfoInt; + serverInfoExport.getServerInfoBool = &getServerInfoBool; + + playerInfoExport.getPlayerInfoChar = &getPlayerInfoChar; + playerInfoExport.getPlayerInfoInt = &getPlayerInfoInt; + playerInfoExport.getPlayerInfoBool = &getPlayerInfoBool; + + serverInfo.id = ""; + serverInfo.name = ""; + serverInfo.description = ""; + serverInfo.password = ""; + serverInfo.maxPlayers = 0; + gameState.connected = false; + gameState.loading = false; + gameState.map = ""; + gameState.mapDisplayName = ""; + gameState.playlist = ""; + gameState.playlistDisplayName = ""; + gameState.players = 0; + + playerInfo.uid = 123; } -EXPORT void* CreateObject(ObjectType type) +// string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading +SQRESULT SQ_UpdateGameStateUI(HSquirrelVM* sqvm) { - switch (type) - { - case ObjectType::CONVAR: - return (void*)new ConVar; - case ObjectType::CONCOMMANDS: - return (void*)new ConCommand; - default: - return NULL; - } + AcquireSRWLockExclusive(&gameStateLock); + gameState.map = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 1); + gameState.mapDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 2); + gameState.playlist = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 3); + gameState.playlistDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 4); + gameState.connected = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 5); + gameState.loading = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 6); + ReleaseSRWLockExclusive(&gameStateLock); + return SQRESULT_NOTNULL; } -std::optional<Plugin> PluginManager::LoadPlugin(fs::path path, PluginInitFuncs* funcs, PluginNorthstarData* data) +// int playerCount, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit +SQRESULT SQ_UpdateGameStateClient(HSquirrelVM* sqvm) { + AcquireSRWLockExclusive(&gameStateLock); + AcquireSRWLockExclusive(&serverInfoLock); + gameState.players = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 1); + serverInfo.maxPlayers = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 2); + gameState.ourScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 3); + gameState.secondHighestScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 4); + gameState.highestScore = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 5); + serverInfo.roundBased = g_pSquirrel<ScriptContext::CLIENT>->getbool(sqvm, 6); + serverInfo.scoreLimit = g_pSquirrel<ScriptContext::CLIENT>->getbool(sqvm, 7); + ReleaseSRWLockExclusive(&gameStateLock); + ReleaseSRWLockExclusive(&serverInfoLock); + return SQRESULT_NOTNULL; +} - Plugin plugin {}; +// string id, string name, string password, int players, int maxPlayers, string map, string mapDisplayName, string playlist, string +// playlistDisplayName +SQRESULT SQ_UpdateServerInfo(HSquirrelVM* sqvm) +{ + AcquireSRWLockExclusive(&gameStateLock); + AcquireSRWLockExclusive(&serverInfoLock); + serverInfo.id = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 1); + serverInfo.name = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 2); + serverInfo.password = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 3); + gameState.players = g_pSquirrel<ScriptContext::UI>->getinteger(sqvm, 4); + serverInfo.maxPlayers = g_pSquirrel<ScriptContext::UI>->getinteger(sqvm, 5); + gameState.map = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 6); + gameState.mapDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 7); + gameState.playlist = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 8); + gameState.playlistDisplayName = g_pSquirrel<ScriptContext::UI>->getstring(sqvm, 9); + ReleaseSRWLockExclusive(&gameStateLock); + ReleaseSRWLockExclusive(&serverInfoLock); + return SQRESULT_NOTNULL; +} - std::string pathstring = (pluginPath / path).string(); - std::wstring wpath = (pluginPath / path).wstring(); +// int maxPlayers +SQRESULT SQ_UpdateServerInfoBetweenRounds(HSquirrelVM* sqvm) +{ + AcquireSRWLockExclusive(&serverInfoLock); + serverInfo.id = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 1); + serverInfo.name = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 2); + serverInfo.password = g_pSquirrel<ScriptContext::CLIENT>->getstring(sqvm, 3); + serverInfo.maxPlayers = g_pSquirrel<ScriptContext::CLIENT>->getinteger(sqvm, 4); + ReleaseSRWLockExclusive(&serverInfoLock); + return SQRESULT_NOTNULL; +} - LPCWSTR wpptr = wpath.c_str(); - HMODULE datafile = LoadLibraryExW(wpptr, 0, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE); // Load the DLL as a data file - if (datafile == NULL) - { - NS::log::PLUGINSYS->info("Failed to load library '{}': ", std::system_category().message(GetLastError())); - return std::nullopt; - } - HRSRC manifestResource = FindResourceW(datafile, MAKEINTRESOURCEW(IDR_RCDATA1), MAKEINTRESOURCEW(RT_RCDATA)); +// float timeInFuture +SQRESULT SQ_UpdateTimeInfo(HSquirrelVM* sqvm) +{ + AcquireSRWLockExclusive(&serverInfoLock); + serverInfo.endTime = ceil(g_pSquirrel<ScriptContext::CLIENT>->getfloat(sqvm, 1)); + ReleaseSRWLockExclusive(&serverInfoLock); + return SQRESULT_NOTNULL; +} - if (manifestResource == NULL) - { - NS::log::PLUGINSYS->info("Could not find manifest for library '{}'", pathstring); - freeLibrary(datafile); - return std::nullopt; - } - HGLOBAL myResourceData = LoadResource(datafile, manifestResource); - if (myResourceData == NULL) - { - NS::log::PLUGINSYS->error("Failed to load manifest from library '{}'", pathstring); - freeLibrary(datafile); - return std::nullopt; - } - int manifestSize = SizeofResource(datafile, manifestResource); - std::string manifest = std::string((const char*)LockResource(myResourceData), 0, manifestSize); - freeLibrary(datafile); +// bool loading +SQRESULT SQ_SetConnected(HSquirrelVM* sqvm) +{ + AcquireSRWLockExclusive(&gameStateLock); + gameState.loading = g_pSquirrel<ScriptContext::UI>->getbool(sqvm, 1); + ReleaseSRWLockExclusive(&gameStateLock); + return SQRESULT_NOTNULL; +} - rapidjson_document manifestJSON; - manifestJSON.Parse(manifest.c_str()); +SQRESULT SQ_UpdateListenServer(HSquirrelVM* sqvm) +{ + AcquireSRWLockExclusive(&serverInfoLock); + serverInfo.id = g_pMasterServerManager->m_sOwnServerId; + serverInfo.password = ""; // g_pServerPresence->Cvar_ns_server_password->GetString(); todo this fr + ReleaseSRWLockExclusive(&serverInfoLock); + return SQRESULT_NOTNULL; +} - if (manifestJSON.HasParseError()) - { - NS::log::PLUGINSYS->error("Manifest for '{}' was invalid", pathstring); - return std::nullopt; - } - if (!manifestJSON.HasMember("name")) - { - NS::log::PLUGINSYS->error("'{}' is missing a name in its manifest", pathstring); - return std::nullopt; - } - if (!manifestJSON.HasMember("displayname")) - { - NS::log::PLUGINSYS->error("'{}' is missing a displayname in its manifest", pathstring); - return std::nullopt; - } - if (!manifestJSON.HasMember("description")) - { - NS::log::PLUGINSYS->error("'{}' is missing a description in its manifest", pathstring); - return std::nullopt; - } - if (!manifestJSON.HasMember("api_version")) - { - NS::log::PLUGINSYS->error("'{}' is missing a api_version in its manifest", pathstring); - return std::nullopt; - } - if (!manifestJSON.HasMember("version")) - { - NS::log::PLUGINSYS->error("'{}' is missing a version in its manifest", pathstring); - return std::nullopt; - } - if (!manifestJSON.HasMember("run_on_server")) - { - NS::log::PLUGINSYS->error("'{}' is missing 'run_on_server' in its manifest", pathstring); - return std::nullopt; - } - if (!manifestJSON.HasMember("run_on_client")) - { - NS::log::PLUGINSYS->error("'{}' is missing 'run_on_client' in its manifest", pathstring); - return std::nullopt; - } - auto test = manifestJSON["api_version"].GetString(); - if (strcmp(manifestJSON["api_version"].GetString(), std::to_string(ABI_VERSION).c_str())) +int getServerInfoChar(char* out_buf, size_t out_buf_len, ServerInfoType var) +{ + AcquireSRWLockShared(&serverInfoLock); + int n = 0; + switch (var) { - NS::log::PLUGINSYS->error( - "'{}' has an incompatible API version number '{}' in its manifest. Current ABI version is '{}'", pathstring, ABI_VERSION); - return std::nullopt; + case ServerInfoType::id: + strncpy(out_buf, serverInfo.id.c_str(), out_buf_len); + break; + case ServerInfoType::name: + strncpy(out_buf, serverInfo.name.c_str(), out_buf_len); + break; + case ServerInfoType::description: + strncpy(out_buf, serverInfo.id.c_str(), out_buf_len); + break; + case ServerInfoType::password: + strncpy(out_buf, serverInfo.password.c_str(), out_buf_len); + break; + default: + n = -1; } - // Passed all checks, going to actually load it now - HMODULE pluginLib = LoadLibraryW(wpptr); // Load the DLL as a data file - if (pluginLib == NULL) - { - NS::log::PLUGINSYS->info("Failed to load library '{}': ", std::system_category().message(GetLastError())); - return std::nullopt; - } - plugin.init = (PLUGIN_INIT_TYPE)GetProcAddress(pluginLib, "PLUGIN_INIT"); - if (plugin.init == NULL) + ReleaseSRWLockShared(&serverInfoLock); + + return n; +} +int getServerInfoInt(int* out_ptr, ServerInfoType var) +{ + AcquireSRWLockShared(&serverInfoLock); + int n = 0; + switch (var) { - NS::log::PLUGINSYS->info("Library '{}' has no function 'PLUGIN_INIT'", pathstring); - return std::nullopt; + case ServerInfoType::maxPlayers: + *out_ptr = serverInfo.maxPlayers; + break; + case ServerInfoType::scoreLimit: + *out_ptr = serverInfo.scoreLimit; + break; + case ServerInfoType::endTime: + *out_ptr = serverInfo.endTime; + break; + default: + n = -1; } - NS::log::PLUGINSYS->info("Succesfully loaded {}", pathstring); - plugin.name = manifestJSON["name"].GetString(); - plugin.displayName = manifestJSON["displayname"].GetString(); - plugin.description = manifestJSON["description"].GetString(); - plugin.api_version = manifestJSON["api_version"].GetString(); - plugin.version = manifestJSON["version"].GetString(); + ReleaseSRWLockShared(&serverInfoLock); - plugin.run_on_client = manifestJSON["run_on_client"].GetBool(); - plugin.run_on_server = manifestJSON["run_on_server"].GetBool(); - - if (!plugin.run_on_server && IsDedicatedServer()) - return std::nullopt; - - if (manifestJSON.HasMember("dependencyName")) - { - plugin.dependencyName = manifestJSON["dependencyName"].GetString(); - } - else + return n; +} +int getServerInfoBool(bool* out_ptr, ServerInfoType var) +{ + AcquireSRWLockShared(&serverInfoLock); + int n = 0; + switch (var) { - plugin.dependencyName = plugin.name; + case ServerInfoType::roundBased: + *out_ptr = serverInfo.roundBased; + break; + default: + n = -1; } - plugin.init_sqvm_client = (PLUGIN_INIT_SQVM_TYPE)GetProcAddress(pluginLib, "PLUGIN_INIT_SQVM_CLIENT"); - plugin.init_sqvm_server = (PLUGIN_INIT_SQVM_TYPE)GetProcAddress(pluginLib, "PLUGIN_INIT_SQVM_SERVER"); - plugin.inform_sqvm_created = (PLUGIN_INFORM_SQVM_CREATED_TYPE)GetProcAddress(pluginLib, "PLUGIN_INFORM_SQVM_CREATED"); - plugin.inform_sqvm_destroyed = (PLUGIN_INFORM_SQVM_DESTROYED_TYPE)GetProcAddress(pluginLib, "PLUGIN_INFORM_SQVM_DESTROYED"); - - plugin.push_presence = (PLUGIN_PUSH_PRESENCE_TYPE)GetProcAddress(pluginLib, "PLUGIN_RECEIVE_PRESENCE"); - - plugin.inform_dll_load = (PLUGIN_INFORM_DLL_LOAD_TYPE)GetProcAddress(pluginLib, "PLUGIN_INFORM_DLL_LOAD"); + ReleaseSRWLockShared(&serverInfoLock); - plugin.handle = m_vLoadedPlugins.size(); - plugin.logger = std::make_shared<ColoredLogger>(plugin.displayName.c_str(), NS::Colors::PLUGIN); - RegisterLogger(plugin.logger); - NS::log::PLUGINSYS->info("Loading plugin {} version {}", plugin.displayName, plugin.version); - m_vLoadedPlugins.push_back(plugin); - - plugin.init(funcs, data); - - return plugin; + return n; } -bool PluginManager::LoadPlugins() +int getGameStateChar(char* out_buf, size_t out_buf_len, GameStateInfoType var) { - std::vector<fs::path> paths; - - pluginPath = GetNorthstarPrefix() + "/plugins"; - - PluginNorthstarData data {}; - std::string ns_version {version}; - - PluginInitFuncs funcs {}; - funcs.logger = PLUGIN_LOG; - funcs.relayInviteFunc = nullptr; - funcs.createObject = CreateObject; - - init_plugincommunicationhandler(); - - data.version = ns_version.c_str(); - data.northstarModule = g_NorthstarModule; - - if (!fs::exists(pluginPath)) - { - NS::log::PLUGINSYS->warn("Could not find a plugins directory. Skipped loading plugins"); - return false; - } - // ensure dirs exist - fs::recursive_directory_iterator iterator(pluginPath); - if (std::filesystem::begin(iterator) == std::filesystem::end(iterator)) - { - NS::log::PLUGINSYS->warn("Could not find any plugins. Skipped loading plugins"); - return false; - } - for (auto const& entry : iterator) + AcquireSRWLockShared(&gameStateLock); + int n = 0; + switch (var) { - if (fs::is_regular_file(entry) && entry.path().extension() == ".dll") - paths.emplace_back(entry.path().filename()); + case GameStateInfoType::map: + strncpy(out_buf, gameState.map.c_str(), out_buf_len); + break; + case GameStateInfoType::mapDisplayName: + strncpy(out_buf, gameState.mapDisplayName.c_str(), out_buf_len); + break; + case GameStateInfoType::playlist: + strncpy(out_buf, gameState.playlist.c_str(), out_buf_len); + break; + case GameStateInfoType::playlistDisplayName: + strncpy(out_buf, gameState.playlistDisplayName.c_str(), out_buf_len); + break; + default: + n = -1; } - for (fs::path path : paths) + + ReleaseSRWLockShared(&gameStateLock); + + return n; +} +int getGameStateInt(int* out_ptr, GameStateInfoType var) +{ + AcquireSRWLockShared(&gameStateLock); + int n = 0; + switch (var) { - if (LoadPlugin(path, &funcs, &data)) - data.pluginHandle += 1; + case GameStateInfoType::ourScore: + *out_ptr = gameState.ourScore; + break; + case GameStateInfoType::secondHighestScore: + *out_ptr = gameState.secondHighestScore; + break; + case GameStateInfoType::highestScore: + *out_ptr = gameState.highestScore; + break; + case GameStateInfoType::players: + *out_ptr = gameState.players; + break; + default: + n = -1; } - return true; -} -void PluginManager::InformSQVMLoad(ScriptContext context, SquirrelFunctions* s) + ReleaseSRWLockShared(&gameStateLock); + + return n; +} +int getGameStateBool(bool* out_ptr, GameStateInfoType var) { - for (auto plugin : m_vLoadedPlugins) + AcquireSRWLockShared(&gameStateLock); + int n = 0; + switch (var) { - if (context == ScriptContext::CLIENT && plugin.init_sqvm_client != NULL) - { - plugin.init_sqvm_client(s); - } - else if (context == ScriptContext::SERVER && plugin.init_sqvm_server != NULL) - { - plugin.init_sqvm_server(s); - } + case GameStateInfoType::connected: + *out_ptr = gameState.connected; + break; + case GameStateInfoType::loading: + *out_ptr = gameState.loading; + break; + default: + n = -1; } + + ReleaseSRWLockShared(&gameStateLock); + + return n; } -void PluginManager::InformSQVMCreated(ScriptContext context, CSquirrelVM* sqvm) +int getPlayerInfoChar(char* out_buf, size_t out_buf_len, PlayerInfoType var) { - for (auto plugin : m_vLoadedPlugins) + AcquireSRWLockShared(&playerInfoLock); + int n = 0; + switch (var) { - if (plugin.inform_sqvm_created != NULL) - { - plugin.inform_sqvm_created(context, sqvm); - } + default: + n = -1; } -} -void PluginManager::InformSQVMDestroyed(ScriptContext context) + ReleaseSRWLockShared(&playerInfoLock); + + return n; +} +int getPlayerInfoInt(int* out_ptr, PlayerInfoType var) { - for (auto plugin : m_vLoadedPlugins) + AcquireSRWLockShared(&playerInfoLock); + int n = 0; + switch (var) { - if (plugin.inform_sqvm_destroyed != NULL) - { - plugin.inform_sqvm_destroyed(context); - } + case PlayerInfoType::uid: + *out_ptr = playerInfo.uid; + break; + default: + n = -1; } -} -void PluginManager::PushPresence(PluginGameStatePresence* data) + ReleaseSRWLockShared(&playerInfoLock); + + return n; +} +int getPlayerInfoBool(bool* out_ptr, PlayerInfoType var) { - for (auto plugin : m_vLoadedPlugins) + AcquireSRWLockShared(&playerInfoLock); + int n = 0; + switch (var) { - if (plugin.push_presence != NULL) - { - plugin.push_presence(data); - } + default: + n = -1; } + + ReleaseSRWLockShared(&playerInfoLock); + + return n; } -void PluginManager::InformDLLLoad(PluginLoadDLL dll, void* data) +ON_DLL_LOAD_CLIENT_RELIESON("client.dll", PluginCommands, ClientSquirrel, (CModule module)) { - for (auto plugin : m_vLoadedPlugins) + // i swear there's a way to make this not have be run in 2 contexts but i can't figure it out + // some funcs i need are just not available in UI or CLIENT + + if (g_pSquirrel<ScriptContext::UI> && g_pSquirrel<ScriptContext::CLIENT>) { - if (plugin.inform_dll_load != NULL) - { - plugin.inform_dll_load(dll, data); - } + g_pSquirrel<ScriptContext::UI>->AddFuncRegistration( + "void", + "NSUpdateGameStateUI", + "string gamemode, string gamemodeName, string map, string mapName, bool connected, bool loading", + "", + SQ_UpdateGameStateUI); + g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration( + "void", + "NSUpdateGameStateClient", + "int playerCount, int maxPlayers, int outScore, int secondHighestScore, int highestScore, bool roundBased, int scoreLimit", + "", + SQ_UpdateGameStateClient); + g_pSquirrel<ScriptContext::UI>->AddFuncRegistration( + "void", + "NSUpdateServerInfo", + "string id, string name, string password, int players, int maxPlayers, string map, string mapDisplayName, string playlist, " + "string " + "playlistDisplayName", + "", + SQ_UpdateServerInfo); + g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration( + "void", "NSUpdateServerInfoReload", "int maxPlayers", "", SQ_UpdateServerInfoBetweenRounds); + g_pSquirrel<ScriptContext::CLIENT>->AddFuncRegistration("void", "NSUpdateTimeInfo", "float timeInFuture", "", SQ_UpdateTimeInfo); + g_pSquirrel<ScriptContext::UI>->AddFuncRegistration("void", "NSSetLoading", "bool loading", "", SQ_SetConnected); + g_pSquirrel<ScriptContext::UI>->AddFuncRegistration("void", "NSUpdateListenServer", "", "", SQ_UpdateListenServer); } } |