Remove load order/sorting from ModLoader (#584)

* - Remove modloader sorting
- remove order.json
- remove LoadBefore and
LoadAfter
- Remove unused var
- Remove unused locals
- Rename vars
- localize new warnings

* revert test change
This commit is contained in:
Cj
2025-09-05 05:03:02 -04:00
committed by GitHub
parent 8197e5c124
commit fafbfeb291
29 changed files with 40 additions and 324 deletions
-124
View File
@@ -1,124 +0,0 @@
using SPTarkov.Server.Core.Models.Spt.Mod;
using SPTarkov.Server.Core.Utils.Cloners;
namespace SPTarkov.Server.Modding;
public class ModLoadOrder(ICloner cloner)
{
protected readonly Dictionary<string, AbstractModMetadata> LoadOrder = new();
protected Dictionary<string, AbstractModMetadata> Mods = new();
protected Dictionary<string, AbstractModMetadata> ModsAvailable = new();
public Dictionary<string, AbstractModMetadata> SetModList(Dictionary<string, AbstractModMetadata> mods)
{
this.Mods = mods;
ModsAvailable = cloner.Clone(this.Mods);
LoadOrder.Clear();
var visited = new HashSet<string>();
// invert loadBefore into loadAfter on specified mods
foreach (var (modGuid, modConfig) in ModsAvailable)
{
if ((modConfig.LoadBefore ?? []).Count > 0)
{
InvertLoadBefore(modGuid);
}
}
foreach (var modGuid in ModsAvailable.Keys)
{
GetLoadOrderRecursive(modGuid, visited);
}
return LoadOrder;
}
public List<string> GetLoadOrder()
{
return [.. LoadOrder.Keys];
}
public HashSet<string> GetModsOnLoadBefore(string modGuid)
{
if (!Mods.TryGetValue(modGuid, out var config))
{
throw new Exception($"The mod: {modGuid} does not exist!");
}
var loadBefore = new HashSet<string>(config.LoadBefore);
foreach (var loadBeforeModGuid in loadBefore)
{
if (!Mods.ContainsKey(loadBeforeModGuid))
{
loadBefore.Remove(loadBeforeModGuid);
}
}
return loadBefore;
}
protected void InvertLoadBefore(string modGuid)
{
var loadBefore = GetModsOnLoadBefore(modGuid);
foreach (var loadBeforeMod in loadBefore)
{
var loadBeforeModConfig = ModsAvailable[loadBeforeMod];
loadBeforeModConfig.LoadAfter ??= [];
loadBeforeModConfig.LoadAfter.Add(modGuid);
ModsAvailable.Add(loadBeforeMod, loadBeforeModConfig);
}
}
protected void GetLoadOrderRecursive(string modGuid, HashSet<string> visited)
{
// Validate package
if (LoadOrder.ContainsKey(modGuid))
{
return;
}
if (visited.Contains(modGuid))
{
// Additional info to help debug
throw new Exception($"Cyclic dependency detected for mod: {modGuid}!");
}
// Check dependencies
if (!ModsAvailable.TryGetValue(modGuid, out var config))
{
throw new Exception("modloader-error_parsing_mod_load_order");
}
config.LoadAfter ??= [];
config.ModDependencies ??= [];
var dependencies = new HashSet<string>(config.ModDependencies.Keys);
foreach (var modAfterGuid in config.LoadAfter)
{
if (ModsAvailable.TryGetValue(modAfterGuid, out var value))
{
if (value?.LoadAfter?.Contains(modGuid) ?? false)
{
throw new Exception("modloader-load_order_conflict");
}
dependencies.Add(modAfterGuid);
}
}
visited.Add(modGuid);
foreach (var nextModGuid in dependencies)
{
GetLoadOrderRecursive(nextModGuid, visited);
}
visited.Remove(modGuid);
LoadOrder.Add(modGuid, config);
}
}
+18 -113
View File
@@ -7,81 +7,20 @@ using LogLevel = SPTarkov.Server.Core.Models.Spt.Logging.LogLevel;
namespace SPTarkov.Server.Modding;
public class ModValidator(
ISptLogger<ModValidator> logger,
ServerLocalisationService localisationService,
ISemVer semVer,
ModLoadOrder modLoadOrder,
JsonUtil jsonUtil,
FileUtil fileUtil
)
public class ModValidator(ISptLogger<ModValidator> logger, ServerLocalisationService localisationService, ISemVer semVer, FileUtil fileUtil)
{
protected const string BasePath = "user/mods/";
protected const string ModOrderPath = "user/mods/order.json";
protected readonly Dictionary<string, SptMod> Imported = [];
protected readonly Dictionary<string, int> Order = [];
protected readonly HashSet<string> SkippedMods = [];
public List<SptMod> ValidateAndSort(IEnumerable<SptMod> mods)
public List<SptMod> ValidateMods(IEnumerable<SptMod> mods)
{
if (ProgramStatics.MODS())
if (!ProgramStatics.MODS())
{
ValidateMods(mods);
var sortedModLoadOrder = modLoadOrder.SetModList(
Imported.ToDictionary(m => m.Value.ModMetadata.ModGuid, m => m.Value.ModMetadata)
);
var finalList = new List<SptMod>();
foreach (var orderMod in SortModsLoadOrder())
{
if (!Imported.TryGetValue(orderMod, out var loadedMod))
{
throw new Exception($"Unable to find mod {orderMod} in loaded mods");
}
finalList.Add(loadedMod);
}
return finalList;
return [];
}
return [];
}
public string GetModPath(string mod)
{
return $"{BasePath}{mod}/";
}
protected void ValidateMods(IEnumerable<SptMod> mods)
{
logger.Info(localisationService.GetText("modloader-loading_mods", mods.Count()));
// Mod order
if (!fileUtil.FileExists(ModOrderPath))
{
logger.Info(localisationService.GetText("modloader-mod_order_missing"));
// Write file with empty order array to disk
fileUtil.WriteFile(ModOrderPath, jsonUtil.Serialize(new ModOrder { Order = [] }));
}
else
{
var modOrder = File.ReadAllText(ModOrderPath);
try
{
var modOrderArray = jsonUtil.Deserialize<ModOrder>(modOrder).Order;
for (var i = 0; i < modOrderArray.Count; i++)
{
Order.Add(modOrderArray[i], i);
}
}
catch (Exception e)
{
logger.Error(localisationService.GetText("modloader-mod_order_error"), e);
}
}
// Validate and remove broken mods from mod list
var validMods = GetValidMods(mods).ToList(); // ToList now so we can .Sort later
@@ -123,20 +62,7 @@ public class ModValidator(
if (errorsFound)
{
logger.Error(localisationService.GetText("modloader-no_mods_loaded"));
return;
}
// sort mod order
var missingFromOrderJSON = new Dictionary<string, bool>();
validMods.Sort((prev, next) => SortMods(prev, next, missingFromOrderJSON));
// log the missing mods from order.json
if (logger.IsLogEnabled(LogLevel.Debug))
{
foreach (var missingMod in missingFromOrderJSON.Keys)
{
logger.Debug(localisationService.GetText("modloader-mod_order_missing_from_json", missingMod));
}
return [];
}
// Add mods
@@ -150,24 +76,8 @@ public class ModValidator(
AddMod(mod);
}
}
protected int SortMods(SptMod prev, SptMod next, Dictionary<string, bool> missingFromOrderJson)
{
// mod is not on the list, move the mod to last
if (!Order.TryGetValue(prev.ModMetadata!.Name!, out var previndex))
{
missingFromOrderJson[prev.ModMetadata.Name!] = true;
return 1;
}
if (!Order.TryGetValue(next.ModMetadata!.Name!, out var nextindex))
{
missingFromOrderJson[next.ModMetadata.Name!] = true;
return -1;
}
return previndex - nextindex;
return Imported.Select(mod => mod.Value).ToList();
}
/// <summary>
@@ -245,22 +155,6 @@ public class ModValidator(
return true;
}
/// <summary>
/// Read loadorder.json (create if doesn't exist) and return sorted list of mods
/// </summary>
/// <returns>string array of sorted mod names</returns>
public List<string> SortModsLoadOrder()
{
// if loadorder.json exists: load it, otherwise generate load order
var loadOrderPath = $"{BasePath}loadorder.json";
if (fileUtil.FileExists(loadOrderPath))
{
return jsonUtil.Deserialize<List<string>>(fileUtil.ReadFile(loadOrderPath));
}
return modLoadOrder.GetLoadOrder();
}
/// <summary>
/// Compile mod and add into class property "imported"
/// </summary>
@@ -299,6 +193,12 @@ public class ModValidator(
return true;
}
// Mod depends on itself, throw a warning but continue anyway.
if (pkg.ModDependencies.ContainsKey(pkg.ModGuid))
{
logger.Warning(localisationService.GetText("modloader-self_dependency", new { mod = pkg.Name }));
}
// used for logging, dont remove
var modName = $"{pkg.Author}-{pkg.Name}";
@@ -339,6 +239,12 @@ public class ModValidator(
return true;
}
// Mod is marked as incompatible with itself, throw a warning but continue anyway
if (modToCheck.Incompatibilities.Contains(modToCheck.ModGuid))
{
logger.Warning(localisationService.GetText("modloader-self_incompatibility", new { mod = modToCheck.Name }));
}
foreach (var incompatibleModGuid in modToCheck.Incompatibilities)
{
// Raise dependency version incompatible if any incompatible mod is found
@@ -371,7 +277,6 @@ public class ModValidator(
protected bool ValidMod(SptMod mod)
{
var modName = mod.ModMetadata.Name;
var modPath = GetModPath(modName);
var modIsCalledBepinEx = string.Equals(modName, "bepinex", StringComparison.OrdinalIgnoreCase);
var modIsCalledUser = string.Equals(modName, "user", StringComparison.OrdinalIgnoreCase);