using System; using System.Collections.Generic; using System.Text; using System.IO; using Barotrauma.Networking; using MoonSharp.Interpreter; using Microsoft.Xna.Framework; using MoonSharp.Interpreter.Interop; using System.IO.Compression; using HarmonyLib; using System.Runtime.CompilerServices; using System.Linq; using System.Reflection; using System.Threading; [assembly: InternalsVisibleTo(Barotrauma.CsScriptBase.NET_SCRIPT_ASSEMBLY, AllInternalsVisible = true)] [assembly: InternalsVisibleTo(Barotrauma.CsScriptBase.NET_ONE_TIME_SCRIPT_ASSEMBLY, AllInternalsVisible = true)] namespace Barotrauma { class LuaCsSetupConfig { public bool FirstTimeCsWarning = true; public LuaCsSetupConfig() { } } partial class LuaCsSetup { public const string LUASETUP_FILE = "Lua/LuaSetup.lua"; public const string VERSION_FILE = "luacsversion.txt"; private const string configFileName = "LuaCsSetupConfig.xml"; #if SERVER public const bool IsServer = true; public const bool IsClient = false; #else public const bool IsServer = false; public const bool IsClient = true; #endif private Script lua; public CsScriptRunner CsScript { get; private set; } /// /// due to there's a race on the process and the unloaded AssemblyLoadContexts, /// should recreate runner after the script runs /// public void RecreateCsScript() { GameMain.LuaCs.CsScript = new CsScriptRunner(GameMain.LuaCs.CsScript.setup); lua.Globals["CsScript"] = CsScript; } public LuaGame Game { get; private set; } public LuaScriptLoader LuaScriptLoader { get; private set; } public LuaCsHook Hook { get; private set; } public LuaCsNetworking Networking { get; private set; } public LuaCsModStore ModStore { get; private set; } private LuaRequire require { get; set; } public CsScriptLoader CsScriptLoader { get; private set; } public CsLua Lua { get; private set; } public LuaCsSetupConfig Config { get; private set; } public LuaCsSetup() { Hook = new LuaCsHook(); ModStore = new LuaCsModStore(); Game = new LuaGame(); Networking = new LuaCsNetworking(); } public void UpdateConfig() { FileStream file; if (!File.Exists(configFileName)) file = File.Create(configFileName); else file = File.Open(configFileName, FileMode.Truncate, FileAccess.Write); LuaCsConfig.Save(file, Config); file.Close(); } public static ContentPackage GetPackage(Identifier name, bool fallbackToAll = true, bool useBackup = false) { foreach (ContentPackage package in ContentPackageManager.EnabledPackages.All) { if (package.NameMatches(name)) { return package; } } if (fallbackToAll) { foreach (ContentPackage package in ContentPackageManager.LocalPackages) { if (package.NameMatches(name)) { return package; } } foreach (ContentPackage package in ContentPackageManager.AllPackages) { if (package.NameMatches(name)) { return package; } } } if (useBackup && ContentPackageManager.EnabledPackages.BackupPackages.Regular != null) { foreach (ContentPackage package in ContentPackageManager.EnabledPackages.BackupPackages.Regular.Value) { if (package.NameMatches(name)) { return package; } } } return null; } public enum ExceptionType { Lua, CSharp, Both } public void HandleException(Exception ex, string extra = "", ExceptionType exceptionType = ExceptionType.Lua) { if (!string.IsNullOrWhiteSpace(extra)) if (exceptionType == ExceptionType.Lua) PrintError(extra); else if (exceptionType == ExceptionType.CSharp) PrintCsError(extra); else PrintBothError(extra); if (ex is InterpreterException) { if (((InterpreterException)ex).DecoratedMessage == null) PrintError(((InterpreterException)ex).Message); else PrintError(((InterpreterException)ex).DecoratedMessage); } else { string msg = ex.StackTrace != null ? ex.ToString() : $"{ex}\n{Environment.StackTrace}"; if (exceptionType == ExceptionType.Lua) PrintError(msg); else if (exceptionType == ExceptionType.CSharp) PrintCsError(msg); else PrintBothError(msg); } } private static void PrintErrorBase(string prefix, object message, string empty) { if (message == null) { message = empty; } string str = message.ToString(); for (int i = 0; i < str.Length; i += 1024) { string subStr = str.Substring(i, Math.Min(1024, str.Length - i)); string errorMsg = subStr; if (i == 0) errorMsg = prefix + errorMsg; DebugConsole.ThrowError(errorMsg); #if SERVER if (GameMain.Server != null) { foreach (var c in GameMain.Server.ConnectedClients) { GameMain.Server.SendDirectChatMessage(ChatMessage.Create("", errorMsg, ChatMessageType.Console, null, textColor: Color.Red), c); } GameServer.Log(errorMsg, ServerLog.MessageType.Error); } #endif } } #if SERVER public void PrintError(object message) => PrintErrorBase("[SV LUA ERROR] ", message, "nil"); public static void PrintCsError(object message) => PrintErrorBase("[SV CS ERROR] ", message, "Null"); public static void PrintBothError(object message) => PrintErrorBase("[SV ERROR] ", message, "Null"); #else private void PrintError(object message) => PrintErrorBase("[CL LUA ERROR] ", message, "nil"); public static void PrintCsError(object message) => PrintErrorBase("[CL CS ERROR] ", message, "Null"); public static void PrintBothError(object message) => PrintErrorBase("[CL ERROR] ", message, "Null"); #endif private static void PrintMessageBase(string prefix, object message, string empty) { if (message == null) { message = empty; } string str = message.ToString(); for (int i = 0; i < str.Length; i += 1024) { string subStr = str.Substring(i, Math.Min(1024, str.Length - i)); #if SERVER if (GameMain.Server != null) { foreach (var c in GameMain.Server.ConnectedClients) { GameMain.Server.SendDirectChatMessage(ChatMessage.Create("", subStr, ChatMessageType.Console, null, textColor: Color.MediumPurple), c); } GameServer.Log(prefix + subStr, ServerLog.MessageType.ServerMessage); } #endif } #if SERVER DebugConsole.NewMessage(message.ToString(), Color.MediumPurple); #else DebugConsole.NewMessage(message.ToString(), Color.Purple); #endif } private void PrintMessage(object message) => PrintMessageBase("[LUA] ", message, "nil"); public static void PrintCsMessage(object message) => PrintMessageBase("[CS] ", message, "Null"); public static void PrintLogMessage(object message) => PrintMessageBase("[LuaCs LOG] ", message, "Null"); private DynValue DoString(string code, Table globalContext = null, string codeStringFriendly = null) { try { return lua.DoString(code, globalContext, codeStringFriendly); } catch (Exception e) { HandleException(e); } return null; } private DynValue DoFile(string file, Table globalContext = null, string codeStringFriendly = null) { if (!LuaCsFile.IsPathAllowedLuaException(file, false)) return null; if (!LuaCsFile.Exists(file)) { HandleException(new Exception($"dofile: File {file} not found.")); return null; } try { return lua.DoFile(file, globalContext, codeStringFriendly); } catch (Exception e) { HandleException(e); } return null; } private DynValue LoadString(string file, Table globalContext = null, string codeStringFriendly = null) { try { return lua.LoadString(file, globalContext, codeStringFriendly); } catch (Exception e) { HandleException(e); } return null; } private DynValue LoadFile(string file, Table globalContext = null, string codeStringFriendly = null) { if (!LuaCsFile.IsPathAllowedLuaException(file, false)) return null; if (!LuaCsFile.Exists(file)) { HandleException(new Exception($"loadfile: File {file} not found.")); return null; } try { return lua.LoadFile(file, globalContext, codeStringFriendly); } catch (Exception e) { HandleException(e); } return null; } public DynValue Require(string moduleName, Table globalContexts) { try { return require.Require(moduleName, globalContexts); } catch (Exception e) { HandleException(e); } return null; } public object CallLuaFunction(object function, params object[] arguments) { try { return lua.Call(function, arguments); } catch (Exception e) { HandleException(e); } return null; } private void SetModulePaths(string[] str) { LuaScriptLoader.ModulePaths = str; } public void Update() { Hook?.Update(); } public void Stop() { foreach (var type in AppDomain.CurrentDomain.GetAssemblies().Where(a => a.GetName().Name == CsScriptBase.NET_SCRIPT_ASSEMBLY).SelectMany(assembly => assembly.GetTypes())) { UserData.UnregisterType(type, true); } foreach (var mod in ACsMod.LoadedMods.ToArray()) mod.Dispose(); ACsMod.LoadedMods.Clear(); if (Thread.CurrentThread == GameMain.MainThread) { Hook?.Call("stop"); } Game?.Stop(); Hook.Clear(); ModStore.Clear(); Game = new LuaGame(); Networking = new LuaCsNetworking(); LuaScriptLoader = null; lua = null; Lua = null; CsScript = null; Config = null; if (CsScriptLoader != null) { CsScriptLoader.Clear(); CsScriptLoader.Unload(); CsScriptLoader = null; GC.Collect(); GC.WaitForPendingFinalizers(); } } public void Initialize() { Stop(); PrintMessage("Lua! Version " + AssemblyInfo.GitRevision); if (File.Exists(configFileName)) { using (var file = File.Open(configFileName, FileMode.Open, FileAccess.Read)) Config = LuaCsConfig.Load(file); } else Config = new LuaCsSetupConfig(); LuaScriptLoader = new LuaScriptLoader(); LuaScriptLoader.ModulePaths = new string[] { }; LuaCustomConverters.RegisterAll(); lua = new Script(CoreModules.Preset_SoftSandbox | CoreModules.Debug); lua.Options.DebugPrint = PrintMessage; lua.Options.ScriptLoader = LuaScriptLoader; lua.Options.CheckThreadAccess = false; Lua = new CsLua(this); CsScript = new CsScriptRunner(this); require = new LuaRequire(lua); Game = new LuaGame(); Networking = new LuaCsNetworking(); Hook.Initialize(); ModStore.Initialize(); UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); UserData.RegisterType(); lua.Globals["printerror"] = (Action)PrintError; lua.Globals["setmodulepaths"] = (Action)SetModulePaths; lua.Globals["dofile"] = (Func)DoFile; lua.Globals["loadfile"] = (Func)LoadFile; lua.Globals["require"] = (Func)Require; lua.Globals["dostring"] = (Func)DoString; lua.Globals["load"] = (Func)LoadString; lua.Globals["CsScript"] = CsScript; lua.Globals["LuaUserData"] = UserData.CreateStatic(); lua.Globals["Game"] = Game; lua.Globals["Hook"] = Hook; lua.Globals["ModStore"] = ModStore; lua.Globals["Timer"] = new LuaCsTimer(); lua.Globals["File"] = UserData.CreateStatic(); lua.Globals["Networking"] = Networking; lua.Globals["SERVER"] = IsServer; lua.Globals["CLIENT"] = IsClient; if (GetPackage("CsForBarotrauma", false, true) != null) { PrintMessage("Cs! Version " + AssemblyInfo.GitRevision); if (Config.FirstTimeCsWarning) { Config.FirstTimeCsWarning = false; UpdateConfig(); LuaCsTimer.Wait((args) => PrintCsError(@" ----==== ====---- WARNING! -- -- -- -- -- -- !Cs Package Enabled! Cs Mods are questionably sandboxed, as they have access to reflection, due to modding needs. USE ON YOUR OWN RISK! ----==== ====---- "), 200); } CsScriptLoader = new CsScriptLoader(this); CsScriptLoader.SearchFolders(); if (CsScriptLoader.HasSources) { try { var modTypes = CsScriptLoader.Compile(); modTypes.ForEach(t => { //Please register `t` in lua-side //UserData.RegisterType(t); t.GetConstructor(new Type[] { })?.Invoke(null); }); } catch (Exception ex) { HandleException(ex, exceptionType: ExceptionType.CSharp); } } } ContentPackage luaPackage = GetPackage("Lua For Barotrauma"); if (File.Exists(LUASETUP_FILE)) { try { lua.Call(lua.LoadFile(LUASETUP_FILE), Path.GetDirectoryName(Path.GetFullPath(LUASETUP_FILE))); } catch (Exception e) { HandleException(e); } } else if (luaPackage != null) { string path = Path.GetDirectoryName(luaPackage.Path); try { string luaPath = Path.Combine(path, "Binary/Lua/LuaSetup.lua"); lua.Call(lua.LoadFile(luaPath), Path.GetDirectoryName(luaPath)); } catch (Exception e) { HandleException(e); } } else { PrintError("LuaSetup.lua not found! Lua/LuaSetup.lua, no Lua scripts will be executed or work."); } } } }