mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 12:14:17 +01:00
Merge branch 'master' into mtrl-improvements
This commit is contained in:
commit
ea44835321
34 changed files with 392 additions and 107 deletions
2
.github/workflows/test_release.yml
vendored
2
.github/workflows/test_release.yml
vendored
|
|
@ -20,7 +20,7 @@ jobs:
|
||||||
run: dotnet restore
|
run: dotnet restore
|
||||||
- name: Download Dalamud
|
- name: Download Dalamud
|
||||||
run: |
|
run: |
|
||||||
Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/latest.zip -OutFile latest.zip
|
Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip
|
||||||
Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev"
|
Expand-Archive -Force latest.zip "$env:AppData\XIVLauncher\addon\Hooks\dev"
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
||||||
Subproject commit 3e6b085749741f35dd6732c33d0720c6a51ebb97
|
Subproject commit 95b8d177883b03f804d77434f45e9de97fdb9adf
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 27cef2c1b8ef8ce9b73bc658e03f543b5c7ef29d
|
Subproject commit eaa8a31db7a483a112931c9a676f4ea2ff45520e
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit bd52d080b72d67263dc47068e461f17c93bdc779
|
Subproject commit dd83f97299ac33cfacb1064bde4f4d1f6a260936
|
||||||
|
|
@ -8,7 +8,10 @@ EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F89C9EAE-25C8-43BE-8108-5921E5A93502}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F89C9EAE-25C8-43BE-8108-5921E5A93502}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
.editorconfig = .editorconfig
|
.editorconfig = .editorconfig
|
||||||
|
.github\workflows\build.yml = .github\workflows\build.yml
|
||||||
|
.github\workflows\release.yml = .github\workflows\release.yml
|
||||||
repo.json = repo.json
|
repo.json = repo.json
|
||||||
|
.github\workflows\test_release.yml = .github\workflows\test_release.yml
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "Penumbra.GameData\Penumbra.GameData.csproj", "{EE551E87-FDB3-4612-B500-DC870C07C605}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "Penumbra.GameData\Penumbra.GameData.csproj", "{EE551E87-FDB3-4612-B500-DC870C07C605}"
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,7 @@ public sealed partial class IndividualCollections : IReadOnlyList<(string Displa
|
||||||
|
|
||||||
// Handle generic NPC
|
// Handle generic NPC
|
||||||
var npcIdentifier = _actors.CreateIndividualUnchecked(IdentifierType.Npc, ByteString.Empty,
|
var npcIdentifier = _actors.CreateIndividualUnchecked(IdentifierType.Npc, ByteString.Empty,
|
||||||
ushort.MaxValue,
|
ushort.MaxValue, identifier.Kind, identifier.DataId);
|
||||||
identifier.Kind, identifier.DataId);
|
|
||||||
if (npcIdentifier.IsValid && _individuals.TryGetValue(npcIdentifier, out collection))
|
if (npcIdentifier.IsValid && _individuals.TryGetValue(npcIdentifier, out collection))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
|
@ -58,8 +57,7 @@ public sealed partial class IndividualCollections : IReadOnlyList<(string Displa
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
identifier = _actors.CreateIndividualUnchecked(IdentifierType.Player, identifier.PlayerName,
|
identifier = _actors.CreateIndividualUnchecked(IdentifierType.Player, identifier.PlayerName,
|
||||||
identifier.HomeWorld.Id,
|
identifier.HomeWorld.Id, ObjectKind.None, uint.MaxValue);
|
||||||
ObjectKind.None, uint.MaxValue);
|
|
||||||
return CheckWorlds(identifier, out collection);
|
return CheckWorlds(identifier, out collection);
|
||||||
}
|
}
|
||||||
case IdentifierType.Npc: return _individuals.TryGetValue(identifier, out collection);
|
case IdentifierType.Npc: return _individuals.TryGetValue(identifier, out collection);
|
||||||
|
|
|
||||||
|
|
@ -33,25 +33,27 @@ public sealed unsafe class ApricotListenerSoundPlayCaller : FastHook<ApricotList
|
||||||
private nint Detour(nint a1, nint unused, float timeOffset)
|
private nint Detour(nint a1, nint unused, float timeOffset)
|
||||||
{
|
{
|
||||||
// Short-circuiting and sanity checks done by game.
|
// Short-circuiting and sanity checks done by game.
|
||||||
var playTime = a1 == nint.Zero ? -1 : *(float*)(a1 + 0x250);
|
var playTime = a1 == nint.Zero ? -1 : *(float*)(a1 + VolatileOffsets.ApricotListenerSoundPlayCaller.PlayTimeOffset);
|
||||||
if (playTime < 0)
|
if (playTime < 0)
|
||||||
return Task.Result.Original(a1, unused, timeOffset);
|
return Task.Result.Original(a1, unused, timeOffset);
|
||||||
|
|
||||||
var someIntermediate = *(nint*)(a1 + 0x1F8);
|
var someIntermediate = *(nint*)(a1 + VolatileOffsets.ApricotListenerSoundPlayCaller.SomeIntermediate);
|
||||||
var flags = someIntermediate == nint.Zero ? (ushort)0 : *(ushort*)(someIntermediate + 0x49C);
|
var flags = someIntermediate == nint.Zero
|
||||||
if (((flags >> 13) & 1) == 0)
|
? (ushort)0
|
||||||
|
: *(ushort*)(someIntermediate + VolatileOffsets.ApricotListenerSoundPlayCaller.Flags);
|
||||||
|
if (((flags >> VolatileOffsets.ApricotListenerSoundPlayCaller.BitShift) & 1) == 0)
|
||||||
return Task.Result.Original(a1, unused, timeOffset);
|
return Task.Result.Original(a1, unused, timeOffset);
|
||||||
|
|
||||||
Penumbra.Log.Excessive(
|
Penumbra.Log.Excessive(
|
||||||
$"[Apricot Listener Sound Play Caller] Invoked on 0x{a1:X} with {unused}, {timeOffset}.");
|
$"[Apricot Listener Sound Play Caller] Invoked on 0x{a1:X} with {unused}, {timeOffset}.");
|
||||||
// Fetch the IInstanceListenner (sixth argument to inlined call of SoundPlay)
|
// Fetch the IInstanceListenner (sixth argument to inlined call of SoundPlay)
|
||||||
var apricotIInstanceListenner = *(nint*)(someIntermediate + 0x270);
|
var apricotIInstanceListenner = *(nint*)(someIntermediate + VolatileOffsets.ApricotListenerSoundPlayCaller.IInstanceListenner);
|
||||||
if (apricotIInstanceListenner == nint.Zero)
|
if (apricotIInstanceListenner == nint.Zero)
|
||||||
return Task.Result.Original(a1, unused, timeOffset);
|
return Task.Result.Original(a1, unused, timeOffset);
|
||||||
|
|
||||||
// In some cases we can obtain the associated caster via vfunc 1.
|
// In some cases we can obtain the associated caster via vfunc 1.
|
||||||
var newData = ResolveData.Invalid;
|
var newData = ResolveData.Invalid;
|
||||||
var gameObject = (*(delegate* unmanaged<nint, GameObject*>**)apricotIInstanceListenner)[1](apricotIInstanceListenner);
|
var gameObject = (*(delegate* unmanaged<nint, GameObject*>**)apricotIInstanceListenner)[VolatileOffsets.ApricotListenerSoundPlayCaller.CasterVFunc](apricotIInstanceListenner);
|
||||||
if (gameObject != null)
|
if (gameObject != null)
|
||||||
{
|
{
|
||||||
newData = _collectionResolver.IdentifyCollection(gameObject, true);
|
newData = _collectionResolver.IdentifyCollection(gameObject, true);
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ public sealed unsafe class SomePapLoad : FastHook<SomePapLoad.Delegate>
|
||||||
private void Detour(nint a1, int a2, nint a3, int a4)
|
private void Detour(nint a1, int a2, nint a3, int a4)
|
||||||
{
|
{
|
||||||
Penumbra.Log.Excessive($"[Some PAP Load] Invoked on 0x{a1:X} with {a2}, {a3}, {a4}.");
|
Penumbra.Log.Excessive($"[Some PAP Load] Invoked on 0x{a1:X} with {a2}, {a3}, {a4}.");
|
||||||
var timelinePtr = a1 + Offsets.TimeLinePtr;
|
var timelinePtr = a1 + VolatileOffsets.AnimationState.TimeLinePtr;
|
||||||
if (timelinePtr != nint.Zero)
|
if (timelinePtr != nint.Zero)
|
||||||
{
|
{
|
||||||
var actorIdx = (int)(*(*(ulong**)timelinePtr + 1) >> 3);
|
var actorIdx = (int)(*(*(ulong**)timelinePtr + 1) >> 3);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using Penumbra.Interop.PathResolving;
|
using Penumbra.Interop.PathResolving;
|
||||||
using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;
|
|
||||||
|
|
||||||
namespace Penumbra.Interop.Hooks.Meta;
|
namespace Penumbra.Interop.Hooks.Meta;
|
||||||
|
|
||||||
|
|
@ -13,8 +13,9 @@ public sealed unsafe class CalculateHeight : FastHook<CalculateHeight.Delegate>
|
||||||
public CalculateHeight(HookManager hooks, CollectionResolver collectionResolver, MetaState metaState)
|
public CalculateHeight(HookManager hooks, CollectionResolver collectionResolver, MetaState metaState)
|
||||||
{
|
{
|
||||||
_collectionResolver = collectionResolver;
|
_collectionResolver = collectionResolver;
|
||||||
_metaState = metaState;
|
_metaState = metaState;
|
||||||
Task = hooks.CreateHook<Delegate>("Calculate Height", (nint)Character.MemberFunctionPointers.CalculateHeight, Detour, !HookOverrides.Instance.Meta.CalculateHeight);
|
Task = hooks.CreateHook<Delegate>("Calculate Height", (nint)Character.MemberFunctionPointers.CalculateHeight, Detour,
|
||||||
|
!HookOverrides.Instance.Meta.CalculateHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
public delegate ulong Delegate(Character* character);
|
public delegate ulong Delegate(Character* character);
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ public sealed unsafe class UpdateModel : FastHook<UpdateModel.Delegate>
|
||||||
{
|
{
|
||||||
// Shortcut because this is called all the time.
|
// Shortcut because this is called all the time.
|
||||||
// Same thing is checked at the beginning of the original function.
|
// Same thing is checked at the beginning of the original function.
|
||||||
if (*(int*)((nint)drawObject + Offsets.UpdateModelSkip) == 0)
|
if (*(int*)((nint)drawObject + VolatileOffsets.UpdateModel.ShortCircuit) == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Penumbra.Log.Excessive($"[Update Model] Invoked on {(nint)drawObject:X}.");
|
Penumbra.Log.Excessive($"[Update Model] Invoked on {(nint)drawObject:X}.");
|
||||||
|
|
|
||||||
|
|
@ -401,7 +401,6 @@ public sealed unsafe class ShaderReplacementFixer : IDisposable, IRequiredServic
|
||||||
private void ModelRendererUnkFuncDetour(CSModelRenderer* modelRenderer, ModelRendererStructs.UnkPayload* unkPayload, uint unk2, uint unk3,
|
private void ModelRendererUnkFuncDetour(CSModelRenderer* modelRenderer, ModelRendererStructs.UnkPayload* unkPayload, uint unk2, uint unk3,
|
||||||
uint unk4, uint unk5)
|
uint unk4, uint unk5)
|
||||||
{
|
{
|
||||||
// If we don't have any on-screen instances of modded iris.shpk or others, we don't need the slow path at all.
|
|
||||||
if (!Enabled || GetTotalMaterialCountForModelRendererUnk() == 0)
|
if (!Enabled || GetTotalMaterialCountForModelRendererUnk() == 0)
|
||||||
{
|
{
|
||||||
_modelRendererUnkFuncHook.Original(modelRenderer, unkPayload, unk2, unk3, unk4, unk5);
|
_modelRendererUnkFuncHook.Original(modelRenderer, unkPayload, unk2, unk3, unk4, unk5);
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,18 @@ public unsafe class ResourceLoader : IDisposable, IService
|
||||||
{
|
{
|
||||||
private readonly ResourceService _resources;
|
private readonly ResourceService _resources;
|
||||||
private readonly FileReadService _fileReadService;
|
private readonly FileReadService _fileReadService;
|
||||||
private readonly TexMdlService _texMdlService;
|
private readonly TexMdlScdService _texMdlScdService;
|
||||||
private readonly PapHandler _papHandler;
|
private readonly PapHandler _papHandler;
|
||||||
private readonly Configuration _config;
|
private readonly Configuration _config;
|
||||||
|
|
||||||
private ResolveData _resolvedData = ResolveData.Invalid;
|
private ResolveData _resolvedData = ResolveData.Invalid;
|
||||||
public event Action<Utf8GamePath, FullPath?, ResolveData>? PapRequested;
|
public event Action<Utf8GamePath, FullPath?, ResolveData>? PapRequested;
|
||||||
|
|
||||||
public ResourceLoader(ResourceService resources, FileReadService fileReadService, TexMdlService texMdlService, Configuration config)
|
public ResourceLoader(ResourceService resources, FileReadService fileReadService, TexMdlScdService texMdlScdService, Configuration config)
|
||||||
{
|
{
|
||||||
_resources = resources;
|
_resources = resources;
|
||||||
_fileReadService = fileReadService;
|
_fileReadService = fileReadService;
|
||||||
_texMdlService = texMdlService;
|
_texMdlScdService = texMdlScdService;
|
||||||
_config = config;
|
_config = config;
|
||||||
ResetResolvePath();
|
ResetResolvePath();
|
||||||
|
|
||||||
|
|
@ -140,7 +140,7 @@ public unsafe class ResourceLoader : IDisposable, IService
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_texMdlService.AddCrc(type, resolvedPath);
|
_texMdlScdService.AddCrc(type, resolvedPath);
|
||||||
// Replace the hash and path with the correct one for the replacement.
|
// Replace the hash and path with the correct one for the replacement.
|
||||||
hash = ComputeHash(resolvedPath.Value.InternalName, parameters);
|
hash = ComputeHash(resolvedPath.Value.InternalName, parameters);
|
||||||
var oldPath = path;
|
var oldPath = path;
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.Re
|
||||||
|
|
||||||
namespace Penumbra.Interop.Hooks.ResourceLoading;
|
namespace Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
|
|
||||||
public unsafe class TexMdlService : IDisposable, IRequiredService
|
public unsafe class TexMdlScdService : IDisposable, IRequiredService
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// We need to be able to obtain the requested LoD level.
|
/// We need to be able to obtain the requested LoD level.
|
||||||
|
|
@ -42,7 +42,7 @@ public unsafe class TexMdlService : IDisposable, IRequiredService
|
||||||
|
|
||||||
private readonly LodService _lodService;
|
private readonly LodService _lodService;
|
||||||
|
|
||||||
public TexMdlService(IGameInteropProvider interop)
|
public TexMdlScdService(IGameInteropProvider interop)
|
||||||
{
|
{
|
||||||
interop.InitializeFromAttributes(this);
|
interop.InitializeFromAttributes(this);
|
||||||
_lodService = new LodService(interop);
|
_lodService = new LodService(interop);
|
||||||
|
|
@ -52,6 +52,7 @@ public unsafe class TexMdlService : IDisposable, IRequiredService
|
||||||
_loadMdlFileExternHook.Enable();
|
_loadMdlFileExternHook.Enable();
|
||||||
if (!HookOverrides.Instance.ResourceLoading.TexResourceHandleOnLoad)
|
if (!HookOverrides.Instance.ResourceLoading.TexResourceHandleOnLoad)
|
||||||
_textureOnLoadHook.Enable();
|
_textureOnLoadHook.Enable();
|
||||||
|
_soundOnLoadHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Add CRC64 if the given file is a model or texture file and has an associated path. </summary>
|
/// <summary> Add CRC64 if the given file is a model or texture file and has an associated path. </summary>
|
||||||
|
|
@ -59,8 +60,9 @@ public unsafe class TexMdlService : IDisposable, IRequiredService
|
||||||
{
|
{
|
||||||
_ = type switch
|
_ = type switch
|
||||||
{
|
{
|
||||||
ResourceType.Mdl when path.HasValue => _customMdlCrc.Add(path.Value.Crc64),
|
ResourceType.Mdl when path.HasValue => _customFileCrc.TryAdd(path.Value.Crc64, ResourceType.Mdl),
|
||||||
ResourceType.Tex when path.HasValue => _customTexCrc.Add(path.Value.Crc64),
|
ResourceType.Tex when path.HasValue => _customFileCrc.TryAdd(path.Value.Crc64, ResourceType.Tex),
|
||||||
|
ResourceType.Scd when path.HasValue => _customFileCrc.TryAdd(path.Value.Crc64, ResourceType.Scd),
|
||||||
_ => false,
|
_ => false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -70,15 +72,16 @@ public unsafe class TexMdlService : IDisposable, IRequiredService
|
||||||
_checkFileStateHook.Dispose();
|
_checkFileStateHook.Dispose();
|
||||||
_loadMdlFileExternHook.Dispose();
|
_loadMdlFileExternHook.Dispose();
|
||||||
_textureOnLoadHook.Dispose();
|
_textureOnLoadHook.Dispose();
|
||||||
|
_soundOnLoadHook.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// We need to keep a list of all CRC64 hash values of our replaced Mdl and Tex files,
|
/// We need to keep a list of all CRC64 hash values of our replaced Mdl and Tex files,
|
||||||
/// i.e. CRC32 of filename in the lower bytes, CRC32 of parent path in the upper bytes.
|
/// i.e. CRC32 of filename in the lower bytes, CRC32 of parent path in the upper bytes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly HashSet<ulong> _customMdlCrc = [];
|
private readonly Dictionary<ulong, ResourceType> _customFileCrc = [];
|
||||||
|
public IReadOnlyDictionary<ulong, ResourceType> CustomCache
|
||||||
private readonly HashSet<ulong> _customTexCrc = [];
|
=> _customFileCrc;
|
||||||
|
|
||||||
private delegate nint CheckFileStatePrototype(nint unk1, ulong crc64);
|
private delegate nint CheckFileStatePrototype(nint unk1, ulong crc64);
|
||||||
|
|
||||||
|
|
@ -86,12 +89,34 @@ public unsafe class TexMdlService : IDisposable, IRequiredService
|
||||||
private readonly Hook<CheckFileStatePrototype> _checkFileStateHook = null!;
|
private readonly Hook<CheckFileStatePrototype> _checkFileStateHook = null!;
|
||||||
|
|
||||||
private readonly ThreadLocal<bool> _texReturnData = new(() => default);
|
private readonly ThreadLocal<bool> _texReturnData = new(() => default);
|
||||||
|
private readonly ThreadLocal<bool> _scdReturnData = new(() => default);
|
||||||
|
|
||||||
private delegate void UpdateCategoryDelegate(TextureResourceHandle* resourceHandle);
|
private delegate void UpdateCategoryDelegate(TextureResourceHandle* resourceHandle);
|
||||||
|
|
||||||
[Signature(Sigs.TexHandleUpdateCategory)]
|
[Signature(Sigs.TexHandleUpdateCategory)]
|
||||||
private readonly UpdateCategoryDelegate _updateCategory = null!;
|
private readonly UpdateCategoryDelegate _updateCategory = null!;
|
||||||
|
|
||||||
|
private delegate byte SoundOnLoadDelegate(ResourceHandle* handle, SeFileDescriptor* descriptor, byte unk);
|
||||||
|
|
||||||
|
[Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 30 8B 79 ?? 48 8B DA 8B D7")]
|
||||||
|
private readonly delegate* unmanaged<ResourceHandle*, SeFileDescriptor*, byte, byte> _loadScdFileLocal = null!;
|
||||||
|
|
||||||
|
[Signature("40 56 57 41 54 48 81 EC 90 00 00 00 80 3A 0B 45 0F B6 E0 48 8B F2", DetourName = nameof(OnScdLoadDetour))]
|
||||||
|
private readonly Hook<SoundOnLoadDelegate> _soundOnLoadHook = null!;
|
||||||
|
|
||||||
|
private byte OnScdLoadDetour(ResourceHandle* handle, SeFileDescriptor* descriptor, byte unk)
|
||||||
|
{
|
||||||
|
var ret = _soundOnLoadHook.Original(handle, descriptor, unk);
|
||||||
|
if (!_scdReturnData.Value)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
// Function failed on a replaced scd, call local.
|
||||||
|
_scdReturnData.Value = false;
|
||||||
|
ret = _loadScdFileLocal(handle, descriptor, unk);
|
||||||
|
_updateCategory((TextureResourceHandle*)handle);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The function that checks a files CRC64 to determine whether it is 'protected'.
|
/// The function that checks a files CRC64 to determine whether it is 'protected'.
|
||||||
/// We use it to check against our stored CRC64s and if it corresponds, we return the custom flag for models.
|
/// We use it to check against our stored CRC64s and if it corresponds, we return the custom flag for models.
|
||||||
|
|
@ -100,14 +125,17 @@ public unsafe class TexMdlService : IDisposable, IRequiredService
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private nint CheckFileStateDetour(nint ptr, ulong crc64)
|
private nint CheckFileStateDetour(nint ptr, ulong crc64)
|
||||||
{
|
{
|
||||||
if (_customMdlCrc.Contains(crc64))
|
if (_customFileCrc.TryGetValue(crc64, out var type))
|
||||||
return CustomFileFlag;
|
switch (type)
|
||||||
|
{
|
||||||
if (_customTexCrc.Contains(crc64))
|
case ResourceType.Mdl: return CustomFileFlag;
|
||||||
{
|
case ResourceType.Tex:
|
||||||
_texReturnData.Value = true;
|
_texReturnData.Value = true;
|
||||||
return nint.Zero;
|
return nint.Zero;
|
||||||
}
|
case ResourceType.Scd:
|
||||||
|
_scdReturnData.Value = true;
|
||||||
|
return nint.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
var ret = _checkFileStateHook.Original(ptr, crc64);
|
var ret = _checkFileStateHook.Original(ptr, crc64);
|
||||||
Penumbra.Log.Excessive($"[CheckFileState] Called on 0x{ptr:X} with CRC {crc64:X16}, returned 0x{ret:X}.");
|
Penumbra.Log.Excessive($"[CheckFileState] Called on 0x{ptr:X} with CRC {crc64:X16}, returned 0x{ret:X}.");
|
||||||
|
|
@ -128,10 +156,10 @@ public unsafe class TexMdlService : IDisposable, IRequiredService
|
||||||
|
|
||||||
private delegate byte TexResourceHandleOnLoadPrototype(TextureResourceHandle* handle, SeFileDescriptor* descriptor, byte unk2);
|
private delegate byte TexResourceHandleOnLoadPrototype(TextureResourceHandle* handle, SeFileDescriptor* descriptor, byte unk2);
|
||||||
|
|
||||||
[Signature(Sigs.TexHandleOnLoad, DetourName = nameof(OnLoadDetour))]
|
[Signature(Sigs.TexHandleOnLoad, DetourName = nameof(OnTexLoadDetour))]
|
||||||
private readonly Hook<TexResourceHandleOnLoadPrototype> _textureOnLoadHook = null!;
|
private readonly Hook<TexResourceHandleOnLoadPrototype> _textureOnLoadHook = null!;
|
||||||
|
|
||||||
private byte OnLoadDetour(TextureResourceHandle* handle, SeFileDescriptor* descriptor, byte unk2)
|
private byte OnTexLoadDetour(TextureResourceHandle* handle, SeFileDescriptor* descriptor, byte unk2)
|
||||||
{
|
{
|
||||||
var ret = _textureOnLoadHook.Original(handle, descriptor, unk2);
|
var ret = _textureOnLoadHook.Original(handle, descriptor, unk2);
|
||||||
if (!_texReturnData.Value)
|
if (!_texReturnData.Value)
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,9 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable
|
||||||
private readonly Hook<PerSlotResolveDelegate> _resolveMdlPathHook;
|
private readonly Hook<PerSlotResolveDelegate> _resolveMdlPathHook;
|
||||||
private readonly Hook<NamedResolveDelegate> _resolveMtrlPathHook;
|
private readonly Hook<NamedResolveDelegate> _resolveMtrlPathHook;
|
||||||
private readonly Hook<NamedResolveDelegate> _resolvePapPathHook;
|
private readonly Hook<NamedResolveDelegate> _resolvePapPathHook;
|
||||||
|
private readonly Hook<PerSlotResolveDelegate> _resolveKdbPathHook;
|
||||||
private readonly Hook<PerSlotResolveDelegate> _resolvePhybPathHook;
|
private readonly Hook<PerSlotResolveDelegate> _resolvePhybPathHook;
|
||||||
|
private readonly Hook<PerSlotResolveDelegate> _resolveBnmbPathHook;
|
||||||
private readonly Hook<PerSlotResolveDelegate> _resolveSklbPathHook;
|
private readonly Hook<PerSlotResolveDelegate> _resolveSklbPathHook;
|
||||||
private readonly Hook<PerSlotResolveDelegate> _resolveSkpPathHook;
|
private readonly Hook<PerSlotResolveDelegate> _resolveSkpPathHook;
|
||||||
private readonly Hook<TmbResolveDelegate> _resolveTmbPathHook;
|
private readonly Hook<TmbResolveDelegate> _resolveTmbPathHook;
|
||||||
|
|
@ -54,11 +56,10 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable
|
||||||
_resolveMdlPathHook = Create<PerSlotResolveDelegate>($"{name}.{nameof(ResolveMdl)}", hooks, vTable[77], type, ResolveMdl, ResolveMdlHuman);
|
_resolveMdlPathHook = Create<PerSlotResolveDelegate>($"{name}.{nameof(ResolveMdl)}", hooks, vTable[77], type, ResolveMdl, ResolveMdlHuman);
|
||||||
_resolveSkpPathHook = Create<PerSlotResolveDelegate>($"{name}.{nameof(ResolveSkp)}", hooks, vTable[78], type, ResolveSkp, ResolveSkpHuman);
|
_resolveSkpPathHook = Create<PerSlotResolveDelegate>($"{name}.{nameof(ResolveSkp)}", hooks, vTable[78], type, ResolveSkp, ResolveSkpHuman);
|
||||||
_resolvePhybPathHook = Create<PerSlotResolveDelegate>($"{name}.{nameof(ResolvePhyb)}", hooks, vTable[79], type, ResolvePhyb, ResolvePhybHuman);
|
_resolvePhybPathHook = Create<PerSlotResolveDelegate>($"{name}.{nameof(ResolvePhyb)}", hooks, vTable[79], type, ResolvePhyb, ResolvePhybHuman);
|
||||||
|
_resolveKdbPathHook = Create<PerSlotResolveDelegate>($"{name}.{nameof(ResolveKdb)}", hooks, vTable[80], type, ResolveKdb, ResolveKdbHuman);
|
||||||
_vFunc81Hook = Create<SkeletonVFuncDelegate>( $"{name}.{nameof(VFunc81)}", hooks, vTable[81], type, null, VFunc81);
|
_vFunc81Hook = Create<SkeletonVFuncDelegate>( $"{name}.{nameof(VFunc81)}", hooks, vTable[81], type, null, VFunc81);
|
||||||
|
_resolveBnmbPathHook = Create<PerSlotResolveDelegate>($"{name}.{nameof(ResolveBnmb)}", hooks, vTable[82], type, ResolveBnmb, ResolveBnmbHuman);
|
||||||
_vFunc83Hook = Create<SkeletonVFuncDelegate>( $"{name}.{nameof(VFunc83)}", hooks, vTable[83], type, null, VFunc83);
|
_vFunc83Hook = Create<SkeletonVFuncDelegate>( $"{name}.{nameof(VFunc83)}", hooks, vTable[83], type, null, VFunc83);
|
||||||
|
|
||||||
_resolvePapPathHook = Create<NamedResolveDelegate>( $"{name}.{nameof(ResolvePap)}", hooks, vTable[84], type, ResolvePap, ResolvePapHuman);
|
_resolvePapPathHook = Create<NamedResolveDelegate>( $"{name}.{nameof(ResolvePap)}", hooks, vTable[84], type, ResolvePap, ResolvePapHuman);
|
||||||
_resolveTmbPathHook = Create<TmbResolveDelegate>( $"{name}.{nameof(ResolveTmb)}", hooks, vTable[85], ResolveTmb);
|
_resolveTmbPathHook = Create<TmbResolveDelegate>( $"{name}.{nameof(ResolveTmb)}", hooks, vTable[85], ResolveTmb);
|
||||||
_resolveMPapPathHook = Create<MPapResolveDelegate>( $"{name}.{nameof(ResolveMPap)}", hooks, vTable[87], ResolveMPap);
|
_resolveMPapPathHook = Create<MPapResolveDelegate>( $"{name}.{nameof(ResolveMPap)}", hooks, vTable[87], ResolveMPap);
|
||||||
|
|
@ -83,7 +84,9 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable
|
||||||
_resolveMdlPathHook.Enable();
|
_resolveMdlPathHook.Enable();
|
||||||
_resolveMtrlPathHook.Enable();
|
_resolveMtrlPathHook.Enable();
|
||||||
_resolvePapPathHook.Enable();
|
_resolvePapPathHook.Enable();
|
||||||
|
_resolveKdbPathHook.Enable();
|
||||||
_resolvePhybPathHook.Enable();
|
_resolvePhybPathHook.Enable();
|
||||||
|
_resolveBnmbPathHook.Enable();
|
||||||
_resolveSklbPathHook.Enable();
|
_resolveSklbPathHook.Enable();
|
||||||
_resolveSkpPathHook.Enable();
|
_resolveSkpPathHook.Enable();
|
||||||
_resolveTmbPathHook.Enable();
|
_resolveTmbPathHook.Enable();
|
||||||
|
|
@ -101,7 +104,9 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable
|
||||||
_resolveMdlPathHook.Disable();
|
_resolveMdlPathHook.Disable();
|
||||||
_resolveMtrlPathHook.Disable();
|
_resolveMtrlPathHook.Disable();
|
||||||
_resolvePapPathHook.Disable();
|
_resolvePapPathHook.Disable();
|
||||||
|
_resolveKdbPathHook.Disable();
|
||||||
_resolvePhybPathHook.Disable();
|
_resolvePhybPathHook.Disable();
|
||||||
|
_resolveBnmbPathHook.Disable();
|
||||||
_resolveSklbPathHook.Disable();
|
_resolveSklbPathHook.Disable();
|
||||||
_resolveSkpPathHook.Disable();
|
_resolveSkpPathHook.Disable();
|
||||||
_resolveTmbPathHook.Disable();
|
_resolveTmbPathHook.Disable();
|
||||||
|
|
@ -119,7 +124,9 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable
|
||||||
_resolveMdlPathHook.Dispose();
|
_resolveMdlPathHook.Dispose();
|
||||||
_resolveMtrlPathHook.Dispose();
|
_resolveMtrlPathHook.Dispose();
|
||||||
_resolvePapPathHook.Dispose();
|
_resolvePapPathHook.Dispose();
|
||||||
|
_resolveKdbPathHook.Dispose();
|
||||||
_resolvePhybPathHook.Dispose();
|
_resolvePhybPathHook.Dispose();
|
||||||
|
_resolveBnmbPathHook.Dispose();
|
||||||
_resolveSklbPathHook.Dispose();
|
_resolveSklbPathHook.Dispose();
|
||||||
_resolveSkpPathHook.Dispose();
|
_resolveSkpPathHook.Dispose();
|
||||||
_resolveTmbPathHook.Dispose();
|
_resolveTmbPathHook.Dispose();
|
||||||
|
|
@ -149,9 +156,15 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable
|
||||||
private nint ResolvePap(nint drawObject, nint pathBuffer, nint pathBufferSize, uint unkAnimationIndex, nint animationName)
|
private nint ResolvePap(nint drawObject, nint pathBuffer, nint pathBufferSize, uint unkAnimationIndex, nint animationName)
|
||||||
=> ResolvePath(drawObject, _resolvePapPathHook.Original(drawObject, pathBuffer, pathBufferSize, unkAnimationIndex, animationName));
|
=> ResolvePath(drawObject, _resolvePapPathHook.Original(drawObject, pathBuffer, pathBufferSize, unkAnimationIndex, animationName));
|
||||||
|
|
||||||
|
private nint ResolveKdb(nint drawObject, nint pathBuffer, nint pathBufferSize, uint partialSkeletonIndex)
|
||||||
|
=> ResolvePath(drawObject, _resolveKdbPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex));
|
||||||
|
|
||||||
private nint ResolvePhyb(nint drawObject, nint pathBuffer, nint pathBufferSize, uint partialSkeletonIndex)
|
private nint ResolvePhyb(nint drawObject, nint pathBuffer, nint pathBufferSize, uint partialSkeletonIndex)
|
||||||
=> ResolvePath(drawObject, _resolvePhybPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex));
|
=> ResolvePath(drawObject, _resolvePhybPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex));
|
||||||
|
|
||||||
|
private nint ResolveBnmb(nint drawObject, nint pathBuffer, nint pathBufferSize, uint partialSkeletonIndex)
|
||||||
|
=> ResolvePath(drawObject, _resolveBnmbPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex));
|
||||||
|
|
||||||
private nint ResolveSklb(nint drawObject, nint pathBuffer, nint pathBufferSize, uint partialSkeletonIndex)
|
private nint ResolveSklb(nint drawObject, nint pathBuffer, nint pathBufferSize, uint partialSkeletonIndex)
|
||||||
=> ResolvePath(drawObject, _resolveSklbPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex));
|
=> ResolvePath(drawObject, _resolveSklbPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex));
|
||||||
|
|
||||||
|
|
@ -188,6 +201,15 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private nint ResolveKdbHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint partialSkeletonIndex)
|
||||||
|
{
|
||||||
|
var collection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true);
|
||||||
|
_parent.MetaState.EstCollection.Push(collection);
|
||||||
|
var ret = ResolvePath(collection, _resolveKdbPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex));
|
||||||
|
_parent.MetaState.EstCollection.Pop();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
private nint ResolvePhybHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint partialSkeletonIndex)
|
private nint ResolvePhybHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint partialSkeletonIndex)
|
||||||
{
|
{
|
||||||
var collection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true);
|
var collection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true);
|
||||||
|
|
@ -197,6 +219,15 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private nint ResolveBnmbHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint partialSkeletonIndex)
|
||||||
|
{
|
||||||
|
var collection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true);
|
||||||
|
_parent.MetaState.EstCollection.Push(collection);
|
||||||
|
var ret = ResolvePath(collection, _resolveBnmbPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex));
|
||||||
|
_parent.MetaState.EstCollection.Pop();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
private nint ResolveSklbHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint partialSkeletonIndex)
|
private nint ResolveSklbHuman(nint drawObject, nint pathBuffer, nint pathBufferSize, uint partialSkeletonIndex)
|
||||||
{
|
{
|
||||||
var collection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true);
|
var collection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
using OtterGui;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.Collections.Manager;
|
using Penumbra.Collections.Manager;
|
||||||
|
|
@ -62,10 +63,11 @@ public sealed unsafe class CollectionResolver(
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (useCache && cache.TryGetValue(gameObject, out var data))
|
// Login screen reuses the same actors and can not be cached.
|
||||||
|
if (LoginScreen(gameObject, out var data))
|
||||||
return data;
|
return data;
|
||||||
|
|
||||||
if (LoginScreen(gameObject, out data))
|
if (useCache && cache.TryGetValue(gameObject, out data))
|
||||||
return data;
|
return data;
|
||||||
|
|
||||||
if (Aesthetician(gameObject, out data))
|
if (Aesthetician(gameObject, out data))
|
||||||
|
|
@ -116,16 +118,17 @@ public sealed unsafe class CollectionResolver(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var notYetReady = false;
|
var notYetReady = false;
|
||||||
var lobby = AgentLobby.Instance();
|
var lobby = AgentLobby.Instance();
|
||||||
if (lobby != null)
|
var characterList = CharaSelectCharacterList.Instance();
|
||||||
|
if (lobby != null && characterList != null)
|
||||||
{
|
{
|
||||||
var span = lobby->LobbyData.CharaSelectEntries.AsSpan();
|
|
||||||
// The lobby uses the first 8 cutscene actors.
|
// The lobby uses the first 8 cutscene actors.
|
||||||
var idx = gameObject->ObjectIndex - ObjectIndex.CutsceneStart.Index;
|
var idx = gameObject->ObjectIndex - ObjectIndex.CutsceneStart.Index;
|
||||||
if (idx >= 0 && idx < span.Length && span[idx].Value != null)
|
if (characterList->CharacterMapping.FindFirst(m => m.ClientObjectIndex == idx, out var mapping)
|
||||||
|
&& lobby->LobbyData.CharaSelectEntries.FindFirst(e => e.Value->ContentId == mapping.ContentId, out var charaEntry))
|
||||||
{
|
{
|
||||||
var item = span[idx].Value;
|
var item = charaEntry.Value;
|
||||||
var identifier = actors.CreatePlayer(new ByteString(item->Name), item->HomeWorldId);
|
var identifier = actors.CreatePlayer(new ByteString(item->Name), item->HomeWorldId);
|
||||||
Penumbra.Log.Verbose(
|
Penumbra.Log.Verbose(
|
||||||
$"Identified {identifier.Incognito(null)} in cutscene for actor {idx + 200} at 0x{(ulong)gameObject:X} of race {(gameObject->IsCharacter() ? ((Character*)gameObject)->DrawData.CustomizeData.Race.ToString() : "Unknown")}.");
|
$"Identified {identifier.Incognito(null)} in cutscene for actor {idx + 200} at 0x{(ulong)gameObject:X} of race {(gameObject->IsCharacter() ? ((Character*)gameObject)->DrawData.CustomizeData.Race.ToString() : "Unknown")}.");
|
||||||
|
|
@ -141,7 +144,7 @@ public sealed unsafe class CollectionResolver(
|
||||||
var collection = collectionManager.Active.ByType(CollectionType.Yourself)
|
var collection = collectionManager.Active.ByType(CollectionType.Yourself)
|
||||||
?? CollectionByAttributes(gameObject, ref notYetReady)
|
?? CollectionByAttributes(gameObject, ref notYetReady)
|
||||||
?? collectionManager.Active.Default;
|
?? collectionManager.Active.Default;
|
||||||
ret = notYetReady ? collection.ToResolveData(gameObject) : cache.Set(collection, ActorIdentifier.Invalid, gameObject);
|
ret = collection.ToResolveData(gameObject);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,10 +199,24 @@ public sealed unsafe class CollectionResolver(
|
||||||
|
|
||||||
/// <summary> Check both temporary and permanent character collections. Temporary first. </summary>
|
/// <summary> Check both temporary and permanent character collections. Temporary first. </summary>
|
||||||
private ModCollection? CollectionByIdentifier(ActorIdentifier identifier)
|
private ModCollection? CollectionByIdentifier(ActorIdentifier identifier)
|
||||||
=> tempCollections.Collections.TryGetCollection(identifier, out var collection)
|
{
|
||||||
|| collectionManager.Active.Individuals.TryGetCollection(identifier, out collection)
|
if (tempCollections.Collections.TryGetCollection(identifier, out var collection))
|
||||||
? collection
|
return collection;
|
||||||
: null;
|
|
||||||
|
// Always inherit ownership for temporary collections.
|
||||||
|
if (identifier.Type is IdentifierType.Owned)
|
||||||
|
{
|
||||||
|
var playerIdentifier = actors.CreateIndividualUnchecked(IdentifierType.Player, identifier.PlayerName,
|
||||||
|
identifier.HomeWorld.Id, ObjectKind.None, uint.MaxValue);
|
||||||
|
if (tempCollections.Collections.TryGetCollection(playerIdentifier, out collection))
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collectionManager.Active.Individuals.TryGetCollection(identifier, out collection))
|
||||||
|
return collection;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Check for the Yourself collection. </summary>
|
/// <summary> Check for the Yourself collection. </summary>
|
||||||
private ModCollection? CheckYourself(ActorIdentifier identifier, Actor actor)
|
private ModCollection? CheckYourself(ActorIdentifier identifier, Actor actor)
|
||||||
|
|
@ -223,7 +240,7 @@ public sealed unsafe class CollectionResolver(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only handle human models.
|
// Only handle human models.
|
||||||
if (!IsModelHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId))
|
if (!IsModelHuman((uint)actor.AsCharacter->ModelContainer.ModelCharaId))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (actor.Customize->Data[0] == 0)
|
if (actor.Customize->Data[0] == 0)
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,10 @@ public class PathResolver : IDisposable, IService
|
||||||
if (resourceType is ResourceType.Lvb or ResourceType.Lgb or ResourceType.Sgb)
|
if (resourceType is ResourceType.Lvb or ResourceType.Lgb or ResourceType.Sgb)
|
||||||
return (null, ResolveData.Invalid);
|
return (null, ResolveData.Invalid);
|
||||||
|
|
||||||
|
// Prevent .atch loading to prevent crashes on outdated .atch files. TODO: handle atch modding differently.
|
||||||
|
if (resourceType is ResourceType.Atch)
|
||||||
|
return (null, ResolveData.Invalid);
|
||||||
|
|
||||||
return category switch
|
return category switch
|
||||||
{
|
{
|
||||||
// Only Interface collection.
|
// Only Interface collection.
|
||||||
|
|
|
||||||
|
|
@ -36,17 +36,16 @@ internal partial record ResolveContext
|
||||||
private Utf8GamePath ResolveEquipmentModelPath()
|
private Utf8GamePath ResolveEquipmentModelPath()
|
||||||
{
|
{
|
||||||
var path = IsEquipmentSlot(SlotIndex)
|
var path = IsEquipmentSlot(SlotIndex)
|
||||||
? GamePaths.Equipment.Mdl.Path(Equipment.Set, ResolveModelRaceCode(), Slot.ToSlot())
|
? GamePaths.Equipment.Mdl.Path(Equipment.Set, ResolveModelRaceCode(), SlotIndex.ToEquipSlot())
|
||||||
: GamePaths.Accessory.Mdl.Path(Equipment.Set, ResolveModelRaceCode(), SlotIndex.ToEquipSlot());
|
: GamePaths.Accessory.Mdl.Path(Equipment.Set, ResolveModelRaceCode(), SlotIndex.ToEquipSlot());
|
||||||
return Utf8GamePath.FromString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty;
|
return Utf8GamePath.FromString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
private GenderRace ResolveModelRaceCode()
|
private GenderRace ResolveModelRaceCode()
|
||||||
=> ResolveEqdpRaceCode(Slot.ToSlot(), Equipment.Set);
|
=> ResolveEqdpRaceCode(SlotIndex, Equipment.Set);
|
||||||
|
|
||||||
private unsafe GenderRace ResolveEqdpRaceCode(EquipSlot slot, PrimaryId primaryId)
|
private unsafe GenderRace ResolveEqdpRaceCode(uint slotIndex, PrimaryId primaryId)
|
||||||
{
|
{
|
||||||
var slotIndex = slot.ToIndex();
|
|
||||||
if (!IsEquipmentOrAccessorySlot(slotIndex) || ModelType != ModelType.Human)
|
if (!IsEquipmentOrAccessorySlot(slotIndex) || ModelType != ModelType.Human)
|
||||||
return GenderRace.MidlanderMale;
|
return GenderRace.MidlanderMale;
|
||||||
|
|
||||||
|
|
@ -61,6 +60,7 @@ internal partial record ResolveContext
|
||||||
var metaCache = Global.Collection.MetaCache;
|
var metaCache = Global.Collection.MetaCache;
|
||||||
var entry = metaCache?.GetEqdpEntry(characterRaceCode, accessory, primaryId)
|
var entry = metaCache?.GetEqdpEntry(characterRaceCode, accessory, primaryId)
|
||||||
?? ExpandedEqdpFile.GetDefault(Global.MetaFileManager, characterRaceCode, accessory, primaryId);
|
?? ExpandedEqdpFile.GetDefault(Global.MetaFileManager, characterRaceCode, accessory, primaryId);
|
||||||
|
var slot = slotIndex.ToEquipSlot();
|
||||||
if (entry.ToBits(slot).Item2)
|
if (entry.ToBits(slot).Item2)
|
||||||
return characterRaceCode;
|
return characterRaceCode;
|
||||||
|
|
||||||
|
|
@ -272,7 +272,7 @@ internal partial record ResolveContext
|
||||||
{
|
{
|
||||||
var human = (Human*)CharacterBase;
|
var human = (Human*)CharacterBase;
|
||||||
var equipment = ((CharacterArmor*)&human->Head)[slot.ToIndex()];
|
var equipment = ((CharacterArmor*)&human->Head)[slot.ToIndex()];
|
||||||
return ResolveHumanExtraSkeletonData(ResolveEqdpRaceCode(slot, equipment.Set), type, equipment.Set);
|
return ResolveHumanExtraSkeletonData(ResolveEqdpRaceCode(slot.ToIndex(), equipment.Set), type, equipment.Set);
|
||||||
}
|
}
|
||||||
|
|
||||||
private (GenderRace RaceCode, string Slot, PrimaryId Set) ResolveHumanExtraSkeletonData(GenderRace raceCode, EstType type,
|
private (GenderRace RaceCode, string Slot, PrimaryId Set) ResolveHumanExtraSkeletonData(GenderRace raceCode, EstType type,
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ public class ResourceTree
|
||||||
Unsafe.AsPointer(ref character->DrawData.EquipmentModelIds[0]), 10),
|
Unsafe.AsPointer(ref character->DrawData.EquipmentModelIds[0]), 10),
|
||||||
_ => [],
|
_ => [],
|
||||||
};
|
};
|
||||||
ModelId = character->CharacterData.ModelCharaId;
|
ModelId = character->ModelContainer.ModelCharaId;
|
||||||
CustomizeData = character->DrawData.CustomizeData;
|
CustomizeData = character->DrawData.CustomizeData;
|
||||||
RaceCode = human != null ? (GenderRace)human->RaceSexId : GenderRace.Unknown;
|
RaceCode = human != null ? (GenderRace)human->RaceSexId : GenderRace.Unknown;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ public unsafe class FontReloader : IService
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_atkModule = &atkModule->AtkModule;
|
_atkModule = &atkModule->AtkModule;
|
||||||
_reloadFontsFunc = ((delegate* unmanaged<AtkModule*, bool, bool, void>*)_atkModule->VirtualTable)[Offsets.ReloadFontsVfunc];
|
_reloadFontsFunc = ((delegate* unmanaged<AtkModule*, bool, bool, void>*)_atkModule->VirtualTable)[VolatileOffsets.FontReloader.ReloadFontsVFunc];
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,10 +38,10 @@ public unsafe partial class RedrawService : IService
|
||||||
|
|
||||||
// VFuncs that disable and enable draw, used only for GPose actors.
|
// VFuncs that disable and enable draw, used only for GPose actors.
|
||||||
private static void DisableDraw(IGameObject actor)
|
private static void DisableDraw(IGameObject actor)
|
||||||
=> ((delegate* unmanaged<nint, void >**)actor.Address)[0][Offsets.DisableDrawVfunc](actor.Address);
|
=> ((delegate* unmanaged<nint, void >**)actor.Address)[0][VolatileOffsets.RedrawService.DisableDrawVFunc](actor.Address);
|
||||||
|
|
||||||
private static void EnableDraw(IGameObject actor)
|
private static void EnableDraw(IGameObject actor)
|
||||||
=> ((delegate* unmanaged<nint, void >**)actor.Address)[0][Offsets.EnableDrawVfunc](actor.Address);
|
=> ((delegate* unmanaged<nint, void >**)actor.Address)[0][VolatileOffsets.RedrawService.EnableDrawVFunc](actor.Address);
|
||||||
|
|
||||||
// Check whether we currently are in GPose.
|
// Check whether we currently are in GPose.
|
||||||
// Also clear the name list.
|
// Also clear the name list.
|
||||||
|
|
|
||||||
35
Penumbra/Interop/VolatileOffsets.cs
Normal file
35
Penumbra/Interop/VolatileOffsets.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
namespace Penumbra.Interop;
|
||||||
|
|
||||||
|
public static class VolatileOffsets
|
||||||
|
{
|
||||||
|
public static class ApricotListenerSoundPlayCaller
|
||||||
|
{
|
||||||
|
public const int PlayTimeOffset = 0x254;
|
||||||
|
public const int SomeIntermediate = 0x1F8;
|
||||||
|
public const int Flags = 0x4A4;
|
||||||
|
public const int IInstanceListenner = 0x270;
|
||||||
|
public const int BitShift = 13;
|
||||||
|
public const int CasterVFunc = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AnimationState
|
||||||
|
{
|
||||||
|
public const int TimeLinePtr = 0x50;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class UpdateModel
|
||||||
|
{
|
||||||
|
public const int ShortCircuit = 0xA2C;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class FontReloader
|
||||||
|
{
|
||||||
|
public const int ReloadFontsVFunc = 43;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RedrawService
|
||||||
|
{
|
||||||
|
public const int EnableDrawVFunc = 12;
|
||||||
|
public const int DisableDrawVFunc = 13;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -79,6 +79,17 @@ public class MetaDictionary
|
||||||
_globalEqp.Clear();
|
_globalEqp.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ClearForDefault()
|
||||||
|
{
|
||||||
|
Count = _globalEqp.Count;
|
||||||
|
_imc.Clear();
|
||||||
|
_eqp.Clear();
|
||||||
|
_eqdp.Clear();
|
||||||
|
_est.Clear();
|
||||||
|
_rsp.Clear();
|
||||||
|
_gmp.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
public bool Equals(MetaDictionary other)
|
public bool Equals(MetaDictionary other)
|
||||||
=> Count == other.Count
|
=> Count == other.Count
|
||||||
&& _imc.SetEquals(other._imc)
|
&& _imc.SetEquals(other._imc)
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,8 @@ public class ModMetaEditor(
|
||||||
public static bool DeleteDefaultValues(MetaFileManager metaFileManager, MetaDictionary dict)
|
public static bool DeleteDefaultValues(MetaFileManager metaFileManager, MetaDictionary dict)
|
||||||
{
|
{
|
||||||
var clone = dict.Clone();
|
var clone = dict.Clone();
|
||||||
dict.Clear();
|
dict.ClearForDefault();
|
||||||
|
|
||||||
var count = 0;
|
var count = 0;
|
||||||
foreach (var (key, value) in clone.Imc)
|
foreach (var (key, value) in clone.Imc)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,7 @@ public sealed class ImcModGroupEditor(CommunicatorService communicator, SaveServ
|
||||||
|
|
||||||
protected override bool MoveOption(ImcModGroup group, int optionIdxFrom, int optionIdxTo)
|
protected override bool MoveOption(ImcModGroup group, int optionIdxFrom, int optionIdxTo)
|
||||||
{
|
{
|
||||||
if (!group.OptionData.Move(optionIdxFrom, optionIdxTo))
|
if (!group.OptionData.Move(ref optionIdxFrom, ref optionIdxTo))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
group.DefaultSettings = group.DefaultSettings.MoveBit(optionIdxFrom, optionIdxTo);
|
group.DefaultSettings = group.DefaultSettings.MoveBit(optionIdxFrom, optionIdxTo);
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ public class ModGroupEditor(
|
||||||
{
|
{
|
||||||
var mod = group.Mod;
|
var mod = group.Mod;
|
||||||
var idxFrom = group.GetIndex();
|
var idxFrom = group.GetIndex();
|
||||||
if (!mod.Groups.Move(idxFrom, groupIdxTo))
|
if (!mod.Groups.Move(ref idxFrom, ref groupIdxTo))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
saveService.SaveAllOptionGroups(mod, false, config.ReplaceNonAsciiOnImport);
|
saveService.SaveAllOptionGroups(mod, false, config.ReplaceNonAsciiOnImport);
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ public sealed class MultiModGroupEditor(CommunicatorService communicator, SaveSe
|
||||||
|
|
||||||
protected override bool MoveOption(MultiModGroup group, int optionIdxFrom, int optionIdxTo)
|
protected override bool MoveOption(MultiModGroup group, int optionIdxFrom, int optionIdxTo)
|
||||||
{
|
{
|
||||||
if (!group.OptionData.Move(optionIdxFrom, optionIdxTo))
|
if (!group.OptionData.Move(ref optionIdxFrom, ref optionIdxTo))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
group.DefaultSettings = group.DefaultSettings.MoveBit(optionIdxFrom, optionIdxTo);
|
group.DefaultSettings = group.DefaultSettings.MoveBit(optionIdxFrom, optionIdxTo);
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ public sealed class SingleModGroupEditor(CommunicatorService communicator, SaveS
|
||||||
|
|
||||||
protected override bool MoveOption(SingleModGroup group, int optionIdxFrom, int optionIdxTo)
|
protected override bool MoveOption(SingleModGroup group, int optionIdxFrom, int optionIdxTo)
|
||||||
{
|
{
|
||||||
if (!group.OptionData.Move(optionIdxFrom, optionIdxTo))
|
if (!group.OptionData.Move(ref optionIdxFrom, ref optionIdxTo))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
group.DefaultSettings = group.DefaultSettings.MoveSingle(optionIdxFrom, optionIdxTo);
|
group.DefaultSettings = group.DefaultSettings.MoveSingle(optionIdxFrom, optionIdxTo);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Log;
|
using OtterGui.Log;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
|
|
@ -20,6 +19,7 @@ using OtterGui.Tasks;
|
||||||
using Penumbra.UI;
|
using Penumbra.UI;
|
||||||
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
|
using Lumina.Excel.Sheets;
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.Interop.Hooks;
|
using Penumbra.Interop.Hooks;
|
||||||
using Penumbra.Interop.Hooks.ResourceLoading;
|
using Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
|
|
@ -111,7 +111,7 @@ public class Penumbra : IDalamudPlugin
|
||||||
private void SetupApi()
|
private void SetupApi()
|
||||||
{
|
{
|
||||||
_services.GetService<IpcProviders>();
|
_services.GetService<IpcProviders>();
|
||||||
var itemSheet = _services.GetService<IDataManager>().GetExcelSheet<Item>()!;
|
var itemSheet = _services.GetService<IDataManager>().GetExcelSheet<Item>();
|
||||||
_communicatorService.ChangedItemHover.Subscribe(it =>
|
_communicatorService.ChangedItemHover.Subscribe(it =>
|
||||||
{
|
{
|
||||||
if (it is IdentifiedItem { Item.Id.IsItem: true })
|
if (it is IdentifiedItem { Item.Id.IsItem: true })
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
"RepoUrl": "https://github.com/xivdev/Penumbra",
|
"RepoUrl": "https://github.com/xivdev/Penumbra",
|
||||||
"ApplicableVersion": "any",
|
"ApplicableVersion": "any",
|
||||||
"Tags": [ "modding" ],
|
"Tags": [ "modding" ],
|
||||||
"DalamudApiLevel": 10,
|
"DalamudApiLevel": 11,
|
||||||
"LoadPriority": 69420,
|
"LoadPriority": 69420,
|
||||||
"LoadState": 2,
|
"LoadState": 2,
|
||||||
"LoadSync": true,
|
"LoadSync": true,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.ImGuiNotification;
|
using Dalamud.Interface.ImGuiNotification;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.Sheets;
|
||||||
using OtterGui.Log;
|
using OtterGui.Log;
|
||||||
using OtterGui.Services;
|
using OtterGui.Services;
|
||||||
using Penumbra.Mods.Manager;
|
using Penumbra.Mods.Manager;
|
||||||
|
|
@ -16,7 +16,7 @@ namespace Penumbra.Services;
|
||||||
public class MessageService(Logger log, IUiBuilder builder, IChatGui chat, INotificationManager notificationManager)
|
public class MessageService(Logger log, IUiBuilder builder, IChatGui chat, INotificationManager notificationManager)
|
||||||
: OtterGui.Classes.MessageService(log, builder, chat, notificationManager), IService
|
: OtterGui.Classes.MessageService(log, builder, chat, notificationManager), IService
|
||||||
{
|
{
|
||||||
public void LinkItem(Item item)
|
public void LinkItem(in Item item)
|
||||||
{
|
{
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
var payloadList = new List<Payload>
|
var payloadList = new List<Payload>
|
||||||
|
|
@ -29,7 +29,7 @@ public class MessageService(Logger log, IUiBuilder builder, IChatGui chat, INoti
|
||||||
new TextPayload($"{(char)SeIconChar.LinkMarker}"),
|
new TextPayload($"{(char)SeIconChar.LinkMarker}"),
|
||||||
new UIForegroundPayload(0),
|
new UIForegroundPayload(0),
|
||||||
new UIGlowPayload(0),
|
new UIGlowPayload(0),
|
||||||
new TextPayload(item.Name),
|
new TextPayload(item.Name.ExtractText()),
|
||||||
new RawPayload([0x02, 0x27, 0x07, 0xCF, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03]),
|
new RawPayload([0x02, 0x27, 0x07, 0xCF, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03]),
|
||||||
new RawPayload([0x02, 0x13, 0x02, 0xEC, 0x03]),
|
new RawPayload([0x02, 0x13, 0x02, 0xEC, 0x03]),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
56
Penumbra/UI/Tabs/Debug/AtchDrawer.cs
Normal file
56
Penumbra/UI/Tabs/Debug/AtchDrawer.cs
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Text;
|
||||||
|
using Penumbra.GameData.Files;
|
||||||
|
|
||||||
|
namespace Penumbra.UI.Tabs.Debug;
|
||||||
|
|
||||||
|
public static class AtchDrawer
|
||||||
|
{
|
||||||
|
public static void Draw(AtchFile file)
|
||||||
|
{
|
||||||
|
using (ImUtf8.Group())
|
||||||
|
{
|
||||||
|
ImUtf8.Text("Entries: "u8);
|
||||||
|
ImUtf8.Text("States: "u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.SameLine();
|
||||||
|
using (ImUtf8.Group())
|
||||||
|
{
|
||||||
|
ImUtf8.Text($"{file.Entries.Count}");
|
||||||
|
if (file.Entries.Count == 0)
|
||||||
|
{
|
||||||
|
ImUtf8.Text("0"u8);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImUtf8.Text($"{file.Entries[0].States.Count}");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (entry, index) in file.Entries.WithIndex())
|
||||||
|
{
|
||||||
|
using var id = ImUtf8.PushId(index);
|
||||||
|
using var tree = ImUtf8.TreeNode($"{index:D3}: {entry.Name.Span}");
|
||||||
|
if (tree)
|
||||||
|
{
|
||||||
|
ImUtf8.TreeNode(entry.Accessory ? "Accessory"u8 : "Weapon"u8, ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||||
|
foreach (var (state, i) in entry.States.WithIndex())
|
||||||
|
{
|
||||||
|
id.Push(i);
|
||||||
|
using var t = ImUtf8.TreeNode(state.Bone.Span);
|
||||||
|
if (t)
|
||||||
|
{
|
||||||
|
ImUtf8.TreeNode($"Scale: {state.Scale}", ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||||
|
ImUtf8.TreeNode($"Offset: {state.Offset.X} | {state.Offset.Y} | {state.Offset.Z}",
|
||||||
|
ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||||
|
ImUtf8.TreeNode($"Rotation: {state.Rotation.X} | {state.Rotation.Y} | {state.Rotation.Z}",
|
||||||
|
ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
id.Pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -42,6 +42,7 @@ using Penumbra.Api.IpcTester;
|
||||||
using Penumbra.Interop.Hooks.PostProcessing;
|
using Penumbra.Interop.Hooks.PostProcessing;
|
||||||
using Penumbra.Interop.Hooks.ResourceLoading;
|
using Penumbra.Interop.Hooks.ResourceLoading;
|
||||||
using Penumbra.GameData.Files.StainMapStructs;
|
using Penumbra.GameData.Files.StainMapStructs;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.UI.AdvancedWindow.Materials;
|
using Penumbra.UI.AdvancedWindow.Materials;
|
||||||
|
|
||||||
namespace Penumbra.UI.Tabs.Debug;
|
namespace Penumbra.UI.Tabs.Debug;
|
||||||
|
|
@ -54,6 +55,9 @@ public class Diagnostics(ServiceManager provider) : IUiService
|
||||||
return;
|
return;
|
||||||
|
|
||||||
using var table = ImRaii.Table("##data", 4, ImGuiTableFlags.RowBg);
|
using var table = ImRaii.Table("##data", 4, ImGuiTableFlags.RowBg);
|
||||||
|
if (!table)
|
||||||
|
return;
|
||||||
|
|
||||||
foreach (var type in typeof(ActorManager).Assembly.GetTypes()
|
foreach (var type in typeof(ActorManager).Assembly.GetTypes()
|
||||||
.Where(t => t is { IsAbstract: false, IsInterface: false } && t.IsAssignableTo(typeof(IAsyncDataContainer))))
|
.Where(t => t is { IsAbstract: false, IsInterface: false } && t.IsAssignableTo(typeof(IAsyncDataContainer))))
|
||||||
{
|
{
|
||||||
|
|
@ -95,13 +99,15 @@ public class DebugTab : Window, ITab, IUiService
|
||||||
private readonly Diagnostics _diagnostics;
|
private readonly Diagnostics _diagnostics;
|
||||||
private readonly ObjectManager _objects;
|
private readonly ObjectManager _objects;
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
|
private readonly IDataManager _dataManager;
|
||||||
private readonly IpcTester _ipcTester;
|
private readonly IpcTester _ipcTester;
|
||||||
private readonly CrashHandlerPanel _crashHandlerPanel;
|
private readonly CrashHandlerPanel _crashHandlerPanel;
|
||||||
private readonly TexHeaderDrawer _texHeaderDrawer;
|
private readonly TexHeaderDrawer _texHeaderDrawer;
|
||||||
private readonly HookOverrideDrawer _hookOverrides;
|
private readonly HookOverrideDrawer _hookOverrides;
|
||||||
|
private readonly TexMdlScdService _texMdlScdService;
|
||||||
|
|
||||||
public DebugTab(PerformanceTracker performance, Configuration config, CollectionManager collectionManager, ObjectManager objects,
|
public DebugTab(PerformanceTracker performance, Configuration config, CollectionManager collectionManager, ObjectManager objects,
|
||||||
IClientState clientState,
|
IClientState clientState, IDataManager dataManager,
|
||||||
ValidityChecker validityChecker, ModManager modManager, HttpApi httpApi, ActorManager actors, StainService stains,
|
ValidityChecker validityChecker, ModManager modManager, HttpApi httpApi, ActorManager actors, StainService stains,
|
||||||
CharacterUtility characterUtility, ResidentResourceManager residentResources,
|
CharacterUtility characterUtility, ResidentResourceManager residentResources,
|
||||||
ResourceManagerService resourceManager, CollectionResolver collectionResolver,
|
ResourceManagerService resourceManager, CollectionResolver collectionResolver,
|
||||||
|
|
@ -109,7 +115,7 @@ public class DebugTab : Window, ITab, IUiService
|
||||||
CutsceneService cutsceneService, ModImportManager modImporter, ImportPopup importPopup, FrameworkManager framework,
|
CutsceneService cutsceneService, ModImportManager modImporter, ImportPopup importPopup, FrameworkManager framework,
|
||||||
TextureManager textureManager, ShaderReplacementFixer shaderReplacementFixer, RedrawService redraws, DictEmote emotes,
|
TextureManager textureManager, ShaderReplacementFixer shaderReplacementFixer, RedrawService redraws, DictEmote emotes,
|
||||||
Diagnostics diagnostics, IpcTester ipcTester, CrashHandlerPanel crashHandlerPanel, TexHeaderDrawer texHeaderDrawer,
|
Diagnostics diagnostics, IpcTester ipcTester, CrashHandlerPanel crashHandlerPanel, TexHeaderDrawer texHeaderDrawer,
|
||||||
HookOverrideDrawer hookOverrides)
|
HookOverrideDrawer hookOverrides, TexMdlScdService texMdlScdService)
|
||||||
: base("Penumbra Debug Window", ImGuiWindowFlags.NoCollapse)
|
: base("Penumbra Debug Window", ImGuiWindowFlags.NoCollapse)
|
||||||
{
|
{
|
||||||
IsOpen = true;
|
IsOpen = true;
|
||||||
|
|
@ -147,8 +153,10 @@ public class DebugTab : Window, ITab, IUiService
|
||||||
_crashHandlerPanel = crashHandlerPanel;
|
_crashHandlerPanel = crashHandlerPanel;
|
||||||
_texHeaderDrawer = texHeaderDrawer;
|
_texHeaderDrawer = texHeaderDrawer;
|
||||||
_hookOverrides = hookOverrides;
|
_hookOverrides = hookOverrides;
|
||||||
|
_texMdlScdService = texMdlScdService;
|
||||||
_objects = objects;
|
_objects = objects;
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
|
_dataManager = dataManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> Label
|
public ReadOnlySpan<byte> Label
|
||||||
|
|
@ -180,6 +188,7 @@ public class DebugTab : Window, ITab, IUiService
|
||||||
DrawDebugCharacterUtility();
|
DrawDebugCharacterUtility();
|
||||||
DrawShaderReplacementFixer();
|
DrawShaderReplacementFixer();
|
||||||
DrawData();
|
DrawData();
|
||||||
|
DrawCrcCache();
|
||||||
DrawResourceProblems();
|
DrawResourceProblems();
|
||||||
_hookOverrides.Draw();
|
_hookOverrides.Draw();
|
||||||
DrawPlayerModelInfo();
|
DrawPlayerModelInfo();
|
||||||
|
|
@ -188,7 +197,7 @@ public class DebugTab : Window, ITab, IUiService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void DrawCollectionCaches()
|
private unsafe void DrawCollectionCaches()
|
||||||
{
|
{
|
||||||
if (!ImGui.CollapsingHeader(
|
if (!ImGui.CollapsingHeader(
|
||||||
$"Collections ({_collectionManager.Caches.Count}/{_collectionManager.Storage.Count - 1} Caches)###Collections"))
|
$"Collections ({_collectionManager.Caches.Count}/{_collectionManager.Storage.Count - 1} Caches)###Collections"))
|
||||||
|
|
@ -199,25 +208,35 @@ public class DebugTab : Window, ITab, IUiService
|
||||||
if (collection.HasCache)
|
if (collection.HasCache)
|
||||||
{
|
{
|
||||||
using var color = PushColor(ImGuiCol.Text, ColorId.FolderExpanded.Value());
|
using var color = PushColor(ImGuiCol.Text, ColorId.FolderExpanded.Value());
|
||||||
using var node = TreeNode($"{collection.AnonymizedName} (Change Counter {collection.ChangeCounter})");
|
using var node = TreeNode($"{collection.Name} (Change Counter {collection.ChangeCounter})###{collection.Name}");
|
||||||
if (!node)
|
if (!node)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
color.Pop();
|
color.Pop();
|
||||||
foreach (var (mod, paths, manips) in collection._cache!.ModData.Data.OrderBy(t => t.Item1.Name))
|
using (var resourceNode = ImUtf8.TreeNode("Custom Resources"u8))
|
||||||
{
|
{
|
||||||
using var id = mod is TemporaryMod t ? PushId(t.Priority.Value) : PushId(((Mod)mod).ModPath.Name);
|
if (resourceNode)
|
||||||
using var node2 = TreeNode(mod.Name.Text);
|
foreach (var (path, resource) in collection._cache!.CustomResources)
|
||||||
if (!node2)
|
ImUtf8.TreeNode($"{path} -> 0x{(ulong)resource.ResourceHandle:X}",
|
||||||
continue;
|
ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||||
|
|
||||||
foreach (var path in paths)
|
|
||||||
|
|
||||||
TreeNode(path.ToString(), ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf).Dispose();
|
|
||||||
|
|
||||||
foreach (var manip in manips)
|
|
||||||
TreeNode(manip.ToString(), ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf).Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using var modNode = ImUtf8.TreeNode("Enabled Mods"u8);
|
||||||
|
if (modNode)
|
||||||
|
foreach (var (mod, paths, manips) in collection._cache!.ModData.Data.OrderBy(t => t.Item1.Name))
|
||||||
|
{
|
||||||
|
using var id = mod is TemporaryMod t ? PushId(t.Priority.Value) : PushId(((Mod)mod).ModPath.Name);
|
||||||
|
using var node2 = TreeNode(mod.Name.Text);
|
||||||
|
if (!node2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
foreach (var path in paths)
|
||||||
|
|
||||||
|
TreeNode(path.ToString(), ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||||
|
|
||||||
|
foreach (var manip in manips)
|
||||||
|
TreeNode(manip.ToString(), ImGuiTreeNodeFlags.Bullet | ImGuiTreeNodeFlags.Leaf).Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -659,11 +678,36 @@ public class DebugTab : Window, ITab, IUiService
|
||||||
|
|
||||||
DrawEmotes();
|
DrawEmotes();
|
||||||
DrawStainTemplates();
|
DrawStainTemplates();
|
||||||
|
DrawAtch();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string _emoteSearchFile = string.Empty;
|
private string _emoteSearchFile = string.Empty;
|
||||||
private string _emoteSearchName = string.Empty;
|
private string _emoteSearchName = string.Empty;
|
||||||
|
|
||||||
|
|
||||||
|
private AtchFile? _atchFile;
|
||||||
|
|
||||||
|
private void DrawAtch()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_atchFile ??= new AtchFile(_dataManager.GetFile("chara/xls/attachOffset/c0101.atch")!.Data);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_atchFile == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using var mainTree = ImUtf8.TreeNode("Atch File C0101"u8);
|
||||||
|
if (!mainTree)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AtchDrawer.Draw(_atchFile);
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawEmotes()
|
private void DrawEmotes()
|
||||||
{
|
{
|
||||||
using var mainTree = TreeNode("Emotes");
|
using var mainTree = TreeNode("Emotes");
|
||||||
|
|
@ -1018,6 +1062,40 @@ public class DebugTab : Window, ITab, IUiService
|
||||||
DrawDebugResidentResources();
|
DrawDebugResidentResources();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string _crcInput = string.Empty;
|
||||||
|
private FullPath _crcPath = FullPath.Empty;
|
||||||
|
|
||||||
|
private unsafe void DrawCrcCache()
|
||||||
|
{
|
||||||
|
var header = ImUtf8.CollapsingHeader("CRC Cache"u8);
|
||||||
|
if (!header)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ImUtf8.InputText("##crcInput"u8, ref _crcInput, "Input path for CRC..."u8))
|
||||||
|
_crcPath = new FullPath(_crcInput);
|
||||||
|
|
||||||
|
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||||
|
ImUtf8.Text($" CRC32: {_crcPath.InternalName.CiCrc32:X8}");
|
||||||
|
ImUtf8.Text($"CI CRC32: {_crcPath.InternalName.Crc32:X8}");
|
||||||
|
ImUtf8.Text($" CRC64: {_crcPath.Crc64:X16}");
|
||||||
|
|
||||||
|
using var table = ImUtf8.Table("table"u8, 2);
|
||||||
|
if (!table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImUtf8.TableSetupColumn("Hash"u8, ImGuiTableColumnFlags.WidthFixed, 18 * UiBuilder.MonoFont.GetCharAdvance('0'));
|
||||||
|
ImUtf8.TableSetupColumn("Type"u8, ImGuiTableColumnFlags.WidthFixed, 5 * UiBuilder.MonoFont.GetCharAdvance('0'));
|
||||||
|
ImGui.TableHeadersRow();
|
||||||
|
|
||||||
|
foreach (var (hash, type) in _texMdlScdService.CustomCache)
|
||||||
|
{
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImUtf8.Text($"{hash:X16}");
|
||||||
|
ImGui.TableNextColumn();
|
||||||
|
ImUtf8.Text($"{type}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary> Draw resources with unusual reference count. </summary>
|
/// <summary> Draw resources with unusual reference count. </summary>
|
||||||
private unsafe void DrawResourceProblems()
|
private unsafe void DrawResourceProblems()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -34,28 +34,49 @@ public class HookOverrideDrawer(IDalamudPluginInterface pluginInterface) : IUiSe
|
||||||
Penumbra.Log.Error($"Could not delete hook override file at {path}:\n{ex}");
|
Penumbra.Log.Error($"Could not delete hook override file at {path}:\n{ex}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool? allVisible = null;
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImUtf8.Button("Disable All Visible Hooks"u8))
|
||||||
|
allVisible = true;
|
||||||
|
ImGui.SameLine();
|
||||||
|
if (ImUtf8.Button("Enable All VisibleHooks"u8))
|
||||||
|
allVisible = false;
|
||||||
|
|
||||||
bool? all = null;
|
bool? all = null;
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImUtf8.Button("Disable All Hooks"u8))
|
if (ImUtf8.Button("Disable All Hooks"))
|
||||||
all = true;
|
all = true;
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
if (ImUtf8.Button("Enable All Hooks"u8))
|
if (ImUtf8.Button("Enable All Hooks"))
|
||||||
all = false;
|
all = false;
|
||||||
|
|
||||||
foreach (var propertyField in typeof(HookOverrides).GetFields().Where(f => f is { IsStatic: false, FieldType.IsValueType: true }))
|
foreach (var propertyField in typeof(HookOverrides).GetFields().Where(f => f is { IsStatic: false, FieldType.IsValueType: true }))
|
||||||
{
|
{
|
||||||
using var tree = ImUtf8.TreeNode(propertyField.Name);
|
using var tree = ImUtf8.TreeNode(propertyField.Name);
|
||||||
if (!tree)
|
if (!tree)
|
||||||
continue;
|
|
||||||
|
|
||||||
var property = propertyField.GetValue(_overrides);
|
|
||||||
foreach (var valueField in propertyField.FieldType.GetFields())
|
|
||||||
{
|
{
|
||||||
var value = valueField.GetValue(property) as bool? ?? false;
|
if (all.HasValue)
|
||||||
if (ImUtf8.Checkbox($"Disable {valueField.Name}", ref value) || all.HasValue)
|
|
||||||
{
|
{
|
||||||
valueField.SetValue(property, all ?? value);
|
var property = propertyField.GetValue(_overrides);
|
||||||
propertyField.SetValue(_overrides, property);
|
foreach (var valueField in propertyField.FieldType.GetFields())
|
||||||
|
{
|
||||||
|
valueField.SetValue(property, all.Value);
|
||||||
|
propertyField.SetValue(_overrides, property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
allVisible ??= all;
|
||||||
|
var property = propertyField.GetValue(_overrides);
|
||||||
|
foreach (var valueField in propertyField.FieldType.GetFields())
|
||||||
|
{
|
||||||
|
var value = valueField.GetValue(property) as bool? ?? false;
|
||||||
|
if (ImUtf8.Checkbox($"Disable {valueField.Name}", ref value) || allVisible.HasValue)
|
||||||
|
{
|
||||||
|
valueField.SetValue(property, allVisible ?? value);
|
||||||
|
propertyField.SetValue(_overrides, property);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@
|
||||||
"Description": "Runtime mod loader and manager.",
|
"Description": "Runtime mod loader and manager.",
|
||||||
"InternalName": "Penumbra",
|
"InternalName": "Penumbra",
|
||||||
"AssemblyVersion": "1.3.0.0",
|
"AssemblyVersion": "1.3.0.0",
|
||||||
"TestingAssemblyVersion": "1.3.0.0",
|
"TestingAssemblyVersion": "1.3.0.4",
|
||||||
"RepoUrl": "https://github.com/xivdev/Penumbra",
|
"RepoUrl": "https://github.com/xivdev/Penumbra",
|
||||||
"ApplicableVersion": "any",
|
"ApplicableVersion": "any",
|
||||||
"DalamudApiLevel": 10,
|
"DalamudApiLevel": 10,
|
||||||
"TestingDalamudApiLevel": 10,
|
"TestingDalamudApiLevel": 11,
|
||||||
"IsHide": "False",
|
"IsHide": "False",
|
||||||
"IsTestingExclusive": "False",
|
"IsTestingExclusive": "False",
|
||||||
"DownloadCount": 0,
|
"DownloadCount": 0,
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
"LoadRequiredState": 2,
|
"LoadRequiredState": 2,
|
||||||
"LoadSync": true,
|
"LoadSync": true,
|
||||||
"DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/1.3.0.0/Penumbra.zip",
|
"DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/1.3.0.0/Penumbra.zip",
|
||||||
"DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/1.3.0.0/Penumbra.zip",
|
"DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_1.3.0.4/Penumbra.zip",
|
||||||
"DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/1.3.0.0/Penumbra.zip",
|
"DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/1.3.0.0/Penumbra.zip",
|
||||||
"IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png"
|
"IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue