mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 12:14:17 +01:00
Update.
This commit is contained in:
parent
710f39768b
commit
b677a14cef
9 changed files with 338 additions and 192 deletions
|
|
@ -1 +1 @@
|
||||||
Subproject commit 43b0b47f2d019af0fe4681dfc578f9232e3ba90c
|
Subproject commit f4c6144ca2012b279e6d8aa52b2bef6cc2ba32d9
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit d7a56b708c73bc9917baeaa66842c1594ca3067b
|
Subproject commit cf5be8af4c9ecbd9190bd3db746743fa5cd1560f
|
||||||
|
|
@ -99,7 +99,8 @@ public class Configuration : IPluginConfiguration, ISavable, IService
|
||||||
public bool UseFileSystemCompression { get; set; } = true;
|
public bool UseFileSystemCompression { get; set; } = true;
|
||||||
public bool EnableHttpApi { 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 string DefaultModImportPath { get; set; } = string.Empty;
|
||||||
public bool AlwaysOpenDefaultImport { get; set; } = false;
|
public bool AlwaysOpenDefaultImport { get; set; } = false;
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using System.IO;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
@ -90,7 +91,7 @@ public partial class TexToolsImporter
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HandleFileMigrations(reader);
|
HandleFileMigrationsAndWrite(reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
++_currentFileIdx;
|
++_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))
|
switch (Path.GetExtension(reader.Entry.Key))
|
||||||
{
|
{
|
||||||
case ".mdl":
|
case ".mdl":
|
||||||
_migrationManager.MigrateMdlDuringExtraction(reader, _currentModDirectory!.FullName, _extractionOptions);
|
_migrationManager.MigrateMdlDuringExtraction(reader, _currentModDirectory!.FullName, _extractionOptions);
|
||||||
break;
|
break;
|
||||||
|
case ".mtrl":
|
||||||
|
_migrationManager.MigrateMtrlDuringExtraction(reader, _currentModDirectory!.FullName, _extractionOptions);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
reader.WriteEntryToDirectory(_currentModDirectory!.FullName, _extractionOptions);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -255,8 +255,9 @@ public partial class TexToolsImporter
|
||||||
|
|
||||||
data.Data = Path.GetExtension(extractedFile.FullName) switch
|
data.Data = Path.GetExtension(extractedFile.FullName) switch
|
||||||
{
|
{
|
||||||
".mdl" => _migrationManager.MigrateTtmpModel(extractedFile.FullName, data.Data),
|
".mdl" => _migrationManager.MigrateTtmpModel(extractedFile.FullName, data.Data),
|
||||||
_ => data.Data,
|
".mtrl" => _migrationManager.MigrateTtmpMaterial(extractedFile.FullName, data.Data),
|
||||||
|
_ => data.Data,
|
||||||
};
|
};
|
||||||
|
|
||||||
_compactor.WriteAllBytesAsync(extractedFile.FullName, data.Data, _token).Wait(_token);
|
_compactor.WriteAllBytesAsync(extractedFile.FullName, data.Data, _token).Wait(_token);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Dalamud.Hooking;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
|
|
@ -16,6 +17,7 @@ public sealed unsafe class ApricotListenerSoundPlay : FastHook<ApricotListenerSo
|
||||||
private readonly CollectionResolver _collectionResolver;
|
private readonly CollectionResolver _collectionResolver;
|
||||||
private readonly CrashHandlerService _crashHandler;
|
private readonly CrashHandlerService _crashHandler;
|
||||||
|
|
||||||
|
// TODO because of inlining.
|
||||||
public ApricotListenerSoundPlay(HookManager hooks, GameState state, CollectionResolver collectionResolver, CrashHandlerService crashHandler)
|
public ApricotListenerSoundPlay(HookManager hooks, GameState state, CollectionResolver collectionResolver, CrashHandlerService crashHandler)
|
||||||
{
|
{
|
||||||
_state = state;
|
_state = state;
|
||||||
|
|
|
||||||
|
|
@ -9,65 +9,61 @@ namespace Penumbra.Services;
|
||||||
|
|
||||||
public class MigrationManager(Configuration config) : IService
|
public class MigrationManager(Configuration config) : IService
|
||||||
{
|
{
|
||||||
|
public enum TaskType : byte
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
MdlMigration,
|
||||||
|
MdlRestoration,
|
||||||
|
MdlCleanup,
|
||||||
|
MtrlMigration,
|
||||||
|
MtrlRestoration,
|
||||||
|
MtrlCleanup,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MigrationData(bool hasUnchanged)
|
||||||
|
|
||||||
|
{
|
||||||
|
public int Changed;
|
||||||
|
public int Unchanged;
|
||||||
|
public int Failed;
|
||||||
|
public bool HasData;
|
||||||
|
public readonly bool HasUnchanged = hasUnchanged;
|
||||||
|
|
||||||
|
public int Total
|
||||||
|
=> Changed + Unchanged + Failed;
|
||||||
|
|
||||||
|
public void Init()
|
||||||
|
{
|
||||||
|
Changed = 0;
|
||||||
|
Unchanged = 0;
|
||||||
|
Failed = 0;
|
||||||
|
HasData = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Task? _currentTask;
|
private Task? _currentTask;
|
||||||
private CancellationTokenSource? _source;
|
private CancellationTokenSource? _source;
|
||||||
|
|
||||||
public bool HasCleanUpTask { get; private set; }
|
public TaskType CurrentTask { get; private set; }
|
||||||
public bool HasMigrationTask { get; private set; }
|
|
||||||
public bool HasRestoreTask { get; private set; }
|
|
||||||
|
|
||||||
public bool IsMigrationTask { get; private set; }
|
public readonly MigrationData MdlMigration = new(true);
|
||||||
public bool IsRestorationTask { get; private set; }
|
public readonly MigrationData MtrlMigration = new(true);
|
||||||
public bool IsCleanupTask { get; private set; }
|
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
|
public bool IsRunning
|
||||||
=> _currentTask is { IsCompleted: false };
|
=> _currentTask is { IsCompleted: false };
|
||||||
|
|
||||||
/// <summary> Writes or migrates a .mdl file during extraction from a regular archive. </summary>
|
public void CleanMdlBackups(string path)
|
||||||
public void MigrateMdlDuringExtraction(IReader reader, string directory, ExtractionOptions options)
|
=> CleanBackups(path, "*.mdl.bak", "model", MdlCleanup, TaskType.MdlCleanup);
|
||||||
{
|
|
||||||
if (!config.MigrateImportedModelsToV6)
|
|
||||||
{
|
|
||||||
reader.WriteEntryToDirectory(directory, options);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var path = Path.Combine(directory, reader.Entry.Key);
|
public void CleanMtrlBackups(string path)
|
||||||
using var s = new MemoryStream();
|
=> CleanBackups(path, "*.mtrl.bak", "material", MtrlCleanup, TaskType.MtrlCleanup);
|
||||||
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 CleanBackups(string path)
|
private void CleanBackups(string path, string extension, string fileType, MigrationData data, TaskType type)
|
||||||
{
|
{
|
||||||
if (IsRunning)
|
if (IsRunning)
|
||||||
return;
|
return;
|
||||||
|
|
@ -76,13 +72,9 @@ public class MigrationManager(Configuration config) : IService
|
||||||
var token = _source.Token;
|
var token = _source.Token;
|
||||||
_currentTask = Task.Run(() =>
|
_currentTask = Task.Run(() =>
|
||||||
{
|
{
|
||||||
HasCleanUpTask = true;
|
CurrentTask = type;
|
||||||
IsCleanupTask = true;
|
data.Init();
|
||||||
IsMigrationTask = false;
|
foreach (var file in Directory.EnumerateFiles(path, extension, SearchOption.AllDirectories))
|
||||||
IsRestorationTask = false;
|
|
||||||
CleanedUp = 0;
|
|
||||||
CleanupFails = 0;
|
|
||||||
foreach (var file in Directory.EnumerateFiles(path, "*.mdl.bak", SearchOption.AllDirectories))
|
|
||||||
{
|
{
|
||||||
if (token.IsCancellationRequested)
|
if (token.IsCancellationRequested)
|
||||||
return;
|
return;
|
||||||
|
|
@ -90,19 +82,25 @@ public class MigrationManager(Configuration config) : IService
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
File.Delete(file);
|
File.Delete(file);
|
||||||
++CleanedUp;
|
++data.Changed;
|
||||||
Penumbra.Log.Debug($"Deleted model backup file {file}.");
|
Penumbra.Log.Debug($"Deleted {fileType} backup file {file}.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Penumbra.Messager.NotificationMessage(ex, $"Failed to delete model backup file {file}", NotificationType.Warning);
|
Penumbra.Messager.NotificationMessage(ex, $"Failed to delete {fileType} backup file {file}", NotificationType.Warning);
|
||||||
++CleanupFails;
|
++data.Failed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, token);
|
}, 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)
|
if (IsRunning)
|
||||||
return;
|
return;
|
||||||
|
|
@ -111,13 +109,9 @@ public class MigrationManager(Configuration config) : IService
|
||||||
var token = _source.Token;
|
var token = _source.Token;
|
||||||
_currentTask = Task.Run(() =>
|
_currentTask = Task.Run(() =>
|
||||||
{
|
{
|
||||||
HasRestoreTask = true;
|
CurrentTask = type;
|
||||||
IsCleanupTask = false;
|
data.Init();
|
||||||
IsMigrationTask = false;
|
foreach (var file in Directory.EnumerateFiles(path, extension, SearchOption.AllDirectories))
|
||||||
IsRestorationTask = true;
|
|
||||||
CleanedUp = 0;
|
|
||||||
CleanupFails = 0;
|
|
||||||
foreach (var file in Directory.EnumerateFiles(path, "*.mdl.bak", SearchOption.AllDirectories))
|
|
||||||
{
|
{
|
||||||
if (token.IsCancellationRequested)
|
if (token.IsCancellationRequested)
|
||||||
return;
|
return;
|
||||||
|
|
@ -126,40 +120,38 @@ public class MigrationManager(Configuration config) : IService
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
File.Copy(file, target, true);
|
File.Copy(file, target, true);
|
||||||
++Restored;
|
++data.Changed;
|
||||||
Penumbra.Log.Debug($"Restored model backup file {file} to {target}.");
|
Penumbra.Log.Debug($"Restored {fileType} backup file {file} to {target}.");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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);
|
NotificationType.Warning);
|
||||||
++RestoreFails;
|
++data.Failed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, token);
|
}, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Update the data of a .mdl file during TTMP extraction. Returns either the existing array or a new one. </summary>
|
public void MigrateMdlDirectory(string path, bool createBackups)
|
||||||
public byte[] MigrateTtmpModel(string path, byte[] data)
|
=> MigrateDirectory(path, createBackups, "*.mdl", "model", MdlMigration, TaskType.MdlMigration, "from V5 to V6", "V6",
|
||||||
{
|
(file, fileData, backups) =>
|
||||||
FixLodNum(data);
|
{
|
||||||
if (!config.MigrateImportedModelsToV6)
|
var mdl = new MdlFile(fileData);
|
||||||
return data;
|
return MigrateModel(file, mdl, backups);
|
||||||
|
});
|
||||||
|
|
||||||
var version = BitConverter.ToUInt32(data);
|
public void MigrateMtrlDirectory(string path, bool createBackups)
|
||||||
if (version != 5)
|
=> MigrateDirectory(path, createBackups, "*.mtrl", "material", MtrlMigration, TaskType.MtrlMigration, "to Dawntrail", "Dawntrail",
|
||||||
return data;
|
(file, fileData, backups) =>
|
||||||
|
{
|
||||||
|
var mtrl = new MtrlFile(fileData);
|
||||||
|
return MigrateMaterial(file, mtrl, backups);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
var mdl = new MdlFile(data);
|
private void MigrateDirectory(string path, bool createBackups, string extension, string fileType, MigrationData data, TaskType type,
|
||||||
if (!mdl.ConvertV5ToV6())
|
string action, string state, Func<string, byte[], bool, bool> func)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
if (IsRunning)
|
if (IsRunning)
|
||||||
return;
|
return;
|
||||||
|
|
@ -168,14 +160,9 @@ public class MigrationManager(Configuration config) : IService
|
||||||
var token = _source.Token;
|
var token = _source.Token;
|
||||||
_currentTask = Task.Run(() =>
|
_currentTask = Task.Run(() =>
|
||||||
{
|
{
|
||||||
HasMigrationTask = true;
|
CurrentTask = type;
|
||||||
IsCleanupTask = false;
|
data.Init();
|
||||||
IsMigrationTask = true;
|
foreach (var file in Directory.EnumerateFiles(path, extension, SearchOption.AllDirectories))
|
||||||
IsRestorationTask = false;
|
|
||||||
Unchanged = 0;
|
|
||||||
Migrated = 0;
|
|
||||||
Failed = 0;
|
|
||||||
foreach (var file in Directory.EnumerateFiles(path, "*.mdl", SearchOption.AllDirectories))
|
|
||||||
{
|
{
|
||||||
if (token.IsCancellationRequested)
|
if (token.IsCancellationRequested)
|
||||||
return;
|
return;
|
||||||
|
|
@ -183,24 +170,24 @@ public class MigrationManager(Configuration config) : IService
|
||||||
var timer = Stopwatch.StartNew();
|
var timer = Stopwatch.StartNew();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var data = File.ReadAllBytes(file);
|
var fileData = File.ReadAllBytes(file);
|
||||||
var mdl = new MdlFile(data);
|
if (func(file, fileData, createBackups))
|
||||||
if (MigrateModel(file, mdl, createBackups))
|
|
||||||
{
|
{
|
||||||
++Migrated;
|
++data.Changed;
|
||||||
Penumbra.Log.Debug($"Migrated model file {file} from V5 to V6 in {timer.ElapsedMilliseconds} ms.");
|
Penumbra.Log.Debug($"Migrated {fileType} file {file} {action} in {timer.ElapsedMilliseconds} ms.");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
++Unchanged;
|
++data.Unchanged;
|
||||||
Penumbra.Log.Verbose($"Verified that model file {file} is already V6 in {timer.ElapsedMilliseconds} ms.");
|
Penumbra.Log.Verbose($"Verified that {fileType} file {file} is already {state} in {timer.ElapsedMilliseconds} ms.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
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);
|
NotificationType.Warning);
|
||||||
++Failed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, token);
|
}, token);
|
||||||
|
|
@ -213,22 +200,6 @@ public class MigrationManager(Configuration config) : IService
|
||||||
_currentTask = null;
|
_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)
|
public static bool TryMigrateSingleModel(string path, bool createBackup)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|
@ -259,6 +230,113 @@ public class MigrationManager(Configuration config) : IService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary> Writes or migrates a .mdl file during extraction from a regular archive. </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Update the data of a .mdl file during TTMP extraction. Returns either the existing array or a new one. </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Update the data of a .mtrl file during TTMP extraction. Returns either the existing array or a new one. </summary>
|
||||||
|
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)
|
private static bool MigrateModel(string path, MdlFile mdl, bool createBackup)
|
||||||
{
|
{
|
||||||
if (!mdl.ConvertV5ToV6())
|
if (!mdl.ConvertV5ToV6())
|
||||||
|
|
@ -284,4 +362,20 @@ public class MigrationManager(Configuration config) : IService
|
||||||
File.WriteAllBytes(path, data);
|
File.WriteAllBytes(path, data);
|
||||||
return true;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,13 @@ public class MigrationSectionDrawer(MigrationManager migrationManager, Configura
|
||||||
_buttonSize = UiHelpers.InputTextWidth;
|
_buttonSize = UiHelpers.InputTextWidth;
|
||||||
DrawSettings();
|
DrawSettings();
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
DrawMigration();
|
DrawMdlMigration();
|
||||||
|
DrawMdlRestore();
|
||||||
|
DrawMdlCleanup();
|
||||||
ImGui.Separator();
|
ImGui.Separator();
|
||||||
DrawCleanup();
|
DrawMtrlMigration();
|
||||||
ImGui.Separator();
|
DrawMtrlRestore();
|
||||||
DrawRestore();
|
DrawMtrlCleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawSettings()
|
private void DrawSettings()
|
||||||
|
|
@ -34,88 +36,125 @@ public class MigrationSectionDrawer(MigrationManager migrationManager, Configura
|
||||||
config.MigrateImportedModelsToV6 = value;
|
config.MigrateImportedModelsToV6 = value;
|
||||||
config.Save();
|
config.Save();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawMigration()
|
ImUtf8.HoverTooltip("This increments the version marker and restructures the bone table to the new version."u8);
|
||||||
{
|
|
||||||
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.SameLineInner();
|
if (ImUtf8.Checkbox("Automatically Migrate Materials to Dawntrail on Import"u8, ref value))
|
||||||
DrawCancelButton(0, "Cancel the migration. This does not revert already finished migrations."u8);
|
|
||||||
DrawSpinner(migrationManager is { IsMigrationTask: true, IsRunning: true });
|
|
||||||
|
|
||||||
if (!migrationManager.HasMigrationTask)
|
|
||||||
{
|
{
|
||||||
ImUtf8.IconDummy();
|
config.MigrateImportedMaterialsToLegacy = value;
|
||||||
return;
|
config.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
var total = migrationManager.Failed + migrationManager.Migrated + migrationManager.Unchanged;
|
ImUtf8.HoverTooltip(
|
||||||
if (total == 0)
|
"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.TextFrameAligned("No model files found."u8);
|
|
||||||
else
|
ImUtf8.Checkbox("Create Backups During Manual Migration", ref _createBackups);
|
||||||
ImUtf8.TextFrameAligned($"{migrationManager.Migrated} files migrated, {migrationManager.Failed} files failed, {total} total files.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawCleanup()
|
private static ReadOnlySpan<byte> 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<byte> 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))
|
if (ImUtf8.ButtonEx("Delete Existing Model Backup Files"u8, "\0"u8, _buttonSize, migrationManager.IsRunning))
|
||||||
migrationManager.CleanBackups(config.ModDirectory);
|
migrationManager.CleanMdlBackups(config.ModDirectory);
|
||||||
|
|
||||||
ImUtf8.SameLineInner();
|
ImUtf8.SameLineInner();
|
||||||
DrawCancelButton(1, "Cancel the cleanup. This is not revertible."u8);
|
DrawCancelButton(MigrationManager.TaskType.MdlCleanup, CleanupTooltip);
|
||||||
DrawSpinner(migrationManager is { IsCleanupTask: true, IsRunning: true });
|
DrawSpinner(migrationManager is { CurrentTask: MigrationManager.TaskType.MdlCleanup, IsRunning: true });
|
||||||
if (!migrationManager.HasCleanUpTask)
|
DrawData(migrationManager.MdlCleanup, "No model backup files found."u8, "deleted"u8);
|
||||||
{
|
|
||||||
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.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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<byte> 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)
|
if (!enabled)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImUtf8.Spinner("Spinner"u8, ImGui.GetTextLineHeight() / 2, 2, ImGui.GetColorU32(ImGuiCol.Text));
|
ImUtf8.Spinner("Spinner"u8, ImGui.GetTextLineHeight() / 2, 2, ImGui.GetColorU32(ImGuiCol.Text));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawRestore()
|
private void DrawCancelButton(MigrationManager.TaskType task, ReadOnlySpan<byte> tooltip)
|
||||||
{
|
{
|
||||||
if (ImUtf8.ButtonEx("Restore Model Backups"u8, "\0"u8, _buttonSize, migrationManager.IsRunning))
|
using var _ = ImUtf8.PushId((int)task);
|
||||||
migrationManager.RestoreBackups(config.ModDirectory);
|
if (ImUtf8.ButtonEx("Cancel"u8, tooltip, disabled: !migrationManager.IsRunning || task != migrationManager.CurrentTask))
|
||||||
|
migrationManager.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
ImUtf8.SameLineInner();
|
private static void DrawData(MigrationManager.MigrationData data, ReadOnlySpan<byte> empty, ReadOnlySpan<byte> action)
|
||||||
DrawCancelButton(2, "Cancel the restoration. This does not revert already finished restoration."u8);
|
{
|
||||||
DrawSpinner(migrationManager is { IsRestorationTask: true, IsRunning: true });
|
if (!data.HasData)
|
||||||
|
|
||||||
if (!migrationManager.HasRestoreTask)
|
|
||||||
{
|
{
|
||||||
ImUtf8.IconDummy();
|
ImUtf8.IconDummy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var total = migrationManager.Restored + migrationManager.RestoreFails;
|
var total = data.Total;
|
||||||
if (total == 0)
|
if (total == 0)
|
||||||
ImUtf8.TextFrameAligned("No model backup files found."u8);
|
ImUtf8.TextFrameAligned(empty);
|
||||||
else
|
else
|
||||||
ImUtf8.TextFrameAligned(
|
ImUtf8.TextFrameAligned($"{data.Changed} files {action}, {data.Failed} files failed, {total} files found.");
|
||||||
$"{migrationManager.Restored} backups restored, {migrationManager.RestoreFails} restorations failed, {total} total backups.");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawCancelButton(int id, ReadOnlySpan<byte> tooltip)
|
|
||||||
{
|
|
||||||
using var _ = ImUtf8.PushId(id);
|
|
||||||
if (ImUtf8.ButtonEx("Cancel"u8, tooltip, disabled: !migrationManager.IsRunning))
|
|
||||||
migrationManager.Cancel();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
|
using OtterGui.Text;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
using Penumbra.Api;
|
using Penumbra.Api;
|
||||||
using Penumbra.Collections.Manager;
|
using Penumbra.Collections.Manager;
|
||||||
|
|
@ -433,10 +434,11 @@ public class DebugTab : Window, ITab, IUiService
|
||||||
foreach (var obj in _objects)
|
foreach (var obj in _objects)
|
||||||
{
|
{
|
||||||
ImGuiUtil.DrawTableColumn(obj.Address != nint.Zero ? $"{((GameObject*)obj.Address)->ObjectIndex}" : "NULL");
|
ImGuiUtil.DrawTableColumn(obj.Address != nint.Zero ? $"{((GameObject*)obj.Address)->ObjectIndex}" : "NULL");
|
||||||
ImGuiUtil.DrawTableColumn($"0x{obj.Address:X}");
|
ImGui.TableNextColumn();
|
||||||
ImGuiUtil.DrawTableColumn(obj.Address == nint.Zero
|
ImGuiUtil.CopyOnClickSelectable($"0x{obj.Address:X}");
|
||||||
? string.Empty
|
ImGui.TableNextColumn();
|
||||||
: $"0x{(nint)((Character*)obj.Address)->GameObject.GetDrawObject():X}");
|
if (obj.Address != nint.Zero)
|
||||||
|
ImGuiUtil.CopyOnClickSelectable($"0x{(nint)((Character*)obj.Address)->GameObject.GetDrawObject():X}");
|
||||||
var identifier = _actors.FromObject(obj, out _, false, true, false);
|
var identifier = _actors.FromObject(obj, out _, false, true, false);
|
||||||
ImGuiUtil.DrawTableColumn(_actors.ToString(identifier));
|
ImGuiUtil.DrawTableColumn(_actors.ToString(identifier));
|
||||||
var id = obj.AsObject->ObjectKind is ObjectKind.BattleNpc
|
var id = obj.AsObject->ObjectKind is ObjectKind.BattleNpc
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue