Skip to content

Commit

Permalink
Experimental support for custom pbd files
Browse files Browse the repository at this point in the history
  • Loading branch information
PassiveModding committed Jul 28, 2024
1 parent 8c3a493 commit 9c5f103
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 19 deletions.
3 changes: 2 additions & 1 deletion Meddle/Meddle.Plugin/Models/Groups.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ public record CharacterGroup(
AttachedModelGroup[] AttachedModelGroups);

public record AttachedModelGroup(Attach Attach, MdlFileGroup[] MdlGroups, Skeleton.Skeleton Skeleton);
public record MdlFileGroup(string CharacterPath, string Path, MdlFile MdlFile, MtrlFileGroup[] MtrlFiles, Model.ShapeAttributeGroup? ShapeAttributeGroup);
public record MdlFileGroup(string CharacterPath, string Path, DeformerGroup? DeformerGroup, MdlFile MdlFile, MtrlFileGroup[] MtrlFiles, Model.ShapeAttributeGroup? ShapeAttributeGroup);
public record MtrlFileGroup(string MdlPath, string Path, MtrlFile MtrlFile, string ShpkPath, ShpkFile ShpkFile, TexResourceGroup[] TexFiles);
public record TexResourceGroup(string MtrlPath, string Path, TextureResource Resource);
public record SklbFileGroup(string Path, SklbFile File);
public record Resource(string MdlPath, Vector3 Position, Quaternion Rotation, Vector3 Scale);
public record DeformerGroup(string Path, ushort RaceSexId, ushort DeformerId);
2 changes: 2 additions & 0 deletions Meddle/Meddle.Plugin/Plugin.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Dalamud.Configuration;
using Dalamud.Hooking;
using Dalamud.IoC;
using Dalamud.Plugin;
using Meddle.Plugin.Services;
Expand Down Expand Up @@ -49,6 +50,7 @@ public Plugin(IDalamudPluginInterface pluginInterface)
.AddSingleton<ExportUtil>()
.AddSingleton<ParseUtil>()
.AddSingleton<DXHelper>()
.AddSingleton<PbdHooks>()
.AddSingleton(new SqPack(Environment.CurrentDirectory))
.AddSingleton<PluginState>()
.AddHostedService<InteropService>();
Expand Down
8 changes: 7 additions & 1 deletion Meddle/Meddle.Plugin/Services/InteropService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.Interop.Generated;
using InteropGenerator.Runtime;
using Meddle.Plugin.Utils;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

Expand All @@ -14,6 +15,7 @@ public class InteropService : IHostedService, IDisposable
private readonly ILogger<InteropService> log;
private readonly IDalamudPluginInterface pluginInterface;
private readonly ISigScanner sigScanner;
private readonly PbdHooks pbdHooks;
private readonly PluginState state;

private bool disposed;
Expand All @@ -23,9 +25,10 @@ public class InteropService : IHostedService, IDisposable
private readonly Hook<PostTickDelegate> postTickHook = null!;

public InteropService(
ISigScanner sigScanner, ILogger<InteropService> log, IDalamudPluginInterface pluginInterface, PluginState state)
ISigScanner sigScanner, PbdHooks pbdHooks, ILogger<InteropService> log, IDalamudPluginInterface pluginInterface, PluginState state)
{
this.sigScanner = sigScanner;
this.pbdHooks = pbdHooks;
this.log = log;
this.pluginInterface = pluginInterface;
this.state = state;
Expand Down Expand Up @@ -55,6 +58,9 @@ public Task StartAsync(CancellationToken cancellationToken)
Resolver.GetInstance.Resolve();
state.InteropResolved = true;
log.LogInformation("Resolved ClientStructs");

pbdHooks.Setup();

return Task.CompletedTask;
}

Expand Down
13 changes: 11 additions & 2 deletions Meddle/Meddle.Plugin/UI/CharacterTab.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,9 @@ public unsafe class CharacterTab : ITab
public CharacterTab(
IObjectTable objectTable,
IClientState clientState,
IFramework framework,
ILogger<CharacterTab> log,
PluginState pluginState,
ExportUtil exportUtil,
IDataManager dataManager,
ITextureProvider textureProvider,
ParseUtil parseUtil,
Configuration config)
Expand Down Expand Up @@ -619,6 +617,17 @@ private void DrawMdlGroup(MdlFileGroup mdlGroup)
ImGui.Text($"Path: {mdlGroup.Path}");
ImGui.Text($"Mtrl Files: {mdlGroup.MtrlFiles.Length}");

if (mdlGroup.DeformerGroup != null)
{
ImGui.Text($"Deformer Path: {mdlGroup.DeformerGroup.Path}");
ImGui.Text($"RaceSexId: {mdlGroup.DeformerGroup.RaceSexId}");
ImGui.Text($"DeformerId: {mdlGroup.DeformerGroup.DeformerId}");
}
else
{
ImGui.Text("No Deformer Group");
}

var shouldShowShapeAttributeMenu =
mdlGroup.ShapeAttributeGroup is {ShapeMasks.Length: > 0} or {AttributeMasks.Length: > 0};

Expand Down
19 changes: 14 additions & 5 deletions Meddle/Meddle.Plugin/Utils/ExportUtil.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System.Diagnostics;
using System.Numerics;
using Meddle.Plugin.Models;
using Meddle.Plugin.Skeleton;
using Meddle.Utils;
using Meddle.Utils.Export;
using Meddle.Utils.Files;
Expand All @@ -13,8 +12,6 @@
using SharpGLTF.Materials;
using SharpGLTF.Scenes;
using SkiaSharp;
using CustomizeData = Meddle.Utils.Export.CustomizeData;
using CustomizeParameter = Meddle.Utils.Export.CustomizeParameter;
using Material = Meddle.Utils.Export.Material;
using Model = Meddle.Utils.Export.Model;

Expand All @@ -35,7 +32,7 @@ public ExportUtil(SqPack pack, ILogger<ExportUtil> logger)
this.logger = new EventLogger<ExportUtil>(logger);
this.logger.OnLogEvent += OnLog;

// chara/xls/bonedeformer/human.pbd
// chara/xls/boneDeformer/human.pbd
var pbdData = pack.GetFile("chara/xls/bonedeformer/human.pbd");
if (pbdData == null) throw new Exception("Failed to load human.pbd");
pbdFile = new PbdFile(pbdData.Value.file.RawData);
Expand Down Expand Up @@ -343,7 +340,19 @@ private MaterialBuilder HandleMaterial(CharacterGroup characterGroup, Material m

if (token.IsCancellationRequested) return meshOutput;

var raceDeformerValue = (characterGroup.GenderRace, new RaceDeformer(pbdFile, bones));
(GenderRace, RaceDeformer) raceDeformerValue;
if (mdlGroup.DeformerGroup != null)
{
var pbdFileData = pack.GetFileOrReadFromDisk(mdlGroup.DeformerGroup.Path);
if (pbdFileData == null) throw new InvalidOperationException($"Failed to get deformer pbd {mdlGroup.DeformerGroup.Path}");
raceDeformerValue = ((GenderRace)mdlGroup.DeformerGroup.RaceSexId, new RaceDeformer(new PbdFile(pbdFileData), bones));
model.RaceCode = (GenderRace)mdlGroup.DeformerGroup.DeformerId;
logger.LogInformation("Using deformer pbd {Path}", mdlGroup.DeformerGroup.Path);
}
else
{
raceDeformerValue = (characterGroup.GenderRace, new RaceDeformer(pbdFile, bones));
}

var meshes = ModelBuilder.BuildMeshes(model, materials, bones, raceDeformerValue);
foreach (var mesh in meshes)
Expand Down
24 changes: 16 additions & 8 deletions Meddle/Meddle.Plugin/Utils/ParseUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
using Meddle.Utils.Skeletons.Havok.Models;
using Microsoft.Extensions.Logging;
using Attach = Meddle.Plugin.Skeleton.Attach;
using CustomizeParameter = Meddle.Plugin.Models.CustomizeParameter;
using Texture = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture;

namespace Meddle.Plugin.Utils;
Expand All @@ -25,18 +24,20 @@ public class ParseUtil : IDisposable
{
private static readonly ActivitySource ActivitySource = new("Meddle.Plugin.Utils.ParseUtil");
private readonly DXHelper dxHelper;
private readonly PbdHooks pbdHooks;
private readonly EventLogger<ParseUtil> logger;
private readonly IFramework framework;
private readonly SqPack pack;
public event Action<LogLevel, string>? OnLogEvent;

private readonly Dictionary<string, ShpkFile> shpkCache = new();

public ParseUtil(SqPack pack, IFramework framework, DXHelper dxHelper, ILogger<ParseUtil> logger)
public ParseUtil(SqPack pack, IFramework framework, DXHelper dxHelper, PbdHooks pbdHooks, ILogger<ParseUtil> logger)
{
this.pack = pack;
this.framework = framework;
this.dxHelper = dxHelper;
this.pbdHooks = pbdHooks;
this.logger = new EventLogger<ParseUtil>(logger);
this.logger.OnLogEvent += OnLog;
}
Expand Down Expand Up @@ -142,19 +143,19 @@ public unsafe CharacterGroup HandleCharacterGroup(
}

public unsafe MdlFileGroup? HandleModelPtr(
CharacterBase* characterBase, int modelIdx, Dictionary<int, ColorTable> colorTables)
CharacterBase* characterBase, int slotIdx, Dictionary<int, ColorTable> colorTables)
{
using var activity = ActivitySource.StartActivity();
var modelPtr = characterBase->ModelsSpan[modelIdx];
var modelPtr = characterBase->ModelsSpan[slotIdx];
if (modelPtr == null || modelPtr.Value == null)
{
//logger.LogWarning("Model Ptr {ModelIndex} is null", modelIdx);
return null;
}
var model = modelPtr.Value;

var mdlFileName = model->ModelResourceHandle->ResourceHandle.FileName.ToString();
var mdlFileActorName = characterBase->ResolveMdlPath((uint)modelIdx);
var mdlFileActorName = characterBase->ResolveMdlPath((uint)slotIdx);
activity?.SetTag("mdl", mdlFileName);
var mdlFileResource = pack.GetFileOrReadFromDisk(mdlFileName);
if (mdlFileResource == null)
Expand Down Expand Up @@ -194,14 +195,21 @@ public unsafe CharacterGroup HandleCharacterGroup(
}

var mdlMtrlFileName = mtrlFileNames[j];
var mtrlGroup = HandleMtrl(mdlMtrlFileName, material, modelIdx, j, colorTables);
var mtrlGroup = HandleMtrl(mdlMtrlFileName, material, slotIdx, j, colorTables);
if (mtrlGroup != null)
{
mtrlGroups.Add(mtrlGroup);
}
}

var deformerData = pbdHooks.TryGetDeformer((nint)characterBase, (uint)slotIdx);
DeformerGroup? deformerGroup = null;
if (deformerData != null)
{
deformerGroup = new DeformerGroup(deformerData.Value.PbdPath, deformerData.Value.RaceSexId, deformerData.Value.DeformerId);
}

return new MdlFileGroup(mdlFileActorName, mdlFileName, mdlFile, mtrlGroups.ToArray(), shapeAttributeGroup);
return new MdlFileGroup(mdlFileActorName, mdlFileName, deformerGroup, mdlFile, mtrlGroups.ToArray(), shapeAttributeGroup);
}

private ShpkFile HandleShpk(string shader)
Expand Down
2 changes: 1 addition & 1 deletion Meddle/Meddle.Plugin/Utils/PathUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public static class PathUtil
return data;
}

var file = pack.GetFile(path);
var file = pack.GetFile(path.ToLower());

return file?.file.RawData.ToArray();
}
Expand Down
109 changes: 109 additions & 0 deletions Meddle/Meddle.Plugin/Utils/PbdHooks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using System.Runtime.InteropServices;
using Dalamud.Game;
using Dalamud.Hooking;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using Microsoft.Extensions.Logging;

namespace Meddle.Plugin.Utils;

public class PbdHooks : IDisposable
{
private readonly ISigScanner sigScanner;
private readonly IGameInteropProvider gameInterop;
private readonly ILogger<PbdHooks> logger;
public const string Human_CreateDeformerSig = "40 53 48 83 EC 20 4C 8B C1 83 FA 0D";
private delegate nint Human_CreateDeformerDelegate(nint humanPtr, uint slot);
private Hook<Human_CreateDeformerDelegate>? humanCreateDeformerHook;
private readonly Dictionary<nint, Dictionary<uint, DeformerCachedStruct>> deformerCache = new();

public PbdHooks(ISigScanner sigScanner, IGameInteropProvider gameInterop, ILogger<PbdHooks> logger)
{
this.sigScanner = sigScanner;
this.gameInterop = gameInterop;
this.logger = logger;
}

public void Setup()
{
if (sigScanner.TryScanText(Human_CreateDeformerSig, out var humanCreateDeformerPtr))
{
logger.LogInformation($"Found Human::CreateDeformer at {humanCreateDeformerPtr:X}");
humanCreateDeformerHook = gameInterop.HookFromAddress<Human_CreateDeformerDelegate>(humanCreateDeformerPtr, Human_CreateDeformerDetour);
humanCreateDeformerHook.Enable();
}
else
{
throw new Exception("Failed to hook into Human::CreateDeformer");
}
}

public DeformerCachedStruct? TryGetDeformer(nint humanPtr, uint slot)
{
if (!deformerCache.TryGetValue(humanPtr, out var slotCache))
return null;
if (!slotCache.TryGetValue(slot, out var deformer))
return null;
return deformer;
}

private unsafe nint Human_CreateDeformerDetour(nint humanPtr, uint slot)
{
var result = humanCreateDeformerHook!.Original(humanPtr, slot);

var deformer = (DeformerStruct*)result;
if (deformer != null && deformer->PbdPointer != null)
{
if (!deformerCache.TryGetValue(humanPtr, out var slotCache))
{
slotCache = new Dictionary<uint, DeformerCachedStruct>();
deformerCache[humanPtr] = slotCache;
}

slotCache[slot] = new DeformerCachedStruct
{
DeformerId = deformer->DeformerId,
RaceSexId = deformer->RaceSexId,
PbdPath = deformer->PbdPointer->FileName.ToString()
};
}
else
{
if (deformerCache.TryGetValue(humanPtr, out var slotCache))
{
slotCache.Remove(slot);
if (slotCache.Count == 0)
deformerCache.Remove(humanPtr);
}
}

return result;
}

public struct DeformerCachedStruct
{
public ushort RaceSexId;
public ushort DeformerId;
public string PbdPath;
}

[StructLayout(LayoutKind.Explicit, Size = 0x20)]
public struct DeformerStruct
{
[FieldOffset(0x10)]
public unsafe ResourceHandle* PbdPointer;

[FieldOffset(0x18)]
public ushort RaceSexId;

[FieldOffset(0x1A)]
public ushort DeformerId;
}

public void Dispose()
{
logger.LogInformation("Disposing PbdHooks");
humanCreateDeformerHook?.Dispose();
deformerCache.Clear();
}
}
2 changes: 1 addition & 1 deletion Meddle/Meddle.Utils/Export/Model.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public unsafe class Model
public string HandlePath { get; private set; }
public string? ResolvedPath { get; private set; }
public string Path => ResolvedPath ?? HandlePath;
public GenderRace RaceCode { get; private set; }
public GenderRace RaceCode { get; set; }

public IReadOnlyList<Material?> Materials { get; private set; }
public IReadOnlyList<Mesh> Meshes { get; private set; }
Expand Down

0 comments on commit 9c5f103

Please sign in to comment.