Merge branch '2.0.3' into dev
Some checks failed
Tag and Release Lightless / tag-and-release (push) Has been cancelled

This commit is contained in:
defnotken
2026-01-19 12:35:29 -06:00

View File

@@ -1,6 +1,5 @@
using Dalamud.Utility; using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Object;
using LightlessSync.API.Data.Enum;
using LightlessSync.FileCache; using LightlessSync.FileCache;
using LightlessSync.Interop.Ipc; using LightlessSync.Interop.Ipc;
using LightlessSync.LightlessConfiguration; using LightlessSync.LightlessConfiguration;
@@ -14,6 +13,7 @@ using Microsoft.Extensions.Logging;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using ObjectKind = LightlessSync.API.Data.Enum.ObjectKind;
namespace LightlessSync.PlayerData.Factories; namespace LightlessSync.PlayerData.Factories;
@@ -119,39 +119,48 @@ public class PlayerDataFactory
return null; return null;
} }
private static readonly int _drawObjectOffset =
(int)Marshal.OffsetOf<GameObject>(nameof(GameObject.DrawObject));
private async Task<bool> CheckForNullDrawObject(IntPtr playerPointer) private async Task<bool> CheckForNullDrawObject(IntPtr playerPointer)
=> await _dalamudUtil.RunOnFrameworkThread(() => CheckForNullDrawObjectUnsafe(playerPointer)).ConfigureAwait(false); => await _dalamudUtil.RunOnFrameworkThread(() =>
private unsafe static bool CheckForNullDrawObjectUnsafe(IntPtr playerPointer)
{ {
if (playerPointer == IntPtr.Zero) nint basePtr = playerPointer;
if (!LooksLikeUserPtr(basePtr))
return true; return true;
if (!IsPointerValid(playerPointer)) nint drawObjAddr = basePtr + _drawObjectOffset;
if (!TryReadIntPtr(drawObjAddr, out var drawObj))
return true; return true;
var character = (Character*)playerPointer; return drawObj == 0;
if (character == null) }).ConfigureAwait(false);
return true;
var gameObject = &character->GameObject; private static bool LooksLikeUserPtr(nint p)
if (gameObject == null) {
return true; if (p == 0) return false;
if (!IsPointerValid((IntPtr)gameObject)) ulong u = (ulong)p;
return true;
return gameObject->DrawObject == null; if (u < 0x0000_0001_0000UL) return false;
if (u > 0x0000_7FFF_FFFF_FFFFUL) return false;
if ((u & 0x7UL) != 0) return false;
return true;
} }
private static bool IsPointerValid(IntPtr ptr) private static bool TryReadIntPtr(nint addr, out nint value)
{ {
if (ptr == IntPtr.Zero) value = 0;
if (!VirtualReadable(addr))
return false; return false;
try try
{ {
_ = Marshal.ReadByte(ptr); value = Marshal.ReadIntPtr(addr);
return true; return true;
} }
catch catch
@@ -160,6 +169,37 @@ public class PlayerDataFactory
} }
} }
private static bool VirtualReadable(nint addr)
{
if (VirtualQuery(addr, out var mbi, (nuint)Marshal.SizeOf<MEMORY_BASIC_INFORMATION>()) == 0)
return false;
const uint MEM_COMMIT = 0x1000;
const uint PAGE_NOACCESS = 0x01;
const uint PAGE_GUARD = 0x100;
if (mbi.State != MEM_COMMIT) return false;
if ((mbi.Protect & PAGE_GUARD) != 0) return false;
if (mbi.Protect == PAGE_NOACCESS) return false;
return true;
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern nuint VirtualQuery(nint lpAddress, out MEMORY_BASIC_INFORMATION lpBuffer, nuint dwLength);
[StructLayout(LayoutKind.Sequential)]
private struct MEMORY_BASIC_INFORMATION
{
public nint BaseAddress;
public nint AllocationBase;
public uint AllocationProtect;
public nuint RegionSize;
public uint State;
public uint Protect;
public uint Type;
}
private static bool IsCacheFresh(CacheEntry entry) private static bool IsCacheFresh(CacheEntry entry)
=> (DateTime.UtcNow - entry.CreatedUtc) <= _characterCacheTtl; => (DateTime.UtcNow - entry.CreatedUtc) <= _characterCacheTtl;