mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 12:14:17 +01:00
Add PBD Post-Processor that appends EPBD data if the loaded PBD does not contain it.
This commit is contained in:
parent
9aff388e21
commit
a7246b9d98
8 changed files with 187 additions and 52 deletions
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
|||
Subproject commit 0eaf7655123bd6502456e93d6ae9593249d3f792
|
||||
Subproject commit 3ea61642a05403fb2b64032112ff674b387825b3
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit 2f5e901314444238ab3aa6c5043368622bca815a
|
||||
Subproject commit 2cf59c61494a01fd14aecf925e7dc6325a7374ac
|
||||
119
Penumbra/Interop/Processing/PbdFilePostProcessor.cs
Normal file
119
Penumbra/Interop/Processing/PbdFilePostProcessor.cs
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
using Dalamud.Game;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.String;
|
||||
|
||||
namespace Penumbra.Interop.Processing;
|
||||
|
||||
public sealed class PbdFilePostProcessor : IFilePostProcessor
|
||||
{
|
||||
private readonly IFileAllocator _allocator;
|
||||
private byte[] _epbdData;
|
||||
private unsafe delegate* unmanaged<ResourceHandle*, void> _loadEpbdData;
|
||||
|
||||
public ResourceType Type
|
||||
=> ResourceType.Pbd;
|
||||
|
||||
public unsafe PbdFilePostProcessor(IDataManager dataManager, XivFileAllocator allocator, ISigScanner scanner)
|
||||
{
|
||||
_allocator = allocator;
|
||||
_epbdData = SetEpbdData(dataManager);
|
||||
_loadEpbdData = (delegate* unmanaged<ResourceHandle*, void>)scanner.ScanText(Sigs.LoadEpbdData);
|
||||
}
|
||||
|
||||
public unsafe void PostProcess(ResourceHandle* resource, CiByteString originalGamePath, ReadOnlySpan<byte> additionalData)
|
||||
{
|
||||
if (_epbdData.Length is 0)
|
||||
return;
|
||||
|
||||
if (resource->LoadState is not LoadState.Success)
|
||||
{
|
||||
Penumbra.Log.Warning($"[ResourceLoader] Requested PBD at {originalGamePath} failed load ({resource->LoadState}).");
|
||||
return;
|
||||
}
|
||||
|
||||
var (data, length) = resource->GetData();
|
||||
if (length is 0 || data == nint.Zero)
|
||||
{
|
||||
Penumbra.Log.Warning($"[ResourceLoader] Requested PBD at {originalGamePath} succeeded load but has no data.");
|
||||
return;
|
||||
}
|
||||
|
||||
var span = new ReadOnlySpan<byte>((void*)data, (int)resource->FileSize);
|
||||
var reader = new PackReader(span);
|
||||
if (reader.HasData)
|
||||
{
|
||||
Penumbra.Log.Excessive($"[ResourceLoader] Successfully loaded PBD at {originalGamePath} with EPBD data.");
|
||||
return;
|
||||
}
|
||||
|
||||
var newData = AppendData(span);
|
||||
fixed (byte* ptr = newData)
|
||||
{
|
||||
// Set the appended data and the actual file size, then re-load the EPBD data via game function call.
|
||||
if (resource->SetData((nint)ptr, newData.Length))
|
||||
{
|
||||
resource->FileSize = (uint)newData.Length;
|
||||
resource->CsHandle.FileSize2 = (uint)newData.Length;
|
||||
resource->CsHandle.FileSize3 = (uint)newData.Length;
|
||||
_loadEpbdData(resource);
|
||||
// Free original data.
|
||||
_allocator.Release((void*)data, length);
|
||||
Penumbra.Log.Verbose($"[ResourceLoader] Loaded {originalGamePath} from file and appended default EPBD data.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Penumbra.Log.Warning(
|
||||
$"[ResourceLoader] Failed to append EPBD data to custom PBD at {originalGamePath}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Combine the given data with the default PBD data using the game's file allocator. </summary>
|
||||
private unsafe ReadOnlySpan<byte> AppendData(ReadOnlySpan<byte> data)
|
||||
{
|
||||
// offset has to be set, otherwise not called.
|
||||
var newLength = data.Length + _epbdData.Length;
|
||||
var memory = _allocator.Allocate(newLength);
|
||||
var span = new Span<byte>(memory, newLength);
|
||||
data.CopyTo(span);
|
||||
_epbdData.CopyTo(span[data.Length..]);
|
||||
return span;
|
||||
}
|
||||
|
||||
/// <summary> Fetch the default EPBD data from the .pbd file of the game's installation. </summary>
|
||||
private static byte[] SetEpbdData(IDataManager dataManager)
|
||||
{
|
||||
try
|
||||
{
|
||||
var file = dataManager.GetFile(GamePaths.Pbd.Path);
|
||||
if (file is null || file.Data.Length is 0)
|
||||
{
|
||||
Penumbra.Log.Warning("Default PBD file has no data.");
|
||||
return [];
|
||||
}
|
||||
|
||||
ReadOnlySpan<byte> span = file.Data;
|
||||
var reader = new PackReader(span);
|
||||
if (!reader.HasData)
|
||||
{
|
||||
Penumbra.Log.Warning("Default PBD file has no EPBD section.");
|
||||
return [];
|
||||
}
|
||||
|
||||
var offset = span.Length - (int)reader.PackLength;
|
||||
var ret = span[offset..];
|
||||
Penumbra.Log.Verbose($"Default PBD file has EPBD section of length {ret.Length} at offset {offset}.");
|
||||
return ret.ToArray();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Penumbra.Log.Error($"Unknown error getting default EPBD data:\n{ex}");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ using OtterGui.Compression;
|
|||
using OtterGui.Raii;
|
||||
using OtterGui.Text;
|
||||
using OtterGui.Widgets;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.Mods.Editor;
|
||||
using Penumbra.Services;
|
||||
|
|
@ -80,7 +81,7 @@ public class FileEditor<T>(
|
|||
private Exception? _currentException;
|
||||
private bool _changed;
|
||||
|
||||
private string _defaultPath = string.Empty;
|
||||
private string _defaultPath = typeof(T) == typeof(ModEditWindow.PbdTab) ? GamePaths.Pbd.Path : string.Empty;
|
||||
private bool _inInput;
|
||||
private Utf8GamePath _defaultPathUtf8;
|
||||
private bool _isDefaultPathUtf8Valid;
|
||||
|
|
|
|||
|
|
@ -1236,16 +1236,12 @@ public class DebugTab : Window, ITab, IUiService
|
|||
}
|
||||
|
||||
public static unsafe void DrawCopyableAddress(ReadOnlySpan<byte> label, void* address)
|
||||
{
|
||||
using (var _ = ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
{
|
||||
if (ImUtf8.Selectable($"0x{(nint)address:X16} {label}"))
|
||||
ImUtf8.SetClipboardText($"0x{(nint)address:X16}");
|
||||
}
|
||||
|
||||
ImUtf8.HoverTooltip("Click to copy address to clipboard."u8);
|
||||
}
|
||||
=> DrawCopyableAddress(label, (nint)address);
|
||||
|
||||
public static unsafe void DrawCopyableAddress(ReadOnlySpan<byte> label, nint address)
|
||||
=> DrawCopyableAddress(label, (void*)address);
|
||||
{
|
||||
Penumbra.Dynamis.DrawPointer(address);
|
||||
ImUtf8.SameLineInner();
|
||||
ImUtf8.Text(label);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,14 +27,31 @@ public unsafe class GlobalVariablesDrawer(
|
|||
return;
|
||||
|
||||
var actionManager = (ActionTimelineManager**)ActionTimelineManager.Instance();
|
||||
DebugTab.DrawCopyableAddress("CharacterUtility"u8, characterUtility.Address);
|
||||
DebugTab.DrawCopyableAddress("ResidentResourceManager"u8, residentResources.Address);
|
||||
DebugTab.DrawCopyableAddress("ScheduleManagement"u8, ScheduleManagement.Instance());
|
||||
DebugTab.DrawCopyableAddress("ActionTimelineManager*"u8, actionManager);
|
||||
DebugTab.DrawCopyableAddress("ActionTimelineManager"u8, actionManager != null ? *actionManager : null);
|
||||
DebugTab.DrawCopyableAddress("SchedulerResourceManagement*"u8, scheduler.Address);
|
||||
DebugTab.DrawCopyableAddress("SchedulerResourceManagement"u8, scheduler.Address != null ? *scheduler.Address : null);
|
||||
DebugTab.DrawCopyableAddress("Device"u8, Device.Instance());
|
||||
using (ImUtf8.Group())
|
||||
{
|
||||
Penumbra.Dynamis.DrawPointer(characterUtility.Address);
|
||||
Penumbra.Dynamis.DrawPointer(residentResources.Address);
|
||||
Penumbra.Dynamis.DrawPointer(ScheduleManagement.Instance());
|
||||
Penumbra.Dynamis.DrawPointer(actionManager);
|
||||
Penumbra.Dynamis.DrawPointer(actionManager != null ? *actionManager : null);
|
||||
Penumbra.Dynamis.DrawPointer(scheduler.Address);
|
||||
Penumbra.Dynamis.DrawPointer(scheduler.Address != null ? *scheduler.Address : null);
|
||||
Penumbra.Dynamis.DrawPointer(Device.Instance());
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
using (ImUtf8.Group())
|
||||
{
|
||||
ImUtf8.Text("CharacterUtility"u8);
|
||||
ImUtf8.Text("ResidentResourceManager"u8);
|
||||
ImUtf8.Text("ScheduleManagement"u8);
|
||||
ImUtf8.Text("ActionTimelineManager*"u8);
|
||||
ImUtf8.Text("ActionTimelineManager"u8);
|
||||
ImUtf8.Text("SchedulerResourceManagement*"u8);
|
||||
ImUtf8.Text("SchedulerResourceManagement"u8);
|
||||
ImUtf8.Text("Device"u8);
|
||||
}
|
||||
|
||||
DrawCharacterUtility();
|
||||
DrawResidentResources();
|
||||
DrawSchedulerResourcesMap();
|
||||
|
|
@ -63,7 +80,7 @@ public unsafe class GlobalVariablesDrawer(
|
|||
var resource = characterUtility.Address->Resource(idx);
|
||||
ImUtf8.DrawTableColumn($"[{idx}]");
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.CopyOnClickSelectable($"0x{(ulong)resource:X}");
|
||||
Penumbra.Dynamis.DrawPointer(resource);
|
||||
if (resource == null)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
|
|
@ -74,25 +91,12 @@ public unsafe class GlobalVariablesDrawer(
|
|||
ImGui.TableNextColumn();
|
||||
var data = (nint)resource->CsHandle.GetData();
|
||||
var length = resource->CsHandle.GetLength();
|
||||
if (ImUtf8.Selectable($"0x{data:X}"))
|
||||
if (data != nint.Zero && length > 0)
|
||||
ImUtf8.SetClipboardText(string.Join("\n",
|
||||
new ReadOnlySpan<byte>((byte*)data, (int)length).ToArray().Select(b => b.ToString("X2"))));
|
||||
|
||||
ImUtf8.HoverTooltip("Click to copy bytes to clipboard."u8);
|
||||
Penumbra.Dynamis.DrawPointer(data);
|
||||
ImUtf8.DrawTableColumn(length.ToString());
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (intern.Value != -1)
|
||||
{
|
||||
ImUtf8.Selectable($"0x{characterUtility.DefaultResource(intern).Address:X}");
|
||||
if (ImGui.IsItemClicked())
|
||||
ImUtf8.SetClipboardText(string.Join("\n",
|
||||
new ReadOnlySpan<byte>((byte*)characterUtility.DefaultResource(intern).Address,
|
||||
characterUtility.DefaultResource(intern).Size).ToArray().Select(b => b.ToString("X2"))));
|
||||
|
||||
ImUtf8.HoverTooltip("Click to copy bytes to clipboard."u8);
|
||||
|
||||
Penumbra.Dynamis.DrawPointer(characterUtility.DefaultResource(intern).Address);
|
||||
ImUtf8.DrawTableColumn($"{characterUtility.DefaultResource(intern).Size}");
|
||||
}
|
||||
else
|
||||
|
|
@ -122,7 +126,7 @@ public unsafe class GlobalVariablesDrawer(
|
|||
var resource = residentResources.Address->ResourceList[idx];
|
||||
ImUtf8.DrawTableColumn($"[{idx}]");
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.CopyOnClickSelectable($"0x{(ulong)resource:X}");
|
||||
Penumbra.Dynamis.DrawPointer(resource);
|
||||
if (resource == null)
|
||||
{
|
||||
ImGui.TableNextRow();
|
||||
|
|
@ -133,12 +137,7 @@ public unsafe class GlobalVariablesDrawer(
|
|||
ImGui.TableNextColumn();
|
||||
var data = (nint)resource->CsHandle.GetData();
|
||||
var length = resource->CsHandle.GetLength();
|
||||
if (ImUtf8.Selectable($"0x{data:X}"))
|
||||
if (data != nint.Zero && length > 0)
|
||||
ImUtf8.SetClipboardText(string.Join("\n",
|
||||
new ReadOnlySpan<byte>((byte*)data, (int)length).ToArray().Select(b => b.ToString("X2"))));
|
||||
|
||||
ImUtf8.HoverTooltip("Click to copy bytes to clipboard."u8);
|
||||
Penumbra.Dynamis.DrawPointer(data);
|
||||
ImUtf8.DrawTableColumn(length.ToString());
|
||||
}
|
||||
}
|
||||
|
|
@ -184,15 +183,15 @@ public unsafe class GlobalVariablesDrawer(
|
|||
ImUtf8.DrawTableColumn($"{resource->Consumers}");
|
||||
ImUtf8.DrawTableColumn($"{resource->Unk1}"); // key
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.CopyOnClickSelectable($"0x{(ulong)resource:X}");
|
||||
Penumbra.Dynamis.DrawPointer(resource);
|
||||
ImGui.TableNextColumn();
|
||||
var resourceHandle = *((ResourceHandle**)resource + 3);
|
||||
ImUtf8.CopyOnClickSelectable($"0x{(ulong)resourceHandle:X}");
|
||||
Penumbra.Dynamis.DrawPointer(resourceHandle);
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.CopyOnClickSelectable(resourceHandle->FileName().Span);
|
||||
ImGui.TableNextColumn();
|
||||
uint dataLength = 0;
|
||||
ImUtf8.CopyOnClickSelectable($"0x{(ulong)resource->GetResourceData(&dataLength):X}");
|
||||
Penumbra.Dynamis.DrawPointer(resource->GetResourceData(&dataLength));
|
||||
ImUtf8.DrawTableColumn($"{dataLength}");
|
||||
++_shownResourcesMap;
|
||||
}
|
||||
|
|
@ -233,15 +232,15 @@ public unsafe class GlobalVariablesDrawer(
|
|||
ImUtf8.DrawTableColumn($"{resource->Consumers}");
|
||||
ImUtf8.DrawTableColumn($"{resource->Unk1}"); // key
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.CopyOnClickSelectable($"0x{(ulong)resource:X}");
|
||||
Penumbra.Dynamis.DrawPointer(resource);
|
||||
ImGui.TableNextColumn();
|
||||
var resourceHandle = *((ResourceHandle**)resource + 3);
|
||||
ImUtf8.CopyOnClickSelectable($"0x{(ulong)resourceHandle:X}");
|
||||
Penumbra.Dynamis.DrawPointer(resourceHandle);
|
||||
ImGui.TableNextColumn();
|
||||
ImUtf8.CopyOnClickSelectable(resourceHandle->FileName().Span);
|
||||
ImGui.TableNextColumn();
|
||||
uint dataLength = 0;
|
||||
ImUtf8.CopyOnClickSelectable($"0x{(ulong)resource->GetResourceData(&dataLength):X}");
|
||||
Penumbra.Dynamis.DrawPointer(resource->GetResourceData(&dataLength));
|
||||
ImUtf8.DrawTableColumn($"{dataLength}");
|
||||
++_shownResourcesList;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,19 @@
|
|||
"resolved": "3.1.11",
|
||||
"contentHash": "JfPLyigLthuE50yi6tMt7Amrenr/fA31t2CvJyhy/kQmfulIBAqo5T/YFUSRHtuYPXRSaUHygFeh6Qd933EoSw=="
|
||||
},
|
||||
"FlatSharp.Compiler": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.9.0",
|
||||
"contentHash": "MU6808xvdbWJ3Ev+5PKalqQuzvVbn1DzzQH8txRDHGFUNDvHjd+ejqpvnYc9BSJ8Qp8VjkkpJD8OzRYilbPp3A=="
|
||||
},
|
||||
"FlatSharp.Runtime": {
|
||||
"type": "Transitive",
|
||||
"resolved": "7.9.0",
|
||||
"contentHash": "Bm8+WqzEsWNpxqrD5x4x+zQ8dyINlToCreM5FI2oNSfUVc9U9ZB+qztX/jd8rlJb3r0vBSlPwVLpw0xBtPa3Vw==",
|
||||
"dependencies": {
|
||||
"System.Memory": "4.5.5"
|
||||
}
|
||||
},
|
||||
"JetBrains.Annotations": {
|
||||
"type": "Transitive",
|
||||
"resolved": "2024.3.0",
|
||||
|
|
@ -94,6 +107,11 @@
|
|||
"resolved": "4.6.0",
|
||||
"contentHash": "lN6tZi7Q46zFzAbRYXTIvfXcyvQQgxnY7Xm6C6xQ9784dEL1amjM6S6Iw4ZpsvesAKnRVsM4scrDQaDqSClkjA=="
|
||||
},
|
||||
"System.Memory": {
|
||||
"type": "Transitive",
|
||||
"resolved": "4.5.5",
|
||||
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw=="
|
||||
},
|
||||
"System.Security.Cryptography.Pkcs": {
|
||||
"type": "Transitive",
|
||||
"resolved": "8.0.1",
|
||||
|
|
@ -133,8 +151,10 @@
|
|||
"penumbra.gamedata": {
|
||||
"type": "Project",
|
||||
"dependencies": {
|
||||
"FlatSharp.Compiler": "[7.9.0, )",
|
||||
"FlatSharp.Runtime": "[7.9.0, )",
|
||||
"OtterGui": "[1.0.0, )",
|
||||
"Penumbra.Api": "[5.6.1, )",
|
||||
"Penumbra.Api": "[5.10.0, )",
|
||||
"Penumbra.String": "[1.0.6, )"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue