Skip to content

Commit a7246b9

Browse files
committed
Add PBD Post-Processor that appends EPBD data if the loaded PBD does not contain it.
1 parent 9aff388 commit a7246b9

File tree

8 files changed

+187
-52
lines changed

8 files changed

+187
-52
lines changed

OtterGui

Penumbra/Interop/Hooks/PostProcessing/PreBoneDeformerReplacer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ private void SetupHssReplacements(CharacterBase* drawObject, uint slotIndex, Spa
6363
if (!_framework.IsInFrameworkUpdateThread)
6464
Penumbra.Log.Warning(
6565
$"{nameof(PreBoneDeformerReplacer)}.{nameof(SetupHssReplacements)}(0x{(nint)drawObject:X}, {slotIndex}) called out of framework thread");
66-
66+
6767
var preBoneDeformer = GetPreBoneDeformerForCharacter(drawObject);
6868
try
6969
{
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
using Dalamud.Game;
2+
using Dalamud.Plugin.Services;
3+
using Penumbra.Api.Enums;
4+
using Penumbra.GameData;
5+
using Penumbra.GameData.Data;
6+
using Penumbra.Interop.Structs;
7+
using Penumbra.Meta.Files;
8+
using Penumbra.String;
9+
10+
namespace Penumbra.Interop.Processing;
11+
12+
public sealed class PbdFilePostProcessor : IFilePostProcessor
13+
{
14+
private readonly IFileAllocator _allocator;
15+
private byte[] _epbdData;
16+
private unsafe delegate* unmanaged<ResourceHandle*, void> _loadEpbdData;
17+
18+
public ResourceType Type
19+
=> ResourceType.Pbd;
20+
21+
public unsafe PbdFilePostProcessor(IDataManager dataManager, XivFileAllocator allocator, ISigScanner scanner)
22+
{
23+
_allocator = allocator;
24+
_epbdData = SetEpbdData(dataManager);
25+
_loadEpbdData = (delegate* unmanaged<ResourceHandle*, void>)scanner.ScanText(Sigs.LoadEpbdData);
26+
}
27+
28+
public unsafe void PostProcess(ResourceHandle* resource, CiByteString originalGamePath, ReadOnlySpan<byte> additionalData)
29+
{
30+
if (_epbdData.Length is 0)
31+
return;
32+
33+
if (resource->LoadState is not LoadState.Success)
34+
{
35+
Penumbra.Log.Warning($"[ResourceLoader] Requested PBD at {originalGamePath} failed load ({resource->LoadState}).");
36+
return;
37+
}
38+
39+
var (data, length) = resource->GetData();
40+
if (length is 0 || data == nint.Zero)
41+
{
42+
Penumbra.Log.Warning($"[ResourceLoader] Requested PBD at {originalGamePath} succeeded load but has no data.");
43+
return;
44+
}
45+
46+
var span = new ReadOnlySpan<byte>((void*)data, (int)resource->FileSize);
47+
var reader = new PackReader(span);
48+
if (reader.HasData)
49+
{
50+
Penumbra.Log.Excessive($"[ResourceLoader] Successfully loaded PBD at {originalGamePath} with EPBD data.");
51+
return;
52+
}
53+
54+
var newData = AppendData(span);
55+
fixed (byte* ptr = newData)
56+
{
57+
// Set the appended data and the actual file size, then re-load the EPBD data via game function call.
58+
if (resource->SetData((nint)ptr, newData.Length))
59+
{
60+
resource->FileSize = (uint)newData.Length;
61+
resource->CsHandle.FileSize2 = (uint)newData.Length;
62+
resource->CsHandle.FileSize3 = (uint)newData.Length;
63+
_loadEpbdData(resource);
64+
// Free original data.
65+
_allocator.Release((void*)data, length);
66+
Penumbra.Log.Verbose($"[ResourceLoader] Loaded {originalGamePath} from file and appended default EPBD data.");
67+
}
68+
else
69+
{
70+
Penumbra.Log.Warning(
71+
$"[ResourceLoader] Failed to append EPBD data to custom PBD at {originalGamePath}.");
72+
}
73+
}
74+
}
75+
76+
/// <summary> Combine the given data with the default PBD data using the game's file allocator. </summary>
77+
private unsafe ReadOnlySpan<byte> AppendData(ReadOnlySpan<byte> data)
78+
{
79+
// offset has to be set, otherwise not called.
80+
var newLength = data.Length + _epbdData.Length;
81+
var memory = _allocator.Allocate(newLength);
82+
var span = new Span<byte>(memory, newLength);
83+
data.CopyTo(span);
84+
_epbdData.CopyTo(span[data.Length..]);
85+
return span;
86+
}
87+
88+
/// <summary> Fetch the default EPBD data from the .pbd file of the game's installation. </summary>
89+
private static byte[] SetEpbdData(IDataManager dataManager)
90+
{
91+
try
92+
{
93+
var file = dataManager.GetFile(GamePaths.Pbd.Path);
94+
if (file is null || file.Data.Length is 0)
95+
{
96+
Penumbra.Log.Warning("Default PBD file has no data.");
97+
return [];
98+
}
99+
100+
ReadOnlySpan<byte> span = file.Data;
101+
var reader = new PackReader(span);
102+
if (!reader.HasData)
103+
{
104+
Penumbra.Log.Warning("Default PBD file has no EPBD section.");
105+
return [];
106+
}
107+
108+
var offset = span.Length - (int)reader.PackLength;
109+
var ret = span[offset..];
110+
Penumbra.Log.Verbose($"Default PBD file has EPBD section of length {ret.Length} at offset {offset}.");
111+
return ret.ToArray();
112+
}
113+
catch (Exception ex)
114+
{
115+
Penumbra.Log.Error($"Unknown error getting default EPBD data:\n{ex}");
116+
return [];
117+
}
118+
}
119+
}

Penumbra/UI/AdvancedWindow/FileEditor.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using OtterGui.Raii;
99
using OtterGui.Text;
1010
using OtterGui.Widgets;
11+
using Penumbra.GameData.Data;
1112
using Penumbra.GameData.Files;
1213
using Penumbra.Mods.Editor;
1314
using Penumbra.Services;
@@ -80,7 +81,7 @@ public void Dispose()
8081
private Exception? _currentException;
8182
private bool _changed;
8283

83-
private string _defaultPath = string.Empty;
84+
private string _defaultPath = typeof(T) == typeof(ModEditWindow.PbdTab) ? GamePaths.Pbd.Path : string.Empty;
8485
private bool _inInput;
8586
private Utf8GamePath _defaultPathUtf8;
8687
private bool _isDefaultPathUtf8Valid;

Penumbra/UI/Tabs/Debug/DebugTab.cs

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,16 +1236,12 @@ public override void OnClose()
12361236
}
12371237

12381238
public static unsafe void DrawCopyableAddress(ReadOnlySpan<byte> label, void* address)
1239-
{
1240-
using (var _ = ImRaii.PushFont(UiBuilder.MonoFont))
1241-
{
1242-
if (ImUtf8.Selectable($"0x{(nint)address:X16} {label}"))
1243-
ImUtf8.SetClipboardText($"0x{(nint)address:X16}");
1244-
}
1245-
1246-
ImUtf8.HoverTooltip("Click to copy address to clipboard."u8);
1247-
}
1239+
=> DrawCopyableAddress(label, (nint)address);
12481240

12491241
public static unsafe void DrawCopyableAddress(ReadOnlySpan<byte> label, nint address)
1250-
=> DrawCopyableAddress(label, (void*)address);
1242+
{
1243+
Penumbra.Dynamis.DrawPointer(address);
1244+
ImUtf8.SameLineInner();
1245+
ImUtf8.Text(label);
1246+
}
12511247
}

Penumbra/UI/Tabs/Debug/GlobalVariablesDrawer.cs

Lines changed: 36 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,31 @@ public void Draw()
2727
return;
2828

2929
var actionManager = (ActionTimelineManager**)ActionTimelineManager.Instance();
30-
DebugTab.DrawCopyableAddress("CharacterUtility"u8, characterUtility.Address);
31-
DebugTab.DrawCopyableAddress("ResidentResourceManager"u8, residentResources.Address);
32-
DebugTab.DrawCopyableAddress("ScheduleManagement"u8, ScheduleManagement.Instance());
33-
DebugTab.DrawCopyableAddress("ActionTimelineManager*"u8, actionManager);
34-
DebugTab.DrawCopyableAddress("ActionTimelineManager"u8, actionManager != null ? *actionManager : null);
35-
DebugTab.DrawCopyableAddress("SchedulerResourceManagement*"u8, scheduler.Address);
36-
DebugTab.DrawCopyableAddress("SchedulerResourceManagement"u8, scheduler.Address != null ? *scheduler.Address : null);
37-
DebugTab.DrawCopyableAddress("Device"u8, Device.Instance());
30+
using (ImUtf8.Group())
31+
{
32+
Penumbra.Dynamis.DrawPointer(characterUtility.Address);
33+
Penumbra.Dynamis.DrawPointer(residentResources.Address);
34+
Penumbra.Dynamis.DrawPointer(ScheduleManagement.Instance());
35+
Penumbra.Dynamis.DrawPointer(actionManager);
36+
Penumbra.Dynamis.DrawPointer(actionManager != null ? *actionManager : null);
37+
Penumbra.Dynamis.DrawPointer(scheduler.Address);
38+
Penumbra.Dynamis.DrawPointer(scheduler.Address != null ? *scheduler.Address : null);
39+
Penumbra.Dynamis.DrawPointer(Device.Instance());
40+
}
41+
42+
ImGui.SameLine();
43+
using (ImUtf8.Group())
44+
{
45+
ImUtf8.Text("CharacterUtility"u8);
46+
ImUtf8.Text("ResidentResourceManager"u8);
47+
ImUtf8.Text("ScheduleManagement"u8);
48+
ImUtf8.Text("ActionTimelineManager*"u8);
49+
ImUtf8.Text("ActionTimelineManager"u8);
50+
ImUtf8.Text("SchedulerResourceManagement*"u8);
51+
ImUtf8.Text("SchedulerResourceManagement"u8);
52+
ImUtf8.Text("Device"u8);
53+
}
54+
3855
DrawCharacterUtility();
3956
DrawResidentResources();
4057
DrawSchedulerResourcesMap();
@@ -63,7 +80,7 @@ private void DrawCharacterUtility()
6380
var resource = characterUtility.Address->Resource(idx);
6481
ImUtf8.DrawTableColumn($"[{idx}]");
6582
ImGui.TableNextColumn();
66-
ImUtf8.CopyOnClickSelectable($"0x{(ulong)resource:X}");
83+
Penumbra.Dynamis.DrawPointer(resource);
6784
if (resource == null)
6885
{
6986
ImGui.TableNextRow();
@@ -74,25 +91,12 @@ private void DrawCharacterUtility()
7491
ImGui.TableNextColumn();
7592
var data = (nint)resource->CsHandle.GetData();
7693
var length = resource->CsHandle.GetLength();
77-
if (ImUtf8.Selectable($"0x{data:X}"))
78-
if (data != nint.Zero && length > 0)
79-
ImUtf8.SetClipboardText(string.Join("\n",
80-
new ReadOnlySpan<byte>((byte*)data, (int)length).ToArray().Select(b => b.ToString("X2"))));
81-
82-
ImUtf8.HoverTooltip("Click to copy bytes to clipboard."u8);
94+
Penumbra.Dynamis.DrawPointer(data);
8395
ImUtf8.DrawTableColumn(length.ToString());
84-
8596
ImGui.TableNextColumn();
8697
if (intern.Value != -1)
8798
{
88-
ImUtf8.Selectable($"0x{characterUtility.DefaultResource(intern).Address:X}");
89-
if (ImGui.IsItemClicked())
90-
ImUtf8.SetClipboardText(string.Join("\n",
91-
new ReadOnlySpan<byte>((byte*)characterUtility.DefaultResource(intern).Address,
92-
characterUtility.DefaultResource(intern).Size).ToArray().Select(b => b.ToString("X2"))));
93-
94-
ImUtf8.HoverTooltip("Click to copy bytes to clipboard."u8);
95-
99+
Penumbra.Dynamis.DrawPointer(characterUtility.DefaultResource(intern).Address);
96100
ImUtf8.DrawTableColumn($"{characterUtility.DefaultResource(intern).Size}");
97101
}
98102
else
@@ -122,7 +126,7 @@ private void DrawResidentResources()
122126
var resource = residentResources.Address->ResourceList[idx];
123127
ImUtf8.DrawTableColumn($"[{idx}]");
124128
ImGui.TableNextColumn();
125-
ImUtf8.CopyOnClickSelectable($"0x{(ulong)resource:X}");
129+
Penumbra.Dynamis.DrawPointer(resource);
126130
if (resource == null)
127131
{
128132
ImGui.TableNextRow();
@@ -133,12 +137,7 @@ private void DrawResidentResources()
133137
ImGui.TableNextColumn();
134138
var data = (nint)resource->CsHandle.GetData();
135139
var length = resource->CsHandle.GetLength();
136-
if (ImUtf8.Selectable($"0x{data:X}"))
137-
if (data != nint.Zero && length > 0)
138-
ImUtf8.SetClipboardText(string.Join("\n",
139-
new ReadOnlySpan<byte>((byte*)data, (int)length).ToArray().Select(b => b.ToString("X2"))));
140-
141-
ImUtf8.HoverTooltip("Click to copy bytes to clipboard."u8);
140+
Penumbra.Dynamis.DrawPointer(data);
142141
ImUtf8.DrawTableColumn(length.ToString());
143142
}
144143
}
@@ -184,15 +183,15 @@ private void DrawSchedulerResourcesMap()
184183
ImUtf8.DrawTableColumn($"{resource->Consumers}");
185184
ImUtf8.DrawTableColumn($"{resource->Unk1}"); // key
186185
ImGui.TableNextColumn();
187-
ImUtf8.CopyOnClickSelectable($"0x{(ulong)resource:X}");
186+
Penumbra.Dynamis.DrawPointer(resource);
188187
ImGui.TableNextColumn();
189188
var resourceHandle = *((ResourceHandle**)resource + 3);
190-
ImUtf8.CopyOnClickSelectable($"0x{(ulong)resourceHandle:X}");
189+
Penumbra.Dynamis.DrawPointer(resourceHandle);
191190
ImGui.TableNextColumn();
192191
ImUtf8.CopyOnClickSelectable(resourceHandle->FileName().Span);
193192
ImGui.TableNextColumn();
194193
uint dataLength = 0;
195-
ImUtf8.CopyOnClickSelectable($"0x{(ulong)resource->GetResourceData(&dataLength):X}");
194+
Penumbra.Dynamis.DrawPointer(resource->GetResourceData(&dataLength));
196195
ImUtf8.DrawTableColumn($"{dataLength}");
197196
++_shownResourcesMap;
198197
}
@@ -233,15 +232,15 @@ private void DrawSchedulerResourcesList()
233232
ImUtf8.DrawTableColumn($"{resource->Consumers}");
234233
ImUtf8.DrawTableColumn($"{resource->Unk1}"); // key
235234
ImGui.TableNextColumn();
236-
ImUtf8.CopyOnClickSelectable($"0x{(ulong)resource:X}");
235+
Penumbra.Dynamis.DrawPointer(resource);
237236
ImGui.TableNextColumn();
238237
var resourceHandle = *((ResourceHandle**)resource + 3);
239-
ImUtf8.CopyOnClickSelectable($"0x{(ulong)resourceHandle:X}");
238+
Penumbra.Dynamis.DrawPointer(resourceHandle);
240239
ImGui.TableNextColumn();
241240
ImUtf8.CopyOnClickSelectable(resourceHandle->FileName().Span);
242241
ImGui.TableNextColumn();
243242
uint dataLength = 0;
244-
ImUtf8.CopyOnClickSelectable($"0x{(ulong)resource->GetResourceData(&dataLength):X}");
243+
Penumbra.Dynamis.DrawPointer(resource->GetResourceData(&dataLength));
245244
ImUtf8.DrawTableColumn($"{dataLength}");
246245
++_shownResourcesList;
247246
}

Penumbra/packages.lock.json

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,19 @@
5858
"resolved": "3.1.11",
5959
"contentHash": "JfPLyigLthuE50yi6tMt7Amrenr/fA31t2CvJyhy/kQmfulIBAqo5T/YFUSRHtuYPXRSaUHygFeh6Qd933EoSw=="
6060
},
61+
"FlatSharp.Compiler": {
62+
"type": "Transitive",
63+
"resolved": "7.9.0",
64+
"contentHash": "MU6808xvdbWJ3Ev+5PKalqQuzvVbn1DzzQH8txRDHGFUNDvHjd+ejqpvnYc9BSJ8Qp8VjkkpJD8OzRYilbPp3A=="
65+
},
66+
"FlatSharp.Runtime": {
67+
"type": "Transitive",
68+
"resolved": "7.9.0",
69+
"contentHash": "Bm8+WqzEsWNpxqrD5x4x+zQ8dyINlToCreM5FI2oNSfUVc9U9ZB+qztX/jd8rlJb3r0vBSlPwVLpw0xBtPa3Vw==",
70+
"dependencies": {
71+
"System.Memory": "4.5.5"
72+
}
73+
},
6174
"JetBrains.Annotations": {
6275
"type": "Transitive",
6376
"resolved": "2024.3.0",
@@ -94,6 +107,11 @@
94107
"resolved": "4.6.0",
95108
"contentHash": "lN6tZi7Q46zFzAbRYXTIvfXcyvQQgxnY7Xm6C6xQ9784dEL1amjM6S6Iw4ZpsvesAKnRVsM4scrDQaDqSClkjA=="
96109
},
110+
"System.Memory": {
111+
"type": "Transitive",
112+
"resolved": "4.5.5",
113+
"contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw=="
114+
},
97115
"System.Security.Cryptography.Pkcs": {
98116
"type": "Transitive",
99117
"resolved": "8.0.1",
@@ -133,8 +151,10 @@
133151
"penumbra.gamedata": {
134152
"type": "Project",
135153
"dependencies": {
154+
"FlatSharp.Compiler": "[7.9.0, )",
155+
"FlatSharp.Runtime": "[7.9.0, )",
136156
"OtterGui": "[1.0.0, )",
137-
"Penumbra.Api": "[5.6.1, )",
157+
"Penumbra.Api": "[5.10.0, )",
138158
"Penumbra.String": "[1.0.6, )"
139159
}
140160
},

0 commit comments

Comments
 (0)