mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-15 05:04:15 +01:00
170 lines
5.5 KiB
C#
170 lines
5.5 KiB
C#
using OtterGui.Services;
|
|
using Penumbra.Mods.Manager;
|
|
using Penumbra.Mods.SubMods;
|
|
using Penumbra.Services;
|
|
using Penumbra.String.Classes;
|
|
|
|
namespace Penumbra.Mods.Editor;
|
|
|
|
public class ModFileEditor(ModFileCollection files, ModManager modManager, CommunicatorService communicator) : IService
|
|
{
|
|
public bool Changes { get; private set; }
|
|
|
|
public void Clear()
|
|
{
|
|
Changes = false;
|
|
}
|
|
|
|
public int Apply(Mod mod, IModDataContainer option)
|
|
{
|
|
var dict = new Dictionary<Utf8GamePath, FullPath>();
|
|
var num = 0;
|
|
foreach (var file in files.Available)
|
|
{
|
|
foreach (var path in file.SubModUsage.Where(p => p.Item1 == option))
|
|
num += dict.TryAdd(path.Item2, file.File) ? 0 : 1;
|
|
}
|
|
|
|
modManager.OptionEditor.SetFiles(option, dict);
|
|
files.UpdatePaths(mod, option);
|
|
Changes = false;
|
|
return num;
|
|
}
|
|
|
|
public void Revert(Mod mod, IModDataContainer option)
|
|
{
|
|
files.UpdateAll(mod, option);
|
|
Changes = false;
|
|
}
|
|
|
|
/// <summary> Remove all path redirections where the pointed-to file does not exist. </summary>
|
|
public void RemoveMissingPaths(Mod mod, IModDataContainer option)
|
|
{
|
|
void HandleSubMod(IModDataContainer subMod)
|
|
{
|
|
var newDict = subMod.Files.Where(kvp => CheckAgainstMissing(mod, subMod, kvp.Value, kvp.Key, subMod == option))
|
|
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
|
if (newDict.Count != subMod.Files.Count)
|
|
modManager.OptionEditor.SetFiles(subMod, newDict);
|
|
}
|
|
|
|
ModEditor.ApplyToAllContainers(mod, HandleSubMod);
|
|
files.ClearMissingFiles();
|
|
}
|
|
|
|
/// <summary> Return whether the given path is already used in the current option. </summary>
|
|
public bool CanAddGamePath(Utf8GamePath path)
|
|
=> !files.UsedPaths.Contains(path);
|
|
|
|
/// <summary>
|
|
/// Try to set a given path for a given file.
|
|
/// Returns false if this is not possible.
|
|
/// If path is empty, it will be deleted instead.
|
|
/// If pathIdx is equal to the total number of paths, path will be added, otherwise replaced.
|
|
/// </summary>
|
|
public bool SetGamePath(IModDataContainer option, int fileIdx, int pathIdx, Utf8GamePath path)
|
|
{
|
|
if (!CanAddGamePath(path) || fileIdx < 0 || fileIdx > files.Available.Count)
|
|
return false;
|
|
|
|
var registry = files.Available[fileIdx];
|
|
if (pathIdx > registry.SubModUsage.Count)
|
|
return false;
|
|
|
|
if ((pathIdx == -1 || pathIdx == registry.SubModUsage.Count) && !path.IsEmpty)
|
|
files.AddUsedPath(option, registry, path);
|
|
else
|
|
files.ChangeUsedPath(registry, pathIdx, path);
|
|
|
|
Changes = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Transform a set of files to the appropriate game paths with the given number of folders skipped,
|
|
/// and add them to the given option.
|
|
/// </summary>
|
|
public int AddPathsToSelected(IModDataContainer option, IEnumerable<FileRegistry> files1, int skipFolders = 0)
|
|
{
|
|
var failed = 0;
|
|
foreach (var file in files1)
|
|
{
|
|
var gamePath = file.RelPath.ToGamePath(skipFolders);
|
|
if (gamePath.IsEmpty)
|
|
{
|
|
++failed;
|
|
continue;
|
|
}
|
|
|
|
if (CanAddGamePath(gamePath))
|
|
{
|
|
files.AddUsedPath(option, file, gamePath);
|
|
Changes = true;
|
|
}
|
|
else
|
|
{
|
|
++failed;
|
|
}
|
|
}
|
|
|
|
return failed;
|
|
}
|
|
|
|
/// <summary> Remove all paths in the current option from the given files. </summary>
|
|
public void RemovePathsFromSelected(IModDataContainer option, IEnumerable<FileRegistry> files1)
|
|
{
|
|
foreach (var file in files1)
|
|
{
|
|
for (var i = 0; i < file.SubModUsage.Count; ++i)
|
|
{
|
|
var (opt, path) = file.SubModUsage[i];
|
|
if (option != opt)
|
|
continue;
|
|
|
|
files.RemoveUsedPath(option, file, path);
|
|
Changes = true;
|
|
--i;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary> Delete all given files from your filesystem </summary>
|
|
public void DeleteFiles(Mod mod, IModDataContainer option, IEnumerable<FileRegistry> files1)
|
|
{
|
|
var deletions = 0;
|
|
foreach (var file in files1)
|
|
{
|
|
try
|
|
{
|
|
File.Delete(file.File.FullName);
|
|
communicator.ModFileChanged.Invoke(mod, file);
|
|
Penumbra.Log.Debug($"[DeleteFiles] Deleted {file.File.FullName} from {mod.Name}.");
|
|
++deletions;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Penumbra.Log.Error($"[DeleteFiles] Could not delete {file.File.FullName} from {mod.Name}:\n{e}");
|
|
}
|
|
}
|
|
|
|
if (deletions <= 0)
|
|
return;
|
|
|
|
modManager.Creator.ReloadMod(mod, false, false, out _);
|
|
files.UpdateAll(mod, option);
|
|
}
|
|
|
|
|
|
private bool CheckAgainstMissing(Mod mod, IModDataContainer option, FullPath file, Utf8GamePath key, bool removeUsed)
|
|
{
|
|
if (!files.Missing.Contains(file))
|
|
return true;
|
|
|
|
if (removeUsed)
|
|
files.RemoveUsedPath(option, file, key);
|
|
|
|
Penumbra.Log.Debug($"[RemoveMissingPaths] Removing {key} -> {file} from {mod.Name}.");
|
|
return false;
|
|
}
|
|
}
|