From 3d51abc56bb94ca410ad3ea7bdc86d8f205c8055 Mon Sep 17 00:00:00 2001 From: Evil Factory <36804725+evilfactory@users.noreply.github.com> Date: Sun, 25 Jan 2026 20:26:14 -0300 Subject: [PATCH] Semi-working Lua scripts --- .../ClientSource/LuaCs/LuaCsSetup.cs | 3 +- .../Lua/CompatibilityLib.lua | 6 +- .../LuaCsForBarotrauma/Lua/DefaultHook.lua | 2 + .../LuaCsForBarotrauma/Lua/LuaSetup.lua | 10 +- .../LuaCsForBarotrauma/Lua/ModLoader.lua | 193 ------------------ .../LuaCs/Lua/LuaClasses/LuaUserData.cs | 2 +- .../SharedSource/LuaCs/LuaCsSetup.cs | 12 +- .../LuaCs/Services/EventService.cs | 2 + .../LuaCs/Services/LoggerService.cs | 47 ++++- .../Services/LuaScriptManagementService.cs | 68 +++++- .../LuaCs/Services/PluginManagementService.cs | 2 +- .../LuaCs/Services/Safe/SafeStorageService.cs | 26 ++- 12 files changed, 154 insertions(+), 219 deletions(-) delete mode 100644 Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/ModLoader.lua diff --git a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs index 35dafe4e2..ffe87afab 100644 --- a/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaClient/ClientSource/LuaCs/LuaCsSetup.cs @@ -85,7 +85,8 @@ namespace Barotrauma // menus and navigation states case MainMenuScreen: case ModDownloadScreen: - case ServerListScreen: + case ServerListScreen: + SetRunState(RunState.Unloaded); SetRunState(RunState.LoadedNoExec); break; // running lobby or editor states diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/CompatibilityLib.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/CompatibilityLib.lua index 524818c5f..e266752b0 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/CompatibilityLib.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/CompatibilityLib.lua @@ -2,11 +2,11 @@ local compatibilityLib = {} -local networking = LuaUserData.RegisterType("Barotrauma.LuaCsNetworking") +-- local networking = LuaUserData.RegisterType("Barotrauma.LuaCsNetworking") -LuaUserData.AddMethod(networking, "RequestGetHTTP", Networking.HttpGet) +-- LuaUserData.AddMethod(networking, "RequestGetHTTP", Networking.HttpGet) -LuaUserData.AddMethod(networking, "RequestPostHTTP", Networking.HttpPost) +-- LuaUserData.AddMethod(networking, "RequestPostHTTP", Networking.HttpPost) compatibilityLib.CreateVector2 = Vector2.__new compatibilityLib.CreateVector3 = Vector3.__new diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultHook.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultHook.lua index 520e884ff..d78c7dc48 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultHook.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/DefaultHook.lua @@ -1,3 +1,5 @@ +if true then return end + Hook.Patch("Barotrauma.Item", "TryInteract", { "Barotrauma.Character", diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua index fc970c3b6..f324b3f7b 100644 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua +++ b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/LuaSetup.lua @@ -1,8 +1,8 @@ LuaSetup = {} -local path = table.pack(...)[1] +local path, resourcesToExecute = ... -package.path = {path .. "/?.lua"} +package.path = {path .. "/Lua/?.lua"} setmodulepaths(package.path) @@ -44,4 +44,8 @@ require("PostSetup") LuaSetup = nil -require("ModLoader") \ No newline at end of file +for resource in resourcesToExecute do + for path in resource.FilePaths do + dofile(path) + end +end \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/ModLoader.lua b/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/ModLoader.lua deleted file mode 100644 index 828b56205..000000000 --- a/Barotrauma/BarotraumaShared/LocalMods/LuaCsForBarotrauma/Lua/ModLoader.lua +++ /dev/null @@ -1,193 +0,0 @@ -local LUA_MOD_REQUIRE_PATH = "/Lua/?.lua" -local LUA_MOD_AUTORUN_PATH = "/Lua/Autorun" -local LUA_MOD_FORCEDAUTORUN_PATH = "/Lua/ForcedAutorun" - -local function EndsWith(str, suffix) - return str:sub(-string.len(suffix)) == suffix -end - -local function GetFileName(file) - return file:match("^.+/(.+)$") -end - -local function ExecuteProtected(s, folder) - loadfile(s)(folder) -end - -local function RunFolder(folder, rootFolder, package) - local search = File.DirSearch(folder) - for i = 1, #search, 1 do - local s = search[i]:gsub("\\", "/") - - if EndsWith(s, ".lua") then - local time = os.clock() - local ok, result = pcall(ExecuteProtected, s, rootFolder) - local diff = os.clock() - time - - print(string.format(" - %s (Took %.5fms)", GetFileName(s), diff)) - if not ok then - printerror(result) - end - end - - end -end - -local function AssertTypes(expectedTypes, ...) - local args = table.pack(...) - assert( - #args == #expectedTypes, - string.format( - "Assertion failed: incorrect number of args\n\texpected = %s\n\tgot = %s", - #expectedTypes, #args - ) - ) - for i = 1, #args do - local arg = args[i] - local expectedType = expectedTypes[i] - assert( - type(arg) == expectedType, - string.format( - "Assertion failed: incorrect argument type (arg #%d)\n\texpected = %s\n\tgot = %s", - i, expectedType, type(arg) - ) - ) - end -end - -local function ExecutionQueue() - local executionQueue = {} - executionQueue.Queue = {} - - executionQueue.Process = function() - while executionQueue.Queue[1] ~= nil do - local folder, rootFolder, package = table.unpack(table.remove(executionQueue.Queue, 1)) - print(string.format("%s %s", package.Name, package.ModVersion)) - RunFolder(folder, rootFolder, package) - end - end - - executionQueue.Add = function(...) - AssertTypes({ 'string', 'string', 'userdata' }, ...) - table.insert(executionQueue.Queue, table.pack(...)) - end - - return executionQueue -end - -local QueueAutorun = ExecutionQueue() -local QueueForcedAutorun = ExecutionQueue() - -local function nocase(s) - s = string.gsub(s, "%a", function(c) - return string.format("[%s%s]", string.lower(c), string.upper(c)) - end) - return s -end - -local function ProcessPackages(packages, fn) - for pkg in packages do - if pkg then - local pkgPath = pkg.Path - :gsub("\\", "/") - :gsub(nocase("/filelist.xml"), "") - fn(pkg, pkgPath) - end - end -end - -ProcessPackages(ContentPackageManager.EnabledPackages.All, function(pkg, pkgPath) - table.insert(package.path, pkgPath .. LUA_MOD_REQUIRE_PATH) - local autorunPath = pkgPath .. LUA_MOD_AUTORUN_PATH - if File.DirectoryExists(autorunPath) then - QueueAutorun.Add(autorunPath, pkgPath, pkg) - end -end) - --- we don't want to execute workshop ForcedAutorun if we have a local Package -local executedLocalPackages = {} - -ProcessPackages(ContentPackageManager.EnabledPackages.All, function(pkg, pkgPath) - table.insert(package.path, pkgPath .. LUA_MOD_REQUIRE_PATH) - local forcedAutorunPath = pkgPath .. LUA_MOD_FORCEDAUTORUN_PATH - if File.DirectoryExists(forcedAutorunPath) then - QueueForcedAutorun.Add(forcedAutorunPath, pkgPath, pkg) - executedLocalPackages[pkg.Name] = true - end -end) - -if not LuaCsConfig.TreatForcedModsAsNormal then - ProcessPackages(ContentPackageManager.LocalPackages, function(pkg, pkgPath) - if not executedLocalPackages[pkg.Name] then - table.insert(package.path, pkgPath .. LUA_MOD_REQUIRE_PATH) - local forcedAutorunPath = pkgPath .. LUA_MOD_FORCEDAUTORUN_PATH - if File.DirectoryExists(forcedAutorunPath) then - QueueForcedAutorun.Add(forcedAutorunPath, pkgPath, pkg) - executedLocalPackages[pkg.Name] = true - end - end - end) - - ProcessPackages(ContentPackageManager.AllPackages, function(pkg, pkgPath) - if not executedLocalPackages[pkg.Name] then - table.insert(package.path, pkgPath .. LUA_MOD_REQUIRE_PATH) - local forcedAutorunPath = pkgPath .. LUA_MOD_FORCEDAUTORUN_PATH - if File.DirectoryExists(forcedAutorunPath) then - QueueForcedAutorun.Add(forcedAutorunPath, pkgPath, pkg) - end - end - end) -end - -setmodulepaths(package.path) -setmodulepaths = nil - -local allExecuted = {} -for key, value in pairs(QueueAutorun.Queue) do table.insert(allExecuted, value[3]) end -for key, value in pairs(QueueForcedAutorun.Queue) do table.insert(allExecuted, value[3]) end - -if SERVER then - Networking.Receive("_luastart", function (message, client) - local num = message.ReadUInt16() - - local packages = {} - - for i = 1, num, 1 do - table.insert(packages, { - Name = message.ReadString(), - Version = message.ReadString(), - Id = message.ReadUInt64(), - Hash = message.ReadString() - }) - end - - Hook.Call("client.packages", client, packages) - end) -elseif Game.IsMultiplayer then - local message = Networking.Start("_luastart") - - message.WriteUInt16(#allExecuted) - - for key, package in pairs(allExecuted) do - local id = package.UgcId - local hash = package.Hash and package.Hash.StringRepresentation or "" - - if id == nil then id = 0 end - - message.WriteString(package.Name) - message.WriteString(package.ModVersion) - message.WriteUInt64(UInt64(id)) - message.WriteString(hash) - end - - Networking.Send(message) -end - -QueueAutorun.Process() -QueueForcedAutorun.Process() - -Hook.Add("stop", "luaSetup.stop", function() - print("Stopping Lua...") -end) - -Hook.Call("loaded") \ No newline at end of file diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs index cd0ecdda5..0918ff9a6 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Lua/LuaClasses/LuaUserData.cs @@ -11,7 +11,7 @@ namespace Barotrauma internal class LuaUserData { [Obsolete("Use IPluginManagementService::GetTypesByName()")] - public static Type GetType(string typeName) => throw new NotImplementedException(); //LuaCsSetup.GetType(typeName); + public static Type GetType(string typeName) => GameMain.LuaCs.PluginManagementService.GetType(typeName); //LuaCsSetup.GetType(typeName); public static IUserDataDescriptor RegisterType(string typeName) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs index 080a55ff9..9413289c8 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/LuaCsSetup.cs @@ -44,7 +44,7 @@ namespace Barotrauma SubscribeToLuaCsEvents(); } - bool ValidateLuaCsContent() + private bool ValidateLuaCsContent() { #if DEBUG // TODO: we just wanna boot for now @@ -57,7 +57,7 @@ namespace Barotrauma throw new NotImplementedException(); } - void SubscribeToLuaCsEvents() + private void SubscribeToLuaCsEvents() { EventService.Subscribe(this); // game state hook in EventService.Subscribe(this); @@ -283,7 +283,13 @@ namespace Barotrauma DisposeLuaCsConfig(); Logger.LogResults(PackageManagementService.UnloadAllPackages()); } - + + LuaScriptManagementService.Reset(); + PackageManagementService.Reset(); + EventService.Reset(); + + SubscribeToLuaCsEvents(); + CurrentRunState = RunState.Unloaded; } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs index 80ce7628c..c3d362960 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/EventService.cs @@ -348,6 +348,7 @@ public class EventService : IEventService, IEventAssemblyContextUnloading _subscriptions.Clear(); _luaSubscriptionFactories.Clear(); _eventTypeNameAliases.Clear(); + _luaLegacySubscriptionFactories.Clear(); GC.SuppressFinalize(this); } @@ -357,6 +358,7 @@ public class EventService : IEventService, IEventAssemblyContextUnloading _subscriptions.Clear(); _luaSubscriptionFactories.Clear(); _eventTypeNameAliases.Clear(); + _luaLegacySubscriptionFactories.Clear(); return FluentResults.Result.Ok(); } diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs index 3e9b9eadc..fc5e23586 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LoggerService.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using Barotrauma.Networking; +using FluentResults; using Microsoft.Xna.Framework; using MoonSharp.Interpreter; @@ -132,6 +133,42 @@ public partial class LoggerService : ILoggerService #endif } + public void HandleException(Exception ex) + { + string errorString = ""; + switch (ex) + { + case NetRuntimeException netRuntimeException: + if (netRuntimeException.DecoratedMessage == null) + { + errorString = netRuntimeException.ToString(); + } + else + { + // FIXME: netRuntimeException.ToString() doesn't print the InnerException's stack trace... + errorString = $"{netRuntimeException.DecoratedMessage}: {netRuntimeException}"; + } + break; + case InterpreterException interpreterException: + if (interpreterException.DecoratedMessage == null) + { + errorString = interpreterException.ToString(); + } + else + { + errorString = interpreterException.DecoratedMessage; + } + break; + default: + errorString = ex.StackTrace != null + ? ex.ToString() + : $"{ex}\n{Environment.StackTrace}"; + break; + } + + LogError(errorString); + } + public void LogResults(FluentResults.Result result) { if (result == null) @@ -149,7 +186,15 @@ public partial class LoggerService : ILoggerService { foreach (var error in result.Errors) { - LogError(error.Message); + if (error is ExceptionalError exceptionalError) + { + HandleException(exceptionalError.Exception); + } + else + { + LogError(error.Message); + } + if (error.Reasons != null) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs index f69e2faff..33c0eb915 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/LuaScriptManagementService.cs @@ -7,10 +7,12 @@ using Barotrauma.Networking; using FluentResults; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Toolkit.Diagnostics; using MonoMod.RuntimeDetour; using MoonSharp.Interpreter; using MoonSharp.Interpreter.Interop; using MoonSharp.Interpreter.Loaders; +using RestSharp.Validation; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -18,12 +20,12 @@ using System.Collections.Immutable; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Globalization; +using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; -using Microsoft.Toolkit.Diagnostics; using static Barotrauma.GameSettings; namespace Barotrauma.LuaCs.Services; @@ -97,6 +99,46 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService return new FluentResults.Result().WithReasons(cacheRes.Value.SelectMany(cr => cr.Item2.Reasons)); } + private DynValue DoFile(string file, Table? globalContext = null, string? codeStringFriendly = null) + { + if (_script == null) + { + throw new Exception("Not running"); + } + + if (!LuaCsFile.CanReadFromPath(file)) + { + throw new ScriptRuntimeException($"dofile: File access to {file} not allowed."); + } + + if (!LuaCsFile.Exists(file)) + { + throw new ScriptRuntimeException($"dofile: File {file} not found."); + } + + return _script.DoFile(file, globalContext, codeStringFriendly); + } + + private DynValue LoadFile(string file, Table? globalContext = null, string? codeStringFriendly = null) + { + if (_script == null) + { + throw new Exception("Not running"); + } + + if (!LuaCsFile.CanReadFromPath(file)) + { + throw new ScriptRuntimeException($"loadfile: File access to {file} not allowed."); + } + + if (!LuaCsFile.Exists(file)) + { + throw new ScriptRuntimeException($"loadfile: File {file} not found."); + } + + return _script.LoadFile(file, globalContext, codeStringFriendly); + } + private void SetupEnvironment() { _script = new Script(CoreModules.Preset_SoftSandbox | CoreModules.Debug | CoreModules.IO | CoreModules.OS_System); @@ -115,9 +157,21 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService RegisterType(typeof(ILuaCsUtility)); RegisterType(typeof(ILuaCsTimer)); RegisterType(typeof(LuaCsFile)); + RegisterType(typeof(ILuaScriptResourceInfo)); + RegisterType(typeof(IResourceInfo)); + RegisterType(typeof(LuaUserData)); + RegisterType(typeof(IUserDataDescriptor)); new LuaConverters(_script).RegisterLuaConverters(); + var luaRequire = new LuaRequire(_script); + + _script.Globals["setmodulepaths"] = (string[] str) => ((LuaScriptLoader)_luaScriptLoader).ModulePaths = str; + + _script.Globals["dofile"] = (Func)DoFile; + _script.Globals["loadfile"] = (Func)LoadFile; + _script.Globals["require"] = (Func)luaRequire.Require; + _script.Globals["printerror"] = (DynValue o) => { LuaCsLogger.LogError(o.ToString()); }; _script.Globals["dostring"] = (Func)_script.DoString; @@ -129,6 +183,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService _script.Globals["File"] = UserData.CreateStatic(); //_script.Globals["Networking"] = _luaCsNetworking; //_script.Globals["Steam"] = Steam; + _script.Globals["LuaUserData"] = UserData.CreateStatic(); _script.Globals["ExecutionNumber"] = 0; _script.Globals["CSActive"] = false; @@ -138,9 +193,7 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService } public FluentResults.Result ExecuteLoadedScripts(ImmutableArray executionOrder) - { - throw new NotImplementedException($"Need to implement {nameof(executionOrder)} logic."); - + { if (_isRunning) { return FluentResults.Result.Fail("Tried to execute Lua scripts without unloading first."); @@ -152,13 +205,16 @@ class LuaScriptManagementService : ILuaScriptManagementService, ILuaDataService var result = FluentResults.Result.Ok(); - foreach (ILuaScriptResourceInfo resource in _resourcesInfo) + List initializationScripts = executionOrder.Where(x => x.OwnerPackage.Name == "LuaCsForBarotrauma").ToList(); + List otherScripts = executionOrder.Except(initializationScripts).ToList(); + + foreach (ILuaScriptResourceInfo resource in initializationScripts) { foreach (ContentPath filePath in resource.FilePaths) { try { - _script?.Call(_script.LoadFile(filePath.FullPath)); + _script?.Call(_script.LoadFile(filePath.FullPath), Path.GetDirectoryName(resource.OwnerPackage.Path), otherScripts.ToList()); } catch(Exception e) { diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs index ba9d9c651..04a180a40 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/PluginManagementService.cs @@ -78,7 +78,7 @@ public class PluginManagementService : IPluginManagementService, IAssemblyManage if (IsDisposed) return FluentResults.Result.Fail($"{nameof(PluginManagementService)} is disposed!"); - throw new NotImplementedException(); + return FluentResults.Result.Fail("not implemented"); } public Result> GetImplementingTypes(bool includeInterfaces = false, diff --git a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs index aa812801b..34c433f21 100644 --- a/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs +++ b/Barotrauma/BarotraumaShared/SharedSource/LuaCs/Services/Safe/SafeStorageService.cs @@ -1,4 +1,11 @@ -using System; +using Barotrauma.IO; +using Barotrauma.LuaCs.Data; +using Barotrauma.Networking; +using FarseerPhysics.Common; +using FluentResults; +using FluentResults.LuaCs; +using Microsoft.Toolkit.Diagnostics; +using System; using System.Collections.Concurrent; using System.Collections.Immutable; using System.IO; @@ -6,12 +13,6 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml.Linq; -using Barotrauma.IO; -using Barotrauma.LuaCs.Data; -using FarseerPhysics.Common; -using FluentResults; -using FluentResults.LuaCs; -using Microsoft.Toolkit.Diagnostics; using Path = System.IO.Path; namespace Barotrauma.LuaCs.Services.Safe; @@ -40,6 +41,17 @@ public class SafeStorageService : StorageService, ISafeStorageService try { path = GetFullPath(path); + + if (path.StartsWith(ConfigData.WorkshopModsDirectory) + || path.StartsWith(ConfigData.LocalModsDirectory) +#if CLIENT + || path.StartsWith(ConfigData.TempDownloadsDirectory) +#endif + ) + { + return true; + } + if (!_fileListRead.ContainsKey(path)) { return false;