Merge branch 'master' into ZoneInitTry

This commit is contained in:
Infi 2026-02-08 12:59:24 +01:00 committed by GitHub
commit 298aeebf6c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 442 additions and 486 deletions

View file

@ -6,7 +6,7 @@
<PropertyGroup Label="Feature"> <PropertyGroup Label="Feature">
<Description>XIV Launcher addon framework</Description> <Description>XIV Launcher addon framework</Description>
<DalamudVersion>14.0.1.0</DalamudVersion> <DalamudVersion>14.0.2.1</DalamudVersion>
<AssemblyVersion>$(DalamudVersion)</AssemblyVersion> <AssemblyVersion>$(DalamudVersion)</AssemblyVersion>
<Version>$(DalamudVersion)</Version> <Version>$(DalamudVersion)</Version>
<FileVersion>$(DalamudVersion)</FileVersion> <FileVersion>$(DalamudVersion)</FileVersion>

View file

@ -0,0 +1,311 @@
using Dalamud.Game.ClientState.Objects.Types;
namespace Dalamud.Game.ClientState.Customize;
/// <summary>
/// This collection represents customization data a <see cref="ICharacter"/> has.
/// </summary>
public interface ICustomizeData
{
/// <summary>
/// Gets the current race.
/// E.g., Miqo'te, Aura.
/// </summary>
public byte Race { get; }
/// <summary>
/// Gets the current sex.
/// </summary>
public byte Sex { get; }
/// <summary>
/// Gets the current body type.
/// </summary>
public byte BodyType { get; }
/// <summary>
/// Gets the current height (0 to 100).
/// </summary>
public byte Height { get; }
/// <summary>
/// Gets the current tribe.
/// E.g., Seeker of the Sun, Keeper of the Moon.
/// </summary>
public byte Tribe { get; }
/// <summary>
/// Gets the current face (1 to 4).
/// </summary>
public byte Face { get; }
/// <summary>
/// Gets the current hairstyle.
/// </summary>
public byte Hairstyle { get; }
/// <summary>
/// Gets the current skin color.
/// </summary>
public byte SkinColor { get; }
/// <summary>
/// Gets the current color of the left eye.
/// </summary>
public byte EyeColorLeft { get; }
/// <summary>
/// Gets the current color of the right eye.
/// </summary>
public byte EyeColorRight { get; }
/// <summary>
/// Gets the current main hair color.
/// </summary>
public byte HairColor { get; }
/// <summary>
/// Gets the current highlight hair color.
/// </summary>
public byte HighlightsColor { get; }
/// <summary>
/// Gets the current tattoo color.
/// </summary>
public byte TattooColor { get; }
/// <summary>
/// Gets the current eyebrow type.
/// </summary>
public byte Eyebrows { get; }
/// <summary>
/// Gets the current nose type.
/// </summary>
public byte Nose { get; }
/// <summary>
/// Gets the current jaw type.
/// </summary>
public byte Jaw { get; }
/// <summary>
/// Gets the current lip color fur pattern.
/// </summary>
public byte LipColorFurPattern { get; }
/// <summary>
/// Gets the current muscle mass value.
/// </summary>
public byte MuscleMass { get; }
/// <summary>
/// Gets the current tail type (1 to 4).
/// </summary>
public byte TailShape { get; }
/// <summary>
/// Gets the current bust size (0 to 100).
/// </summary>
public byte BustSize { get; }
/// <summary>
/// Gets the current color of the face paint.
/// </summary>
public byte FacePaintColor { get; }
/// <summary>
/// Gets a value indicating whether highlight color is used.
/// </summary>
public bool Highlights { get; }
/// <summary>
/// Gets a value indicating whether this facial feature is used.
/// </summary>
public bool FacialFeature1 { get; }
/// <inheritdoc cref="FacialFeature1"/>
public bool FacialFeature2 { get; }
/// <inheritdoc cref="FacialFeature1"/>
public bool FacialFeature3 { get; }
/// <inheritdoc cref="FacialFeature1"/>
public bool FacialFeature4 { get; }
/// <inheritdoc cref="FacialFeature1"/>
public bool FacialFeature5 { get; }
/// <inheritdoc cref="FacialFeature1"/>
public bool FacialFeature6 { get; }
/// <inheritdoc cref="FacialFeature1"/>
public bool FacialFeature7 { get; }
/// <summary>
/// Gets a value indicating whether the legacy tattoo is used.
/// </summary>
public bool LegacyTattoo { get; }
/// <summary>
/// Gets the current eye shape type.
/// </summary>
public byte EyeShape { get; }
/// <summary>
/// Gets a value indicating whether small iris is used.
/// </summary>
public bool SmallIris { get; }
/// <summary>
/// Gets the current mouth type.
/// </summary>
public byte Mouth { get; }
/// <summary>
/// Gets a value indicating whether lipstick is used.
/// </summary>
public bool Lipstick { get; }
/// <summary>
/// Gets the current face paint type.
/// </summary>
public byte FacePaint { get; }
/// <summary>
/// Gets a value indicating whether face paint reversed is used.
/// </summary>
public bool FacePaintReversed { get; }
}
/// <inheritdoc/>
internal readonly unsafe struct CustomizeData : ICustomizeData
{
/// <summary>
/// Gets or sets the address of the customize data struct in memory.
/// </summary>
public readonly nint Address;
/// <summary>
/// Initializes a new instance of the <see cref="CustomizeData"/> struct.
/// </summary>
/// <param name="address">Address of the status list.</param>
internal CustomizeData(nint address)
{
this.Address = address;
}
/// <inheritdoc/>
public byte Race => this.Struct->Race;
/// <inheritdoc/>
public byte Sex => this.Struct->Sex;
/// <inheritdoc/>
public byte BodyType => this.Struct->BodyType;
/// <inheritdoc/>
public byte Height => this.Struct->Height;
/// <inheritdoc/>
public byte Tribe => this.Struct->Tribe;
/// <inheritdoc/>
public byte Face => this.Struct->Face;
/// <inheritdoc/>
public byte Hairstyle => this.Struct->Hairstyle;
/// <inheritdoc/>
public byte SkinColor => this.Struct->SkinColor;
/// <inheritdoc/>
public byte EyeColorLeft => this.Struct->EyeColorLeft;
/// <inheritdoc/>
public byte EyeColorRight => this.Struct->EyeColorRight;
/// <inheritdoc/>
public byte HairColor => this.Struct->HairColor;
/// <inheritdoc/>
public byte HighlightsColor => this.Struct->HighlightsColor;
/// <inheritdoc/>
public byte TattooColor => this.Struct->TattooColor;
/// <inheritdoc/>
public byte Eyebrows => this.Struct->Eyebrows;
/// <inheritdoc/>
public byte Nose => this.Struct->Nose;
/// <inheritdoc/>
public byte Jaw => this.Struct->Jaw;
/// <inheritdoc/>
public byte LipColorFurPattern => this.Struct->LipColorFurPattern;
/// <inheritdoc/>
public byte MuscleMass => this.Struct->MuscleMass;
/// <inheritdoc/>
public byte TailShape => this.Struct->TailShape;
/// <inheritdoc/>
public byte BustSize => this.Struct->BustSize;
/// <inheritdoc/>
public byte FacePaintColor => this.Struct->FacePaintColor;
/// <inheritdoc/>
public bool Highlights => this.Struct->Highlights;
/// <inheritdoc/>
public bool FacialFeature1 => this.Struct->FacialFeature1;
/// <inheritdoc/>
public bool FacialFeature2 => this.Struct->FacialFeature2;
/// <inheritdoc/>
public bool FacialFeature3 => this.Struct->FacialFeature3;
/// <inheritdoc/>
public bool FacialFeature4 => this.Struct->FacialFeature4;
/// <inheritdoc/>
public bool FacialFeature5 => this.Struct->FacialFeature5;
/// <inheritdoc/>
public bool FacialFeature6 => this.Struct->FacialFeature6;
/// <inheritdoc/>
public bool FacialFeature7 => this.Struct->FacialFeature7;
/// <inheritdoc/>
public bool LegacyTattoo => this.Struct->LegacyTattoo;
/// <inheritdoc/>
public byte EyeShape => this.Struct->EyeShape;
/// <inheritdoc/>
public bool SmallIris => this.Struct->SmallIris;
/// <inheritdoc/>
public byte Mouth => this.Struct->Mouth;
/// <inheritdoc/>
public bool Lipstick => this.Struct->Lipstick;
/// <inheritdoc/>
public byte FacePaint => this.Struct->FacePaint;
/// <inheritdoc/>
public bool FacePaintReversed => this.Struct->FacePaintReversed;
/// <summary>
/// Gets the underlying structure.
/// </summary>
internal FFXIVClientStructs.FFXIV.Client.Game.Character.CustomizeData* Struct =>
(FFXIVClientStructs.FFXIV.Client.Game.Character.CustomizeData*)this.Address;
}

View file

@ -1,6 +1,8 @@
using Dalamud.Data; using Dalamud.Data;
using Dalamud.Game.ClientState.Customize;
using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Utility;
using Lumina.Excel; using Lumina.Excel;
using Lumina.Excel.Sheets; using Lumina.Excel.Sheets;
@ -13,68 +15,73 @@ namespace Dalamud.Game.ClientState.Objects.Types;
public interface ICharacter : IGameObject public interface ICharacter : IGameObject
{ {
/// <summary> /// <summary>
/// Gets the current HP of this Chara. /// Gets the current HP of this character.
/// </summary> /// </summary>
public uint CurrentHp { get; } public uint CurrentHp { get; }
/// <summary> /// <summary>
/// Gets the maximum HP of this Chara. /// Gets the maximum HP of this character.
/// </summary> /// </summary>
public uint MaxHp { get; } public uint MaxHp { get; }
/// <summary> /// <summary>
/// Gets the current MP of this Chara. /// Gets the current MP of this character.
/// </summary> /// </summary>
public uint CurrentMp { get; } public uint CurrentMp { get; }
/// <summary> /// <summary>
/// Gets the maximum MP of this Chara. /// Gets the maximum MP of this character.
/// </summary> /// </summary>
public uint MaxMp { get; } public uint MaxMp { get; }
/// <summary> /// <summary>
/// Gets the current GP of this Chara. /// Gets the current GP of this character.
/// </summary> /// </summary>
public uint CurrentGp { get; } public uint CurrentGp { get; }
/// <summary> /// <summary>
/// Gets the maximum GP of this Chara. /// Gets the maximum GP of this character.
/// </summary> /// </summary>
public uint MaxGp { get; } public uint MaxGp { get; }
/// <summary> /// <summary>
/// Gets the current CP of this Chara. /// Gets the current CP of this character.
/// </summary> /// </summary>
public uint CurrentCp { get; } public uint CurrentCp { get; }
/// <summary> /// <summary>
/// Gets the maximum CP of this Chara. /// Gets the maximum CP of this character.
/// </summary> /// </summary>
public uint MaxCp { get; } public uint MaxCp { get; }
/// <summary> /// <summary>
/// Gets the shield percentage of this Chara. /// Gets the shield percentage of this character.
/// </summary> /// </summary>
public byte ShieldPercentage { get; } public byte ShieldPercentage { get; }
/// <summary> /// <summary>
/// Gets the ClassJob of this Chara. /// Gets the ClassJob of this character.
/// </summary> /// </summary>
public RowRef<ClassJob> ClassJob { get; } public RowRef<ClassJob> ClassJob { get; }
/// <summary> /// <summary>
/// Gets the level of this Chara. /// Gets the level of this character.
/// </summary> /// </summary>
public byte Level { get; } public byte Level { get; }
/// <summary> /// <summary>
/// Gets a byte array describing the visual appearance of this Chara. /// Gets a byte array describing the visual appearance of this character.
/// Indexed by <see cref="CustomizeIndex"/>. /// Indexed by <see cref="CustomizeIndex"/>.
/// </summary> /// </summary>
public byte[] Customize { get; } public byte[] Customize { get; }
/// <summary> /// <summary>
/// Gets the Free Company tag of this chara. /// Gets the underlying CustomizeData struct for this character.
/// </summary>
public ICustomizeData CustomizeData { get; }
/// <summary>
/// Gets the Free Company tag of this character.
/// </summary> /// </summary>
public SeString CompanyTag { get; } public SeString CompanyTag { get; }
@ -92,12 +99,12 @@ public interface ICharacter : IGameObject
/// Gets the status flags. /// Gets the status flags.
/// </summary> /// </summary>
public StatusFlags StatusFlags { get; } public StatusFlags StatusFlags { get; }
/// <summary> /// <summary>
/// Gets the current mount for this character. Will be <c>null</c> if the character doesn't have a mount. /// Gets the current mount for this character. Will be <c>null</c> if the character doesn't have a mount.
/// </summary> /// </summary>
public RowRef<Mount>? CurrentMount { get; } public RowRef<Mount>? CurrentMount { get; }
/// <summary> /// <summary>
/// Gets the current minion summoned for this character. Will be <c>null</c> if the character doesn't have a minion. /// Gets the current minion summoned for this character. Will be <c>null</c> if the character doesn't have a minion.
/// This method *will* return information about a spawned (but invisible) minion, e.g. if the character is riding a /// This method *will* return information about a spawned (but invisible) minion, e.g. if the character is riding a
@ -116,7 +123,7 @@ internal unsafe class Character : GameObject, ICharacter
/// This represents a non-static entity. /// This represents a non-static entity.
/// </summary> /// </summary>
/// <param name="address">The address of this character in memory.</param> /// <param name="address">The address of this character in memory.</param>
internal Character(IntPtr address) internal Character(nint address)
: base(address) : base(address)
{ {
} }
@ -155,8 +162,12 @@ internal unsafe class Character : GameObject, ICharacter
public byte Level => this.Struct->CharacterData.Level; public byte Level => this.Struct->CharacterData.Level;
/// <inheritdoc/> /// <inheritdoc/>
[Api15ToDo("Do not allocate on each call, use the CS Span and let consumers do allocation if necessary")]
public byte[] Customize => this.Struct->DrawData.CustomizeData.Data.ToArray(); public byte[] Customize => this.Struct->DrawData.CustomizeData.Data.ToArray();
/// <inheritdoc/>
public ICustomizeData CustomizeData => new CustomizeData((nint)(&this.Struct->DrawData.CustomizeData));
/// <inheritdoc/> /// <inheritdoc/>
public SeString CompanyTag => SeString.Parse(this.Struct->FreeCompanyTag); public SeString CompanyTag => SeString.Parse(this.Struct->FreeCompanyTag);
@ -183,14 +194,14 @@ internal unsafe class Character : GameObject, ICharacter
(this.Struct->IsAllianceMember ? StatusFlags.AllianceMember : StatusFlags.None) | (this.Struct->IsAllianceMember ? StatusFlags.AllianceMember : StatusFlags.None) |
(this.Struct->IsFriend ? StatusFlags.Friend : StatusFlags.None) | (this.Struct->IsFriend ? StatusFlags.Friend : StatusFlags.None) |
(this.Struct->IsCasting ? StatusFlags.IsCasting : StatusFlags.None); (this.Struct->IsCasting ? StatusFlags.IsCasting : StatusFlags.None);
/// <inheritdoc /> /// <inheritdoc />
public RowRef<Mount>? CurrentMount public RowRef<Mount>? CurrentMount
{ {
get get
{ {
if (this.Struct->IsNotMounted()) return null; // just for safety. if (this.Struct->IsNotMounted()) return null; // just for safety.
var mountId = this.Struct->Mount.MountId; var mountId = this.Struct->Mount.MountId;
return mountId == 0 ? null : LuminaUtils.CreateRef<Mount>(mountId); return mountId == 0 ? null : LuminaUtils.CreateRef<Mount>(mountId);
} }
@ -201,7 +212,7 @@ internal unsafe class Character : GameObject, ICharacter
{ {
get get
{ {
if (this.Struct->CompanionObject != null) if (this.Struct->CompanionObject != null)
return LuminaUtils.CreateRef<Companion>(this.Struct->CompanionObject->BaseId); return LuminaUtils.CreateRef<Companion>(this.Struct->CompanionObject->BaseId);
// this is only present if a minion is summoned but hidden (e.g. the player's on a mount). // this is only present if a minion is summoned but hidden (e.g. the player's on a mount).

View file

@ -121,9 +121,9 @@ internal sealed class Framework : IInternalDisposableService, IFramework
/// <inheritdoc/> /// <inheritdoc/>
public Task DelayTicks(long numTicks, CancellationToken cancellationToken = default) public Task DelayTicks(long numTicks, CancellationToken cancellationToken = default)
{ {
if (this.frameworkDestroy.IsCancellationRequested) if (this.frameworkDestroy.IsCancellationRequested) // Going away
return Task.FromCanceled(this.frameworkDestroy.Token); return Task.FromCanceled(this.frameworkDestroy.Token);
if (numTicks <= 0) if (numTicks <= 0 || this.frameworkThreadTaskScheduler.BoundThread == null) // Nonsense or before first tick
return Task.CompletedTask; return Task.CompletedTask;
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);

View file

@ -336,7 +336,7 @@ internal sealed unsafe class ContextMenu : IInternalDisposableService, IContextM
this.MenuCallbackIds.Clear(); this.MenuCallbackIds.Clear();
this.SelectedAgent = agent; this.SelectedAgent = agent;
var unitManager = RaptureAtkUnitManager.Instance(); var unitManager = RaptureAtkUnitManager.Instance();
this.SelectedParentAddon = unitManager->GetAddonById(unitManager->GetAddonByName(addonName)->ContextMenuParentId); this.SelectedParentAddon = unitManager->GetAddonById(unitManager->GetAddonByName(addonName)->BlockedParentId);
this.SelectedEventInterfaces.Clear(); this.SelectedEventInterfaces.Clear();
if (this.SelectedAgent == AgentInventoryContext.Instance()) if (this.SelectedAgent == AgentInventoryContext.Instance())
{ {

View file

@ -6,6 +6,8 @@ using Dalamud.Configuration.Internal;
using Dalamud.Hooking.Internal; using Dalamud.Hooking.Internal;
using Dalamud.Hooking.Internal.Verification; using Dalamud.Hooking.Internal.Verification;
using TerraFX.Interop.Windows;
namespace Dalamud.Hooking; namespace Dalamud.Hooking;
/// <summary> /// <summary>
@ -20,6 +22,8 @@ public abstract class Hook<T> : IDalamudHook where T : Delegate
private const ulong IMAGE_ORDINAL_FLAG64 = 0x8000000000000000; private const ulong IMAGE_ORDINAL_FLAG64 = 0x8000000000000000;
// ReSharper disable once InconsistentNaming // ReSharper disable once InconsistentNaming
private const uint IMAGE_ORDINAL_FLAG32 = 0x80000000; private const uint IMAGE_ORDINAL_FLAG32 = 0x80000000;
// ReSharper disable once InconsistentNaming
private const int IMAGE_DIRECTORY_ENTRY_IMPORT = 1;
#pragma warning restore SA1310 #pragma warning restore SA1310
private readonly IntPtr address; private readonly IntPtr address;
@ -124,25 +128,25 @@ public abstract class Hook<T> : IDalamudHook where T : Delegate
module ??= Process.GetCurrentProcess().MainModule; module ??= Process.GetCurrentProcess().MainModule;
if (module == null) if (module == null)
throw new InvalidOperationException("Current module is null?"); throw new InvalidOperationException("Current module is null?");
var pDos = (PeHeader.IMAGE_DOS_HEADER*)module.BaseAddress; var pDos = (IMAGE_DOS_HEADER*)module.BaseAddress;
var pNt = (PeHeader.IMAGE_FILE_HEADER*)(module.BaseAddress + (int)pDos->e_lfanew + 4); var pNt = (IMAGE_FILE_HEADER*)(module.BaseAddress + pDos->e_lfanew + 4);
var isPe64 = pNt->SizeOfOptionalHeader == Marshal.SizeOf<PeHeader.IMAGE_OPTIONAL_HEADER64>(); var isPe64 = pNt->SizeOfOptionalHeader == Marshal.SizeOf<IMAGE_OPTIONAL_HEADER64>();
PeHeader.IMAGE_DATA_DIRECTORY* pDataDirectory; IMAGE_DATA_DIRECTORY* pDataDirectory;
if (isPe64) if (isPe64)
{ {
var pOpt = (PeHeader.IMAGE_OPTIONAL_HEADER64*)(module.BaseAddress + (int)pDos->e_lfanew + 4 + Marshal.SizeOf<PeHeader.IMAGE_FILE_HEADER>()); var pOpt = (IMAGE_OPTIONAL_HEADER64*)(module.BaseAddress + pDos->e_lfanew + 4 + Marshal.SizeOf<IMAGE_FILE_HEADER>());
pDataDirectory = &pOpt->ImportTable; pDataDirectory = &pOpt->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
} }
else else
{ {
var pOpt = (PeHeader.IMAGE_OPTIONAL_HEADER32*)(module.BaseAddress + (int)pDos->e_lfanew + 4 + Marshal.SizeOf<PeHeader.IMAGE_FILE_HEADER>()); var pOpt = (IMAGE_OPTIONAL_HEADER32*)(module.BaseAddress + pDos->e_lfanew + 4 + Marshal.SizeOf<IMAGE_FILE_HEADER>());
pDataDirectory = &pOpt->ImportTable; pDataDirectory = &pOpt->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
} }
var moduleNameLowerWithNullTerminator = (moduleName + "\0").ToLowerInvariant(); var moduleNameLowerWithNullTerminator = (moduleName + "\0").ToLowerInvariant();
foreach (ref var importDescriptor in new Span<PeHeader.IMAGE_IMPORT_DESCRIPTOR>( foreach (ref var importDescriptor in new Span<IMAGE_IMPORT_DESCRIPTOR>(
(PeHeader.IMAGE_IMPORT_DESCRIPTOR*)(module.BaseAddress + (int)pDataDirectory->VirtualAddress), (IMAGE_IMPORT_DESCRIPTOR*)(module.BaseAddress + (int)pDataDirectory->VirtualAddress),
(int)(pDataDirectory->Size / Marshal.SizeOf<PeHeader.IMAGE_IMPORT_DESCRIPTOR>()))) (int)(pDataDirectory->Size / Marshal.SizeOf<IMAGE_IMPORT_DESCRIPTOR>())))
{ {
// Having all zero values signals the end of the table. We didn't find anything. // Having all zero values signals the end of the table. We didn't find anything.
if (importDescriptor.Characteristics == 0) if (importDescriptor.Characteristics == 0)
@ -248,7 +252,7 @@ public abstract class Hook<T> : IDalamudHook where T : Delegate
ObjectDisposedException.ThrowIf(this.IsDisposed, this); ObjectDisposedException.ThrowIf(this.IsDisposed, this);
} }
private static unsafe IntPtr FromImportHelper32(IntPtr baseAddress, ref PeHeader.IMAGE_IMPORT_DESCRIPTOR desc, ref PeHeader.IMAGE_DATA_DIRECTORY dir, string functionName, uint hintOrOrdinal) private static unsafe IntPtr FromImportHelper32(IntPtr baseAddress, ref IMAGE_IMPORT_DESCRIPTOR desc, ref IMAGE_DATA_DIRECTORY dir, string functionName, uint hintOrOrdinal)
{ {
var importLookupsOversizedSpan = new Span<uint>((uint*)(baseAddress + (int)desc.OriginalFirstThunk), (int)((dir.Size - desc.OriginalFirstThunk) / Marshal.SizeOf<int>())); var importLookupsOversizedSpan = new Span<uint>((uint*)(baseAddress + (int)desc.OriginalFirstThunk), (int)((dir.Size - desc.OriginalFirstThunk) / Marshal.SizeOf<int>()));
var importAddressesOversizedSpan = new Span<uint>((uint*)(baseAddress + (int)desc.FirstThunk), (int)((dir.Size - desc.FirstThunk) / Marshal.SizeOf<int>())); var importAddressesOversizedSpan = new Span<uint>((uint*)(baseAddress + (int)desc.FirstThunk), (int)((dir.Size - desc.FirstThunk) / Marshal.SizeOf<int>()));
@ -298,7 +302,7 @@ public abstract class Hook<T> : IDalamudHook where T : Delegate
throw new MissingMethodException("Specified method not found"); throw new MissingMethodException("Specified method not found");
} }
private static unsafe IntPtr FromImportHelper64(IntPtr baseAddress, ref PeHeader.IMAGE_IMPORT_DESCRIPTOR desc, ref PeHeader.IMAGE_DATA_DIRECTORY dir, string functionName, uint hintOrOrdinal) private static unsafe IntPtr FromImportHelper64(IntPtr baseAddress, ref IMAGE_IMPORT_DESCRIPTOR desc, ref IMAGE_DATA_DIRECTORY dir, string functionName, uint hintOrOrdinal)
{ {
var importLookupsOversizedSpan = new Span<ulong>((ulong*)(baseAddress + (int)desc.OriginalFirstThunk), (int)((dir.Size - desc.OriginalFirstThunk) / Marshal.SizeOf<ulong>())); var importLookupsOversizedSpan = new Span<ulong>((ulong*)(baseAddress + (int)desc.OriginalFirstThunk), (int)((dir.Size - desc.OriginalFirstThunk) / Marshal.SizeOf<ulong>()));
var importAddressesOversizedSpan = new Span<ulong>((ulong*)(baseAddress + (int)desc.FirstThunk), (int)((dir.Size - desc.FirstThunk) / Marshal.SizeOf<ulong>())); var importAddressesOversizedSpan = new Span<ulong>((ulong*)(baseAddress + (int)desc.FirstThunk), (int)((dir.Size - desc.FirstThunk) / Marshal.SizeOf<ulong>()));

View file

@ -1,390 +0,0 @@
using System.Runtime.InteropServices;
#pragma warning disable
namespace Dalamud.Hooking.Internal;
internal class PeHeader
{
public struct IMAGE_DOS_HEADER
{
public UInt16 e_magic;
public UInt16 e_cblp;
public UInt16 e_cp;
public UInt16 e_crlc;
public UInt16 e_cparhdr;
public UInt16 e_minalloc;
public UInt16 e_maxalloc;
public UInt16 e_ss;
public UInt16 e_sp;
public UInt16 e_csum;
public UInt16 e_ip;
public UInt16 e_cs;
public UInt16 e_lfarlc;
public UInt16 e_ovno;
public UInt16 e_res_0;
public UInt16 e_res_1;
public UInt16 e_res_2;
public UInt16 e_res_3;
public UInt16 e_oemid;
public UInt16 e_oeminfo;
public UInt16 e_res2_0;
public UInt16 e_res2_1;
public UInt16 e_res2_2;
public UInt16 e_res2_3;
public UInt16 e_res2_4;
public UInt16 e_res2_5;
public UInt16 e_res2_6;
public UInt16 e_res2_7;
public UInt16 e_res2_8;
public UInt16 e_res2_9;
public UInt32 e_lfanew;
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_DATA_DIRECTORY
{
public UInt32 VirtualAddress;
public UInt32 Size;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct IMAGE_OPTIONAL_HEADER32
{
public UInt16 Magic;
public Byte MajorLinkerVersion;
public Byte MinorLinkerVersion;
public UInt32 SizeOfCode;
public UInt32 SizeOfInitializedData;
public UInt32 SizeOfUninitializedData;
public UInt32 AddressOfEntryPoint;
public UInt32 BaseOfCode;
public UInt32 BaseOfData;
public UInt32 ImageBase;
public UInt32 SectionAlignment;
public UInt32 FileAlignment;
public UInt16 MajorOperatingSystemVersion;
public UInt16 MinorOperatingSystemVersion;
public UInt16 MajorImageVersion;
public UInt16 MinorImageVersion;
public UInt16 MajorSubsystemVersion;
public UInt16 MinorSubsystemVersion;
public UInt32 Win32VersionValue;
public UInt32 SizeOfImage;
public UInt32 SizeOfHeaders;
public UInt32 CheckSum;
public UInt16 Subsystem;
public UInt16 DllCharacteristics;
public UInt32 SizeOfStackReserve;
public UInt32 SizeOfStackCommit;
public UInt32 SizeOfHeapReserve;
public UInt32 SizeOfHeapCommit;
public UInt32 LoaderFlags;
public UInt32 NumberOfRvaAndSizes;
public IMAGE_DATA_DIRECTORY ExportTable;
public IMAGE_DATA_DIRECTORY ImportTable;
public IMAGE_DATA_DIRECTORY ResourceTable;
public IMAGE_DATA_DIRECTORY ExceptionTable;
public IMAGE_DATA_DIRECTORY CertificateTable;
public IMAGE_DATA_DIRECTORY BaseRelocationTable;
public IMAGE_DATA_DIRECTORY Debug;
public IMAGE_DATA_DIRECTORY Architecture;
public IMAGE_DATA_DIRECTORY GlobalPtr;
public IMAGE_DATA_DIRECTORY TLSTable;
public IMAGE_DATA_DIRECTORY LoadConfigTable;
public IMAGE_DATA_DIRECTORY BoundImport;
public IMAGE_DATA_DIRECTORY IAT;
public IMAGE_DATA_DIRECTORY DelayImportDescriptor;
public IMAGE_DATA_DIRECTORY CLRRuntimeHeader;
public IMAGE_DATA_DIRECTORY Reserved;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct IMAGE_OPTIONAL_HEADER64
{
public UInt16 Magic;
public Byte MajorLinkerVersion;
public Byte MinorLinkerVersion;
public UInt32 SizeOfCode;
public UInt32 SizeOfInitializedData;
public UInt32 SizeOfUninitializedData;
public UInt32 AddressOfEntryPoint;
public UInt32 BaseOfCode;
public UInt64 ImageBase;
public UInt32 SectionAlignment;
public UInt32 FileAlignment;
public UInt16 MajorOperatingSystemVersion;
public UInt16 MinorOperatingSystemVersion;
public UInt16 MajorImageVersion;
public UInt16 MinorImageVersion;
public UInt16 MajorSubsystemVersion;
public UInt16 MinorSubsystemVersion;
public UInt32 Win32VersionValue;
public UInt32 SizeOfImage;
public UInt32 SizeOfHeaders;
public UInt32 CheckSum;
public UInt16 Subsystem;
public UInt16 DllCharacteristics;
public UInt64 SizeOfStackReserve;
public UInt64 SizeOfStackCommit;
public UInt64 SizeOfHeapReserve;
public UInt64 SizeOfHeapCommit;
public UInt32 LoaderFlags;
public UInt32 NumberOfRvaAndSizes;
public IMAGE_DATA_DIRECTORY ExportTable;
public IMAGE_DATA_DIRECTORY ImportTable;
public IMAGE_DATA_DIRECTORY ResourceTable;
public IMAGE_DATA_DIRECTORY ExceptionTable;
public IMAGE_DATA_DIRECTORY CertificateTable;
public IMAGE_DATA_DIRECTORY BaseRelocationTable;
public IMAGE_DATA_DIRECTORY Debug;
public IMAGE_DATA_DIRECTORY Architecture;
public IMAGE_DATA_DIRECTORY GlobalPtr;
public IMAGE_DATA_DIRECTORY TLSTable;
public IMAGE_DATA_DIRECTORY LoadConfigTable;
public IMAGE_DATA_DIRECTORY BoundImport;
public IMAGE_DATA_DIRECTORY IAT;
public IMAGE_DATA_DIRECTORY DelayImportDescriptor;
public IMAGE_DATA_DIRECTORY CLRRuntimeHeader;
public IMAGE_DATA_DIRECTORY Reserved;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct IMAGE_FILE_HEADER
{
public UInt16 Machine;
public UInt16 NumberOfSections;
public UInt32 TimeDateStamp;
public UInt32 PointerToSymbolTable;
public UInt32 NumberOfSymbols;
public UInt16 SizeOfOptionalHeader;
public UInt16 Characteristics;
}
[StructLayout(LayoutKind.Explicit)]
public struct IMAGE_SECTION_HEADER
{
[FieldOffset(0)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public char[] Name;
[FieldOffset(8)]
public UInt32 VirtualSize;
[FieldOffset(12)]
public UInt32 VirtualAddress;
[FieldOffset(16)]
public UInt32 SizeOfRawData;
[FieldOffset(20)]
public UInt32 PointerToRawData;
[FieldOffset(24)]
public UInt32 PointerToRelocations;
[FieldOffset(28)]
public UInt32 PointerToLinenumbers;
[FieldOffset(32)]
public UInt16 NumberOfRelocations;
[FieldOffset(34)]
public UInt16 NumberOfLinenumbers;
[FieldOffset(36)]
public DataSectionFlags Characteristics;
public string Section
{
get { return new string(Name); }
}
}
[Flags]
public enum DataSectionFlags : uint
{
/// <summary>
/// Reserved for future use.
/// </summary>
TypeReg = 0x00000000,
/// <summary>
/// Reserved for future use.
/// </summary>
TypeDsect = 0x00000001,
/// <summary>
/// Reserved for future use.
/// </summary>
TypeNoLoad = 0x00000002,
/// <summary>
/// Reserved for future use.
/// </summary>
TypeGroup = 0x00000004,
/// <summary>
/// The section should not be padded to the next boundary. This flag is obsolete and is replaced by IMAGE_SCN_ALIGN_1BYTES. This is valid only for object files.
/// </summary>
TypeNoPadded = 0x00000008,
/// <summary>
/// Reserved for future use.
/// </summary>
TypeCopy = 0x00000010,
/// <summary>
/// The section contains executable code.
/// </summary>
ContentCode = 0x00000020,
/// <summary>
/// The section contains initialized data.
/// </summary>
ContentInitializedData = 0x00000040,
/// <summary>
/// The section contains uninitialized data.
/// </summary>
ContentUninitializedData = 0x00000080,
/// <summary>
/// Reserved for future use.
/// </summary>
LinkOther = 0x00000100,
/// <summary>
/// The section contains comments or other information. The .drectve section has this type. This is valid for object files only.
/// </summary>
LinkInfo = 0x00000200,
/// <summary>
/// Reserved for future use.
/// </summary>
TypeOver = 0x00000400,
/// <summary>
/// The section will not become part of the image. This is valid only for object files.
/// </summary>
LinkRemove = 0x00000800,
/// <summary>
/// The section contains COMDAT data. For more information, see section 5.5.6, COMDAT Sections (Object Only). This is valid only for object files.
/// </summary>
LinkComDat = 0x00001000,
/// <summary>
/// Reset speculative exceptions handling bits in the TLB entries for this section.
/// </summary>
NoDeferSpecExceptions = 0x00004000,
/// <summary>
/// The section contains data referenced through the global pointer (GP).
/// </summary>
RelativeGP = 0x00008000,
/// <summary>
/// Reserved for future use.
/// </summary>
MemPurgeable = 0x00020000,
/// <summary>
/// Reserved for future use.
/// </summary>
Memory16Bit = 0x00020000,
/// <summary>
/// Reserved for future use.
/// </summary>
MemoryLocked = 0x00040000,
/// <summary>
/// Reserved for future use.
/// </summary>
MemoryPreload = 0x00080000,
/// <summary>
/// Align data on a 1-byte boundary. Valid only for object files.
/// </summary>
Align1Bytes = 0x00100000,
/// <summary>
/// Align data on a 2-byte boundary. Valid only for object files.
/// </summary>
Align2Bytes = 0x00200000,
/// <summary>
/// Align data on a 4-byte boundary. Valid only for object files.
/// </summary>
Align4Bytes = 0x00300000,
/// <summary>
/// Align data on an 8-byte boundary. Valid only for object files.
/// </summary>
Align8Bytes = 0x00400000,
/// <summary>
/// Align data on a 16-byte boundary. Valid only for object files.
/// </summary>
Align16Bytes = 0x00500000,
/// <summary>
/// Align data on a 32-byte boundary. Valid only for object files.
/// </summary>
Align32Bytes = 0x00600000,
/// <summary>
/// Align data on a 64-byte boundary. Valid only for object files.
/// </summary>
Align64Bytes = 0x00700000,
/// <summary>
/// Align data on a 128-byte boundary. Valid only for object files.
/// </summary>
Align128Bytes = 0x00800000,
/// <summary>
/// Align data on a 256-byte boundary. Valid only for object files.
/// </summary>
Align256Bytes = 0x00900000,
/// <summary>
/// Align data on a 512-byte boundary. Valid only for object files.
/// </summary>
Align512Bytes = 0x00A00000,
/// <summary>
/// Align data on a 1024-byte boundary. Valid only for object files.
/// </summary>
Align1024Bytes = 0x00B00000,
/// <summary>
/// Align data on a 2048-byte boundary. Valid only for object files.
/// </summary>
Align2048Bytes = 0x00C00000,
/// <summary>
/// Align data on a 4096-byte boundary. Valid only for object files.
/// </summary>
Align4096Bytes = 0x00D00000,
/// <summary>
/// Align data on an 8192-byte boundary. Valid only for object files.
/// </summary>
Align8192Bytes = 0x00E00000,
/// <summary>
/// The section contains extended relocations.
/// </summary>
LinkExtendedRelocationOverflow = 0x01000000,
/// <summary>
/// The section can be discarded as needed.
/// </summary>
MemoryDiscardable = 0x02000000,
/// <summary>
/// The section cannot be cached.
/// </summary>
MemoryNotCached = 0x04000000,
/// <summary>
/// The section is not pageable.
/// </summary>
MemoryNotPaged = 0x08000000,
/// <summary>
/// The section can be shared in memory.
/// </summary>
MemoryShared = 0x10000000,
/// <summary>
/// The section can be executed as code.
/// </summary>
MemoryExecute = 0x20000000,
/// <summary>
/// The section can be read.
/// </summary>
MemoryRead = 0x40000000,
/// <summary>
/// The section can be written to.
/// </summary>
MemoryWrite = 0x80000000
}
[StructLayout(LayoutKind.Explicit)]
public struct IMAGE_IMPORT_DESCRIPTOR
{
[FieldOffset(0)]
public uint Characteristics;
[FieldOffset(0)]
public uint OriginalFirstThunk;
[FieldOffset(4)]
public uint TimeDateStamp;
[FieldOffset(8)]
public uint ForwarderChain;
[FieldOffset(12)]
public uint Name;
[FieldOffset(16)]
public uint FirstThunk;
}
}

View file

@ -62,7 +62,7 @@ internal class GaugeWidget : IDataWindowWidget
40 => jobGauges.Get<SGEGauge>(), 40 => jobGauges.Get<SGEGauge>(),
41 => jobGauges.Get<VPRGauge>(), 41 => jobGauges.Get<VPRGauge>(),
42 => jobGauges.Get<PCTGauge>(), 42 => jobGauges.Get<PCTGauge>(),
_ => null _ => null,
}; };
if (gauge == null) if (gauge == null)

View file

@ -50,7 +50,7 @@ internal unsafe class HookWidget : IDataWindowWidget
{ {
MessageBoxW, MessageBoxW,
AddonFinalize, AddonFinalize,
Random Random,
} }
/// <inheritdoc/> /// <inheritdoc/>

View file

@ -2,7 +2,6 @@ using System.Collections.Concurrent;
using System.Threading; using System.Threading;
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using Dalamud.Game;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Interface.Components; using Dalamud.Interface.Components;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
@ -22,10 +21,11 @@ internal unsafe class NetworkMonitorWidget : IDataWindowWidget
{ {
private readonly ConcurrentQueue<NetworkPacketData> packets = new(); private readonly ConcurrentQueue<NetworkPacketData> packets = new();
private Hook<PacketDispatcher.Delegates.OnReceivePacket>? hookDown; private Hook<PacketDispatcher.Delegates.OnReceivePacket>? hookZoneDown;
private Hook<ZoneClientSendPacketDelegate>? hookUp; private Hook<ZoneClient.Delegates.SendPacket>? hookZoneUp;
private bool trackNetwork; private bool trackZoneUp;
private bool trackZoneDown;
private int trackedPackets = 20; private int trackedPackets = 20;
private ulong nextPacketIndex; private ulong nextPacketIndex;
private string filterString = string.Empty; private string filterString = string.Empty;
@ -36,12 +36,10 @@ internal unsafe class NetworkMonitorWidget : IDataWindowWidget
/// <summary> Finalizes an instance of the <see cref="NetworkMonitorWidget"/> class. </summary> /// <summary> Finalizes an instance of the <see cref="NetworkMonitorWidget"/> class. </summary>
~NetworkMonitorWidget() ~NetworkMonitorWidget()
{ {
this.hookDown?.Dispose(); this.hookZoneDown?.Dispose();
this.hookUp?.Dispose(); this.hookZoneUp?.Dispose();
} }
private delegate byte ZoneClientSendPacketDelegate(ZoneClient* thisPtr, nint packet, uint a3, uint a4, byte a5);
private enum NetworkMessageDirection private enum NetworkMessageDirection
{ {
ZoneDown, ZoneDown,
@ -58,34 +56,46 @@ internal unsafe class NetworkMonitorWidget : IDataWindowWidget
public bool Ready { get; set; } public bool Ready { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public void Load() public void Load() => this.Ready = true;
{
this.hookDown = Hook<PacketDispatcher.Delegates.OnReceivePacket>.FromAddress(
(nint)PacketDispatcher.StaticVirtualTablePointer->OnReceivePacket,
this.OnReceivePacketDetour);
// TODO: switch to ZoneClient.SendPacket from CS
if (Service<TargetSigScanner>.Get().TryScanText("E8 ?? ?? ?? ?? 4C 8B 44 24 ?? E9", out var address))
this.hookUp = Hook<ZoneClientSendPacketDelegate>.FromAddress(address, this.SendPacketDetour);
this.Ready = true;
}
/// <inheritdoc/> /// <inheritdoc/>
public void Draw() public void Draw()
{ {
if (ImGui.Checkbox("Track Network Packets"u8, ref this.trackNetwork)) this.hookZoneDown ??= Hook<PacketDispatcher.Delegates.OnReceivePacket>.FromAddress(
(nint)PacketDispatcher.StaticVirtualTablePointer->OnReceivePacket,
this.OnReceivePacketDetour);
this.hookZoneUp ??= Hook<ZoneClient.Delegates.SendPacket>.FromAddress(
(nint)ZoneClient.MemberFunctionPointers.SendPacket,
this.SendPacketDetour);
if (ImGui.Checkbox("Track ZoneUp"u8, ref this.trackZoneUp))
{ {
if (this.trackNetwork) if (this.trackZoneUp)
{ {
this.nextPacketIndex = 0; if (!this.trackZoneDown)
this.hookDown?.Enable(); this.nextPacketIndex = 0;
this.hookUp?.Enable();
this.hookZoneUp?.Enable();
} }
else else
{ {
this.hookDown?.Disable(); this.hookZoneUp?.Disable();
this.hookUp?.Disable(); }
}
if (ImGui.Checkbox("Track ZoneDown"u8, ref this.trackZoneDown))
{
if (this.trackZoneDown)
{
if (!this.trackZoneUp)
this.nextPacketIndex = 0;
this.hookZoneDown?.Enable();
}
else
{
this.hookZoneDown?.Disable();
} }
} }
@ -98,6 +108,7 @@ internal unsafe class NetworkMonitorWidget : IDataWindowWidget
if (ImGui.Button("Clear Stored Packets"u8)) if (ImGui.Button("Clear Stored Packets"u8))
{ {
this.packets.Clear(); this.packets.Clear();
this.nextPacketIndex = 0;
} }
ImGui.SameLine(); ImGui.SameLine();
@ -108,7 +119,7 @@ internal unsafe class NetworkMonitorWidget : IDataWindowWidget
ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X);
ImGui.Checkbox("##FilterRecording"u8, ref this.filterRecording); ImGui.Checkbox("##FilterRecording"u8, ref this.filterRecording);
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
ImGui.SetTooltip("Apply filter to incoming packets.\nUncheck to record all packets and filter the table instead."u8); ImGui.SetTooltip("When enabled, packets are filtered before being recorded.\nWhen disabled, all packets are recorded and filtering only affects packets displayed in the table."u8);
ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X);
ImGuiComponents.HelpMarker("Enter OpCodes in a comma-separated list.\nRanges are supported. Exclude OpCodes with exclamation mark.\nExample: -400,!50-100,650,700-980,!941"); ImGuiComponents.HelpMarker("Enter OpCodes in a comma-separated list.\nRanges are supported. Exclude OpCodes with exclamation mark.\nExample: -400,!50-100,650,700-980,!941");
@ -210,14 +221,14 @@ internal unsafe class NetworkMonitorWidget : IDataWindowWidget
var opCode = *(ushort*)(packet + 2); var opCode = *(ushort*)(packet + 2);
var targetName = GetTargetName(targetId); var targetName = GetTargetName(targetId);
this.RecordPacket(new NetworkPacketData(Interlocked.Increment(ref this.nextPacketIndex), DateTime.Now, opCode, NetworkMessageDirection.ZoneDown, targetId, targetName)); this.RecordPacket(new NetworkPacketData(Interlocked.Increment(ref this.nextPacketIndex), DateTime.Now, opCode, NetworkMessageDirection.ZoneDown, targetId, targetName));
this.hookDown.OriginalDisposeSafe(thisPtr, targetId, packet); this.hookZoneDown.OriginalDisposeSafe(thisPtr, targetId, packet);
} }
private byte SendPacketDetour(ZoneClient* thisPtr, nint packet, uint a3, uint a4, byte a5) private bool SendPacketDetour(ZoneClient* thisPtr, nint packet, uint a3, uint a4, bool a5)
{ {
var opCode = *(ushort*)packet; var opCode = *(ushort*)packet;
this.RecordPacket(new NetworkPacketData(Interlocked.Increment(ref this.nextPacketIndex), DateTime.Now, opCode, NetworkMessageDirection.ZoneUp, 0, string.Empty)); this.RecordPacket(new NetworkPacketData(Interlocked.Increment(ref this.nextPacketIndex), DateTime.Now, opCode, NetworkMessageDirection.ZoneUp, 0, string.Empty));
return this.hookUp.OriginalDisposeSafe(thisPtr, packet, a3, a4, a5); return this.hookZoneUp.OriginalDisposeSafe(thisPtr, packet, a3, a4, a5);
} }
private void RecordPacket(NetworkPacketData packet) private void RecordPacket(NetworkPacketData packet)

View file

@ -3804,7 +3804,7 @@ internal class PluginInstallerWindow : Window, IDisposable
if (!manifest.Punchline.IsNullOrEmpty()) if (!manifest.Punchline.IsNullOrEmpty())
scores.Add(matcher.Matches(manifest.Punchline.ToLowerInvariant()) * 100); scores.Add(matcher.Matches(manifest.Punchline.ToLowerInvariant()) * 100);
if (manifest.Tags != null) if (manifest.Tags != null)
scores.Add(matcher.MatchesAny(manifest.Tags.ToArray()) * 100); scores.Add(matcher.MatchesAny(manifest.Tags.Select(tag => tag.ToLowerInvariant()).ToArray()) * 100);
return scores.Max(); return scores.Max();
} }

View file

@ -50,6 +50,6 @@ internal sealed class BitmapCodecInfo : IBitmapCodecInfo
_ = readFuncPtr(codecInfo, 0, null, &cch); _ = readFuncPtr(codecInfo, 0, null, &cch);
var buf = stackalloc char[(int)cch + 1]; var buf = stackalloc char[(int)cch + 1];
Marshal.ThrowExceptionForHR(readFuncPtr(codecInfo, cch + 1, buf, &cch)); Marshal.ThrowExceptionForHR(readFuncPtr(codecInfo, cch + 1, buf, &cch));
return new(buf, 0, (int)cch); return new string(buf, 0, (int)cch).Trim('\0');
} }
} }

View file

@ -70,7 +70,7 @@ internal sealed class GamePathSharedImmediateTexture : SharedImmediateTexture
} }
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
var wrap = tm.NoThrottleCreateFromTexFile(file); var wrap = tm.NoThrottleCreateFromTexFile(file.Header, file.TextureBuffer);
tm.BlameSetName(wrap, this.ToString()); tm.BlameSetName(wrap, this.ToString());
return wrap; return wrap;
} }

View file

@ -6,7 +6,6 @@ using System.Threading.Tasks;
using Dalamud.Configuration.Internal; using Dalamud.Configuration.Internal;
using Dalamud.Data; using Dalamud.Data;
using Dalamud.Game; using Dalamud.Game;
using Dalamud.Interface.ImGuiSeStringRenderer.Internal;
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using Dalamud.Interface.Textures.Internal.SharedImmediateTextures; using Dalamud.Interface.Textures.Internal.SharedImmediateTextures;
using Dalamud.Interface.Textures.TextureWraps; using Dalamud.Interface.Textures.TextureWraps;
@ -20,6 +19,7 @@ using Dalamud.Utility.TerraFxCom;
using Lumina.Data; using Lumina.Data;
using Lumina.Data.Files; using Lumina.Data.Files;
using Lumina.Data.Parsing.Tex.Buffers;
using TerraFX.Interop.DirectX; using TerraFX.Interop.DirectX;
using TerraFX.Interop.Windows; using TerraFX.Interop.Windows;
@ -219,7 +219,7 @@ internal sealed partial class TextureManager
null, null,
_ => Task.FromResult( _ => Task.FromResult(
this.BlameSetName( this.BlameSetName(
this.NoThrottleCreateFromTexFile(file), this.NoThrottleCreateFromTexFile(file.Header, file.TextureBuffer),
debugName ?? $"{nameof(this.CreateFromTexFile)}({ForceNullable(file.FilePath)?.Path})")), debugName ?? $"{nameof(this.CreateFromTexFile)}({ForceNullable(file.FilePath)?.Path})")),
cancellationToken); cancellationToken);
@ -345,14 +345,14 @@ internal sealed partial class TextureManager
/// <summary>Creates a texture from the given <see cref="TexFile"/>. Skips the load throttler; intended to be used /// <summary>Creates a texture from the given <see cref="TexFile"/>. Skips the load throttler; intended to be used
/// from implementation of <see cref="SharedImmediateTexture"/>s.</summary> /// from implementation of <see cref="SharedImmediateTexture"/>s.</summary>
/// <param name="file">The data.</param> /// <param name="header">Header of a <c>.tex</c> file.</param>
/// <param name="buffer">Texture buffer.</param>
/// <returns>The loaded texture.</returns> /// <returns>The loaded texture.</returns>
internal IDalamudTextureWrap NoThrottleCreateFromTexFile(TexFile file) internal IDalamudTextureWrap NoThrottleCreateFromTexFile(TexFile.TexHeader header, TextureBuffer buffer)
{ {
ObjectDisposedException.ThrowIf(this.disposeCts.IsCancellationRequested, this); ObjectDisposedException.ThrowIf(this.disposeCts.IsCancellationRequested, this);
var buffer = file.TextureBuffer; var (dxgiFormat, conversion) = TexFile.GetDxgiFormatFromTextureFormat(header.Format, false);
var (dxgiFormat, conversion) = TexFile.GetDxgiFormatFromTextureFormat(file.Header.Format, false);
if (conversion != TexFile.DxgiFormatConversion.NoConversion || if (conversion != TexFile.DxgiFormatConversion.NoConversion ||
!this.IsDxgiFormatSupported((DXGI_FORMAT)dxgiFormat)) !this.IsDxgiFormatSupported((DXGI_FORMAT)dxgiFormat))
{ {
@ -361,34 +361,31 @@ internal sealed partial class TextureManager
} }
var wrap = this.NoThrottleCreateFromRaw(new(buffer.Width, buffer.Height, dxgiFormat), buffer.RawData); var wrap = this.NoThrottleCreateFromRaw(new(buffer.Width, buffer.Height, dxgiFormat), buffer.RawData);
this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromTexFile)}({ForceNullable(file.FilePath).Path})"); this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromTexFile)}({header.Width} x {header.Height})");
return wrap; return wrap;
static T? ForceNullable<T>(T s) => s;
} }
/// <summary>Creates a texture from the given <paramref name="fileBytes"/>, trying to interpret it as a /// <summary>Creates a texture from the given <paramref name="fileBytes"/>, trying to interpret it as a
/// <see cref="TexFile"/>.</summary> /// <see cref="TexFile"/>.</summary>
/// <param name="fileBytes">The file bytes.</param> /// <param name="fileBytes">The file bytes.</param>
/// <returns>The loaded texture.</returns> /// <returns>The loaded texture.</returns>
internal IDalamudTextureWrap NoThrottleCreateFromTexFile(ReadOnlySpan<byte> fileBytes) internal unsafe IDalamudTextureWrap NoThrottleCreateFromTexFile(ReadOnlySpan<byte> fileBytes)
{ {
ObjectDisposedException.ThrowIf(this.disposeCts.IsCancellationRequested, this); ObjectDisposedException.ThrowIf(this.disposeCts.IsCancellationRequested, this);
if (!TexFileExtensions.IsPossiblyTexFile2D(fileBytes)) if (!TexFileExtensions.IsPossiblyTexFile2D(fileBytes))
throw new InvalidDataException("The file is not a TexFile."); throw new InvalidDataException("The file is not a TexFile.");
var bytesArray = fileBytes.ToArray(); TexFile.TexHeader header;
var tf = new TexFile(); TextureBuffer buffer;
typeof(TexFile).GetProperty(nameof(tf.Data))!.GetSetMethod(true)!.Invoke( fixed (byte* p = fileBytes)
tf, {
[bytesArray]); var lbr = new LuminaBinaryReader(new UnmanagedMemoryStream(p, fileBytes.Length));
typeof(TexFile).GetProperty(nameof(tf.Reader))!.GetSetMethod(true)!.Invoke( header = lbr.ReadStructure<TexFile.TexHeader>();
tf, buffer = TextureBuffer.FromStream(header, lbr);
[new LuminaBinaryReader(bytesArray)]); }
// Note: FileInfo and FilePath are not used from TexFile; skip it.
var wrap = this.NoThrottleCreateFromTexFile(tf); var wrap = this.NoThrottleCreateFromTexFile(header, buffer);
this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromTexFile)}({fileBytes.Length:n0})"); this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromTexFile)}({fileBytes.Length:n0})");
return wrap; return wrap;
} }

View file

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
@ -322,7 +322,7 @@ public interface ITextureProvider : IDalamudService
/// <param name="leaveWrapOpen">Whether to leave <paramref name="wrap"/> non-disposed when the returned /// <param name="leaveWrapOpen">Whether to leave <paramref name="wrap"/> non-disposed when the returned
/// <see cref="Task{TResult}"/> completes.</param> /// <see cref="Task{TResult}"/> completes.</param>
/// <returns>Address of the new <see cref="FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture"/>.</returns> /// <returns>Address of the new <see cref="FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Texture"/>.</returns>
/// <example>See <c>PrintTextureInfo</c> in <see cref="Interface.Internal.UiDebug.Browsing.ImageNodeTre"/> for an example /// <example>See <c>PrintTextureInfo</c> in <see cref="Interface.Internal.UiDebug.Browsing.ImageNodeTree"/> for an example
/// of replacing the texture of an image node.</example> /// of replacing the texture of an image node.</example>
/// <remarks> /// <remarks>
/// <para>If the returned kernel texture is to be destroyed, call the fourth function in its vtable, by calling /// <para>If the returned kernel texture is to be destroyed, call the fourth function in its vtable, by calling

View file

@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -68,6 +69,7 @@ public static class Troubleshooting
{ {
var payload = new TroubleshootingPayload var payload = new TroubleshootingPayload
{ {
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
LoadedPlugins = pluginManager?.InstalledPlugins?.Select(x => x.Manifest as LocalPluginManifest)?.OrderByDescending(x => x.InternalName).ToArray(), LoadedPlugins = pluginManager?.InstalledPlugins?.Select(x => x.Manifest as LocalPluginManifest)?.OrderByDescending(x => x.InternalName).ToArray(),
PluginStates = pluginManager?.InstalledPlugins?.Where(x => !x.IsDev).ToDictionary(x => x.Manifest.InternalName, x => x.IsBanned ? "Banned" : x.State.ToString()), PluginStates = pluginManager?.InstalledPlugins?.Where(x => !x.IsDev).ToDictionary(x => x.Manifest.InternalName, x => x.IsBanned ? "Banned" : x.State.ToString()),
EverStartedLoadingPlugins = pluginManager?.InstalledPlugins.Where(x => x.HasEverStartedLoad).Select(x => x.InternalName).ToList(), EverStartedLoadingPlugins = pluginManager?.InstalledPlugins.Where(x => x.HasEverStartedLoad).Select(x => x.InternalName).ToList(),
@ -85,6 +87,13 @@ public static class Troubleshooting
var encodedPayload = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload))); var encodedPayload = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload)));
Log.Information($"TROUBLESHOOTING:{encodedPayload}"); Log.Information($"TROUBLESHOOTING:{encodedPayload}");
File.WriteAllText(
Path.Join(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"XIVLauncher",
"dalamud.troubleshooting.json"),
JsonConvert.SerializeObject(payload, Formatting.Indented));
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -103,6 +112,8 @@ public static class Troubleshooting
private class TroubleshootingPayload private class TroubleshootingPayload
{ {
public long Timestamp { get; set; }
public LocalPluginManifest[]? LoadedPlugins { get; set; } public LocalPluginManifest[]? LoadedPlugins { get; set; }
public Dictionary<string, string>? PluginStates { get; set; } public Dictionary<string, string>? PluginStates { get; set; }

View file

@ -476,6 +476,7 @@ void export_tspack(HWND hWndParent, const std::filesystem::path& logDir, const s
"launcher.log", // XIVLauncher.Core for [mostly] Linux "launcher.log", // XIVLauncher.Core for [mostly] Linux
"patcher.log", "patcher.log",
"dalamud.log", "dalamud.log",
"dalamud.troubleshooting.json",
"dalamud.injector.log", "dalamud.injector.log",
"dalamud.boot.log", "dalamud.boot.log",
"aria.log", "aria.log",
@ -693,7 +694,7 @@ void restart_game_using_injector(int nRadioButton, const std::vector<std::wstrin
void get_cpu_info(wchar_t *vendor, wchar_t *brand) void get_cpu_info(wchar_t *vendor, wchar_t *brand)
{ {
// Gotten and reformatted to not include all data as listed at https://learn.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex?view=msvc-170#example // Gotten and reformatted to not include all data as listed at https://learn.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex?view=msvc-170#example
// int cpuInfo[4] = {-1}; // int cpuInfo[4] = {-1};
std::array<int, 4> cpui; std::array<int, 4> cpui;
int nIds_; int nIds_;

@ -1 +1 @@
Subproject commit a02536a4bf6862036403c03945a02fcd6689e445 Subproject commit 28421f946ae9ec262630f04b2b4a114b54527ff9