diff --git a/OtterGui b/OtterGui
index f641a34f..f48c6886 160000
--- a/OtterGui
+++ b/OtterGui
@@ -1 +1 @@
-Subproject commit f641a34ffa80e89bd61701f60f15d15c4c5b361e
+Subproject commit f48c6886cbc163c5a292fa8b9fd919cb01c11d7b
diff --git a/Penumbra/Mods/Editor/DuplicateManager.cs b/Penumbra/Mods/Editor/DuplicateManager.cs
index 77d10cc4..c8530936 100644
--- a/Penumbra/Mods/Editor/DuplicateManager.cs
+++ b/Penumbra/Mods/Editor/DuplicateManager.cs
@@ -1,3 +1,4 @@
+using OtterGui.Classes;
using Penumbra.Mods.Manager;
using Penumbra.Mods.Subclasses;
using Penumbra.Services;
@@ -81,7 +82,7 @@ public class DuplicateManager(ModManager modManager, SaveService saveService, Co
if (useModManager)
{
- modManager.OptionEditor.OptionSetFiles(mod, groupIdx, optionIdx, dict);
+ modManager.OptionEditor.OptionSetFiles(mod, groupIdx, optionIdx, dict, SaveType.ImmediateSync);
}
else
{
@@ -216,18 +217,21 @@ public class DuplicateManager(ModManager modManager, SaveService saveService, Co
}
/// Deduplicate a mod simply by its directory without any confirmation or waiting time.
- internal void DeduplicateMod(DirectoryInfo modDirectory)
+ internal void DeduplicateMod(DirectoryInfo modDirectory, bool useModManager = false)
{
try
{
- var mod = new Mod(modDirectory);
- modManager.Creator.ReloadMod(mod, true, out _);
+ if (!useModManager || !modManager.TryGetMod(modDirectory.Name, string.Empty, out var mod))
+ {
+ mod = new Mod(modDirectory);
+ modManager.Creator.ReloadMod(mod, true, out _);
+ }
Clear();
var files = new ModFileCollection();
files.UpdateAll(mod, mod.Default);
- CheckDuplicates(files.Available.OrderByDescending(f => f.FileSize).ToArray(), CancellationToken.None);
- DeleteDuplicates(files, mod, mod.Default, false);
+ CheckDuplicates([.. files.Available.OrderByDescending(f => f.FileSize)], CancellationToken.None);
+ DeleteDuplicates(files, mod, mod.Default, useModManager);
}
catch (Exception e)
{
diff --git a/Penumbra/Mods/Editor/ModMerger.cs b/Penumbra/Mods/Editor/ModMerger.cs
index f5d0e4a4..842b1bb3 100644
--- a/Penumbra/Mods/Editor/ModMerger.cs
+++ b/Penumbra/Mods/Editor/ModMerger.cs
@@ -36,7 +36,7 @@ public class ModMerger : IDisposable
public readonly HashSet SelectedOptions = [];
- public readonly IReadOnlyList Warnings = new List();
+ public readonly IReadOnlyList Warnings = [];
public Exception? Error { get; private set; }
public ModMerger(ModManager mods, ModOptionEditor editor, ModFileSystemSelector selector, DuplicateManager duplicates,
@@ -78,7 +78,8 @@ public class ModMerger : IDisposable
MergeWithOptions();
else
MergeIntoOption(OptionGroupName, OptionName);
- _duplicates.DeduplicateMod(MergeToMod.ModPath);
+
+ _duplicates.DeduplicateMod(MergeToMod.ModPath, true);
}
catch (Exception ex)
{
@@ -134,10 +135,10 @@ public class ModMerger : IDisposable
return;
}
- var (group, groupIdx, groupCreated) = _editor.FindOrAddModGroup(MergeToMod!, GroupType.Multi, groupName);
+ var (group, groupIdx, groupCreated) = _editor.FindOrAddModGroup(MergeToMod!, GroupType.Multi, groupName, SaveType.None);
if (groupCreated)
_createdGroups.Add(groupIdx);
- var (option, optionCreated) = _editor.FindOrAddOption(MergeToMod!, groupIdx, optionName);
+ var (option, optionCreated) = _editor.FindOrAddOption(MergeToMod!, groupIdx, optionName, SaveType.None);
if (optionCreated)
_createdOptions.Add(option);
var dir = ModCreator.NewOptionDirectory(MergeToMod!.ModPath, groupName, _config.ReplaceNonAsciiOnImport);
@@ -156,27 +157,6 @@ public class ModMerger : IDisposable
var swaps = option.FileSwapData.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
var manips = option.ManipulationData.ToHashSet();
- bool GetFullPath(FullPath input, out FullPath ret)
- {
- if (fromFileToFile)
- {
- if (!_fileToFile.TryGetValue(input.FullName, out var s))
- {
- ret = input;
- return false;
- }
-
- ret = new FullPath(s);
- return true;
- }
-
- if (!Utf8RelPath.FromFile(input, MergeFromMod!.ModPath, out var relPath))
- throw new Exception($"Could not create relative path from {input} and {MergeFromMod!.ModPath}.");
-
- ret = new FullPath(MergeToMod!.ModPath, relPath);
- return true;
- }
-
foreach (var originalOption in mergeOptions)
{
foreach (var manip in originalOption.Manipulations)
@@ -204,9 +184,31 @@ public class ModMerger : IDisposable
}
}
- _editor.OptionSetFiles(MergeToMod!, option.GroupIdx, option.OptionIdx, redirections);
- _editor.OptionSetFileSwaps(MergeToMod!, option.GroupIdx, option.OptionIdx, swaps);
- _editor.OptionSetManipulations(MergeToMod!, option.GroupIdx, option.OptionIdx, manips);
+ _editor.OptionSetFiles(MergeToMod!, option.GroupIdx, option.OptionIdx, redirections, SaveType.None);
+ _editor.OptionSetFileSwaps(MergeToMod!, option.GroupIdx, option.OptionIdx, swaps, SaveType.None);
+ _editor.OptionSetManipulations(MergeToMod!, option.GroupIdx, option.OptionIdx, manips, SaveType.ImmediateSync);
+ return;
+
+ bool GetFullPath(FullPath input, out FullPath ret)
+ {
+ if (fromFileToFile)
+ {
+ if (!_fileToFile.TryGetValue(input.FullName, out var s))
+ {
+ ret = input;
+ return false;
+ }
+
+ ret = new FullPath(s);
+ return true;
+ }
+
+ if (!Utf8RelPath.FromFile(input, MergeFromMod!.ModPath, out var relPath))
+ throw new Exception($"Could not create relative path from {input} and {MergeFromMod!.ModPath}.");
+
+ ret = new FullPath(MergeToMod!.ModPath, relPath);
+ return true;
+ }
}
private void CopyFiles(DirectoryInfo directory)
diff --git a/Penumbra/Mods/Manager/ModOptionEditor.cs b/Penumbra/Mods/Manager/ModOptionEditor.cs
index 3459ce1a..60508d33 100644
--- a/Penumbra/Mods/Manager/ModOptionEditor.cs
+++ b/Penumbra/Mods/Manager/ModOptionEditor.cs
@@ -78,7 +78,7 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
}
/// Add a new, empty option group of the given type and name.
- public void AddModGroup(Mod mod, GroupType type, string newName)
+ public void AddModGroup(Mod mod, GroupType type, string newName, SaveType saveType = SaveType.ImmediateSync)
{
if (!VerifyFileName(mod, null, newName, true))
return;
@@ -96,18 +96,18 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
Name = newName,
Priority = maxPriority,
});
- saveService.ImmediateSave(new ModSaveGroup(mod, mod.Groups.Count - 1, config.ReplaceNonAsciiOnImport));
+ saveService.Save(saveType, new ModSaveGroup(mod, mod.Groups.Count - 1, config.ReplaceNonAsciiOnImport));
communicator.ModOptionChanged.Invoke(ModOptionChangeType.GroupAdded, mod, mod.Groups.Count - 1, -1, -1);
}
/// Add a new mod, empty option group of the given type and name if it does not exist already.
- public (IModGroup, int, bool) FindOrAddModGroup(Mod mod, GroupType type, string newName)
+ public (IModGroup, int, bool) FindOrAddModGroup(Mod mod, GroupType type, string newName, SaveType saveType = SaveType.ImmediateSync)
{
var idx = mod.Groups.IndexOf(g => g.Name == newName);
if (idx >= 0)
return (mod.Groups[idx], idx, false);
- AddModGroup(mod, type, newName);
+ AddModGroup(mod, type, newName, saveType);
if (mod.Groups[^1].Name != newName)
throw new Exception($"Could not create new mod group with name {newName}.");
@@ -226,7 +226,7 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
}
/// Add a new empty option of the given name for the given group.
- public void AddOption(Mod mod, int groupIdx, string newName)
+ public void AddOption(Mod mod, int groupIdx, string newName, SaveType saveType = SaveType.Queue)
{
var group = mod.Groups[groupIdx];
var subMod = new SubMod(mod) { Name = newName };
@@ -241,19 +241,19 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
break;
}
- saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
+ saveService.Save(saveType, new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionAdded, mod, groupIdx, group.Count - 1, -1);
}
/// Add a new empty option of the given name for the given group if it does not exist already.
- public (SubMod, bool) FindOrAddOption(Mod mod, int groupIdx, string newName)
+ public (SubMod, bool) FindOrAddOption(Mod mod, int groupIdx, string newName, SaveType saveType = SaveType.Queue)
{
var group = mod.Groups[groupIdx];
var idx = group.IndexOf(o => o.Name == newName);
if (idx >= 0)
return ((SubMod)group[idx], false);
- AddOption(mod, groupIdx, newName);
+ AddOption(mod, groupIdx, newName, saveType);
if (group[^1].Name != newName)
throw new Exception($"Could not create new option with name {newName} in {group.Name}.");
@@ -324,7 +324,8 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
}
/// Set the meta manipulations for a given option. Replaces existing manipulations.
- public void OptionSetManipulations(Mod mod, int groupIdx, int optionIdx, HashSet manipulations)
+ public void OptionSetManipulations(Mod mod, int groupIdx, int optionIdx, HashSet manipulations,
+ SaveType saveType = SaveType.Queue)
{
var subMod = GetSubMod(mod, groupIdx, optionIdx);
if (subMod.Manipulations.Count == manipulations.Count
@@ -333,12 +334,13 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
subMod.ManipulationData.SetTo(manipulations);
- saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
+ saveService.Save(saveType, new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionMetaChanged, mod, groupIdx, optionIdx, -1);
}
/// Set the file redirections for a given option. Replaces existing redirections.
- public void OptionSetFiles(Mod mod, int groupIdx, int optionIdx, IReadOnlyDictionary replacements)
+ public void OptionSetFiles(Mod mod, int groupIdx, int optionIdx, IReadOnlyDictionary replacements,
+ SaveType saveType = SaveType.Queue)
{
var subMod = GetSubMod(mod, groupIdx, optionIdx);
if (subMod.FileData.SetEquals(replacements))
@@ -346,7 +348,7 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
subMod.FileData.SetTo(replacements);
- saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
+ saveService.Save(saveType, new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionFilesChanged, mod, groupIdx, optionIdx, -1);
}
@@ -364,7 +366,8 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
}
/// Set the file swaps for a given option. Replaces existing swaps.
- public void OptionSetFileSwaps(Mod mod, int groupIdx, int optionIdx, IReadOnlyDictionary swaps)
+ public void OptionSetFileSwaps(Mod mod, int groupIdx, int optionIdx, IReadOnlyDictionary swaps,
+ SaveType saveType = SaveType.Queue)
{
var subMod = GetSubMod(mod, groupIdx, optionIdx);
if (subMod.FileSwapData.SetEquals(swaps))
@@ -372,7 +375,7 @@ public class ModOptionEditor(CommunicatorService communicator, SaveService saveS
communicator.ModOptionChanged.Invoke(ModOptionChangeType.PrepareChange, mod, groupIdx, optionIdx, -1);
subMod.FileSwapData.SetTo(swaps);
- saveService.QueueSave(new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
+ saveService.Save(saveType, new ModSaveGroup(mod, groupIdx, config.ReplaceNonAsciiOnImport));
communicator.ModOptionChanged.Invoke(ModOptionChangeType.OptionSwapsChanged, mod, groupIdx, optionIdx, -1);
}
diff --git a/Penumbra/Services/CrashHandlerService.cs b/Penumbra/Services/CrashHandlerService.cs
index 5423ec15..078b812b 100644
--- a/Penumbra/Services/CrashHandlerService.cs
+++ b/Penumbra/Services/CrashHandlerService.cs
@@ -213,7 +213,7 @@ public sealed class CrashHandlerService : IDisposable, IService
}
catch (Exception ex)
{
- Penumbra.Log.Debug($"Could not delete {dir}:\n{ex}");
+ Penumbra.Log.Verbose($"Could not delete {dir}. This is generally not an error:\n{ex}");
}
}
}