From ac7c4e889a853c61a7b08b71cfb73e57000a304e Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sun, 25 Jan 2026 12:40:51 +0100 Subject: [PATCH 01/15] Write troubleshooting to json file --- Dalamud/Support/Troubleshooting.cs | 8 ++++++++ DalamudCrashHandler/DalamudCrashHandler.cpp | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Dalamud/Support/Troubleshooting.cs b/Dalamud/Support/Troubleshooting.cs index 2dd0fb623..e94252027 100644 --- a/Dalamud/Support/Troubleshooting.cs +++ b/Dalamud/Support/Troubleshooting.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; @@ -85,6 +86,13 @@ public static class Troubleshooting var encodedPayload = Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payload))); 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) { diff --git a/DalamudCrashHandler/DalamudCrashHandler.cpp b/DalamudCrashHandler/DalamudCrashHandler.cpp index f28715dc1..b72733983 100644 --- a/DalamudCrashHandler/DalamudCrashHandler.cpp +++ b/DalamudCrashHandler/DalamudCrashHandler.cpp @@ -476,6 +476,7 @@ void export_tspack(HWND hWndParent, const std::filesystem::path& logDir, const s "launcher.log", // XIVLauncher.Core for [mostly] Linux "patcher.log", "dalamud.log", + "dalamud.troubleshooting.json", "dalamud.injector.log", "dalamud.boot.log", "aria.log", @@ -693,7 +694,7 @@ void restart_game_using_injector(int nRadioButton, const std::vector cpui; int nIds_; From b9c4c97eba8c6c83d6511452e294ca1f862b700d Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sun, 25 Jan 2026 16:23:24 +0100 Subject: [PATCH 02/15] Add timestamp to TroubleshootingPayload --- Dalamud/Support/Troubleshooting.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dalamud/Support/Troubleshooting.cs b/Dalamud/Support/Troubleshooting.cs index e94252027..f9e084db8 100644 --- a/Dalamud/Support/Troubleshooting.cs +++ b/Dalamud/Support/Troubleshooting.cs @@ -69,6 +69,7 @@ public static class Troubleshooting { var payload = new TroubleshootingPayload { + Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(), 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()), EverStartedLoadingPlugins = pluginManager?.InstalledPlugins.Where(x => x.HasEverStartedLoad).Select(x => x.InternalName).ToList(), @@ -111,6 +112,8 @@ public static class Troubleshooting private class TroubleshootingPayload { + public long Timestamp { get; set; } + public LocalPluginManifest[]? LoadedPlugins { get; set; } public Dictionary? PluginStates { get; set; } From 934df7da8adbf199eab6ec5dfa7bd182ea97ea9b Mon Sep 17 00:00:00 2001 From: nebel <9887+nebel@users.noreply.github.com> Date: Thu, 29 Jan 2026 00:16:41 +0900 Subject: [PATCH 03/15] Properly lowercase for tags for plugin installer search --- .../Internal/Windows/PluginInstaller/PluginInstallerWindow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs index e32d31181..d132f0d8d 100644 --- a/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs +++ b/Dalamud/Interface/Internal/Windows/PluginInstaller/PluginInstallerWindow.cs @@ -3804,7 +3804,7 @@ internal class PluginInstallerWindow : Window, IDisposable if (!manifest.Punchline.IsNullOrEmpty()) scores.Add(matcher.Matches(manifest.Punchline.ToLowerInvariant()) * 100); 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(); } From 73edaadbcad5311c8cc7aa28e88d599a36c3b273 Mon Sep 17 00:00:00 2001 From: pohky <1510568+pohky@users.noreply.github.com> Date: Fri, 30 Jan 2026 14:51:29 +0100 Subject: [PATCH 04/15] Fix null characters in BitmapCodecInfo strings --- Dalamud/Interface/Textures/Internal/BitmapCodecInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Interface/Textures/Internal/BitmapCodecInfo.cs b/Dalamud/Interface/Textures/Internal/BitmapCodecInfo.cs index ec56caadd..7f5ed4fda 100644 --- a/Dalamud/Interface/Textures/Internal/BitmapCodecInfo.cs +++ b/Dalamud/Interface/Textures/Internal/BitmapCodecInfo.cs @@ -50,6 +50,6 @@ internal sealed class BitmapCodecInfo : IBitmapCodecInfo _ = readFuncPtr(codecInfo, 0, null, &cch); var buf = stackalloc char[(int)cch + 1]; Marshal.ThrowExceptionForHR(readFuncPtr(codecInfo, cch + 1, buf, &cch)); - return new(buf, 0, (int)cch); + return new string(buf, 0, (int)cch).Trim('\0'); } } From 252b7eeb9b4d6b3081471b283538c4c1b3203126 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Fri, 30 Jan 2026 19:21:46 +0100 Subject: [PATCH 05/15] Replace PeHeader with TerraFX --- Dalamud/Hooking/Hook.cs | 30 ++- Dalamud/Hooking/Internal/PeHeader.cs | 390 --------------------------- 2 files changed, 17 insertions(+), 403 deletions(-) delete mode 100644 Dalamud/Hooking/Internal/PeHeader.cs diff --git a/Dalamud/Hooking/Hook.cs b/Dalamud/Hooking/Hook.cs index c54a8f399..265b118bc 100644 --- a/Dalamud/Hooking/Hook.cs +++ b/Dalamud/Hooking/Hook.cs @@ -6,6 +6,8 @@ using Dalamud.Configuration.Internal; using Dalamud.Hooking.Internal; using Dalamud.Hooking.Internal.Verification; +using TerraFX.Interop.Windows; + namespace Dalamud.Hooking; /// @@ -20,6 +22,8 @@ public abstract class Hook : IDalamudHook where T : Delegate private const ulong IMAGE_ORDINAL_FLAG64 = 0x8000000000000000; // ReSharper disable once InconsistentNaming private const uint IMAGE_ORDINAL_FLAG32 = 0x80000000; + // ReSharper disable once InconsistentNaming + private const int IMAGE_DIRECTORY_ENTRY_IMPORT = 1; #pragma warning restore SA1310 private readonly IntPtr address; @@ -124,25 +128,25 @@ public abstract class Hook : IDalamudHook where T : Delegate module ??= Process.GetCurrentProcess().MainModule; if (module == null) throw new InvalidOperationException("Current module is null?"); - var pDos = (PeHeader.IMAGE_DOS_HEADER*)module.BaseAddress; - var pNt = (PeHeader.IMAGE_FILE_HEADER*)(module.BaseAddress + (int)pDos->e_lfanew + 4); - var isPe64 = pNt->SizeOfOptionalHeader == Marshal.SizeOf(); - PeHeader.IMAGE_DATA_DIRECTORY* pDataDirectory; + var pDos = (IMAGE_DOS_HEADER*)module.BaseAddress; + var pNt = (IMAGE_FILE_HEADER*)(module.BaseAddress + pDos->e_lfanew + 4); + var isPe64 = pNt->SizeOfOptionalHeader == Marshal.SizeOf(); + IMAGE_DATA_DIRECTORY* pDataDirectory; if (isPe64) { - var pOpt = (PeHeader.IMAGE_OPTIONAL_HEADER64*)(module.BaseAddress + (int)pDos->e_lfanew + 4 + Marshal.SizeOf()); - pDataDirectory = &pOpt->ImportTable; + var pOpt = (IMAGE_OPTIONAL_HEADER64*)(module.BaseAddress + pDos->e_lfanew + 4 + Marshal.SizeOf()); + pDataDirectory = &pOpt->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; } else { - var pOpt = (PeHeader.IMAGE_OPTIONAL_HEADER32*)(module.BaseAddress + (int)pDos->e_lfanew + 4 + Marshal.SizeOf()); - pDataDirectory = &pOpt->ImportTable; + var pOpt = (IMAGE_OPTIONAL_HEADER32*)(module.BaseAddress + pDos->e_lfanew + 4 + Marshal.SizeOf()); + pDataDirectory = &pOpt->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; } var moduleNameLowerWithNullTerminator = (moduleName + "\0").ToLowerInvariant(); - foreach (ref var importDescriptor in new Span( - (PeHeader.IMAGE_IMPORT_DESCRIPTOR*)(module.BaseAddress + (int)pDataDirectory->VirtualAddress), - (int)(pDataDirectory->Size / Marshal.SizeOf()))) + foreach (ref var importDescriptor in new Span( + (IMAGE_IMPORT_DESCRIPTOR*)(module.BaseAddress + (int)pDataDirectory->VirtualAddress), + (int)(pDataDirectory->Size / Marshal.SizeOf()))) { // Having all zero values signals the end of the table. We didn't find anything. if (importDescriptor.Characteristics == 0) @@ -248,7 +252,7 @@ public abstract class Hook : IDalamudHook where T : Delegate 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*)(baseAddress + (int)desc.OriginalFirstThunk), (int)((dir.Size - desc.OriginalFirstThunk) / Marshal.SizeOf())); var importAddressesOversizedSpan = new Span((uint*)(baseAddress + (int)desc.FirstThunk), (int)((dir.Size - desc.FirstThunk) / Marshal.SizeOf())); @@ -298,7 +302,7 @@ public abstract class Hook : IDalamudHook where T : Delegate 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*)(baseAddress + (int)desc.OriginalFirstThunk), (int)((dir.Size - desc.OriginalFirstThunk) / Marshal.SizeOf())); var importAddressesOversizedSpan = new Span((ulong*)(baseAddress + (int)desc.FirstThunk), (int)((dir.Size - desc.FirstThunk) / Marshal.SizeOf())); diff --git a/Dalamud/Hooking/Internal/PeHeader.cs b/Dalamud/Hooking/Internal/PeHeader.cs deleted file mode 100644 index 51df4a174..000000000 --- a/Dalamud/Hooking/Internal/PeHeader.cs +++ /dev/null @@ -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 - { - /// - /// Reserved for future use. - /// - TypeReg = 0x00000000, - /// - /// Reserved for future use. - /// - TypeDsect = 0x00000001, - /// - /// Reserved for future use. - /// - TypeNoLoad = 0x00000002, - /// - /// Reserved for future use. - /// - TypeGroup = 0x00000004, - /// - /// 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. - /// - TypeNoPadded = 0x00000008, - /// - /// Reserved for future use. - /// - TypeCopy = 0x00000010, - /// - /// The section contains executable code. - /// - ContentCode = 0x00000020, - /// - /// The section contains initialized data. - /// - ContentInitializedData = 0x00000040, - /// - /// The section contains uninitialized data. - /// - ContentUninitializedData = 0x00000080, - /// - /// Reserved for future use. - /// - LinkOther = 0x00000100, - /// - /// The section contains comments or other information. The .drectve section has this type. This is valid for object files only. - /// - LinkInfo = 0x00000200, - /// - /// Reserved for future use. - /// - TypeOver = 0x00000400, - /// - /// The section will not become part of the image. This is valid only for object files. - /// - LinkRemove = 0x00000800, - /// - /// The section contains COMDAT data. For more information, see section 5.5.6, COMDAT Sections (Object Only). This is valid only for object files. - /// - LinkComDat = 0x00001000, - /// - /// Reset speculative exceptions handling bits in the TLB entries for this section. - /// - NoDeferSpecExceptions = 0x00004000, - /// - /// The section contains data referenced through the global pointer (GP). - /// - RelativeGP = 0x00008000, - /// - /// Reserved for future use. - /// - MemPurgeable = 0x00020000, - /// - /// Reserved for future use. - /// - Memory16Bit = 0x00020000, - /// - /// Reserved for future use. - /// - MemoryLocked = 0x00040000, - /// - /// Reserved for future use. - /// - MemoryPreload = 0x00080000, - /// - /// Align data on a 1-byte boundary. Valid only for object files. - /// - Align1Bytes = 0x00100000, - /// - /// Align data on a 2-byte boundary. Valid only for object files. - /// - Align2Bytes = 0x00200000, - /// - /// Align data on a 4-byte boundary. Valid only for object files. - /// - Align4Bytes = 0x00300000, - /// - /// Align data on an 8-byte boundary. Valid only for object files. - /// - Align8Bytes = 0x00400000, - /// - /// Align data on a 16-byte boundary. Valid only for object files. - /// - Align16Bytes = 0x00500000, - /// - /// Align data on a 32-byte boundary. Valid only for object files. - /// - Align32Bytes = 0x00600000, - /// - /// Align data on a 64-byte boundary. Valid only for object files. - /// - Align64Bytes = 0x00700000, - /// - /// Align data on a 128-byte boundary. Valid only for object files. - /// - Align128Bytes = 0x00800000, - /// - /// Align data on a 256-byte boundary. Valid only for object files. - /// - Align256Bytes = 0x00900000, - /// - /// Align data on a 512-byte boundary. Valid only for object files. - /// - Align512Bytes = 0x00A00000, - /// - /// Align data on a 1024-byte boundary. Valid only for object files. - /// - Align1024Bytes = 0x00B00000, - /// - /// Align data on a 2048-byte boundary. Valid only for object files. - /// - Align2048Bytes = 0x00C00000, - /// - /// Align data on a 4096-byte boundary. Valid only for object files. - /// - Align4096Bytes = 0x00D00000, - /// - /// Align data on an 8192-byte boundary. Valid only for object files. - /// - Align8192Bytes = 0x00E00000, - /// - /// The section contains extended relocations. - /// - LinkExtendedRelocationOverflow = 0x01000000, - /// - /// The section can be discarded as needed. - /// - MemoryDiscardable = 0x02000000, - /// - /// The section cannot be cached. - /// - MemoryNotCached = 0x04000000, - /// - /// The section is not pageable. - /// - MemoryNotPaged = 0x08000000, - /// - /// The section can be shared in memory. - /// - MemoryShared = 0x10000000, - /// - /// The section can be executed as code. - /// - MemoryExecute = 0x20000000, - /// - /// The section can be read. - /// - MemoryRead = 0x40000000, - /// - /// The section can be written to. - /// - 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; - } -} From d8a13a72aa941bb41ff52020f1a848e197d4a65a Mon Sep 17 00:00:00 2001 From: Infi Date: Thu, 5 Feb 2026 00:20:39 +0100 Subject: [PATCH 06/15] - Add the CustomizeData struct to ICharacter - API 15 note the Customize array --- .../ClientState/Customize/CustomizeData.cs | 314 ++++++++++++++++++ .../ClientState/Objects/Types/Character.cs | 49 +-- 2 files changed, 344 insertions(+), 19 deletions(-) create mode 100644 Dalamud/Game/ClientState/Customize/CustomizeData.cs diff --git a/Dalamud/Game/ClientState/Customize/CustomizeData.cs b/Dalamud/Game/ClientState/Customize/CustomizeData.cs new file mode 100644 index 000000000..644bec5e8 --- /dev/null +++ b/Dalamud/Game/ClientState/Customize/CustomizeData.cs @@ -0,0 +1,314 @@ +using Dalamud.Game.ClientState.Objects.Types; + +namespace Dalamud.Game.ClientState.Customize; + +/// +/// This collection represents customization data a has. +/// +public interface ICustomizeData +{ + /// + /// Gets the current race. + /// E.g., Miqo'te, Aura + /// + public byte Race { get; } + + /// + /// Gets the current sex. + /// + public byte Sex { get; } + + /// + /// Gets the current body type. + /// + public byte BodyType { get; } + + /// + /// Gets the current height. + /// 0 to 100 + /// + public byte Height { get; } + + /// + /// Gets the current tribe. + /// E.g., Seeker of the Sun, Keeper of the Moon + /// + public byte Tribe { get; } + + /// + /// Gets the current face. + /// 1 to 4 + /// + public byte Face { get; } + + /// + /// Gets the current hairstyle. + /// + public byte Hairstyle { get; } + + /// + /// Gets the current skin color. + /// + public byte SkinColor { get; } + + /// + /// Gets the current color of the left eye. + /// + public byte EyeColorLeft { get; } + + /// + /// Gets the current color of the right eye. + /// + public byte EyeColorRight { get; } + + /// + /// Gets the current main hair color. + /// + public byte HairColor { get; } + + /// + /// Gets the current highlight hair color. + /// + public byte HighlightsColor { get; } + + /// + /// Gets the current tattoo color. + /// + public byte TattooColor { get; } + + /// + /// Gets the current eyebrow type. + /// + public byte Eyebrows { get; } + + /// + /// Gets the current nose type. + /// + public byte Nose { get; } + + /// + /// Gets the current jaw type. + /// + public byte Jaw { get; } + + /// + /// Gets the current lip color fur pattern. + /// + public byte LipColorFurPattern { get; } + + /// + /// Gets the current muscle mass value. + /// + public byte MuscleMass { get; } + + /// + /// Gets the current tail type. + /// + public byte TailShape { get; } + + /// + /// Gets the current bust size. + /// 0 to 100 + /// + public byte BustSize { get; } + + /// + /// Gets the current color of the face paint. + /// + public byte FacePaintColor { get; } + + /// + /// Gets a value indicating whether highlight color is used. + /// + public bool Highlights { get; } + + /// + /// Gets a value indicating whether this facial feature is used. + /// + public bool FacialFeature1 { get; } + + /// + public bool FacialFeature2 { get; } + + /// + public bool FacialFeature3 { get; } + + /// + public bool FacialFeature4 { get; } + + /// + public bool FacialFeature5 { get; } + + /// + public bool FacialFeature6 { get; } + + /// + public bool FacialFeature7 { get; } + + /// + /// Gets a value indicating whether the legacy tattoo is used. + /// + public bool LegacyTattoo { get; } + + /// + /// Gets the current eye shape type. + /// + public byte EyeShape { get; } + + /// + /// Gets a value indicating whether small iris is used. + /// + public bool SmallIris { get; } + + /// + /// Gets the current mouth type. + /// + public byte Mouth { get; } + + /// + /// Gets a value indicating whether lipstick is used. + /// + public bool Lipstick { get; } + + /// + /// Gets the current face paint type. + /// + public byte FacePaint { get; } + + /// + /// Gets a value indicating whether face paint reversed is used. + /// + public bool FacePaintReversed { get; } +} + +/// +internal readonly unsafe struct CustomizeData : ICustomizeData +{ + /// + /// Gets or sets the address of the customize data struct in memory. + /// + public readonly nint Address; + + /// + /// Initializes a new instance of the struct. + /// + /// Address of the status list. + internal CustomizeData(nint address) + { + this.Address = address; + } + + /// + public byte Race => this.Struct->Race; + + /// + public byte Sex => this.Struct->Sex; + + /// + public byte BodyType => this.Struct->BodyType; + + /// + public byte Height => this.Struct->Height; + + /// + public byte Tribe => this.Struct->Tribe; + + /// + public byte Face => this.Struct->Face; + + /// + public byte Hairstyle => this.Struct->Hairstyle; + + /// + public byte SkinColor => this.Struct->SkinColor; + + /// + public byte EyeColorLeft => this.Struct->EyeColorLeft; + + /// + public byte EyeColorRight => this.Struct->EyeColorRight; + + /// + public byte HairColor => this.Struct->HairColor; + + /// + public byte HighlightsColor => this.Struct->HighlightsColor; + + /// + public byte TattooColor => this.Struct->TattooColor; + + /// + public byte Eyebrows => this.Struct->Eyebrows; + + /// + public byte Nose => this.Struct->Nose; + + /// + public byte Jaw => this.Struct->Jaw; + + /// + public byte LipColorFurPattern => this.Struct->LipColorFurPattern; + + /// + public byte MuscleMass => this.Struct->MuscleMass; + + /// + public byte TailShape => this.Struct->TailShape; + + /// + public byte BustSize => this.Struct->BustSize; + + /// + public byte FacePaintColor => this.Struct->FacePaintColor; + + /// + public bool Highlights => this.Struct->Highlights; + + /// + public bool FacialFeature1 => this.Struct->FacialFeature1; + + /// + public bool FacialFeature2 => this.Struct->FacialFeature2; + + /// + public bool FacialFeature3 => this.Struct->FacialFeature3; + + /// + public bool FacialFeature4 => this.Struct->FacialFeature4; + + /// + public bool FacialFeature5 => this.Struct->FacialFeature5; + + /// + public bool FacialFeature6 => this.Struct->FacialFeature6; + + /// + public bool FacialFeature7 => this.Struct->FacialFeature7; + + /// + public bool LegacyTattoo => this.Struct->LegacyTattoo; + + /// + public byte EyeShape => this.Struct->EyeShape; + + /// + public bool SmallIris => this.Struct->SmallIris; + + /// + public byte Mouth => this.Struct->Mouth; + + /// + public bool Lipstick => this.Struct->Lipstick; + + /// + public byte FacePaint => this.Struct->FacePaint; + + /// + public bool FacePaintReversed => this.Struct->FacePaintReversed; + + /// + /// Gets the underlying structure. + /// + internal FFXIVClientStructs.FFXIV.Client.Game.Character.CustomizeData* Struct => + (FFXIVClientStructs.FFXIV.Client.Game.Character.CustomizeData*)this.Address; +} diff --git a/Dalamud/Game/ClientState/Objects/Types/Character.cs b/Dalamud/Game/ClientState/Objects/Types/Character.cs index 2002a16b8..f122f1f27 100644 --- a/Dalamud/Game/ClientState/Objects/Types/Character.cs +++ b/Dalamud/Game/ClientState/Objects/Types/Character.cs @@ -1,6 +1,8 @@ using Dalamud.Data; +using Dalamud.Game.ClientState.Customize; using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Utility; using Lumina.Excel; using Lumina.Excel.Sheets; @@ -13,68 +15,73 @@ namespace Dalamud.Game.ClientState.Objects.Types; public interface ICharacter : IGameObject { /// - /// Gets the current HP of this Chara. + /// Gets the current HP of this character. /// public uint CurrentHp { get; } /// - /// Gets the maximum HP of this Chara. + /// Gets the maximum HP of this character. /// public uint MaxHp { get; } /// - /// Gets the current MP of this Chara. + /// Gets the current MP of this character. /// public uint CurrentMp { get; } /// - /// Gets the maximum MP of this Chara. + /// Gets the maximum MP of this character. /// public uint MaxMp { get; } /// - /// Gets the current GP of this Chara. + /// Gets the current GP of this character. /// public uint CurrentGp { get; } /// - /// Gets the maximum GP of this Chara. + /// Gets the maximum GP of this character. /// public uint MaxGp { get; } /// - /// Gets the current CP of this Chara. + /// Gets the current CP of this character. /// public uint CurrentCp { get; } /// - /// Gets the maximum CP of this Chara. + /// Gets the maximum CP of this character. /// public uint MaxCp { get; } /// - /// Gets the shield percentage of this Chara. + /// Gets the shield percentage of this character. /// public byte ShieldPercentage { get; } /// - /// Gets the ClassJob of this Chara. + /// Gets the ClassJob of this character. /// public RowRef ClassJob { get; } /// - /// Gets the level of this Chara. + /// Gets the level of this character. /// public byte Level { get; } /// - /// Gets a byte array describing the visual appearance of this Chara. + /// Gets a byte array describing the visual appearance of this character. /// Indexed by . /// public byte[] Customize { get; } /// - /// Gets the Free Company tag of this chara. + /// Gets the underlying CustomizeData struct for this character. + /// + public ICustomizeData CustomizeData { get; } + + /// + /// Gets the Free Company tag of this character. /// public SeString CompanyTag { get; } @@ -92,12 +99,12 @@ public interface ICharacter : IGameObject /// Gets the status flags. /// public StatusFlags StatusFlags { get; } - + /// /// Gets the current mount for this character. Will be null if the character doesn't have a mount. /// public RowRef? CurrentMount { get; } - + /// /// Gets the current minion summoned for this character. Will be null 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 @@ -116,7 +123,7 @@ internal unsafe class Character : GameObject, ICharacter /// This represents a non-static entity. /// /// The address of this character in memory. - internal Character(IntPtr address) + internal Character(nint address) : base(address) { } @@ -155,8 +162,12 @@ internal unsafe class Character : GameObject, ICharacter public byte Level => this.Struct->CharacterData.Level; /// + [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 ICustomizeData CustomizeData => new CustomizeData((nint)(&this.Struct->DrawData.CustomizeData)); + /// 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->IsFriend ? StatusFlags.Friend : StatusFlags.None) | (this.Struct->IsCasting ? StatusFlags.IsCasting : StatusFlags.None); - + /// public RowRef? CurrentMount { get { if (this.Struct->IsNotMounted()) return null; // just for safety. - + var mountId = this.Struct->Mount.MountId; return mountId == 0 ? null : LuminaUtils.CreateRef(mountId); } @@ -201,7 +212,7 @@ internal unsafe class Character : GameObject, ICharacter { get { - if (this.Struct->CompanionObject != null) + if (this.Struct->CompanionObject != null) return LuminaUtils.CreateRef(this.Struct->CompanionObject->BaseId); // this is only present if a minion is summoned but hidden (e.g. the player's on a mount). From dc77235c969c56c00fd11827ff48de5724c0ddf2 Mon Sep 17 00:00:00 2001 From: Infi Date: Thu, 5 Feb 2026 00:37:12 +0100 Subject: [PATCH 07/15] - Fix style cop warnings --- Dalamud/Game/ClientState/Customize/CustomizeData.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Dalamud/Game/ClientState/Customize/CustomizeData.cs b/Dalamud/Game/ClientState/Customize/CustomizeData.cs index 644bec5e8..5e861db65 100644 --- a/Dalamud/Game/ClientState/Customize/CustomizeData.cs +++ b/Dalamud/Game/ClientState/Customize/CustomizeData.cs @@ -9,7 +9,7 @@ public interface ICustomizeData { /// /// Gets the current race. - /// E.g., Miqo'te, Aura + /// E.g., Miqo'te, Aura. /// public byte Race { get; } @@ -31,13 +31,13 @@ public interface ICustomizeData /// /// Gets the current tribe. - /// E.g., Seeker of the Sun, Keeper of the Moon + /// E.g., Seeker of the Sun, Keeper of the Moon. /// public byte Tribe { get; } /// /// Gets the current face. - /// 1 to 4 + /// 1 to 4. /// public byte Face { get; } @@ -108,7 +108,7 @@ public interface ICustomizeData /// /// Gets the current bust size. - /// 0 to 100 + /// 0 to 100. /// public byte BustSize { get; } From bcf4f396d6d1fd58826c42efe2c842027eba383e Mon Sep 17 00:00:00 2001 From: Infi Date: Thu, 5 Feb 2026 01:12:00 +0100 Subject: [PATCH 08/15] - Adjust comments --- Dalamud/Game/ClientState/Customize/CustomizeData.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Dalamud/Game/ClientState/Customize/CustomizeData.cs b/Dalamud/Game/ClientState/Customize/CustomizeData.cs index 5e861db65..baf8d3a0a 100644 --- a/Dalamud/Game/ClientState/Customize/CustomizeData.cs +++ b/Dalamud/Game/ClientState/Customize/CustomizeData.cs @@ -24,8 +24,7 @@ public interface ICustomizeData public byte BodyType { get; } /// - /// Gets the current height. - /// 0 to 100 + /// Gets the current height (0 to 100). /// public byte Height { get; } @@ -36,8 +35,7 @@ public interface ICustomizeData public byte Tribe { get; } /// - /// Gets the current face. - /// 1 to 4. + /// Gets the current face (1 to 4). /// public byte Face { get; } @@ -102,13 +100,12 @@ public interface ICustomizeData public byte MuscleMass { get; } /// - /// Gets the current tail type. + /// Gets the current tail type (1 to 4). /// public byte TailShape { get; } /// - /// Gets the current bust size. - /// 0 to 100. + /// Gets the current bust size (0 to 100). /// public byte BustSize { get; } From d3b9c75e505c33753e32268b98c8000a03f5bfda Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 5 Feb 2026 17:35:21 +0000 Subject: [PATCH 09/15] Update ClientStructs --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index cb1f076a6..9ba281cab 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit cb1f076a6fcb6131cd8e1d5bda438adc980b3ee3 +Subproject commit 9ba281cab958049b47bbf7199ab14742c609cd4b From 7d2f12c6e2de70806ed6eea776a0c902577d5877 Mon Sep 17 00:00:00 2001 From: goaaats Date: Thu, 5 Feb 2026 19:56:25 +0100 Subject: [PATCH 10/15] build: 14.0.2.1 --- Dalamud/Dalamud.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Dalamud.csproj b/Dalamud/Dalamud.csproj index 34b546faf..adaf876ee 100644 --- a/Dalamud/Dalamud.csproj +++ b/Dalamud/Dalamud.csproj @@ -6,7 +6,7 @@ XIV Launcher addon framework - 14.0.2.0 + 14.0.2.1 $(DalamudVersion) $(DalamudVersion) $(DalamudVersion) From b30a93816b82a5a2ae16a33a1820f45117adbc87 Mon Sep 17 00:00:00 2001 From: Soreepeong <3614868+Soreepeong@users.noreply.github.com> Date: Fri, 6 Feb 2026 18:59:02 +0900 Subject: [PATCH 11/15] Directly work with TexHeader and TextureBuffer --- .../GamePathSharedImmediateTexture.cs | 2 +- .../Textures/Internal/TextureManager.cs | 37 +++++++++---------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs index 185ae07b9..a320d921e 100644 --- a/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs +++ b/Dalamud/Interface/Textures/Internal/SharedImmediateTextures/GamePathSharedImmediateTexture.cs @@ -70,7 +70,7 @@ internal sealed class GamePathSharedImmediateTexture : SharedImmediateTexture } cancellationToken.ThrowIfCancellationRequested(); - var wrap = tm.NoThrottleCreateFromTexFile(file); + var wrap = tm.NoThrottleCreateFromTexFile(file.Header, file.TextureBuffer); tm.BlameSetName(wrap, this.ToString()); return wrap; } diff --git a/Dalamud/Interface/Textures/Internal/TextureManager.cs b/Dalamud/Interface/Textures/Internal/TextureManager.cs index 982b5c58d..22a257395 100644 --- a/Dalamud/Interface/Textures/Internal/TextureManager.cs +++ b/Dalamud/Interface/Textures/Internal/TextureManager.cs @@ -6,7 +6,6 @@ using System.Threading.Tasks; using Dalamud.Configuration.Internal; using Dalamud.Data; using Dalamud.Game; -using Dalamud.Interface.ImGuiSeStringRenderer.Internal; using Dalamud.Interface.Internal; using Dalamud.Interface.Textures.Internal.SharedImmediateTextures; using Dalamud.Interface.Textures.TextureWraps; @@ -20,6 +19,7 @@ using Dalamud.Utility.TerraFxCom; using Lumina.Data; using Lumina.Data.Files; +using Lumina.Data.Parsing.Tex.Buffers; using TerraFX.Interop.DirectX; using TerraFX.Interop.Windows; @@ -219,7 +219,7 @@ internal sealed partial class TextureManager null, _ => Task.FromResult( this.BlameSetName( - this.NoThrottleCreateFromTexFile(file), + this.NoThrottleCreateFromTexFile(file.Header, file.TextureBuffer), debugName ?? $"{nameof(this.CreateFromTexFile)}({ForceNullable(file.FilePath)?.Path})")), cancellationToken); @@ -345,14 +345,14 @@ internal sealed partial class TextureManager /// Creates a texture from the given . Skips the load throttler; intended to be used /// from implementation of s. - /// The data. + /// Header of a .tex file. + /// Texture buffer. /// The loaded texture. - internal IDalamudTextureWrap NoThrottleCreateFromTexFile(TexFile file) + internal IDalamudTextureWrap NoThrottleCreateFromTexFile(TexFile.TexHeader header, TextureBuffer buffer) { ObjectDisposedException.ThrowIf(this.disposeCts.IsCancellationRequested, this); - var buffer = file.TextureBuffer; - var (dxgiFormat, conversion) = TexFile.GetDxgiFormatFromTextureFormat(file.Header.Format, false); + var (dxgiFormat, conversion) = TexFile.GetDxgiFormatFromTextureFormat(header.Format, false); if (conversion != TexFile.DxgiFormatConversion.NoConversion || !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); - this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromTexFile)}({ForceNullable(file.FilePath).Path})"); + this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromTexFile)}({header.Width} x {header.Height})"); return wrap; - - static T? ForceNullable(T s) => s; } /// Creates a texture from the given , trying to interpret it as a /// . /// The file bytes. /// The loaded texture. - internal IDalamudTextureWrap NoThrottleCreateFromTexFile(ReadOnlySpan fileBytes) + internal unsafe IDalamudTextureWrap NoThrottleCreateFromTexFile(ReadOnlySpan fileBytes) { ObjectDisposedException.ThrowIf(this.disposeCts.IsCancellationRequested, this); if (!TexFileExtensions.IsPossiblyTexFile2D(fileBytes)) throw new InvalidDataException("The file is not a TexFile."); - var bytesArray = fileBytes.ToArray(); - var tf = new TexFile(); - typeof(TexFile).GetProperty(nameof(tf.Data))!.GetSetMethod(true)!.Invoke( - tf, - [bytesArray]); - typeof(TexFile).GetProperty(nameof(tf.Reader))!.GetSetMethod(true)!.Invoke( - tf, - [new LuminaBinaryReader(bytesArray)]); - // Note: FileInfo and FilePath are not used from TexFile; skip it. + TexFile.TexHeader header; + TextureBuffer buffer; + fixed (byte* p = fileBytes) + { + var lbr = new LuminaBinaryReader(new UnmanagedMemoryStream(p, fileBytes.Length)); + header = lbr.ReadStructure(); + buffer = TextureBuffer.FromStream(header, lbr); + } - var wrap = this.NoThrottleCreateFromTexFile(tf); + var wrap = this.NoThrottleCreateFromTexFile(header, buffer); this.BlameSetName(wrap, $"{nameof(this.NoThrottleCreateFromTexFile)}({fileBytes.Length:n0})"); return wrap; } From 0490a71990946a34495fbeb6d9f8005e023d4638 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Sat, 7 Feb 2026 20:36:20 +0100 Subject: [PATCH 12/15] Update Network Monitor Widget - Separate checkboxes for up and down tracking - Clarify tracking is for ZoneUp/ZoneDown - Rephrase filter checkbox tooltip --- .../Data/Widgets/NetworkMonitorWidget.cs | 51 ++++++++++++------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/NetworkMonitorWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/NetworkMonitorWidget.cs index 4460a9f9a..73916761b 100644 --- a/Dalamud/Interface/Internal/Windows/Data/Widgets/NetworkMonitorWidget.cs +++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/NetworkMonitorWidget.cs @@ -21,10 +21,11 @@ internal unsafe class NetworkMonitorWidget : IDataWindowWidget { private readonly ConcurrentQueue packets = new(); - private Hook? hookDown; - private Hook? hookUp; + private Hook? hookZoneDown; + private Hook? hookZoneUp; - private bool trackNetwork; + private bool trackZoneUp; + private bool trackZoneDown; private int trackedPackets = 20; private ulong nextPacketIndex; private string filterString = string.Empty; @@ -35,8 +36,8 @@ internal unsafe class NetworkMonitorWidget : IDataWindowWidget /// Finalizes an instance of the class. ~NetworkMonitorWidget() { - this.hookDown?.Dispose(); - this.hookUp?.Dispose(); + this.hookZoneDown?.Dispose(); + this.hookZoneUp?.Dispose(); } private enum NetworkMessageDirection @@ -60,26 +61,41 @@ internal unsafe class NetworkMonitorWidget : IDataWindowWidget /// public void Draw() { - this.hookDown ??= Hook.FromAddress( + this.hookZoneDown ??= Hook.FromAddress( (nint)PacketDispatcher.StaticVirtualTablePointer->OnReceivePacket, this.OnReceivePacketDetour); - this.hookUp ??= Hook.FromAddress( + this.hookZoneUp ??= Hook.FromAddress( (nint)ZoneClient.MemberFunctionPointers.SendPacket, this.SendPacketDetour); - if (ImGui.Checkbox("Track Network Packets"u8, ref this.trackNetwork)) + if (ImGui.Checkbox("Track ZoneUp"u8, ref this.trackZoneUp)) { - if (this.trackNetwork) + if (this.trackZoneUp) { - this.nextPacketIndex = 0; - this.hookDown?.Enable(); - this.hookUp?.Enable(); + if (!this.trackZoneDown) + this.nextPacketIndex = 0; + + this.hookZoneUp?.Enable(); } else { - this.hookDown?.Disable(); - this.hookUp?.Disable(); + this.hookZoneUp?.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(); } } @@ -92,6 +108,7 @@ internal unsafe class NetworkMonitorWidget : IDataWindowWidget if (ImGui.Button("Clear Stored Packets"u8)) { this.packets.Clear(); + this.nextPacketIndex = 0; } ImGui.SameLine(); @@ -102,7 +119,7 @@ internal unsafe class NetworkMonitorWidget : IDataWindowWidget ImGui.SameLine(0, ImGui.GetStyle().ItemInnerSpacing.X); ImGui.Checkbox("##FilterRecording"u8, ref this.filterRecording); 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); 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"); @@ -204,14 +221,14 @@ internal unsafe class NetworkMonitorWidget : IDataWindowWidget var opCode = *(ushort*)(packet + 2); var targetName = GetTargetName(targetId); 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 bool SendPacketDetour(ZoneClient* thisPtr, nint packet, uint a3, uint a4, bool a5) { var opCode = *(ushort*)packet; 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) From 34f13b3823f4f114742f5eadd04db74418016281 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 8 Feb 2026 06:57:53 +0000 Subject: [PATCH 13/15] Update ClientStructs --- lib/FFXIVClientStructs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs index 9ba281cab..28421f946 160000 --- a/lib/FFXIVClientStructs +++ b/lib/FFXIVClientStructs @@ -1 +1 @@ -Subproject commit 9ba281cab958049b47bbf7199ab14742c609cd4b +Subproject commit 28421f946ae9ec262630f04b2b4a114b54527ff9 From 3de8c511bf3990e7efa9eb049df60e9e02cf9e79 Mon Sep 17 00:00:00 2001 From: balloon41 Date: Tue, 10 Feb 2026 13:37:58 -0600 Subject: [PATCH 14/15] Update IPlayerState.cs (#2617) Fixed type in BaseRestedExperience summary --- Dalamud/Plugin/Services/IPlayerState.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dalamud/Plugin/Services/IPlayerState.cs b/Dalamud/Plugin/Services/IPlayerState.cs index 21d88010b..838d5a346 100644 --- a/Dalamud/Plugin/Services/IPlayerState.cs +++ b/Dalamud/Plugin/Services/IPlayerState.cs @@ -159,7 +159,7 @@ public interface IPlayerState : IDalamudService RowRef FreeAetheryte { get; } /// - /// Gets the amount of received player commendations of the local player. + /// Gets the amount of rested experience available to the local player. /// uint BaseRestedExperience { get; } From 0a070970a07fd9f3c22bd03c6fd254a68cdc374f Mon Sep 17 00:00:00 2001 From: marzent Date: Wed, 11 Feb 2026 13:03:32 +0100 Subject: [PATCH 15/15] Fix troubleshooting json error on non-Windows platforms --- Dalamud/Support/Troubleshooting.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dalamud/Support/Troubleshooting.cs b/Dalamud/Support/Troubleshooting.cs index f9e084db8..779754ee8 100644 --- a/Dalamud/Support/Troubleshooting.cs +++ b/Dalamud/Support/Troubleshooting.cs @@ -90,8 +90,7 @@ public static class Troubleshooting File.WriteAllText( Path.Join( - Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), - "XIVLauncher", + startInfo.LogPath, "dalamud.troubleshooting.json"), JsonConvert.SerializeObject(payload, Formatting.Indented)); }