Client LuaCsForBarotrauma
BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs
1 using System;
2 using System.IO;
3 using MoonSharp.Interpreter;
4 using MoonSharp.Interpreter.Interop;
5 using System.Runtime.CompilerServices;
6 using System.Linq;
7 using System.Threading;
8 using LuaCsCompatPatchFunc = Barotrauma.LuaCsPatch;
9 using System.Diagnostics;
10 using MoonSharp.VsCodeDebugger;
11 using System.Reflection;
12 using System.Runtime.Loader;
13 using System.Xml.Linq;
15 
16 namespace Barotrauma
17 {
19  {
20  public bool EnableCsScripting = false;
21  public bool TreatForcedModsAsNormal = true;
22  public bool PreferToUseWorkshopLuaSetup = false;
23  public bool DisableErrorGUIOverlay = false;
24  public bool HideUserNames
25  {
26  get { return LuaCsLogger.HideUserNames; }
27  set { LuaCsLogger.HideUserNames = value; }
28  }
29 
30  public LuaCsSetupConfig() { }
32  {
37  }
38  }
39 
40  internal delegate void LuaCsMessageLogger(string message);
41  internal delegate void LuaCsErrorHandler(Exception ex, LuaCsMessageOrigin origin);
42  internal delegate void LuaCsExceptionHandler(Exception ex, LuaCsMessageOrigin origin);
43 
44  partial class LuaCsSetup
45  {
46  public const string LuaSetupFile = "Lua/LuaSetup.lua";
47  public const string VersionFile = "luacsversion.txt";
48 #if WINDOWS
49  public static ContentPackageId LuaForBarotraumaId = new SteamWorkshopId(2559634234);
50 #elif LINUX
51  public static ContentPackageId LuaForBarotraumaId = new SteamWorkshopId(2970628943);
52 #elif OSX
53  public static ContentPackageId LuaForBarotraumaId = new SteamWorkshopId(2970890020);
54 #endif
55 
56  public static ContentPackageId CsForBarotraumaId = new SteamWorkshopId(2795927223);
57 
58 
59  private const string configFileName = "LuaCsSetupConfig.xml";
60 
61 #if SERVER
62  public const bool IsServer = true;
63  public const bool IsClient = false;
64 #else
65  public const bool IsServer = false;
66  public const bool IsClient = true;
67 #endif
68 
69  public static bool IsRunningInsideWorkshop
70  {
71  get
72  {
73 #if SERVER
74  return Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location) == Directory.GetCurrentDirectory();
75 #else
76  return false; // unnecessary but just keeps things clear that this is NOT for client stuff
77 #endif
78  }
79  }
80 
81  private static int executionNumber = 0;
82 
83 
84  public Script Lua { get; private set; }
85  public LuaScriptLoader LuaScriptLoader { get; private set; }
86 
87  public LuaGame Game { get; private set; }
88  public LuaCsHook Hook { get; private set; }
89  public LuaCsTimer Timer { get; private set; }
90  public LuaCsNetworking Networking { get; private set; }
91  public LuaCsSteam Steam { get; private set; }
92  public LuaCsPerformanceCounter PerformanceCounter { get; private set; }
93 
94  // must be available at anytime
95  private static AssemblyManager _assemblyManager;
96  public static AssemblyManager AssemblyManager => _assemblyManager ??= new AssemblyManager();
97 
98  private CsPackageManager _pluginPackageManager;
99  public CsPackageManager PluginPackageManager => _pluginPackageManager ??= new CsPackageManager(AssemblyManager, this);
100 
101  public LuaCsModStore ModStore { get; private set; }
102  private LuaRequire require { get; set; }
103  public LuaCsSetupConfig Config { get; private set; }
104  public MoonSharpVsCodeDebugServer DebugServer { get; private set; }
105  public bool IsInitialized { get; private set; }
106 
107  private bool ShouldRunCs
108  {
109  get
110  {
111 #if SERVER
112  if (GetPackage(CsForBarotraumaId, false, false) != null && GameMain.Server.ServerPeer is LidgrenServerPeer) { return true; }
113 #endif
114 
115  return Config.EnableCsScripting;
116  }
117  }
118 
119  public LuaCsSetup()
120  {
121  Script.GlobalOptions.Platform = new LuaPlatformAccessor();
122 
123  Hook = new LuaCsHook(this);
124  ModStore = new LuaCsModStore();
125 
126  Game = new LuaGame();
127  Networking = new LuaCsNetworking();
128  DebugServer = new MoonSharpVsCodeDebugServer();
129 
130  ReadSettings();
131  }
132 
133  [Obsolete("Use AssemblyManager::GetTypesByName()")]
134  public static Type GetType(string typeName, bool throwOnError = false, bool ignoreCase = false)
135  {
136  return AssemblyManager.GetTypesByName(typeName).FirstOrDefault((Type)null);
137  }
138 
139  public void ToggleDebugger(int port = 41912)
140  {
141  if (!GameMain.LuaCs.DebugServer.IsStarted)
142  {
143  DebugServer.Start();
144  AttachDebugger();
145 
146  LuaCsLogger.Log($"Lua Debug Server started on port {port}.");
147  }
148  else
149  {
150  DetachDebugger();
151  DebugServer.Stop();
152 
153  LuaCsLogger.Log($"Lua Debug Server stopped.");
154  }
155  }
156 
157  public void AttachDebugger()
158  {
159  DebugServer.AttachToScript(Lua, "Script", s =>
160  {
161  if (s.Name.StartsWith("LocalMods") || s.Name.StartsWith("Lua"))
162  {
163  return Environment.CurrentDirectory + "/" + s.Name;
164  }
165  return s.Name;
166  });
167  }
168 
169  public void DetachDebugger() => DebugServer.Detach(Lua);
170 
171  public void ReadSettings()
172  {
173  Config = new LuaCsSetupConfig();
174 
175  if (File.Exists(configFileName))
176  {
177  try
178  {
179  using (var file = File.Open(configFileName, FileMode.Open, FileAccess.Read))
180  {
181  XDocument document = XDocument.Load(file);
182  Config.EnableCsScripting = document.Root.GetAttributeBool("EnableCsScripting", Config.EnableCsScripting);
183  Config.TreatForcedModsAsNormal = document.Root.GetAttributeBool("TreatForcedModsAsNormal", Config.TreatForcedModsAsNormal);
184  Config.PreferToUseWorkshopLuaSetup = document.Root.GetAttributeBool("PreferToUseWorkshopLuaSetup", Config.PreferToUseWorkshopLuaSetup);
185  Config.DisableErrorGUIOverlay = document.Root.GetAttributeBool("DisableErrorGUIOverlay", Config.DisableErrorGUIOverlay);
186  Config.HideUserNames = document.Root.GetAttributeBool("HideUserNames", Config.HideUserNames);
187  }
188  }
189  catch (Exception e)
190  {
191  LuaCsLogger.HandleException(e, LuaCsMessageOrigin.LuaCs);
192  }
193  }
194  }
195 
196  public void WriteSettings()
197  {
198  XDocument document = new XDocument();
199  document.Add(new XElement("LuaCsSetupConfig"));
200  document.Root.SetAttributeValue("EnableCsScripting", Config.EnableCsScripting);
201  document.Root.SetAttributeValue("EnableCsScripting", Config.EnableCsScripting);
202  document.Root.SetAttributeValue("TreatForcedModsAsNormal", Config.TreatForcedModsAsNormal);
203  document.Root.SetAttributeValue("PreferToUseWorkshopLuaSetup", Config.PreferToUseWorkshopLuaSetup);
204  document.Root.SetAttributeValue("DisableErrorGUIOverlay", Config.DisableErrorGUIOverlay);
205  document.Root.SetAttributeValue("HideUserNames", Config.HideUserNames);
206  document.Save(configFileName);
207  }
208 
209  public static ContentPackage GetPackage(ContentPackageId id, bool fallbackToAll = true, bool useBackup = false)
210  {
211  foreach (ContentPackage package in ContentPackageManager.EnabledPackages.All)
212  {
213  if (package.UgcId.ValueEquals(id))
214  {
215  return package;
216  }
217  }
218 
219  if (fallbackToAll)
220  {
221  foreach (ContentPackage package in ContentPackageManager.LocalPackages)
222  {
223  if (package.UgcId.ValueEquals(id))
224  {
225  return package;
226  }
227  }
228 
229  foreach (ContentPackage package in ContentPackageManager.AllPackages)
230  {
231  if (package.UgcId.ValueEquals(id))
232  {
233  return package;
234  }
235  }
236  }
237 
238  if (useBackup && ContentPackageManager.EnabledPackages.BackupPackages.Regular != null)
239  {
240  foreach (ContentPackage package in ContentPackageManager.EnabledPackages.BackupPackages.Regular.Value)
241  {
242  if (package.UgcId.ValueEquals(id))
243  {
244  return package;
245  }
246  }
247  }
248 
249  return null;
250  }
251 
252  private DynValue DoFile(string file, Table globalContext = null, string codeStringFriendly = null)
253  {
254  if (!LuaCsFile.CanReadFromPath(file))
255  {
256  throw new ScriptRuntimeException($"dofile: File access to {file} not allowed.");
257  }
258 
259  if (!LuaCsFile.Exists(file))
260  {
261  throw new ScriptRuntimeException($"dofile: File {file} not found.");
262  }
263 
264  return Lua.DoFile(file, globalContext, codeStringFriendly);
265  }
266 
267  private DynValue LoadFile(string file, Table globalContext = null, string codeStringFriendly = null)
268  {
269  if (!LuaCsFile.CanReadFromPath(file))
270  {
271  throw new ScriptRuntimeException($"loadfile: File access to {file} not allowed.");
272  }
273 
274  if (!LuaCsFile.Exists(file))
275  {
276  throw new ScriptRuntimeException($"loadfile: File {file} not found.");
277  }
278 
279  return Lua.LoadFile(file, globalContext, codeStringFriendly);
280  }
281 
282  public DynValue CallLuaFunction(object function, params object[] args)
283  {
284  // XXX: `lua` might be null if `LuaCsSetup.Stop()` is called while
285  // a patched function is still running.
286  if (Lua == null) { return null; }
287 
288  lock (Lua)
289  {
290  try
291  {
292  return Lua.Call(function, args);
293  }
294  catch (Exception e)
295  {
296  LuaCsLogger.HandleException(e, LuaCsMessageOrigin.LuaMod);
297  }
298  return null;
299  }
300  }
301 
302  private void SetModulePaths(string[] str)
303  {
304  LuaScriptLoader.ModulePaths = str;
305  }
306 
307  public void Update()
308  {
309  Timer?.Update();
310  Steam?.Update();
311 
312 #if CLIENT
313  Stopwatch luaSw = new Stopwatch();
314  luaSw.Start();
315 #endif
316  Hook?.Call("think");
317 #if CLIENT
318  luaSw.Stop();
319  GameMain.PerformanceCounter.AddElapsedTicks("Think Hook", luaSw.ElapsedTicks);
320 #endif
321  }
322 
323  public void Stop()
324  {
326 
327  // unregister types
328  foreach (Type type in AssemblyManager.GetAllLoadedACLs().SelectMany(
329  acl => acl.AssembliesTypes.Select(kvp => kvp.Value)))
330  {
331  UserData.UnregisterType(type, true);
332  }
333 
334  if (Lua?.Globals is not null)
335  {
336  Lua.Globals.Remove("CsPackageManager");
337  Lua.Globals.Remove("AssemblyManager");
338  }
339 
340  if (Thread.CurrentThread == GameMain.MainThread)
341  {
342  Hook?.Call("stop");
343  }
344 
345  if (Lua != null && DebugServer.IsStarted)
346  {
347  DebugServer.Detach(Lua);
348  }
349 
350  Game?.Stop();
351 
352  Hook?.Clear();
353  ModStore.Clear();
354  LuaScriptLoader = null;
355  Lua = null;
356 
357  // we can only unload assemblies after clearing ModStore/references.
359 #pragma warning disable CS0618
360  ACsMod.LoadedMods.Clear();
361 #pragma warning restore CS0618
362 
363  Game = new LuaGame();
364  Networking = new LuaCsNetworking();
365  Timer = new LuaCsTimer();
366  Steam = new LuaCsSteam();
368 
369  IsInitialized = false;
370  }
371 
372  public void Initialize(bool forceEnableCs = false)
373  {
374  if (IsInitialized)
375  {
376  Stop();
377  }
378 
379  IsInitialized = true;
380 
381  LuaCsLogger.LogMessage("Lua! Version " + AssemblyInfo.GitRevision);
382 
383  bool csActive = ShouldRunCs || forceEnableCs;
384 
386  LuaScriptLoader.ModulePaths = new string[] { };
387 
388  RegisterLuaConverters();
389 
390  Lua = new Script(CoreModules.Preset_SoftSandbox | CoreModules.Debug | CoreModules.IO | CoreModules.OS_System);
391  Lua.Options.DebugPrint = (o) => { LuaCsLogger.LogMessage(o); };
392  Lua.Options.ScriptLoader = LuaScriptLoader;
393  Lua.Options.CheckThreadAccess = false;
394  Script.GlobalOptions.ShouldPCallCatchException = (Exception ex) => { return true; };
395 
396  require = new LuaRequire(Lua);
397 
398  Game = new LuaGame();
399  Networking = new LuaCsNetworking();
400  Timer = new LuaCsTimer();
401  Steam = new LuaCsSteam();
403  Hook.Initialize();
406 
407  UserData.RegisterType<LuaCsLogger>();
408  UserData.RegisterType<LuaCsConfig>();
409  UserData.RegisterType<LuaCsSetupConfig>();
410  UserData.RegisterType<LuaCsAction>();
411  UserData.RegisterType<LuaCsFile>();
412  UserData.RegisterType<LuaCsCompatPatchFunc>();
413  UserData.RegisterType<LuaCsPatchFunc>();
414  UserData.RegisterType<LuaGame>();
415  UserData.RegisterType<LuaCsTimer>();
416  UserData.RegisterType<LuaCsFile>();
417  UserData.RegisterType<LuaCsNetworking>();
418  UserData.RegisterType<LuaCsSteam>();
419  UserData.RegisterType<LuaUserData>();
421  UserData.RegisterType<IUserDataDescriptor>();
422 
423  UserData.RegisterExtensionType(typeof(MathUtils));
424  UserData.RegisterExtensionType(typeof(XMLExtensions));
425 
426  Lua.Globals["printerror"] = (DynValue o) => { LuaCsLogger.LogError(o.ToString(), LuaCsMessageOrigin.LuaMod); };
427 
428  Lua.Globals["setmodulepaths"] = (Action<string[]>)SetModulePaths;
429 
430  Lua.Globals["dofile"] = (Func<string, Table, string, DynValue>)DoFile;
431  Lua.Globals["loadfile"] = (Func<string, Table, string, DynValue>)LoadFile;
432  Lua.Globals["require"] = (Func<string, Table, DynValue>)require.Require;
433 
434  Lua.Globals["dostring"] = (Func<string, Table, string, DynValue>)Lua.DoString;
435  Lua.Globals["load"] = (Func<string, Table, string, DynValue>)Lua.LoadString;
436 
437  Lua.Globals["Logger"] = UserData.CreateStatic<LuaCsLogger>();
438  Lua.Globals["LuaUserData"] = UserData.CreateStatic<LuaUserData>();
439  Lua.Globals["Game"] = Game;
440  Lua.Globals["Hook"] = Hook;
441  Lua.Globals["ModStore"] = ModStore;
442  Lua.Globals["Timer"] = Timer;
443  Lua.Globals["File"] = UserData.CreateStatic<LuaCsFile>();
444  Lua.Globals["Networking"] = Networking;
445  Lua.Globals["Steam"] = Steam;
446  Lua.Globals["PerformanceCounter"] = PerformanceCounter;
447  Lua.Globals["LuaCsConfig"] = new LuaCsSetupConfig(Config);
448 
449  Lua.Globals["ExecutionNumber"] = executionNumber;
450  Lua.Globals["CSActive"] = csActive;
451 
452  Lua.Globals["SERVER"] = IsServer;
453  Lua.Globals["CLIENT"] = IsClient;
454 
455  if (DebugServer.IsStarted)
456  {
457  AttachDebugger();
458  }
459 
460  if (csActive)
461  {
462  LuaCsLogger.LogMessage("Cs! Version " + AssemblyInfo.GitRevision);
463 
464  UserData.RegisterType<CsPackageManager>();
465  UserData.RegisterType<AssemblyManager>();
466  UserData.RegisterType<IAssemblyPlugin>();
467 
468  Lua.Globals["PluginPackageManager"] = PluginPackageManager;
469  Lua.Globals["AssemblyManager"] = AssemblyManager;
470 
471  try
472  {
473  Stopwatch taskTimer = new();
474  taskTimer.Start();
475  ModStore.Clear();
476 
478  if (state is AssemblyLoadingSuccessState.Success or AssemblyLoadingSuccessState.AlreadyLoaded)
479  {
483  PluginPackageManager.RunPluginsPreInit(); // this is intended to be called at startup in the future
486  state = AssemblyLoadingSuccessState.Success;
487  taskTimer.Stop();
488  ModUtils.Logging.PrintMessage($"{nameof(LuaCsSetup)}: Completed assembly loading. Total time {taskTimer.ElapsedMilliseconds}ms.");
489  }
490  else
491  {
492  PluginPackageManager.Dispose(); // cleanup if there's an error
493  }
494 
495  if(state is not AssemblyLoadingSuccessState.Success)
496  {
497  ModUtils.Logging.PrintError($"{nameof(LuaCsSetup)}: Error while loading Cs-Assembly Mods | Err: {state}");
498  taskTimer.Stop();
499  }
500  }
501  catch (Exception e)
502  {
503  ModUtils.Logging.PrintError($"{nameof(LuaCsSetup)}::{nameof(Initialize)}() | Error while loading assemblies! Details: {e.Message} | {e.StackTrace}");
504  }
505  }
506 
507 
508  ContentPackage luaPackage = GetPackage(LuaForBarotraumaId);
509 
510  void RunLocal()
511  {
512  LuaCsLogger.LogMessage("Using LuaSetup.lua from the Barotrauma Lua/ folder.");
513  string luaPath = LuaSetupFile;
514  CallLuaFunction(Lua.LoadFile(luaPath), Path.GetDirectoryName(Path.GetFullPath(luaPath)));
515  }
516 
517  void RunWorkshop()
518  {
519  LuaCsLogger.LogMessage("Using LuaSetup.lua from the content package.");
520  string luaPath = Path.Combine(Path.GetDirectoryName(luaPackage.Path), "Binary/Lua/LuaSetup.lua");
521  CallLuaFunction(Lua.LoadFile(luaPath), Path.GetDirectoryName(Path.GetFullPath(luaPath)));
522  }
523 
524  void RunNone()
525  {
526  LuaCsLogger.LogError("LuaSetup.lua not found! Lua/LuaSetup.lua, no Lua scripts will be executed or work.", LuaCsMessageOrigin.LuaMod);
527  }
528 
530  {
531  if (luaPackage != null) { RunWorkshop(); }
532  else if (File.Exists(LuaSetupFile)) { RunLocal(); }
533  else { RunNone(); }
534  }
535  else
536  {
537  if (File.Exists(LuaSetupFile)) { RunLocal(); }
538  else if (luaPackage != null) { RunWorkshop(); }
539  else { RunNone(); }
540  }
541 
542 #if SERVER
543  GameMain.Server.ServerSettings.LoadClientPermissions();
544 #endif
545 
546  executionNumber++;
547  }
548  }
549 }
Provides functionality for the loading, unloading and management of plugins implementing IAssemblyPlu...
IEnumerable< Type > GetTypesByName(string typeName)
Allows iteration over all types, including interfaces, in all loaded assemblies in the AsmMgr who's n...
IEnumerable< LoadedACL > GetAllLoadedACLs()
Returns a list of all loaded ACLs. WARNING: References to these ACLs outside of the AssemblyManager s...
static List< ACsMod > LoadedMods
Definition: ACsMod.cs:11
readonly Option< ContentPackageId > UgcId
static Thread MainThread
Definition: GameMain.cs:82
static PerformanceCounter PerformanceCounter
Definition: GameMain.cs:37
static LuaCsSetup LuaCs
Definition: GameMain.cs:26
static bool CanReadFromPath(string path)
Definition: LuaCsUtility.cs:19
object Call(string name, params object[] args)
static void LogError(string message, LuaCsMessageOrigin origin)
static void LogMessage(string message, Color? serverColor=null, Color? clientColor=null)
static void Log(string message, Color? color=null, ServerLog.MessageType messageType=ServerLog.MessageType.ServerMessage)
static void HandleException(Exception ex, LuaCsMessageOrigin origin)
static Type GetType(string typeName, bool throwOnError=false, bool ignoreCase=false)
DynValue CallLuaFunction(object function, params object[] args)
static ContentPackage GetPackage(ContentPackageId id, bool fallbackToAll=true, bool useBackup=false)
DynValue Require(string moduleName, Table globalContext)
Definition: LuaRequire.cs:95
static IUserDataDescriptor RegisterType(string typeName)
Definition: LuaUserData.cs:14
void AddElapsedTicks(string identifier, long ticks)
void RunPluginsInit()
Executes instantiated plugins' Initialize() and OnLoadCompleted() methods.
void RunPluginsPreInit()
Executes instantiated plugins' PreInitPatching() method.
void InstantiatePlugins(bool force=false)
Initializes plugin types that are registered.
AssemblyLoadingSuccessState LoadAssemblyPackages()
Begins the loading process of scanning packages for scripts and binary assemblies,...
bool PluginsPreInit
Whether or not loaded plugins had their preloader run.
bool PluginsInitialized
Whether or not plugins' types have been instantiated.
bool PluginsLoaded
Whether or not plugins are fully loaded.
delegate void LuaCsAction(params object[] args)
delegate DynValue LuaCsPatchFunc(object instance, LuaCsHook.ParameterTable ptable)