From b677a14ceffe6d8a1c0e94d47e1af3bfd19e1616 Mon Sep 17 00:00:00 2001 From: Ottermandias Date: Tue, 9 Jul 2024 16:34:31 +0200 Subject: [PATCH] Update. --- Penumbra.Api | 2 +- Penumbra.GameData | 2 +- Penumbra/Configuration.cs | 3 +- Penumbra/Import/TexToolsImporter.Archives.cs | 11 +- Penumbra/Import/TexToolsImporter.ModPack.cs | 5 +- .../Animation/ApricotListenerSoundPlay.cs | 2 + Penumbra/Services/MigrationManager.cs | 340 +++++++++++------- Penumbra/UI/Classes/MigrationSectionDrawer.cs | 155 +++++--- Penumbra/UI/Tabs/Debug/DebugTab.cs | 10 +- 9 files changed, 338 insertions(+), 192 deletions(-) diff --git a/Penumbra.Api b/Penumbra.Api index 43b0b47f..f4c6144c 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 43b0b47f2d019af0fe4681dfc578f9232e3ba90c +Subproject commit f4c6144ca2012b279e6d8aa52b2bef6cc2ba32d9 diff --git a/Penumbra.GameData b/Penumbra.GameData index d7a56b70..cf5be8af 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit d7a56b708c73bc9917baeaa66842c1594ca3067b +Subproject commit cf5be8af4c9ecbd9190bd3db746743fa5cd1560f diff --git a/Penumbra/Configuration.cs b/Penumbra/Configuration.cs index 8d0f7fd8..f16569b5 100644 --- a/Penumbra/Configuration.cs +++ b/Penumbra/Configuration.cs @@ -99,7 +99,8 @@ public class Configuration : IPluginConfiguration, ISavable, IService public bool UseFileSystemCompression { get; set; } = true; public bool EnableHttpApi { get; set; } = true; - public bool MigrateImportedModelsToV6 { get; set; } = false; + public bool MigrateImportedModelsToV6 { get; set; } = true; + public bool MigrateImportedMaterialsToLegacy { get; set; } = true; public string DefaultModImportPath { get; set; } = string.Empty; public bool AlwaysOpenDefaultImport { get; set; } = false; diff --git a/Penumbra/Import/TexToolsImporter.Archives.cs b/Penumbra/Import/TexToolsImporter.Archives.cs index a51dbc61..63c170cb 100644 --- a/Penumbra/Import/TexToolsImporter.Archives.cs +++ b/Penumbra/Import/TexToolsImporter.Archives.cs @@ -1,3 +1,4 @@ +using System.IO; using Dalamud.Utility; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -90,7 +91,7 @@ public partial class TexToolsImporter } else { - HandleFileMigrations(reader); + HandleFileMigrationsAndWrite(reader); } ++_currentFileIdx; @@ -118,13 +119,19 @@ public partial class TexToolsImporter } - private void HandleFileMigrations(IReader reader) + private void HandleFileMigrationsAndWrite(IReader reader) { switch (Path.GetExtension(reader.Entry.Key)) { case ".mdl": _migrationManager.MigrateMdlDuringExtraction(reader, _currentModDirectory!.FullName, _extractionOptions); break; + case ".mtrl": + _migrationManager.MigrateMtrlDuringExtraction(reader, _currentModDirectory!.FullName, _extractionOptions); + break; + default: + reader.WriteEntryToDirectory(_currentModDirectory!.FullName, _extractionOptions); + break; } } diff --git a/Penumbra/Import/TexToolsImporter.ModPack.cs b/Penumbra/Import/TexToolsImporter.ModPack.cs index ba294353..3ae1eda9 100644 --- a/Penumbra/Import/TexToolsImporter.ModPack.cs +++ b/Penumbra/Import/TexToolsImporter.ModPack.cs @@ -255,8 +255,9 @@ public partial class TexToolsImporter data.Data = Path.GetExtension(extractedFile.FullName) switch { - ".mdl" => _migrationManager.MigrateTtmpModel(extractedFile.FullName, data.Data), - _ => data.Data, + ".mdl" => _migrationManager.MigrateTtmpModel(extractedFile.FullName, data.Data), + ".mtrl" => _migrationManager.MigrateTtmpMaterial(extractedFile.FullName, data.Data), + _ => data.Data, }; _compactor.WriteAllBytesAsync(extractedFile.FullName, data.Data, _token).Wait(_token); diff --git a/Penumbra/Interop/Hooks/Animation/ApricotListenerSoundPlay.cs b/Penumbra/Interop/Hooks/Animation/ApricotListenerSoundPlay.cs index e58c7268..2e05c1b6 100644 --- a/Penumbra/Interop/Hooks/Animation/ApricotListenerSoundPlay.cs +++ b/Penumbra/Interop/Hooks/Animation/ApricotListenerSoundPlay.cs @@ -1,3 +1,4 @@ +using Dalamud.Hooking; using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using OtterGui.Services; @@ -16,6 +17,7 @@ public sealed unsafe class ApricotListenerSoundPlay : FastHook Changed + Unchanged + Failed; + + public void Init() + { + Changed = 0; + Unchanged = 0; + Failed = 0; + HasData = true; + } + } + private Task? _currentTask; private CancellationTokenSource? _source; - public bool HasCleanUpTask { get; private set; } - public bool HasMigrationTask { get; private set; } - public bool HasRestoreTask { get; private set; } + public TaskType CurrentTask { get; private set; } - public bool IsMigrationTask { get; private set; } - public bool IsRestorationTask { get; private set; } - public bool IsCleanupTask { get; private set; } + public readonly MigrationData MdlMigration = new(true); + public readonly MigrationData MtrlMigration = new(true); + public readonly MigrationData MdlCleanup = new(false); + public readonly MigrationData MtrlCleanup = new(false); + public readonly MigrationData MdlRestoration = new(false); + public readonly MigrationData MtrlRestoration = new(false); - public int Restored { get; private set; } - public int RestoreFails { get; private set; } - - public int CleanedUp { get; private set; } - - public int CleanupFails { get; private set; } - - public int Migrated { get; private set; } - - public int Unchanged { get; private set; } - - public int Failed { get; private set; } - public bool IsRunning => _currentTask is { IsCompleted: false }; - /// Writes or migrates a .mdl file during extraction from a regular archive. - public void MigrateMdlDuringExtraction(IReader reader, string directory, ExtractionOptions options) - { - if (!config.MigrateImportedModelsToV6) - { - reader.WriteEntryToDirectory(directory, options); - return; - } + public void CleanMdlBackups(string path) + => CleanBackups(path, "*.mdl.bak", "model", MdlCleanup, TaskType.MdlCleanup); - var path = Path.Combine(directory, reader.Entry.Key); - using var s = new MemoryStream(); - using var e = reader.OpenEntryStream(); - e.CopyTo(s); - using var b = new BinaryReader(s); - var version = b.ReadUInt32(); - if (version == MdlFile.V5) - { - var data = s.ToArray(); - var mdl = new MdlFile(data); - MigrateModel(path, mdl, false); - Penumbra.Log.Debug($"Migrated model {reader.Entry.Key} from V5 to V6 during import."); - } - else - { - using var f = File.Open(path, FileMode.Create, FileAccess.Write); - s.Seek(0, SeekOrigin.Begin); - s.WriteTo(f); - } - } + public void CleanMtrlBackups(string path) + => CleanBackups(path, "*.mtrl.bak", "material", MtrlCleanup, TaskType.MtrlCleanup); - public void CleanBackups(string path) + private void CleanBackups(string path, string extension, string fileType, MigrationData data, TaskType type) { if (IsRunning) return; @@ -76,13 +72,9 @@ public class MigrationManager(Configuration config) : IService var token = _source.Token; _currentTask = Task.Run(() => { - HasCleanUpTask = true; - IsCleanupTask = true; - IsMigrationTask = false; - IsRestorationTask = false; - CleanedUp = 0; - CleanupFails = 0; - foreach (var file in Directory.EnumerateFiles(path, "*.mdl.bak", SearchOption.AllDirectories)) + CurrentTask = type; + data.Init(); + foreach (var file in Directory.EnumerateFiles(path, extension, SearchOption.AllDirectories)) { if (token.IsCancellationRequested) return; @@ -90,19 +82,25 @@ public class MigrationManager(Configuration config) : IService try { File.Delete(file); - ++CleanedUp; - Penumbra.Log.Debug($"Deleted model backup file {file}."); + ++data.Changed; + Penumbra.Log.Debug($"Deleted {fileType} backup file {file}."); } catch (Exception ex) { - Penumbra.Messager.NotificationMessage(ex, $"Failed to delete model backup file {file}", NotificationType.Warning); - ++CleanupFails; + Penumbra.Messager.NotificationMessage(ex, $"Failed to delete {fileType} backup file {file}", NotificationType.Warning); + ++data.Failed; } } }, token); } - public void RestoreBackups(string path) + public void RestoreMdlBackups(string path) + => RestoreBackups(path, "*.mdl.bak", "model", MdlRestoration, TaskType.MdlRestoration); + + public void RestoreMtrlBackups(string path) + => RestoreBackups(path, "*.mtrl.bak", "material", MtrlRestoration, TaskType.MtrlRestoration); + + private void RestoreBackups(string path, string extension, string fileType, MigrationData data, TaskType type) { if (IsRunning) return; @@ -111,13 +109,9 @@ public class MigrationManager(Configuration config) : IService var token = _source.Token; _currentTask = Task.Run(() => { - HasRestoreTask = true; - IsCleanupTask = false; - IsMigrationTask = false; - IsRestorationTask = true; - CleanedUp = 0; - CleanupFails = 0; - foreach (var file in Directory.EnumerateFiles(path, "*.mdl.bak", SearchOption.AllDirectories)) + CurrentTask = type; + data.Init(); + foreach (var file in Directory.EnumerateFiles(path, extension, SearchOption.AllDirectories)) { if (token.IsCancellationRequested) return; @@ -126,40 +120,38 @@ public class MigrationManager(Configuration config) : IService try { File.Copy(file, target, true); - ++Restored; - Penumbra.Log.Debug($"Restored model backup file {file} to {target}."); + ++data.Changed; + Penumbra.Log.Debug($"Restored {fileType} backup file {file} to {target}."); } catch (Exception ex) { - Penumbra.Messager.NotificationMessage(ex, $"Failed to restore model backup file {file} to {target}", + Penumbra.Messager.NotificationMessage(ex, $"Failed to restore {fileType} backup file {file} to {target}", NotificationType.Warning); - ++RestoreFails; + ++data.Failed; } } }, token); } - /// Update the data of a .mdl file during TTMP extraction. Returns either the existing array or a new one. - public byte[] MigrateTtmpModel(string path, byte[] data) - { - FixLodNum(data); - if (!config.MigrateImportedModelsToV6) - return data; + public void MigrateMdlDirectory(string path, bool createBackups) + => MigrateDirectory(path, createBackups, "*.mdl", "model", MdlMigration, TaskType.MdlMigration, "from V5 to V6", "V6", + (file, fileData, backups) => + { + var mdl = new MdlFile(fileData); + return MigrateModel(file, mdl, backups); + }); - var version = BitConverter.ToUInt32(data); - if (version != 5) - return data; + public void MigrateMtrlDirectory(string path, bool createBackups) + => MigrateDirectory(path, createBackups, "*.mtrl", "material", MtrlMigration, TaskType.MtrlMigration, "to Dawntrail", "Dawntrail", + (file, fileData, backups) => + { + var mtrl = new MtrlFile(fileData); + return MigrateMaterial(file, mtrl, backups); + } + ); - var mdl = new MdlFile(data); - if (!mdl.ConvertV5ToV6()) - return data; - - data = mdl.Write(); - Penumbra.Log.Debug($"Migrated model {path} from V5 to V6 during import."); - return data; - } - - public void MigrateDirectory(string path, bool createBackups) + private void MigrateDirectory(string path, bool createBackups, string extension, string fileType, MigrationData data, TaskType type, + string action, string state, Func func) { if (IsRunning) return; @@ -168,14 +160,9 @@ public class MigrationManager(Configuration config) : IService var token = _source.Token; _currentTask = Task.Run(() => { - HasMigrationTask = true; - IsCleanupTask = false; - IsMigrationTask = true; - IsRestorationTask = false; - Unchanged = 0; - Migrated = 0; - Failed = 0; - foreach (var file in Directory.EnumerateFiles(path, "*.mdl", SearchOption.AllDirectories)) + CurrentTask = type; + data.Init(); + foreach (var file in Directory.EnumerateFiles(path, extension, SearchOption.AllDirectories)) { if (token.IsCancellationRequested) return; @@ -183,24 +170,24 @@ public class MigrationManager(Configuration config) : IService var timer = Stopwatch.StartNew(); try { - var data = File.ReadAllBytes(file); - var mdl = new MdlFile(data); - if (MigrateModel(file, mdl, createBackups)) + var fileData = File.ReadAllBytes(file); + if (func(file, fileData, createBackups)) { - ++Migrated; - Penumbra.Log.Debug($"Migrated model file {file} from V5 to V6 in {timer.ElapsedMilliseconds} ms."); + ++data.Changed; + Penumbra.Log.Debug($"Migrated {fileType} file {file} {action} in {timer.ElapsedMilliseconds} ms."); } else { - ++Unchanged; - Penumbra.Log.Verbose($"Verified that model file {file} is already V6 in {timer.ElapsedMilliseconds} ms."); + ++data.Unchanged; + Penumbra.Log.Verbose($"Verified that {fileType} file {file} is already {state} in {timer.ElapsedMilliseconds} ms."); } } catch (Exception ex) { - Penumbra.Messager.NotificationMessage(ex, $"Failed to migrate model file {file} to V6 in {timer.ElapsedMilliseconds} ms", + ++data.Failed; + Penumbra.Messager.NotificationMessage(ex, + $"Failed to migrate {fileType} file {file} to {state} in {timer.ElapsedMilliseconds} ms", NotificationType.Warning); - ++Failed; } } }, token); @@ -213,22 +200,6 @@ public class MigrationManager(Configuration config) : IService _currentTask = null; } - private static void FixLodNum(byte[] data) - { - const int modelHeaderLodOffset = 22; - - // Model file header LOD num - data[64] = 1; - - // Model header LOD num - var stackSize = BitConverter.ToUInt32(data, 4); - var runtimeBegin = stackSize + 0x44; - var stringsLengthOffset = runtimeBegin + 4; - var stringsLength = BitConverter.ToUInt32(data, (int)stringsLengthOffset); - var modelHeaderStart = stringsLengthOffset + stringsLength + 4; - data[modelHeaderStart + modelHeaderLodOffset] = 1; - } - public static bool TryMigrateSingleModel(string path, bool createBackup) { try @@ -259,6 +230,113 @@ public class MigrationManager(Configuration config) : IService } } + /// Writes or migrates a .mdl file during extraction from a regular archive. + public void MigrateMdlDuringExtraction(IReader reader, string directory, ExtractionOptions options) + { + if (!config.MigrateImportedModelsToV6) + { + reader.WriteEntryToDirectory(directory, options); + return; + } + + var path = Path.Combine(directory, reader.Entry.Key); + using var s = new MemoryStream(); + using var e = reader.OpenEntryStream(); + e.CopyTo(s); + using var b = new BinaryReader(s); + var version = b.ReadUInt32(); + if (version == MdlFile.V5) + { + var data = s.ToArray(); + var mdl = new MdlFile(data); + MigrateModel(path, mdl, false); + Penumbra.Log.Debug($"Migrated model {reader.Entry.Key} from V5 to V6 during import."); + } + else + { + using var f = File.Open(path, FileMode.Create, FileAccess.Write); + s.Seek(0, SeekOrigin.Begin); + s.WriteTo(f); + } + } + + public void MigrateMtrlDuringExtraction(IReader reader, string directory, ExtractionOptions options) + { + if (!config.MigrateImportedMaterialsToLegacy) + { + reader.WriteEntryToDirectory(directory, options); + return; + } + + var path = Path.Combine(directory, reader.Entry.Key); + using var s = new MemoryStream(); + using var e = reader.OpenEntryStream(); + e.CopyTo(s); + var file = new MtrlFile(s.GetBuffer()); + if (!file.IsDawnTrail) + { + file.MigrateToDawntrail(); + Penumbra.Log.Debug($"Migrated material {reader.Entry.Key} to Dawntrail during import."); + } + + using var f = File.Open(path, FileMode.Create, FileAccess.Write); + s.Seek(0, SeekOrigin.Begin); + s.WriteTo(f); + } + + /// Update the data of a .mdl file during TTMP extraction. Returns either the existing array or a new one. + public byte[] MigrateTtmpModel(string path, byte[] data) + { + FixLodNum(data); + if (!config.MigrateImportedModelsToV6) + return data; + + var version = BitConverter.ToUInt32(data); + if (version != 5) + return data; + + try + { + var mdl = new MdlFile(data); + if (!mdl.ConvertV5ToV6()) + return data; + + data = mdl.Write(); + Penumbra.Log.Debug($"Migrated model {path} from V5 to V6 during import."); + return data; + } + catch (Exception ex) + { + Penumbra.Log.Warning($"Failed to migrate model {path} from V5 to V6 during import:\n{ex}"); + return data; + } + } + + /// Update the data of a .mtrl file during TTMP extraction. Returns either the existing array or a new one. + public byte[] MigrateTtmpMaterial(string path, byte[] data) + { + if (!config.MigrateImportedMaterialsToLegacy) + return data; + + try + { + var mtrl = new MtrlFile(data); + if (mtrl.IsDawnTrail) + return data; + + mtrl.MigrateToDawntrail(); + data = mtrl.Write(); + Penumbra.Log.Debug($"Migrated material {path} to Dawntrail during import."); + return data; + } + catch (Exception ex) + { + Penumbra.Log.Warning($"Failed to migrate material {path} to Dawntrail during import:\n{ex}"); + return data; + } + } + + private static bool MigrateModel(string path, MdlFile mdl, bool createBackup) { if (!mdl.ConvertV5ToV6()) @@ -284,4 +362,20 @@ public class MigrationManager(Configuration config) : IService File.WriteAllBytes(path, data); return true; } + + private static void FixLodNum(byte[] data) + { + const int modelHeaderLodOffset = 22; + + // Model file header LOD num + data[64] = 1; + + // Model header LOD num + var stackSize = BitConverter.ToUInt32(data, 4); + var runtimeBegin = stackSize + 0x44; + var stringsLengthOffset = runtimeBegin + 4; + var stringsLength = BitConverter.ToUInt32(data, (int)stringsLengthOffset); + var modelHeaderStart = stringsLengthOffset + stringsLength + 4; + data[modelHeaderStart + modelHeaderLodOffset] = 1; + } } diff --git a/Penumbra/UI/Classes/MigrationSectionDrawer.cs b/Penumbra/UI/Classes/MigrationSectionDrawer.cs index 75d37368..ec76ddae 100644 --- a/Penumbra/UI/Classes/MigrationSectionDrawer.cs +++ b/Penumbra/UI/Classes/MigrationSectionDrawer.cs @@ -19,11 +19,13 @@ public class MigrationSectionDrawer(MigrationManager migrationManager, Configura _buttonSize = UiHelpers.InputTextWidth; DrawSettings(); ImGui.Separator(); - DrawMigration(); + DrawMdlMigration(); + DrawMdlRestore(); + DrawMdlCleanup(); ImGui.Separator(); - DrawCleanup(); - ImGui.Separator(); - DrawRestore(); + DrawMtrlMigration(); + DrawMtrlRestore(); + DrawMtrlCleanup(); } private void DrawSettings() @@ -34,88 +36,125 @@ public class MigrationSectionDrawer(MigrationManager migrationManager, Configura config.MigrateImportedModelsToV6 = value; config.Save(); } - } - private void DrawMigration() - { - ImUtf8.Checkbox("Create Backups During Manual Migration", ref _createBackups); - if (ImUtf8.ButtonEx("Migrate Model Files From V5 to V6"u8, "\0"u8, _buttonSize, migrationManager.IsRunning)) - migrationManager.MigrateDirectory(config.ModDirectory, _createBackups); + ImUtf8.HoverTooltip("This increments the version marker and restructures the bone table to the new version."u8); - ImUtf8.SameLineInner(); - DrawCancelButton(0, "Cancel the migration. This does not revert already finished migrations."u8); - DrawSpinner(migrationManager is { IsMigrationTask: true, IsRunning: true }); - - if (!migrationManager.HasMigrationTask) + if (ImUtf8.Checkbox("Automatically Migrate Materials to Dawntrail on Import"u8, ref value)) { - ImUtf8.IconDummy(); - return; + config.MigrateImportedMaterialsToLegacy = value; + config.Save(); } - var total = migrationManager.Failed + migrationManager.Migrated + migrationManager.Unchanged; - if (total == 0) - ImUtf8.TextFrameAligned("No model files found."u8); - else - ImUtf8.TextFrameAligned($"{migrationManager.Migrated} files migrated, {migrationManager.Failed} files failed, {total} total files."); + ImUtf8.HoverTooltip( + "This currently only increases the color-table size and switches the shader from 'character.shpk' to 'characterlegacy.shpk', if the former is used."u8); + + ImUtf8.Checkbox("Create Backups During Manual Migration", ref _createBackups); } - private void DrawCleanup() + private static ReadOnlySpan MigrationTooltip + => "Cancel the migration. This does not revert already finished migrations."u8; + + private void DrawMdlMigration() + { + if (ImUtf8.ButtonEx("Migrate Model Files From V5 to V6"u8, "\0"u8, _buttonSize, migrationManager.IsRunning)) + migrationManager.MigrateMdlDirectory(config.ModDirectory, _createBackups); + + ImUtf8.SameLineInner(); + DrawCancelButton(MigrationManager.TaskType.MdlMigration, "Cancel the migration. This does not revert already finished migrations."u8); + DrawSpinner(migrationManager is { CurrentTask: MigrationManager.TaskType.MdlMigration, IsRunning: true }); + DrawData(migrationManager.MdlMigration, "No model files found."u8, "migrated"u8); + } + + private void DrawMtrlMigration() + { + if (ImUtf8.ButtonEx("Migrate Material Files to Dawntrail"u8, "\0"u8, _buttonSize, migrationManager.IsRunning)) + migrationManager.MigrateMtrlDirectory(config.ModDirectory, _createBackups); + + ImUtf8.SameLineInner(); + DrawCancelButton(MigrationManager.TaskType.MtrlMigration, MigrationTooltip); + DrawSpinner(migrationManager is { CurrentTask: MigrationManager.TaskType.MtrlMigration, IsRunning: true }); + DrawData(migrationManager.MtrlMigration, "No material files found."u8, "migrated"u8); + } + + + private static ReadOnlySpan CleanupTooltip + => "Cancel the cleanup. This is not revertible."u8; + + private void DrawMdlCleanup() { if (ImUtf8.ButtonEx("Delete Existing Model Backup Files"u8, "\0"u8, _buttonSize, migrationManager.IsRunning)) - migrationManager.CleanBackups(config.ModDirectory); + migrationManager.CleanMdlBackups(config.ModDirectory); ImUtf8.SameLineInner(); - DrawCancelButton(1, "Cancel the cleanup. This is not revertible."u8); - DrawSpinner(migrationManager is { IsCleanupTask: true, IsRunning: true }); - if (!migrationManager.HasCleanUpTask) - { - ImUtf8.IconDummy(); - return; - } - - var total = migrationManager.CleanedUp + migrationManager.CleanupFails; - if (total == 0) - ImUtf8.TextFrameAligned("No model backup files found."u8); - else - ImUtf8.TextFrameAligned( - $"{migrationManager.CleanedUp} backups deleted, {migrationManager.CleanupFails} deletions failed, {total} total backups."); + DrawCancelButton(MigrationManager.TaskType.MdlCleanup, CleanupTooltip); + DrawSpinner(migrationManager is { CurrentTask: MigrationManager.TaskType.MdlCleanup, IsRunning: true }); + DrawData(migrationManager.MdlCleanup, "No model backup files found."u8, "deleted"u8); } - private void DrawSpinner(bool enabled) + private void DrawMtrlCleanup() + { + if (ImUtf8.ButtonEx("Delete Existing Material Backup Files"u8, "\0"u8, _buttonSize, migrationManager.IsRunning)) + migrationManager.CleanMtrlBackups(config.ModDirectory); + + ImUtf8.SameLineInner(); + DrawCancelButton(MigrationManager.TaskType.MtrlCleanup, CleanupTooltip); + DrawSpinner(migrationManager is { CurrentTask: MigrationManager.TaskType.MtrlCleanup, IsRunning: true }); + DrawData(migrationManager.MtrlCleanup, "No material backup files found."u8, "deleted"u8); + } + + private static ReadOnlySpan RestorationTooltip + => "Cancel the restoration. This does not revert already finished restoration."u8; + + private void DrawMdlRestore() + { + if (ImUtf8.ButtonEx("Restore Model Backups"u8, "\0"u8, _buttonSize, migrationManager.IsRunning)) + migrationManager.RestoreMdlBackups(config.ModDirectory); + + ImUtf8.SameLineInner(); + DrawCancelButton(MigrationManager.TaskType.MdlRestoration, RestorationTooltip); + DrawSpinner(migrationManager is { CurrentTask: MigrationManager.TaskType.MdlRestoration, IsRunning: true }); + DrawData(migrationManager.MdlRestoration, "No model backup files found."u8, "restored"u8); + } + + private void DrawMtrlRestore() + { + if (ImUtf8.ButtonEx("Restore Material Backups"u8, "\0"u8, _buttonSize, migrationManager.IsRunning)) + migrationManager.RestoreMtrlBackups(config.ModDirectory); + + ImUtf8.SameLineInner(); + DrawCancelButton(MigrationManager.TaskType.MtrlRestoration, RestorationTooltip); + DrawSpinner(migrationManager is { CurrentTask: MigrationManager.TaskType.MtrlRestoration, IsRunning: true }); + DrawData(migrationManager.MtrlRestoration, "No material backup files found."u8, "restored"u8); + } + + private static void DrawSpinner(bool enabled) { if (!enabled) return; + ImGui.SameLine(); ImUtf8.Spinner("Spinner"u8, ImGui.GetTextLineHeight() / 2, 2, ImGui.GetColorU32(ImGuiCol.Text)); } - private void DrawRestore() + private void DrawCancelButton(MigrationManager.TaskType task, ReadOnlySpan tooltip) { - if (ImUtf8.ButtonEx("Restore Model Backups"u8, "\0"u8, _buttonSize, migrationManager.IsRunning)) - migrationManager.RestoreBackups(config.ModDirectory); + using var _ = ImUtf8.PushId((int)task); + if (ImUtf8.ButtonEx("Cancel"u8, tooltip, disabled: !migrationManager.IsRunning || task != migrationManager.CurrentTask)) + migrationManager.Cancel(); + } - ImUtf8.SameLineInner(); - DrawCancelButton(2, "Cancel the restoration. This does not revert already finished restoration."u8); - DrawSpinner(migrationManager is { IsRestorationTask: true, IsRunning: true }); - - if (!migrationManager.HasRestoreTask) + private static void DrawData(MigrationManager.MigrationData data, ReadOnlySpan empty, ReadOnlySpan action) + { + if (!data.HasData) { ImUtf8.IconDummy(); return; } - var total = migrationManager.Restored + migrationManager.RestoreFails; + var total = data.Total; if (total == 0) - ImUtf8.TextFrameAligned("No model backup files found."u8); + ImUtf8.TextFrameAligned(empty); else - ImUtf8.TextFrameAligned( - $"{migrationManager.Restored} backups restored, {migrationManager.RestoreFails} restorations failed, {total} total backups."); - } - - private void DrawCancelButton(int id, ReadOnlySpan tooltip) - { - using var _ = ImUtf8.PushId(id); - if (ImUtf8.ButtonEx("Cancel"u8, tooltip, disabled: !migrationManager.IsRunning)) - migrationManager.Cancel(); + ImUtf8.TextFrameAligned($"{data.Changed} files {action}, {data.Failed} files failed, {total} files found."); } } diff --git a/Penumbra/UI/Tabs/Debug/DebugTab.cs b/Penumbra/UI/Tabs/Debug/DebugTab.cs index ace3d6a3..be92b94e 100644 --- a/Penumbra/UI/Tabs/Debug/DebugTab.cs +++ b/Penumbra/UI/Tabs/Debug/DebugTab.cs @@ -15,6 +15,7 @@ using Microsoft.Extensions.DependencyInjection; using OtterGui; using OtterGui.Classes; using OtterGui.Services; +using OtterGui.Text; using OtterGui.Widgets; using Penumbra.Api; using Penumbra.Collections.Manager; @@ -433,10 +434,11 @@ public class DebugTab : Window, ITab, IUiService foreach (var obj in _objects) { ImGuiUtil.DrawTableColumn(obj.Address != nint.Zero ? $"{((GameObject*)obj.Address)->ObjectIndex}" : "NULL"); - ImGuiUtil.DrawTableColumn($"0x{obj.Address:X}"); - ImGuiUtil.DrawTableColumn(obj.Address == nint.Zero - ? string.Empty - : $"0x{(nint)((Character*)obj.Address)->GameObject.GetDrawObject():X}"); + ImGui.TableNextColumn(); + ImGuiUtil.CopyOnClickSelectable($"0x{obj.Address:X}"); + ImGui.TableNextColumn(); + if (obj.Address != nint.Zero) + ImGuiUtil.CopyOnClickSelectable($"0x{(nint)((Character*)obj.Address)->GameObject.GetDrawObject():X}"); var identifier = _actors.FromObject(obj, out _, false, true, false); ImGuiUtil.DrawTableColumn(_actors.ToString(identifier)); var id = obj.AsObject->ObjectKind is ObjectKind.BattleNpc