diff --git a/Penumbra/Mods/Groups/ModSaveGroup.cs b/Penumbra/Mods/Groups/ModSaveGroup.cs index ed81f42f..e87fc012 100644 --- a/Penumbra/Mods/Groups/ModSaveGroup.cs +++ b/Penumbra/Mods/Groups/ModSaveGroup.cs @@ -51,7 +51,7 @@ public readonly struct ModSaveGroup : ISavable if (_groupIdx >= 0) _group!.WriteJson(j, serializer); else - IModDataContainer.WriteModData(j, serializer, _defaultMod!, _basePath); + SubModHelpers.WriteModContainer(j, serializer, _defaultMod!, _basePath); j.WriteEndObject(); } } diff --git a/Penumbra/Mods/Groups/MultiModGroup.cs b/Penumbra/Mods/Groups/MultiModGroup.cs index f39f2e70..1c8c769c 100644 --- a/Penumbra/Mods/Groups/MultiModGroup.cs +++ b/Penumbra/Mods/Groups/MultiModGroup.cs @@ -54,7 +54,7 @@ public sealed class MultiModGroup(Mod mod) : IModGroup, ITexToolsGroup return OptionData.Count - 1; } - public static MultiModGroup? Load(Mod mod, JObject json, int groupIdx) + public static MultiModGroup? Load(Mod mod, JObject json) { var ret = new MultiModGroup(mod) { @@ -140,10 +140,10 @@ public sealed class MultiModGroup(Mod mod) : IModGroup, ITexToolsGroup jWriter.WriteStartArray(); foreach (var option in OptionData) { - IModOption.WriteModOption(jWriter, option); + SubModHelpers.WriteModOption(jWriter, option); jWriter.WritePropertyName(nameof(option.Priority)); jWriter.WriteValue(option.Priority.Value); - IModDataContainer.WriteModData(jWriter, serializer, option, basePath ?? Mod.ModPath); + SubModHelpers.WriteModContainer(jWriter, serializer, option, basePath ?? Mod.ModPath); } jWriter.WriteEndArray(); diff --git a/Penumbra/Mods/Groups/SingleModGroup.cs b/Penumbra/Mods/Groups/SingleModGroup.cs index 6011185a..11542968 100644 --- a/Penumbra/Mods/Groups/SingleModGroup.cs +++ b/Penumbra/Mods/Groups/SingleModGroup.cs @@ -52,7 +52,7 @@ public sealed class SingleModGroup(Mod mod) : IModGroup, ITexToolsGroup public bool IsOption => OptionData.Count > 1; - public static SingleModGroup? Load(Mod mod, JObject json, int groupIdx) + public static SingleModGroup? Load(Mod mod, JObject json) { var options = json["Options"]; var ret = new SingleModGroup(mod) @@ -144,8 +144,8 @@ public sealed class SingleModGroup(Mod mod) : IModGroup, ITexToolsGroup jWriter.WriteStartArray(); foreach (var option in OptionData) { - IModOption.WriteModOption(jWriter, option); - IModDataContainer.WriteModData(jWriter, serializer, option, basePath ?? Mod.ModPath); + SubModHelpers.WriteModOption(jWriter, option); + SubModHelpers.WriteModContainer(jWriter, serializer, option, basePath ?? Mod.ModPath); } jWriter.WriteEndArray(); diff --git a/Penumbra/Mods/Manager/ModOptionEditor.cs b/Penumbra/Mods/Manager/ModOptionEditor.cs index b66fec17..a02c8d68 100644 --- a/Penumbra/Mods/Manager/ModOptionEditor.cs +++ b/Penumbra/Mods/Manager/ModOptionEditor.cs @@ -251,7 +251,7 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS Description = option.Description, }; if (option is IModDataContainer data) - IModDataContainer.Clone(data, newOption); + SubModHelpers.Clone(data, newOption); s.OptionData.Add(newOption); break; } @@ -265,7 +265,7 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS Priority = option is MultiSubMod s ? s.Priority : ModPriority.Default, }; if (option is IModDataContainer data) - IModDataContainer.Clone(data, newOption); + SubModHelpers.Clone(data, newOption); m.OptionData.Add(newOption); break; } diff --git a/Penumbra/Mods/Mod.cs b/Penumbra/Mods/Mod.cs index fc84afcc..6f6eb8ce 100644 --- a/Penumbra/Mods/Mod.cs +++ b/Penumbra/Mods/Mod.cs @@ -78,7 +78,7 @@ public sealed class Mod : IMod group.AddData(config, dictRedirections, setManips); } - Default.AddDataTo(dictRedirections, setManips); + Default.AddTo(dictRedirections, setManips); return new AppliedModData(dictRedirections, setManips); } diff --git a/Penumbra/Mods/ModCreator.cs b/Penumbra/Mods/ModCreator.cs index e8113ee1..0626bc9d 100644 --- a/Penumbra/Mods/ModCreator.cs +++ b/Penumbra/Mods/ModCreator.cs @@ -115,7 +115,7 @@ public partial class ModCreator( try { var jObject = File.Exists(defaultFile) ? JObject.Parse(File.ReadAllText(defaultFile)) : new JObject(); - IModDataContainer.Load(jObject, mod.Default, mod.ModPath); + SubModHelpers.LoadDataContainer(jObject, mod.Default, mod.ModPath); } catch (Exception e) { @@ -162,7 +162,7 @@ public partial class ModCreator( deleteList.AddRange(localDeleteList); } - IModDataContainer.DeleteDeleteList(deleteList, delete); + DeleteDeleteList(deleteList, delete); if (!changes) return; @@ -221,7 +221,7 @@ public partial class ModCreator( } } - IModDataContainer.DeleteDeleteList(deleteList, delete); + DeleteDeleteList(deleteList, delete); return (oldSize < option.Manipulations.Count, deleteList); } @@ -392,10 +392,8 @@ public partial class ModCreator( Penumbra.Log.Debug($"Writing the first {IModGroup.MaxMultiOptions} options to {Path.GetFileName(oldPath)} after split."); using (var oldFile = File.CreateText(oldPath)) { - using var j = new JsonTextWriter(oldFile) - { - Formatting = Formatting.Indented, - }; + using var j = new JsonTextWriter(oldFile); + j.Formatting = Formatting.Indented; json.WriteTo(j); } @@ -403,10 +401,8 @@ public partial class ModCreator( $"Writing the remaining {options.Count - IModGroup.MaxMultiOptions} options to {Path.GetFileName(newPath)} after split."); using (var newFile = File.CreateText(newPath)) { - using var j = new JsonTextWriter(newFile) - { - Formatting = Formatting.Indented, - }; + using var j = new JsonTextWriter(newFile); + j.Formatting = Formatting.Indented; clone.WriteTo(j); } @@ -436,8 +432,8 @@ public partial class ModCreator( var json = JObject.Parse(File.ReadAllText(file.FullName)); switch (json[nameof(Type)]?.ToObject() ?? GroupType.Single) { - case GroupType.Multi: return MultiModGroup.Load(mod, json, groupIdx); - case GroupType.Single: return SingleModGroup.Load(mod, json, groupIdx); + case GroupType.Multi: return MultiModGroup.Load(mod, json); + case GroupType.Single: return SingleModGroup.Load(mod, json); } } catch (Exception e) @@ -446,5 +442,23 @@ public partial class ModCreator( } return null; + } + + internal static void DeleteDeleteList(IEnumerable deleteList, bool delete) + { + if (!delete) + return; + + foreach (var file in deleteList) + { + try + { + File.Delete(file); + } + catch (Exception e) + { + Penumbra.Log.Error($"Could not delete incorporated meta file {file}:\n{e}"); + } + } } } diff --git a/Penumbra/Mods/SubMods/DefaultSubMod.cs b/Penumbra/Mods/SubMods/DefaultSubMod.cs index 8b166505..8b00e2ae 100644 --- a/Penumbra/Mods/SubMods/DefaultSubMod.cs +++ b/Penumbra/Mods/SubMods/DefaultSubMod.cs @@ -22,9 +22,9 @@ public class DefaultSubMod(IMod mod) : IModDataContainer IModGroup? IModDataContainer.Group => null; - public void AddDataTo(Dictionary redirections, HashSet manipulations) - => IModDataContainer.AddDataTo(this, redirections, manipulations); + public void AddTo(Dictionary redirections, HashSet manipulations) + => SubModHelpers.AddContainerTo(this, redirections, manipulations); public (int GroupIndex, int DataIndex) GetDataIndices() => (-1, 0); -} +} diff --git a/Penumbra/Mods/SubMods/IModDataContainer.cs b/Penumbra/Mods/SubMods/IModDataContainer.cs index c9420821..1e676816 100644 --- a/Penumbra/Mods/SubMods/IModDataContainer.cs +++ b/Penumbra/Mods/SubMods/IModDataContainer.cs @@ -1,5 +1,3 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using Penumbra.Meta.Manipulations; using Penumbra.Mods.Editor; using Penumbra.Mods.Groups; @@ -7,6 +5,7 @@ using Penumbra.String.Classes; namespace Penumbra.Mods.SubMods; + public interface IModDataContainer { public IMod Mod { get; } @@ -16,116 +15,24 @@ public interface IModDataContainer public Dictionary FileSwaps { get; set; } public HashSet Manipulations { get; set; } - public static void AddDataTo(IModDataContainer container, Dictionary redirections, HashSet manipulations) - { - foreach (var (path, file) in container.Files) - redirections.TryAdd(path, file); - - foreach (var (path, file) in container.FileSwaps) - redirections.TryAdd(path, file); - manipulations.UnionWith(container.Manipulations); - } - public string GetName() => this switch { - IModOption o => o.FullName, + IModOption o => o.FullName, DefaultSubMod => DefaultSubMod.FullName, - _ => $"Container {GetDataIndices().DataIndex + 1}", + _ => $"Container {GetDataIndices().DataIndex + 1}", }; public string GetFullName() => this switch { - IModOption o => o.FullName, - DefaultSubMod => DefaultSubMod.FullName, + IModOption o => o.FullName, + DefaultSubMod => DefaultSubMod.FullName, _ when Group != null => $"{Group.Name}: Container {GetDataIndices().DataIndex + 1}", - _ => $"Container {GetDataIndices().DataIndex + 1}", + _ => $"Container {GetDataIndices().DataIndex + 1}", }; - public static void Clone(IModDataContainer from, IModDataContainer to) - { - to.Files = new Dictionary(from.Files); - to.FileSwaps = new Dictionary(from.FileSwaps); - to.Manipulations = [.. from.Manipulations]; - } - public (int GroupIndex, int DataIndex) GetDataIndices(); - - public static void Load(JToken json, IModDataContainer data, DirectoryInfo basePath) - { - data.Files.Clear(); - data.FileSwaps.Clear(); - data.Manipulations.Clear(); - - var files = (JObject?)json[nameof(Files)]; - if (files != null) - foreach (var property in files.Properties()) - { - if (Utf8GamePath.FromString(property.Name, out var p, true)) - data.Files.TryAdd(p, new FullPath(basePath, property.Value.ToObject())); - } - - var swaps = (JObject?)json[nameof(FileSwaps)]; - if (swaps != null) - foreach (var property in swaps.Properties()) - { - if (Utf8GamePath.FromString(property.Name, out var p, true)) - data.FileSwaps.TryAdd(p, new FullPath(property.Value.ToObject()!)); - } - - var manips = json[nameof(Manipulations)]; - if (manips != null) - foreach (var s in manips.Children().Select(c => c.ToObject()) - .Where(m => m.Validate())) - data.Manipulations.Add(s); - } - - public static void WriteModData(JsonWriter j, JsonSerializer serializer, IModDataContainer data, DirectoryInfo basePath) - { - j.WritePropertyName(nameof(data.Files)); - j.WriteStartObject(); - foreach (var (gamePath, file) in data.Files) - { - if (file.ToRelPath(basePath, out var relPath)) - { - j.WritePropertyName(gamePath.ToString()); - j.WriteValue(relPath.ToString()); - } - } - - j.WriteEndObject(); - j.WritePropertyName(nameof(data.FileSwaps)); - j.WriteStartObject(); - foreach (var (gamePath, file) in data.FileSwaps) - { - j.WritePropertyName(gamePath.ToString()); - j.WriteValue(file.ToString()); - } - - j.WriteEndObject(); - j.WritePropertyName(nameof(data.Manipulations)); - serializer.Serialize(j, data.Manipulations); - j.WriteEndObject(); - } - - internal static void DeleteDeleteList(IEnumerable deleteList, bool delete) - { - if (!delete) - return; - - foreach (var file in deleteList) - { - try - { - File.Delete(file); - } - catch (Exception e) - { - Penumbra.Log.Error($"Could not delete incorporated meta file {file}:\n{e}"); - } - } - } } public interface IModDataOption : IModOption, IModDataContainer; diff --git a/Penumbra/Mods/SubMods/IModOption.cs b/Penumbra/Mods/SubMods/IModOption.cs index f1ce1d4c..83d632a0 100644 --- a/Penumbra/Mods/SubMods/IModOption.cs +++ b/Penumbra/Mods/SubMods/IModOption.cs @@ -1,27 +1,10 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - namespace Penumbra.Mods.SubMods; public interface IModOption { - public string Name { get; set; } - public string FullName { get; } + public string Name { get; set; } + public string FullName { get; } public string Description { get; set; } - public static void Load(JToken json, IModOption option) - { - option.Name = json[nameof(Name)]?.ToObject() ?? string.Empty; - option.Description = json[nameof(Description)]?.ToObject() ?? string.Empty; - } - public (int GroupIndex, int OptionIndex) GetOptionIndices(); - - public static void WriteModOption(JsonWriter j, IModOption option) - { - j.WritePropertyName(nameof(Name)); - j.WriteValue(option.Name); - j.WritePropertyName(nameof(Description)); - j.WriteValue(option.Description); - } } diff --git a/Penumbra/Mods/SubMods/MultiSubMod.cs b/Penumbra/Mods/SubMods/MultiSubMod.cs index 12dfcada..ccb787a5 100644 --- a/Penumbra/Mods/SubMods/MultiSubMod.cs +++ b/Penumbra/Mods/SubMods/MultiSubMod.cs @@ -35,8 +35,8 @@ public class MultiSubMod(Mod mod, MultiModGroup group) : IModDataOption public MultiSubMod(Mod mod, MultiModGroup group, JToken json) : this(mod, group) { - IModOption.Load(json, this); - IModDataContainer.Load(json, this, mod.ModPath); + SubModHelpers.LoadOptionData(json, this); + SubModHelpers.LoadDataContainer(json, this, mod.ModPath); Priority = json[nameof(IModGroup.Priority)]?.ToObject() ?? ModPriority.Default; } @@ -48,7 +48,7 @@ public class MultiSubMod(Mod mod, MultiModGroup group) : IModDataOption Description = Description, Priority = Priority, }; - IModDataContainer.Clone(this, ret); + SubModHelpers.Clone(this, ret); return ret; } @@ -60,12 +60,12 @@ public class MultiSubMod(Mod mod, MultiModGroup group) : IModDataOption Name = Name, Description = Description, }; - IModDataContainer.Clone(this, ret); + SubModHelpers.Clone(this, ret); return ret; } public void AddDataTo(Dictionary redirections, HashSet manipulations) - => IModDataContainer.AddDataTo(this, redirections, manipulations); + => SubModHelpers.AddContainerTo(this, redirections, manipulations); public static MultiSubMod CreateForSaving(string name, string description, ModPriority priority) => new(null!, null!) diff --git a/Penumbra/Mods/SubMods/SingleSubMod.cs b/Penumbra/Mods/SubMods/SingleSubMod.cs index ba91e271..e6740a47 100644 --- a/Penumbra/Mods/SubMods/SingleSubMod.cs +++ b/Penumbra/Mods/SubMods/SingleSubMod.cs @@ -33,8 +33,8 @@ public class SingleSubMod(Mod mod, SingleModGroup group) : IModDataOption public SingleSubMod(Mod mod, SingleModGroup group, JToken json) : this(mod, group) { - IModOption.Load(json, this); - IModDataContainer.Load(json, this, mod.ModPath); + SubModHelpers.LoadOptionData(json, this); + SubModHelpers.LoadDataContainer(json, this, mod.ModPath); } public SingleSubMod Clone(Mod mod, SingleModGroup group) @@ -44,7 +44,7 @@ public class SingleSubMod(Mod mod, SingleModGroup group) : IModDataOption Name = Name, Description = Description, }; - IModDataContainer.Clone(this, ret); + SubModHelpers.Clone(this, ret); return ret; } @@ -57,13 +57,13 @@ public class SingleSubMod(Mod mod, SingleModGroup group) : IModDataOption Description = Description, Priority = priority, }; - IModDataContainer.Clone(this, ret); + SubModHelpers.Clone(this, ret); return ret; } public void AddDataTo(Dictionary redirections, HashSet manipulations) - => IModDataContainer.AddDataTo(this, redirections, manipulations); + => SubModHelpers.AddContainerTo(this, redirections, manipulations); public (int GroupIndex, int DataIndex) GetDataIndices() => (Group.GetIndex(), GetDataIndex()); diff --git a/Penumbra/Mods/SubMods/SubModHelpers.cs b/Penumbra/Mods/SubMods/SubModHelpers.cs new file mode 100644 index 00000000..9992b6e8 --- /dev/null +++ b/Penumbra/Mods/SubMods/SubModHelpers.cs @@ -0,0 +1,105 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Penumbra.Meta.Manipulations; +using Penumbra.Mods.ItemSwap; +using Penumbra.String.Classes; + +namespace Penumbra.Mods.SubMods; + +public static class SubModHelpers +{ + /// Add all unique meta manipulations, file redirections and then file swaps from a ModDataContainer to the given sets. Skip any keys that are already contained. + public static void AddContainerTo(IModDataContainer container, Dictionary redirections, + HashSet manipulations) + { + foreach (var (path, file) in container.Files) + redirections.TryAdd(path, file); + + foreach (var (path, file) in container.FileSwaps) + redirections.TryAdd(path, file); + manipulations.UnionWith(container.Manipulations); + } + + /// Replace all data of with the data of . + public static void Clone(IModDataContainer from, IModDataContainer to) + { + to.Files = new Dictionary(from.Files); + to.FileSwaps = new Dictionary(from.FileSwaps); + to.Manipulations = [.. from.Manipulations]; + } + + /// Load all file redirections, file swaps and meta manipulations from a JToken of that option into a data container. + public static void LoadDataContainer(JToken json, IModDataContainer data, DirectoryInfo basePath) + { + data.Files.Clear(); + data.FileSwaps.Clear(); + data.Manipulations.Clear(); + + var files = (JObject?)json[nameof(data.Files)]; + if (files != null) + foreach (var property in files.Properties()) + { + if (Utf8GamePath.FromString(property.Name, out var p, true)) + data.Files.TryAdd(p, new FullPath(basePath, property.Value.ToObject())); + } + + var swaps = (JObject?)json[nameof(data.FileSwaps)]; + if (swaps != null) + foreach (var property in swaps.Properties()) + { + if (Utf8GamePath.FromString(property.Name, out var p, true)) + data.FileSwaps.TryAdd(p, new FullPath(property.Value.ToObject()!)); + } + + var manips = json[nameof(data.Manipulations)]; + if (manips != null) + foreach (var s in manips.Children().Select(c => c.ToObject()) + .Where(m => m.Validate())) + data.Manipulations.Add(s); + } + + /// Load the relevant data for a selectable option from a JToken of that option. + public static void LoadOptionData(JToken json, IModOption option) + { + option.Name = json[nameof(option.Name)]?.ToObject() ?? string.Empty; + option.Description = json[nameof(option.Description)]?.ToObject() ?? string.Empty; + } + + /// Write file redirections, file swaps and meta manipulations from a data container on a JsonWriter. + public static void WriteModContainer(JsonWriter j, JsonSerializer serializer, IModDataContainer data, DirectoryInfo basePath) + { + j.WritePropertyName(nameof(data.Files)); + j.WriteStartObject(); + foreach (var (gamePath, file) in data.Files) + { + if (file.ToRelPath(basePath, out var relPath)) + { + j.WritePropertyName(gamePath.ToString()); + j.WriteValue(relPath.ToString()); + } + } + + j.WriteEndObject(); + j.WritePropertyName(nameof(data.FileSwaps)); + j.WriteStartObject(); + foreach (var (gamePath, file) in data.FileSwaps) + { + j.WritePropertyName(gamePath.ToString()); + j.WriteValue(file.ToString()); + } + + j.WriteEndObject(); + j.WritePropertyName(nameof(data.Manipulations)); + serializer.Serialize(j, data.Manipulations); + j.WriteEndObject(); + } + + /// Write the data for a selectable mod option on a JsonWriter. + public static void WriteModOption(JsonWriter j, IModOption option) + { + j.WritePropertyName(nameof(option.Name)); + j.WriteValue(option.Name); + j.WritePropertyName(nameof(option.Description)); + j.WriteValue(option.Description); + } +}