mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Add automatic reduplication for ui files in pmps, test.
This commit is contained in:
parent
6beb2416dc
commit
40be298d67
6 changed files with 145 additions and 39 deletions
|
|
@ -96,6 +96,7 @@ public class Configuration : IPluginConfiguration, ISavable, IService
|
||||||
public DoubleModifier DeleteModModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);
|
public DoubleModifier DeleteModModifier { get; set; } = new(ModifierHotkey.Control, ModifierHotkey.Shift);
|
||||||
public bool PrintSuccessfulCommandsToChat { get; set; } = true;
|
public bool PrintSuccessfulCommandsToChat { get; set; } = true;
|
||||||
public bool AutoDeduplicateOnImport { get; set; } = true;
|
public bool AutoDeduplicateOnImport { get; set; } = true;
|
||||||
|
public bool AutoReduplicateUiOnImport { get; set; } = true;
|
||||||
public bool UseFileSystemCompression { get; set; } = true;
|
public bool UseFileSystemCompression { get; set; } = true;
|
||||||
public bool EnableHttpApi { get; set; } = true;
|
public bool EnableHttpApi { get; set; } = true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
using System.IO;
|
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
using Penumbra.GameData.Files;
|
|
||||||
using Penumbra.Import.Structs;
|
using Penumbra.Import.Structs;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using SharpCompress.Archives;
|
using SharpCompress.Archives;
|
||||||
|
|
@ -11,7 +9,6 @@ using SharpCompress.Archives.Rar;
|
||||||
using SharpCompress.Archives.SevenZip;
|
using SharpCompress.Archives.SevenZip;
|
||||||
using SharpCompress.Common;
|
using SharpCompress.Common;
|
||||||
using SharpCompress.Readers;
|
using SharpCompress.Readers;
|
||||||
using static FFXIVClientStructs.FFXIV.Client.UI.Misc.ConfigModule;
|
|
||||||
using ZipArchive = SharpCompress.Archives.Zip.ZipArchive;
|
using ZipArchive = SharpCompress.Archives.Zip.ZipArchive;
|
||||||
|
|
||||||
namespace Penumbra.Import;
|
namespace Penumbra.Import;
|
||||||
|
|
@ -114,6 +111,7 @@ public partial class TexToolsImporter
|
||||||
|
|
||||||
_currentModDirectory.Refresh();
|
_currentModDirectory.Refresh();
|
||||||
_modManager.Creator.SplitMultiGroups(_currentModDirectory);
|
_modManager.Creator.SplitMultiGroups(_currentModDirectory);
|
||||||
|
_editor.ModNormalizer.NormalizeUi(_currentModDirectory);
|
||||||
|
|
||||||
return _currentModDirectory;
|
return _currentModDirectory;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,59 +20,61 @@ public partial class TexToolsImporter
|
||||||
private string _currentOptionName = string.Empty;
|
private string _currentOptionName = string.Empty;
|
||||||
private string _currentFileName = string.Empty;
|
private string _currentFileName = string.Empty;
|
||||||
|
|
||||||
public void DrawProgressInfo(Vector2 size)
|
public bool DrawProgressInfo(Vector2 size)
|
||||||
{
|
{
|
||||||
if (_modPackCount == 0)
|
if (_modPackCount == 0)
|
||||||
{
|
{
|
||||||
ImGuiUtil.Center("Nothing to extract.");
|
ImGuiUtil.Center("Nothing to extract.");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
else if (_modPackCount == _currentModPackIdx)
|
|
||||||
{
|
if (_modPackCount == _currentModPackIdx)
|
||||||
DrawEndState();
|
return DrawEndState();
|
||||||
}
|
|
||||||
|
ImGui.NewLine();
|
||||||
|
var percentage = (float)_currentModPackIdx / _modPackCount;
|
||||||
|
ImGui.ProgressBar(percentage, size, $"Mod {_currentModPackIdx + 1} / {_modPackCount}");
|
||||||
|
ImGui.NewLine();
|
||||||
|
if (State == ImporterState.DeduplicatingFiles)
|
||||||
|
ImGui.TextUnformatted($"Deduplicating {_currentModName}...");
|
||||||
else
|
else
|
||||||
|
ImGui.TextUnformatted($"Extracting {_currentModName}...");
|
||||||
|
|
||||||
|
if (_currentNumOptions > 1)
|
||||||
{
|
{
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
var percentage = (float)_currentModPackIdx / _modPackCount;
|
|
||||||
ImGui.ProgressBar(percentage, size, $"Mod {_currentModPackIdx + 1} / {_modPackCount}");
|
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
if (State == ImporterState.DeduplicatingFiles)
|
percentage = _currentNumOptions == 0 ? 1f : _currentOptionIdx / (float)_currentNumOptions;
|
||||||
ImGui.TextUnformatted($"Deduplicating {_currentModName}...");
|
ImGui.ProgressBar(percentage, size, $"Option {_currentOptionIdx + 1} / {_currentNumOptions}");
|
||||||
else
|
|
||||||
ImGui.TextUnformatted($"Extracting {_currentModName}...");
|
|
||||||
|
|
||||||
if (_currentNumOptions > 1)
|
|
||||||
{
|
|
||||||
ImGui.NewLine();
|
|
||||||
ImGui.NewLine();
|
|
||||||
percentage = _currentNumOptions == 0 ? 1f : _currentOptionIdx / (float)_currentNumOptions;
|
|
||||||
ImGui.ProgressBar(percentage, size, $"Option {_currentOptionIdx + 1} / {_currentNumOptions}");
|
|
||||||
ImGui.NewLine();
|
|
||||||
if (State != ImporterState.DeduplicatingFiles)
|
|
||||||
ImGui.TextUnformatted(
|
|
||||||
$"Extracting option {(_currentGroupName.Length == 0 ? string.Empty : $"{_currentGroupName} - ")}{_currentOptionName}...");
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui.NewLine();
|
|
||||||
ImGui.NewLine();
|
|
||||||
percentage = _currentNumFiles == 0 ? 1f : _currentFileIdx / (float)_currentNumFiles;
|
|
||||||
ImGui.ProgressBar(percentage, size, $"File {_currentFileIdx + 1} / {_currentNumFiles}");
|
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
if (State != ImporterState.DeduplicatingFiles)
|
if (State != ImporterState.DeduplicatingFiles)
|
||||||
ImGui.TextUnformatted($"Extracting file {_currentFileName}...");
|
ImGui.TextUnformatted(
|
||||||
|
$"Extracting option {(_currentGroupName.Length == 0 ? string.Empty : $"{_currentGroupName} - ")}{_currentOptionName}...");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImGui.NewLine();
|
||||||
|
ImGui.NewLine();
|
||||||
|
percentage = _currentNumFiles == 0 ? 1f : _currentFileIdx / (float)_currentNumFiles;
|
||||||
|
ImGui.ProgressBar(percentage, size, $"File {_currentFileIdx + 1} / {_currentNumFiles}");
|
||||||
|
ImGui.NewLine();
|
||||||
|
if (State != ImporterState.DeduplicatingFiles)
|
||||||
|
ImGui.TextUnformatted($"Extracting file {_currentFileName}...");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void DrawEndState()
|
private bool DrawEndState()
|
||||||
{
|
{
|
||||||
var success = ExtractedMods.Count(t => t.Error == null);
|
var success = ExtractedMods.Count(t => t.Error == null);
|
||||||
|
|
||||||
|
if (ImGui.IsKeyPressed(ImGuiKey.Escape))
|
||||||
|
return true;
|
||||||
|
|
||||||
ImGui.TextUnformatted($"Successfully extracted {success} / {ExtractedMods.Count} files.");
|
ImGui.TextUnformatted($"Successfully extracted {success} / {ExtractedMods.Count} files.");
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
using var table = ImRaii.Table("##files", 2);
|
using var table = ImRaii.Table("##files", 2);
|
||||||
if (!table)
|
if (!table)
|
||||||
return;
|
return false;
|
||||||
|
|
||||||
foreach (var (file, dir, ex) in ExtractedMods)
|
foreach (var (file, dir, ex) in ExtractedMods)
|
||||||
{
|
{
|
||||||
|
|
@ -91,6 +93,8 @@ public partial class TexToolsImporter
|
||||||
ImGuiUtil.HoverTooltip(ex.ToString());
|
ImGuiUtil.HoverTooltip(ex.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool DrawCancelButton(Vector2 size)
|
public bool DrawCancelButton(Vector2 size)
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,15 @@ using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using OtterGui.Tasks;
|
using OtterGui.Tasks;
|
||||||
|
using Penumbra.Mods.Groups;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
using Penumbra.Mods.SubMods;
|
using Penumbra.Mods.SubMods;
|
||||||
|
using Penumbra.Services;
|
||||||
using Penumbra.String.Classes;
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods.Editor;
|
namespace Penumbra.Mods.Editor;
|
||||||
|
|
||||||
public class ModNormalizer(ModManager _modManager, Configuration _config) : IService
|
public class ModNormalizer(ModManager modManager, Configuration config, SaveService saveService) : IService
|
||||||
{
|
{
|
||||||
private readonly List<List<Dictionary<Utf8GamePath, FullPath>>> _redirections = [];
|
private readonly List<List<Dictionary<Utf8GamePath, FullPath>>> _redirections = [];
|
||||||
|
|
||||||
|
|
@ -39,6 +41,103 @@ public class ModNormalizer(ModManager _modManager, Configuration _config) : ISer
|
||||||
Worker = TrackedTask.Run(NormalizeSync);
|
Worker = TrackedTask.Run(NormalizeSync);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void NormalizeUi(DirectoryInfo modDirectory)
|
||||||
|
{
|
||||||
|
if (!config.AutoReduplicateUiOnImport)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (modManager.Creator.LoadMod(modDirectory, false) is not { } mod)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Dictionary<FullPath, List<(IModDataContainer, Utf8GamePath)>> paths = [];
|
||||||
|
Dictionary<IModDataContainer, string> containers = [];
|
||||||
|
foreach (var container in mod.AllDataContainers)
|
||||||
|
{
|
||||||
|
foreach (var (gamePath, path) in container.Files)
|
||||||
|
{
|
||||||
|
if (!gamePath.Path.StartsWith("ui/"u8))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!paths.TryGetValue(path, out var list))
|
||||||
|
{
|
||||||
|
list = [];
|
||||||
|
paths.Add(path, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Add((container, gamePath));
|
||||||
|
containers.TryAdd(container, string.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var container in containers.Keys.ToList())
|
||||||
|
{
|
||||||
|
if (container.Group == null)
|
||||||
|
containers[container] = mod.ModPath.FullName;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var groupDir = ModCreator.NewOptionDirectory(mod.ModPath, container.Group.Name, config.ReplaceNonAsciiOnImport);
|
||||||
|
var optionDir = ModCreator.NewOptionDirectory(groupDir, container.GetName(), config.ReplaceNonAsciiOnImport);
|
||||||
|
containers[container] = optionDir.FullName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var anyChanges = 0;
|
||||||
|
var modRootLength = mod.ModPath.FullName.Length + 1;
|
||||||
|
foreach (var (file, gamePaths) in paths)
|
||||||
|
{
|
||||||
|
if (gamePaths.Count < 2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var keptPath = false;
|
||||||
|
foreach (var (container, gamePath) in gamePaths)
|
||||||
|
{
|
||||||
|
var directory = containers[container];
|
||||||
|
var relPath = new Utf8RelPath(gamePath).ToString();
|
||||||
|
var newFilePath = Path.Combine(directory, relPath);
|
||||||
|
if (newFilePath == file.FullName)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Verbose($"[UIReduplication] Kept {file.FullName[modRootLength..]} because new path was identical.");
|
||||||
|
keptPath = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(Path.GetDirectoryName(newFilePath)!);
|
||||||
|
File.Copy(file.FullName, newFilePath, false);
|
||||||
|
Penumbra.Log.Verbose($"[UIReduplication] Copied {file.FullName[modRootLength..]} to {newFilePath[modRootLength..]}.");
|
||||||
|
container.Files[gamePath] = new FullPath(newFilePath);
|
||||||
|
++anyChanges;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error(
|
||||||
|
$"[UIReduplication] Failed to copy {file.FullName[modRootLength..]} to {newFilePath[modRootLength..]}:\n{ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keptPath)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(file.FullName);
|
||||||
|
Penumbra.Log.Verbose($"[UIReduplication] Deleted {file.FullName[modRootLength..]} because no new path matched.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error($"[UIReduplication] Failed to delete {file.FullName[modRootLength..]}:\n{ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anyChanges == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
saveService.Save(SaveType.ImmediateSync, new ModSaveGroup(mod.Default, config.ReplaceNonAsciiOnImport));
|
||||||
|
saveService.SaveAllOptionGroups(mod, false, config.ReplaceNonAsciiOnImport);
|
||||||
|
Penumbra.Log.Information($"[UIReduplication] Saved groups after {anyChanges} changes.");
|
||||||
|
}
|
||||||
|
|
||||||
private void NormalizeSync()
|
private void NormalizeSync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -168,7 +267,7 @@ public class ModNormalizer(ModManager _modManager, Configuration _config) : ISer
|
||||||
// Normalize all other options.
|
// Normalize all other options.
|
||||||
foreach (var (group, groupIdx) in Mod.Groups.WithIndex())
|
foreach (var (group, groupIdx) in Mod.Groups.WithIndex())
|
||||||
{
|
{
|
||||||
var groupDir = ModCreator.CreateModFolder(directory, group.Name, _config.ReplaceNonAsciiOnImport, true);
|
var groupDir = ModCreator.CreateModFolder(directory, group.Name, config.ReplaceNonAsciiOnImport, true);
|
||||||
_redirections[groupIdx + 1].EnsureCapacity(group.DataContainers.Count);
|
_redirections[groupIdx + 1].EnsureCapacity(group.DataContainers.Count);
|
||||||
for (var i = _redirections[groupIdx + 1].Count; i < group.DataContainers.Count; ++i)
|
for (var i = _redirections[groupIdx + 1].Count; i < group.DataContainers.Count; ++i)
|
||||||
_redirections[groupIdx + 1].Add([]);
|
_redirections[groupIdx + 1].Add([]);
|
||||||
|
|
@ -188,7 +287,7 @@ public class ModNormalizer(ModManager _modManager, Configuration _config) : ISer
|
||||||
void HandleSubMod(DirectoryInfo groupDir, IModDataContainer option, Dictionary<Utf8GamePath, FullPath> newDict)
|
void HandleSubMod(DirectoryInfo groupDir, IModDataContainer option, Dictionary<Utf8GamePath, FullPath> newDict)
|
||||||
{
|
{
|
||||||
var name = option.GetName();
|
var name = option.GetName();
|
||||||
var optionDir = ModCreator.CreateModFolder(groupDir, name, _config.ReplaceNonAsciiOnImport, true);
|
var optionDir = ModCreator.CreateModFolder(groupDir, name, config.ReplaceNonAsciiOnImport, true);
|
||||||
|
|
||||||
newDict.Clear();
|
newDict.Clear();
|
||||||
newDict.EnsureCapacity(option.Files.Count);
|
newDict.EnsureCapacity(option.Files.Count);
|
||||||
|
|
@ -277,10 +376,10 @@ public class ModNormalizer(ModManager _modManager, Configuration _config) : ISer
|
||||||
|
|
||||||
private void ApplyRedirections()
|
private void ApplyRedirections()
|
||||||
{
|
{
|
||||||
_modManager.OptionEditor.SetFiles(Mod.Default, _redirections[0][0]);
|
modManager.OptionEditor.SetFiles(Mod.Default, _redirections[0][0]);
|
||||||
foreach (var (group, groupIdx) in Mod.Groups.WithIndex())
|
foreach (var (group, groupIdx) in Mod.Groups.WithIndex())
|
||||||
foreach (var (container, containerIdx) in group.DataContainers.WithIndex())
|
foreach (var (container, containerIdx) in group.DataContainers.WithIndex())
|
||||||
_modManager.OptionEditor.SetFiles(container, _redirections[groupIdx + 1][containerIdx]);
|
modManager.OptionEditor.SetFiles(container, _redirections[groupIdx + 1][containerIdx]);
|
||||||
|
|
||||||
++Step;
|
++Step;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -213,6 +213,7 @@ public class Penumbra : IDalamudPlugin
|
||||||
sb.Append(
|
sb.Append(
|
||||||
$"> **`Free Drive Space: `** {(drive != null ? Functions.HumanReadableSize(drive.AvailableFreeSpace) : "Unknown")}\n");
|
$"> **`Free Drive Space: `** {(drive != null ? Functions.HumanReadableSize(drive.AvailableFreeSpace) : "Unknown")}\n");
|
||||||
sb.Append($"> **`Auto-Deduplication: `** {_config.AutoDeduplicateOnImport}\n");
|
sb.Append($"> **`Auto-Deduplication: `** {_config.AutoDeduplicateOnImport}\n");
|
||||||
|
sb.Append($"> **`Auto-UI-Reduplication: `** {_config.AutoReduplicateUiOnImport}\n");
|
||||||
sb.Append($"> **`Debug Mode: `** {_config.DebugMode}\n");
|
sb.Append($"> **`Debug Mode: `** {_config.DebugMode}\n");
|
||||||
sb.Append(
|
sb.Append(
|
||||||
$"> **`Synchronous Load (Dalamud): `** {(_services.GetService<DalamudConfigService>().GetDalamudConfig(DalamudConfigService.WaitingForPluginsOption, out bool v) ? v.ToString() : "Unknown")}\n");
|
$"> **`Synchronous Load (Dalamud): `** {(_services.GetService<DalamudConfigService>().GetDalamudConfig(DalamudConfigService.WaitingForPluginsOption, out bool v) ? v.ToString() : "Unknown")}\n");
|
||||||
|
|
|
||||||
|
|
@ -757,6 +757,9 @@ public class SettingsTab : ITab, IUiService
|
||||||
Checkbox("Auto Deduplicate on Import",
|
Checkbox("Auto Deduplicate on Import",
|
||||||
"Automatically deduplicate mod files on import. This will make mod file sizes smaller, but deletes (binary identical) files.",
|
"Automatically deduplicate mod files on import. This will make mod file sizes smaller, but deletes (binary identical) files.",
|
||||||
_config.AutoDeduplicateOnImport, v => _config.AutoDeduplicateOnImport = v);
|
_config.AutoDeduplicateOnImport, v => _config.AutoDeduplicateOnImport = v);
|
||||||
|
Checkbox("Auto Reduplicate UI Files on PMP Import",
|
||||||
|
"Automatically reduplicate and normalize UI-specific files on import from PMP files. This is STRONGLY recommended because deduplicated UI files crash the game.",
|
||||||
|
_config.AutoReduplicateUiOnImport, v => _config.AutoReduplicateUiOnImport = v);
|
||||||
DrawCompressionBox();
|
DrawCompressionBox();
|
||||||
Checkbox("Keep Default Metadata Changes on Import",
|
Checkbox("Keep Default Metadata Changes on Import",
|
||||||
"Normally, metadata changes that equal their default values, which are sometimes exported by TexTools, are discarded. "
|
"Normally, metadata changes that equal their default values, which are sometimes exported by TexTools, are discarded. "
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue