mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +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
|
||||
- name: Download Dalamud
|
||||
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"
|
||||
- name: Build
|
||||
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}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.editorconfig = .editorconfig
|
||||
.github\workflows\build.yml = .github\workflows\build.yml
|
||||
.github\workflows\release.yml = .github\workflows\release.yml
|
||||
repo.json = repo.json
|
||||
.github\workflows\test_release.yml = .github\workflows\test_release.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
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
|
||||
var npcIdentifier = _actors.CreateIndividualUnchecked(IdentifierType.Npc, ByteString.Empty,
|
||||
ushort.MaxValue,
|
||||
identifier.Kind, identifier.DataId);
|
||||
ushort.MaxValue, identifier.Kind, identifier.DataId);
|
||||
if (npcIdentifier.IsValid && _individuals.TryGetValue(npcIdentifier, out collection))
|
||||
return true;
|
||||
|
||||
|
|
@ -58,8 +57,7 @@ public sealed partial class IndividualCollections : IReadOnlyList<(string Displa
|
|||
return false;
|
||||
|
||||
identifier = _actors.CreateIndividualUnchecked(IdentifierType.Player, identifier.PlayerName,
|
||||
identifier.HomeWorld.Id,
|
||||
ObjectKind.None, uint.MaxValue);
|
||||
identifier.HomeWorld.Id, ObjectKind.None, uint.MaxValue);
|
||||
return CheckWorlds(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)
|
||||
{
|
||||
// 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)
|
||||
return Task.Result.Original(a1, unused, timeOffset);
|
||||
|
||||
var someIntermediate = *(nint*)(a1 + 0x1F8);
|
||||
var flags = someIntermediate == nint.Zero ? (ushort)0 : *(ushort*)(someIntermediate + 0x49C);
|
||||
if (((flags >> 13) & 1) == 0)
|
||||
var someIntermediate = *(nint*)(a1 + VolatileOffsets.ApricotListenerSoundPlayCaller.SomeIntermediate);
|
||||
var flags = someIntermediate == nint.Zero
|
||||
? (ushort)0
|
||||
: *(ushort*)(someIntermediate + VolatileOffsets.ApricotListenerSoundPlayCaller.Flags);
|
||||
if (((flags >> VolatileOffsets.ApricotListenerSoundPlayCaller.BitShift) & 1) == 0)
|
||||
return Task.Result.Original(a1, unused, timeOffset);
|
||||
|
||||
Penumbra.Log.Excessive(
|
||||
$"[Apricot Listener Sound Play Caller] Invoked on 0x{a1:X} with {unused}, {timeOffset}.");
|
||||
// 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)
|
||||
return Task.Result.Original(a1, unused, timeOffset);
|
||||
|
||||
// In some cases we can obtain the associated caster via vfunc 1.
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
var actorIdx = (int)(*(*(ulong**)timelinePtr + 1) >> 3);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Interop.PathResolving;
|
||||
using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;
|
||||
|
||||
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)
|
||||
{
|
||||
_collectionResolver = collectionResolver;
|
||||
_metaState = metaState;
|
||||
Task = hooks.CreateHook<Delegate>("Calculate Height", (nint)Character.MemberFunctionPointers.CalculateHeight, Detour, !HookOverrides.Instance.Meta.CalculateHeight);
|
||||
_metaState = metaState;
|
||||
Task = hooks.CreateHook<Delegate>("Calculate Height", (nint)Character.MemberFunctionPointers.CalculateHeight, Detour,
|
||||
!HookOverrides.Instance.Meta.CalculateHeight);
|
||||
}
|
||||
|
||||
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.
|
||||
// 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;
|
||||
|
||||
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,
|
||||
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)
|
||||
{
|
||||
_modelRendererUnkFuncHook.Original(modelRenderer, unkPayload, unk2, unk3, unk4, unk5);
|
||||
|
|
|
|||
|
|
@ -15,18 +15,18 @@ public unsafe class ResourceLoader : IDisposable, IService
|
|||
{
|
||||
private readonly ResourceService _resources;
|
||||
private readonly FileReadService _fileReadService;
|
||||
private readonly TexMdlService _texMdlService;
|
||||
private readonly TexMdlScdService _texMdlScdService;
|
||||
private readonly PapHandler _papHandler;
|
||||
private readonly Configuration _config;
|
||||
|
||||
private ResolveData _resolvedData = ResolveData.Invalid;
|
||||
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;
|
||||
_fileReadService = fileReadService;
|
||||
_texMdlService = texMdlService;
|
||||
_texMdlScdService = texMdlScdService;
|
||||
_config = config;
|
||||
ResetResolvePath();
|
||||
|
||||
|
|
@ -140,7 +140,7 @@ public unsafe class ResourceLoader : IDisposable, IService
|
|||
return;
|
||||
}
|
||||
|
||||
_texMdlService.AddCrc(type, resolvedPath);
|
||||
_texMdlScdService.AddCrc(type, resolvedPath);
|
||||
// Replace the hash and path with the correct one for the replacement.
|
||||
hash = ComputeHash(resolvedPath.Value.InternalName, parameters);
|
||||
var oldPath = path;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.Re
|
|||
|
||||
namespace Penumbra.Interop.Hooks.ResourceLoading;
|
||||
|
||||
public unsafe class TexMdlService : IDisposable, IRequiredService
|
||||
public unsafe class TexMdlScdService : IDisposable, IRequiredService
|
||||
{
|
||||
/// <summary>
|
||||
/// 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;
|
||||
|
||||
public TexMdlService(IGameInteropProvider interop)
|
||||
public TexMdlScdService(IGameInteropProvider interop)
|
||||
{
|
||||
interop.InitializeFromAttributes(this);
|
||||
_lodService = new LodService(interop);
|
||||
|
|
@ -52,6 +52,7 @@ public unsafe class TexMdlService : IDisposable, IRequiredService
|
|||
_loadMdlFileExternHook.Enable();
|
||||
if (!HookOverrides.Instance.ResourceLoading.TexResourceHandleOnLoad)
|
||||
_textureOnLoadHook.Enable();
|
||||
_soundOnLoadHook.Enable();
|
||||
}
|
||||
|
||||
/// <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
|
||||
{
|
||||
ResourceType.Mdl when path.HasValue => _customMdlCrc.Add(path.Value.Crc64),
|
||||
ResourceType.Tex when path.HasValue => _customTexCrc.Add(path.Value.Crc64),
|
||||
ResourceType.Mdl when path.HasValue => _customFileCrc.TryAdd(path.Value.Crc64, ResourceType.Mdl),
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
|
@ -70,15 +72,16 @@ public unsafe class TexMdlService : IDisposable, IRequiredService
|
|||
_checkFileStateHook.Dispose();
|
||||
_loadMdlFileExternHook.Dispose();
|
||||
_textureOnLoadHook.Dispose();
|
||||
_soundOnLoadHook.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
private readonly HashSet<ulong> _customMdlCrc = [];
|
||||
|
||||
private readonly HashSet<ulong> _customTexCrc = [];
|
||||
private readonly Dictionary<ulong, ResourceType> _customFileCrc = [];
|
||||
public IReadOnlyDictionary<ulong, ResourceType> CustomCache
|
||||
=> _customFileCrc;
|
||||
|
||||
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 ThreadLocal<bool> _texReturnData = new(() => default);
|
||||
private readonly ThreadLocal<bool> _scdReturnData = new(() => default);
|
||||
|
||||
private delegate void UpdateCategoryDelegate(TextureResourceHandle* resourceHandle);
|
||||
|
||||
[Signature(Sigs.TexHandleUpdateCategory)]
|
||||
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>
|
||||
/// 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.
|
||||
|
|
@ -100,14 +125,17 @@ public unsafe class TexMdlService : IDisposable, IRequiredService
|
|||
/// </summary>
|
||||
private nint CheckFileStateDetour(nint ptr, ulong crc64)
|
||||
{
|
||||
if (_customMdlCrc.Contains(crc64))
|
||||
return CustomFileFlag;
|
||||
|
||||
if (_customTexCrc.Contains(crc64))
|
||||
{
|
||||
_texReturnData.Value = true;
|
||||
return nint.Zero;
|
||||
}
|
||||
if (_customFileCrc.TryGetValue(crc64, out var type))
|
||||
switch (type)
|
||||
{
|
||||
case ResourceType.Mdl: return CustomFileFlag;
|
||||
case ResourceType.Tex:
|
||||
_texReturnData.Value = true;
|
||||
return nint.Zero;
|
||||
case ResourceType.Scd:
|
||||
_scdReturnData.Value = true;
|
||||
return nint.Zero;
|
||||
}
|
||||
|
||||
var ret = _checkFileStateHook.Original(ptr, crc64);
|
||||
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);
|
||||
|
||||
[Signature(Sigs.TexHandleOnLoad, DetourName = nameof(OnLoadDetour))]
|
||||
[Signature(Sigs.TexHandleOnLoad, DetourName = nameof(OnTexLoadDetour))]
|
||||
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);
|
||||
if (!_texReturnData.Value)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,9 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable
|
|||
private readonly Hook<PerSlotResolveDelegate> _resolveMdlPathHook;
|
||||
private readonly Hook<NamedResolveDelegate> _resolveMtrlPathHook;
|
||||
private readonly Hook<NamedResolveDelegate> _resolvePapPathHook;
|
||||
private readonly Hook<PerSlotResolveDelegate> _resolveKdbPathHook;
|
||||
private readonly Hook<PerSlotResolveDelegate> _resolvePhybPathHook;
|
||||
private readonly Hook<PerSlotResolveDelegate> _resolveBnmbPathHook;
|
||||
private readonly Hook<PerSlotResolveDelegate> _resolveSklbPathHook;
|
||||
private readonly Hook<PerSlotResolveDelegate> _resolveSkpPathHook;
|
||||
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);
|
||||
_resolveSkpPathHook = Create<PerSlotResolveDelegate>($"{name}.{nameof(ResolveSkp)}", hooks, vTable[78], type, ResolveSkp, ResolveSkpHuman);
|
||||
_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);
|
||||
|
||||
_resolveBnmbPathHook = Create<PerSlotResolveDelegate>($"{name}.{nameof(ResolveBnmb)}", hooks, vTable[82], type, ResolveBnmb, ResolveBnmbHuman);
|
||||
_vFunc83Hook = Create<SkeletonVFuncDelegate>( $"{name}.{nameof(VFunc83)}", hooks, vTable[83], type, null, VFunc83);
|
||||
|
||||
_resolvePapPathHook = Create<NamedResolveDelegate>( $"{name}.{nameof(ResolvePap)}", hooks, vTable[84], type, ResolvePap, ResolvePapHuman);
|
||||
_resolveTmbPathHook = Create<TmbResolveDelegate>( $"{name}.{nameof(ResolveTmb)}", hooks, vTable[85], ResolveTmb);
|
||||
_resolveMPapPathHook = Create<MPapResolveDelegate>( $"{name}.{nameof(ResolveMPap)}", hooks, vTable[87], ResolveMPap);
|
||||
|
|
@ -83,7 +84,9 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable
|
|||
_resolveMdlPathHook.Enable();
|
||||
_resolveMtrlPathHook.Enable();
|
||||
_resolvePapPathHook.Enable();
|
||||
_resolveKdbPathHook.Enable();
|
||||
_resolvePhybPathHook.Enable();
|
||||
_resolveBnmbPathHook.Enable();
|
||||
_resolveSklbPathHook.Enable();
|
||||
_resolveSkpPathHook.Enable();
|
||||
_resolveTmbPathHook.Enable();
|
||||
|
|
@ -101,7 +104,9 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable
|
|||
_resolveMdlPathHook.Disable();
|
||||
_resolveMtrlPathHook.Disable();
|
||||
_resolvePapPathHook.Disable();
|
||||
_resolveKdbPathHook.Disable();
|
||||
_resolvePhybPathHook.Disable();
|
||||
_resolveBnmbPathHook.Disable();
|
||||
_resolveSklbPathHook.Disable();
|
||||
_resolveSkpPathHook.Disable();
|
||||
_resolveTmbPathHook.Disable();
|
||||
|
|
@ -119,7 +124,9 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable
|
|||
_resolveMdlPathHook.Dispose();
|
||||
_resolveMtrlPathHook.Dispose();
|
||||
_resolvePapPathHook.Dispose();
|
||||
_resolveKdbPathHook.Dispose();
|
||||
_resolvePhybPathHook.Dispose();
|
||||
_resolveBnmbPathHook.Dispose();
|
||||
_resolveSklbPathHook.Dispose();
|
||||
_resolveSkpPathHook.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)
|
||||
=> 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)
|
||||
=> 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)
|
||||
=> ResolvePath(drawObject, _resolveSklbPathHook.Original(drawObject, pathBuffer, pathBufferSize, partialSkeletonIndex));
|
||||
|
||||
|
|
@ -188,6 +201,15 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable
|
|||
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)
|
||||
{
|
||||
var collection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true);
|
||||
|
|
@ -197,6 +219,15 @@ public sealed unsafe class ResolvePathHooksBase : IDisposable
|
|||
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)
|
||||
{
|
||||
var collection = _parent.CollectionResolver.IdentifyCollection((DrawObject*)drawObject, true);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||
using OtterGui;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Collections;
|
||||
using Penumbra.Collections.Manager;
|
||||
|
|
@ -62,10 +63,11 @@ public sealed unsafe class CollectionResolver(
|
|||
|
||||
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;
|
||||
|
||||
if (LoginScreen(gameObject, out data))
|
||||
if (useCache && cache.TryGetValue(gameObject, out data))
|
||||
return data;
|
||||
|
||||
if (Aesthetician(gameObject, out data))
|
||||
|
|
@ -116,16 +118,17 @@ public sealed unsafe class CollectionResolver(
|
|||
return true;
|
||||
}
|
||||
|
||||
var notYetReady = false;
|
||||
var lobby = AgentLobby.Instance();
|
||||
if (lobby != null)
|
||||
var notYetReady = false;
|
||||
var lobby = AgentLobby.Instance();
|
||||
var characterList = CharaSelectCharacterList.Instance();
|
||||
if (lobby != null && characterList != null)
|
||||
{
|
||||
var span = lobby->LobbyData.CharaSelectEntries.AsSpan();
|
||||
// The lobby uses the first 8 cutscene actors.
|
||||
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);
|
||||
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")}.");
|
||||
|
|
@ -141,7 +144,7 @@ public sealed unsafe class CollectionResolver(
|
|||
var collection = collectionManager.Active.ByType(CollectionType.Yourself)
|
||||
?? CollectionByAttributes(gameObject, ref notYetReady)
|
||||
?? collectionManager.Active.Default;
|
||||
ret = notYetReady ? collection.ToResolveData(gameObject) : cache.Set(collection, ActorIdentifier.Invalid, gameObject);
|
||||
ret = collection.ToResolveData(gameObject);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -196,10 +199,24 @@ public sealed unsafe class CollectionResolver(
|
|||
|
||||
/// <summary> Check both temporary and permanent character collections. Temporary first. </summary>
|
||||
private ModCollection? CollectionByIdentifier(ActorIdentifier identifier)
|
||||
=> tempCollections.Collections.TryGetCollection(identifier, out var collection)
|
||||
|| collectionManager.Active.Individuals.TryGetCollection(identifier, out collection)
|
||||
? collection
|
||||
: null;
|
||||
{
|
||||
if (tempCollections.Collections.TryGetCollection(identifier, out var collection))
|
||||
return collection;
|
||||
|
||||
// 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>
|
||||
private ModCollection? CheckYourself(ActorIdentifier identifier, Actor actor)
|
||||
|
|
@ -223,7 +240,7 @@ public sealed unsafe class CollectionResolver(
|
|||
}
|
||||
|
||||
// Only handle human models.
|
||||
if (!IsModelHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId))
|
||||
if (!IsModelHuman((uint)actor.AsCharacter->ModelContainer.ModelCharaId))
|
||||
return null;
|
||||
|
||||
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)
|
||||
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
|
||||
{
|
||||
// Only Interface collection.
|
||||
|
|
|
|||
|
|
@ -36,17 +36,16 @@ internal partial record ResolveContext
|
|||
private Utf8GamePath ResolveEquipmentModelPath()
|
||||
{
|
||||
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());
|
||||
return Utf8GamePath.FromString(path, out var gamePath) ? gamePath : Utf8GamePath.Empty;
|
||||
}
|
||||
|
||||
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)
|
||||
return GenderRace.MidlanderMale;
|
||||
|
||||
|
|
@ -61,6 +60,7 @@ internal partial record ResolveContext
|
|||
var metaCache = Global.Collection.MetaCache;
|
||||
var entry = metaCache?.GetEqdpEntry(characterRaceCode, accessory, primaryId)
|
||||
?? ExpandedEqdpFile.GetDefault(Global.MetaFileManager, characterRaceCode, accessory, primaryId);
|
||||
var slot = slotIndex.ToEquipSlot();
|
||||
if (entry.ToBits(slot).Item2)
|
||||
return characterRaceCode;
|
||||
|
||||
|
|
@ -272,7 +272,7 @@ internal partial record ResolveContext
|
|||
{
|
||||
var human = (Human*)CharacterBase;
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ public class ResourceTree
|
|||
Unsafe.AsPointer(ref character->DrawData.EquipmentModelIds[0]), 10),
|
||||
_ => [],
|
||||
};
|
||||
ModelId = character->CharacterData.ModelCharaId;
|
||||
ModelId = character->ModelContainer.ModelCharaId;
|
||||
CustomizeData = character->DrawData.CustomizeData;
|
||||
RaceCode = human != null ? (GenderRace)human->RaceSexId : GenderRace.Unknown;
|
||||
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ public unsafe class FontReloader : IService
|
|||
return;
|
||||
|
||||
_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.
|
||||
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)
|
||||
=> ((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.
|
||||
// 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();
|
||||
}
|
||||
|
||||
public void ClearForDefault()
|
||||
{
|
||||
Count = _globalEqp.Count;
|
||||
_imc.Clear();
|
||||
_eqp.Clear();
|
||||
_eqdp.Clear();
|
||||
_est.Clear();
|
||||
_rsp.Clear();
|
||||
_gmp.Clear();
|
||||
}
|
||||
|
||||
public bool Equals(MetaDictionary other)
|
||||
=> Count == other.Count
|
||||
&& _imc.SetEquals(other._imc)
|
||||
|
|
|
|||
|
|
@ -69,7 +69,8 @@ public class ModMetaEditor(
|
|||
public static bool DeleteDefaultValues(MetaFileManager metaFileManager, MetaDictionary dict)
|
||||
{
|
||||
var clone = dict.Clone();
|
||||
dict.Clear();
|
||||
dict.ClearForDefault();
|
||||
|
||||
var count = 0;
|
||||
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)
|
||||
{
|
||||
if (!group.OptionData.Move(optionIdxFrom, optionIdxTo))
|
||||
if (!group.OptionData.Move(ref optionIdxFrom, ref optionIdxTo))
|
||||
return false;
|
||||
|
||||
group.DefaultSettings = group.DefaultSettings.MoveBit(optionIdxFrom, optionIdxTo);
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ public class ModGroupEditor(
|
|||
{
|
||||
var mod = group.Mod;
|
||||
var idxFrom = group.GetIndex();
|
||||
if (!mod.Groups.Move(idxFrom, groupIdxTo))
|
||||
if (!mod.Groups.Move(ref idxFrom, ref groupIdxTo))
|
||||
return;
|
||||
|
||||
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)
|
||||
{
|
||||
if (!group.OptionData.Move(optionIdxFrom, optionIdxTo))
|
||||
if (!group.OptionData.Move(ref optionIdxFrom, ref optionIdxTo))
|
||||
return false;
|
||||
|
||||
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)
|
||||
{
|
||||
if (!group.OptionData.Move(optionIdxFrom, optionIdxTo))
|
||||
if (!group.OptionData.Move(ref optionIdxFrom, ref optionIdxTo))
|
||||
return false;
|
||||
|
||||
group.DefaultSettings = group.DefaultSettings.MoveSingle(optionIdxFrom, optionIdxTo);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using Dalamud.Plugin;
|
||||
using ImGuiNET;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using OtterGui;
|
||||
using OtterGui.Log;
|
||||
using OtterGui.Services;
|
||||
|
|
@ -20,6 +19,7 @@ using OtterGui.Tasks;
|
|||
using Penumbra.UI;
|
||||
using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.Interop.Hooks;
|
||||
using Penumbra.Interop.Hooks.ResourceLoading;
|
||||
|
|
@ -111,7 +111,7 @@ public class Penumbra : IDalamudPlugin
|
|||
private void SetupApi()
|
||||
{
|
||||
_services.GetService<IpcProviders>();
|
||||
var itemSheet = _services.GetService<IDataManager>().GetExcelSheet<Item>()!;
|
||||
var itemSheet = _services.GetService<IDataManager>().GetExcelSheet<Item>();
|
||||
_communicatorService.ChangedItemHover.Subscribe(it =>
|
||||
{
|
||||
if (it is IdentifiedItem { Item.Id.IsItem: true })
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
"RepoUrl": "https://github.com/xivdev/Penumbra",
|
||||
"ApplicableVersion": "any",
|
||||
"Tags": [ "modding" ],
|
||||
"DalamudApiLevel": 10,
|
||||
"DalamudApiLevel": 11,
|
||||
"LoadPriority": 69420,
|
||||
"LoadState": 2,
|
||||
"LoadSync": true,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ using Dalamud.Game.Text.SeStringHandling.Payloads;
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Lumina.Excel.Sheets;
|
||||
using OtterGui.Log;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.Mods.Manager;
|
||||
|
|
@ -16,7 +16,7 @@ namespace Penumbra.Services;
|
|||
public class MessageService(Logger log, IUiBuilder builder, IChatGui chat, INotificationManager notificationManager)
|
||||
: OtterGui.Classes.MessageService(log, builder, chat, notificationManager), IService
|
||||
{
|
||||
public void LinkItem(Item item)
|
||||
public void LinkItem(in Item item)
|
||||
{
|
||||
// @formatter:off
|
||||
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 UIForegroundPayload(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, 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.ResourceLoading;
|
||||
using Penumbra.GameData.Files.StainMapStructs;
|
||||
using Penumbra.String.Classes;
|
||||
using Penumbra.UI.AdvancedWindow.Materials;
|
||||
|
||||
namespace Penumbra.UI.Tabs.Debug;
|
||||
|
|
@ -54,6 +55,9 @@ public class Diagnostics(ServiceManager provider) : IUiService
|
|||
return;
|
||||
|
||||
using var table = ImRaii.Table("##data", 4, ImGuiTableFlags.RowBg);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
foreach (var type in typeof(ActorManager).Assembly.GetTypes()
|
||||
.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 ObjectManager _objects;
|
||||
private readonly IClientState _clientState;
|
||||
private readonly IDataManager _dataManager;
|
||||
private readonly IpcTester _ipcTester;
|
||||
private readonly CrashHandlerPanel _crashHandlerPanel;
|
||||
private readonly TexHeaderDrawer _texHeaderDrawer;
|
||||
private readonly HookOverrideDrawer _hookOverrides;
|
||||
private readonly TexMdlScdService _texMdlScdService;
|
||||
|
||||
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,
|
||||
CharacterUtility characterUtility, ResidentResourceManager residentResources,
|
||||
ResourceManagerService resourceManager, CollectionResolver collectionResolver,
|
||||
|
|
@ -109,7 +115,7 @@ public class DebugTab : Window, ITab, IUiService
|
|||
CutsceneService cutsceneService, ModImportManager modImporter, ImportPopup importPopup, FrameworkManager framework,
|
||||
TextureManager textureManager, ShaderReplacementFixer shaderReplacementFixer, RedrawService redraws, DictEmote emotes,
|
||||
Diagnostics diagnostics, IpcTester ipcTester, CrashHandlerPanel crashHandlerPanel, TexHeaderDrawer texHeaderDrawer,
|
||||
HookOverrideDrawer hookOverrides)
|
||||
HookOverrideDrawer hookOverrides, TexMdlScdService texMdlScdService)
|
||||
: base("Penumbra Debug Window", ImGuiWindowFlags.NoCollapse)
|
||||
{
|
||||
IsOpen = true;
|
||||
|
|
@ -147,8 +153,10 @@ public class DebugTab : Window, ITab, IUiService
|
|||
_crashHandlerPanel = crashHandlerPanel;
|
||||
_texHeaderDrawer = texHeaderDrawer;
|
||||
_hookOverrides = hookOverrides;
|
||||
_texMdlScdService = texMdlScdService;
|
||||
_objects = objects;
|
||||
_clientState = clientState;
|
||||
_dataManager = dataManager;
|
||||
}
|
||||
|
||||
public ReadOnlySpan<byte> Label
|
||||
|
|
@ -180,6 +188,7 @@ public class DebugTab : Window, ITab, IUiService
|
|||
DrawDebugCharacterUtility();
|
||||
DrawShaderReplacementFixer();
|
||||
DrawData();
|
||||
DrawCrcCache();
|
||||
DrawResourceProblems();
|
||||
_hookOverrides.Draw();
|
||||
DrawPlayerModelInfo();
|
||||
|
|
@ -188,7 +197,7 @@ public class DebugTab : Window, ITab, IUiService
|
|||
}
|
||||
|
||||
|
||||
private void DrawCollectionCaches()
|
||||
private unsafe void DrawCollectionCaches()
|
||||
{
|
||||
if (!ImGui.CollapsingHeader(
|
||||
$"Collections ({_collectionManager.Caches.Count}/{_collectionManager.Storage.Count - 1} Caches)###Collections"))
|
||||
|
|
@ -199,25 +208,35 @@ public class DebugTab : Window, ITab, IUiService
|
|||
if (collection.HasCache)
|
||||
{
|
||||
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)
|
||||
continue;
|
||||
|
||||
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);
|
||||
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();
|
||||
if (resourceNode)
|
||||
foreach (var (path, resource) in collection._cache!.CustomResources)
|
||||
ImUtf8.TreeNode($"{path} -> 0x{(ulong)resource.ResourceHandle:X}",
|
||||
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
|
||||
{
|
||||
|
|
@ -659,11 +678,36 @@ public class DebugTab : Window, ITab, IUiService
|
|||
|
||||
DrawEmotes();
|
||||
DrawStainTemplates();
|
||||
DrawAtch();
|
||||
}
|
||||
|
||||
private string _emoteSearchFile = 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()
|
||||
{
|
||||
using var mainTree = TreeNode("Emotes");
|
||||
|
|
@ -1018,6 +1062,40 @@ public class DebugTab : Window, ITab, IUiService
|
|||
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>
|
||||
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}");
|
||||
}
|
||||
|
||||
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;
|
||||
ImGui.SameLine();
|
||||
if (ImUtf8.Button("Disable All Hooks"u8))
|
||||
if (ImUtf8.Button("Disable All Hooks"))
|
||||
all = true;
|
||||
ImGui.SameLine();
|
||||
if (ImUtf8.Button("Enable All Hooks"u8))
|
||||
if (ImUtf8.Button("Enable All Hooks"))
|
||||
all = false;
|
||||
|
||||
foreach (var propertyField in typeof(HookOverrides).GetFields().Where(f => f is { IsStatic: false, FieldType.IsValueType: true }))
|
||||
{
|
||||
using var tree = ImUtf8.TreeNode(propertyField.Name);
|
||||
if (!tree)
|
||||
continue;
|
||||
|
||||
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) || all.HasValue)
|
||||
if (all.HasValue)
|
||||
{
|
||||
valueField.SetValue(property, all ?? value);
|
||||
propertyField.SetValue(_overrides, property);
|
||||
var property = propertyField.GetValue(_overrides);
|
||||
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.",
|
||||
"InternalName": "Penumbra",
|
||||
"AssemblyVersion": "1.3.0.0",
|
||||
"TestingAssemblyVersion": "1.3.0.0",
|
||||
"TestingAssemblyVersion": "1.3.0.4",
|
||||
"RepoUrl": "https://github.com/xivdev/Penumbra",
|
||||
"ApplicableVersion": "any",
|
||||
"DalamudApiLevel": 10,
|
||||
"TestingDalamudApiLevel": 10,
|
||||
"TestingDalamudApiLevel": 11,
|
||||
"IsHide": "False",
|
||||
"IsTestingExclusive": "False",
|
||||
"DownloadCount": 0,
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
"LoadRequiredState": 2,
|
||||
"LoadSync": true,
|
||||
"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",
|
||||
"IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue