Compare commits
16 Commits
2.0.2.80-D
...
2.0.2.83-D
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84a3293f6b | ||
|
|
28db3d6fd2 | ||
| d83ca98008 | |||
| e6df37bcca | |||
| 60d144b881 | |||
|
|
995e11371a | ||
| 5089dbd6c8 | |||
|
|
abc324bf4f | ||
|
|
eee0e072bd | ||
|
|
d8335eb04f | ||
|
|
994335c6b0 | ||
|
|
172288c755 | ||
|
|
1c17be53d0 | ||
| 68b4863f52 | |||
|
|
22fe9901a4 | ||
|
|
cff866dcc2 |
@@ -92,7 +92,7 @@ public sealed class PenumbraTexture : PenumbraBase
|
|||||||
{
|
{
|
||||||
token.ThrowIfCancellationRequested();
|
token.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
logger.LogInformation("Converting texture {Input} -> {Output} ({Target})", job.InputFile, job.OutputFile, job.TargetType);
|
logger.LogDebug("Converting texture {Input} -> {Output} ({Target})", job.InputFile, job.OutputFile, job.TargetType);
|
||||||
var convertTask = _convertTextureFile.Invoke(job.InputFile, job.OutputFile, job.TargetType, job.IncludeMipMaps);
|
var convertTask = _convertTextureFile.Invoke(job.InputFile, job.OutputFile, job.TargetType, job.IncludeMipMaps);
|
||||||
await convertTask.ConfigureAwait(false);
|
await convertTask.ConfigureAwait(false);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors></Authors>
|
<Authors></Authors>
|
||||||
<Company></Company>
|
<Company></Company>
|
||||||
<Version>2.0.2.80</Version>
|
<Version>2.0.2.83</Version>
|
||||||
<Description></Description>
|
<Description></Description>
|
||||||
<Copyright></Copyright>
|
<Copyright></Copyright>
|
||||||
<PackageProjectUrl>https://github.com/Light-Public-Syncshells/LightlessClient</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/Light-Public-Syncshells/LightlessClient</PackageProjectUrl>
|
||||||
|
|||||||
@@ -127,79 +127,23 @@ public class PlayerDataFactory
|
|||||||
{
|
{
|
||||||
nint basePtr = playerPointer;
|
nint basePtr = playerPointer;
|
||||||
|
|
||||||
if (!LooksLikeUserPtr(basePtr))
|
if (!PtrGuard.LooksLikePtr(basePtr))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
nint drawObjAddr = basePtr + _drawObjectOffset;
|
nint drawObjAddr = basePtr + _drawObjectOffset;
|
||||||
|
|
||||||
if (!TryReadIntPtr(drawObjAddr, out var drawObj))
|
if (!PtrGuard.IsReadable(drawObjAddr, (nuint)IntPtr.Size))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!PtrGuard.TryReadIntPtr(drawObjAddr, out var drawObj))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (drawObj != 0 && !PtrGuard.LooksLikePtr(drawObj))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
return drawObj == 0;
|
return drawObj == 0;
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
private static bool LooksLikeUserPtr(nint p)
|
|
||||||
{
|
|
||||||
if (p == 0) return false;
|
|
||||||
|
|
||||||
ulong u = (ulong)p;
|
|
||||||
|
|
||||||
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 TryReadIntPtr(nint addr, out nint value)
|
|
||||||
{
|
|
||||||
value = 0;
|
|
||||||
|
|
||||||
if (!VirtualReadable(addr))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
value = Marshal.ReadIntPtr(addr);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,12 @@
|
|||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using LightlessSync.Services;
|
using LightlessSync.Services;
|
||||||
using LightlessSync.Services.Mediator;
|
using LightlessSync.Services.Mediator;
|
||||||
|
using LightlessSync.Utils;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using static FFXIVClientStructs.FFXIV.Client.Game.Character.DrawDataContainer;
|
using static FFXIVClientStructs.FFXIV.Client.Game.Character.DrawDataContainer;
|
||||||
using VisibilityFlags = FFXIVClientStructs.FFXIV.Client.Game.Object.VisibilityFlags;
|
|
||||||
using ObjectKind = LightlessSync.API.Data.Enum.ObjectKind;
|
using ObjectKind = LightlessSync.API.Data.Enum.ObjectKind;
|
||||||
|
using VisibilityFlags = FFXIVClientStructs.FFXIV.Client.Game.Object.VisibilityFlags;
|
||||||
|
|
||||||
namespace LightlessSync.PlayerData.Handlers;
|
namespace LightlessSync.PlayerData.Handlers;
|
||||||
|
|
||||||
@@ -177,18 +178,41 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
|||||||
var prevDrawObj = DrawObjectAddress;
|
var prevDrawObj = DrawObjectAddress;
|
||||||
string? nameString = null;
|
string? nameString = null;
|
||||||
|
|
||||||
Address = _getAddress();
|
var nextAddr = _getAddress();
|
||||||
|
|
||||||
|
if (nextAddr != IntPtr.Zero && !PtrGuard.LooksLikePtr(nextAddr))
|
||||||
|
{
|
||||||
|
nextAddr = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextAddr != IntPtr.Zero &&
|
||||||
|
!PtrGuard.IsReadable(nextAddr, (nuint)sizeof(FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject)))
|
||||||
|
{
|
||||||
|
nextAddr = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
Address = nextAddr;
|
||||||
|
|
||||||
if (Address != IntPtr.Zero)
|
if (Address != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
var gameObject = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Address;
|
var gameObject = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Address;
|
||||||
DrawObjectAddress = (IntPtr)gameObject->DrawObject;
|
|
||||||
|
var draw = (nint)gameObject->DrawObject;
|
||||||
|
|
||||||
|
if (!PtrGuard.LooksLikePtr(draw) || !PtrGuard.IsReadable(draw, (nuint)sizeof(DrawObject)))
|
||||||
|
draw = 0;
|
||||||
|
|
||||||
|
DrawObjectAddress = draw;
|
||||||
EntityId = gameObject->EntityId;
|
EntityId = gameObject->EntityId;
|
||||||
|
|
||||||
var chara = (Character*)Address;
|
if (PtrGuard.IsReadable(Address, (nuint)sizeof(Character)))
|
||||||
nameString = chara->GameObject.NameString;
|
{
|
||||||
if (!string.IsNullOrEmpty(nameString) && !string.Equals(nameString, Name, StringComparison.Ordinal))
|
var chara = (Character*)Address;
|
||||||
Name = nameString;
|
nameString = chara->GameObject.NameString;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(nameString) && !string.Equals(nameString, Name, StringComparison.Ordinal))
|
||||||
|
Name = nameString;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -196,22 +220,27 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
|||||||
EntityId = uint.MaxValue;
|
EntityId = uint.MaxValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
CurrentDrawCondition = IsBeingDrawnUnsafe();
|
CurrentDrawCondition = (Address != IntPtr.Zero && DrawObjectAddress != IntPtr.Zero)
|
||||||
|
? IsBeingDrawnUnsafe()
|
||||||
|
: DrawCondition.DrawObjectZero;
|
||||||
|
|
||||||
if (_haltProcessing || !allowPublish) return;
|
if (_haltProcessing || !allowPublish) return;
|
||||||
|
|
||||||
bool drawObjDiff = DrawObjectAddress != prevDrawObj;
|
bool drawObjDiff = DrawObjectAddress != prevDrawObj;
|
||||||
bool addrDiff = Address != prevAddr;
|
bool addrDiff = Address != prevAddr;
|
||||||
|
|
||||||
if (Address != IntPtr.Zero && DrawObjectAddress != IntPtr.Zero)
|
if (Address != IntPtr.Zero && DrawObjectAddress != IntPtr.Zero
|
||||||
|
&& PtrGuard.IsReadable(Address, (nuint)sizeof(Character))
|
||||||
|
&& PtrGuard.IsReadable(DrawObjectAddress, (nuint)sizeof(DrawObject)))
|
||||||
{
|
{
|
||||||
var chara = (Character*)Address;
|
var chara = (Character*)Address;
|
||||||
var drawObj = (DrawObject*)DrawObjectAddress;
|
var drawObj = (DrawObject*)DrawObjectAddress;
|
||||||
|
|
||||||
var objType = drawObj->Object.GetObjectType();
|
var objType = drawObj->Object.GetObjectType();
|
||||||
var isHuman = objType == ObjectType.CharacterBase
|
var isHuman = objType == ObjectType.CharacterBase
|
||||||
&& ((CharacterBase*)drawObj)->GetModelType() == CharacterBase.ModelType.Human;
|
&& ((CharacterBase*)drawObj)->GetModelType() == CharacterBase.ModelType.Human;
|
||||||
|
|
||||||
nameString ??= ((Character*)Address)->GameObject.NameString;
|
nameString ??= chara->GameObject.NameString;
|
||||||
var nameChange = !string.Equals(nameString, Name, StringComparison.Ordinal);
|
var nameChange = !string.Equals(nameString, Name, StringComparison.Ordinal);
|
||||||
if (nameChange) Name = nameString;
|
if (nameChange) Name = nameString;
|
||||||
|
|
||||||
@@ -219,32 +248,36 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
|||||||
|
|
||||||
if (isHuman)
|
if (isHuman)
|
||||||
{
|
{
|
||||||
var classJob = chara->CharacterData.ClassJob;
|
if (PtrGuard.IsReadable(DrawObjectAddress, (nuint)sizeof(Human)))
|
||||||
if (classJob != _classJob)
|
|
||||||
{
|
{
|
||||||
Logger.LogTrace("[{this}] classjob changed from {old} to {new}", this, _classJob, classJob);
|
var classJob = chara->CharacterData.ClassJob;
|
||||||
_classJob = classJob;
|
if (classJob != _classJob)
|
||||||
Mediator.Publish(new ClassJobChangedMessage(this));
|
{
|
||||||
|
Logger.LogTrace("[{this}] classjob changed from {old} to {new}", this, _classJob, classJob);
|
||||||
|
_classJob = classJob;
|
||||||
|
Mediator.Publish(new ClassJobChangedMessage(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
equipDiff = CompareAndUpdateEquipByteData((byte*)&((Human*)drawObj)->Head);
|
||||||
|
|
||||||
|
ref var mh = ref chara->DrawData.Weapon(WeaponSlot.MainHand);
|
||||||
|
ref var oh = ref chara->DrawData.Weapon(WeaponSlot.OffHand);
|
||||||
|
|
||||||
|
equipDiff |= CompareAndUpdateMainHand((Weapon*)mh.DrawObject);
|
||||||
|
equipDiff |= CompareAndUpdateOffHand((Weapon*)oh.DrawObject);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
isHuman = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
equipDiff = CompareAndUpdateEquipByteData((byte*)&((Human*)drawObj)->Head);
|
|
||||||
|
|
||||||
ref var mh = ref chara->DrawData.Weapon(WeaponSlot.MainHand);
|
|
||||||
ref var oh = ref chara->DrawData.Weapon(WeaponSlot.OffHand);
|
|
||||||
equipDiff |= CompareAndUpdateMainHand((Weapon*)mh.DrawObject);
|
|
||||||
equipDiff |= CompareAndUpdateOffHand((Weapon*)oh.DrawObject);
|
|
||||||
|
|
||||||
if (equipDiff)
|
|
||||||
Logger.LogTrace("Checking [{this}] equip data as human from draw obj, result: {diff}", this, equipDiff);
|
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (!isHuman)
|
||||||
{
|
{
|
||||||
equipDiff = CompareAndUpdateEquipByteData((byte*)Unsafe.AsPointer(ref chara->DrawData.EquipmentModelIds[0]));
|
equipDiff = CompareAndUpdateEquipByteData((byte*)Unsafe.AsPointer(ref chara->DrawData.EquipmentModelIds[0]));
|
||||||
if (equipDiff)
|
|
||||||
Logger.LogTrace("Checking [{this}] equip data from game obj, result: {diff}", this, equipDiff);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (equipDiff && !_isOwnedObject) // send the message out immediately and cancel out, no reason to continue if not self
|
if (equipDiff && !_isOwnedObject)
|
||||||
{
|
{
|
||||||
Logger.LogTrace("[{this}] Changed", this);
|
Logger.LogTrace("[{this}] Changed", this);
|
||||||
return;
|
return;
|
||||||
@@ -252,11 +285,13 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
|||||||
|
|
||||||
bool customizeDiff = false;
|
bool customizeDiff = false;
|
||||||
|
|
||||||
if (isHuman)
|
if (isHuman && PtrGuard.IsReadable(DrawObjectAddress, (nuint)sizeof(Human)))
|
||||||
{
|
{
|
||||||
var gender = ((Human*)drawObj)->Customize.Sex;
|
var human = (Human*)drawObj;
|
||||||
var raceId = ((Human*)drawObj)->Customize.Race;
|
|
||||||
var tribeId = ((Human*)drawObj)->Customize.Tribe;
|
var gender = human->Customize.Sex;
|
||||||
|
var raceId = human->Customize.Race;
|
||||||
|
var tribeId = human->Customize.Tribe;
|
||||||
|
|
||||||
if (_isOwnedObject && ObjectKind == ObjectKind.Player
|
if (_isOwnedObject && ObjectKind == ObjectKind.Player
|
||||||
&& (gender != Gender || raceId != RaceId || tribeId != TribeId))
|
&& (gender != Gender || raceId != RaceId || tribeId != TribeId))
|
||||||
@@ -267,15 +302,11 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
|||||||
TribeId = tribeId;
|
TribeId = tribeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
customizeDiff = CompareAndUpdateCustomizeData(((Human*)drawObj)->Customize.Data);
|
customizeDiff = CompareAndUpdateCustomizeData(human->Customize.Data);
|
||||||
if (customizeDiff)
|
|
||||||
Logger.LogTrace("Checking [{this}] customize data as human from draw obj, result: {diff}", this, customizeDiff);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
customizeDiff = CompareAndUpdateCustomizeData(chara->DrawData.CustomizeData.Data);
|
customizeDiff = CompareAndUpdateCustomizeData(chara->DrawData.CustomizeData.Data);
|
||||||
if (customizeDiff)
|
|
||||||
Logger.LogTrace("Checking [{this}] customize data from game obj, result: {diff}", this, equipDiff);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((addrDiff || drawObjDiff || equipDiff || customizeDiff || nameChange) && _isOwnedObject)
|
if ((addrDiff || drawObjDiff || equipDiff || customizeDiff || nameChange) && _isOwnedObject)
|
||||||
@@ -289,12 +320,11 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
|||||||
CurrentDrawCondition = DrawCondition.DrawObjectZero;
|
CurrentDrawCondition = DrawCondition.DrawObjectZero;
|
||||||
Logger.LogTrace("[{this}] Changed", this);
|
Logger.LogTrace("[{this}] Changed", this);
|
||||||
if (_isOwnedObject && ObjectKind != ObjectKind.Player)
|
if (_isOwnedObject && ObjectKind != ObjectKind.Player)
|
||||||
{
|
|
||||||
Mediator.Publish(new ClearCacheForObjectMessage(this));
|
Mediator.Publish(new ClearCacheForObjectMessage(this));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private unsafe bool CompareAndUpdateCustomizeData(Span<byte> customizeData)
|
private unsafe bool CompareAndUpdateCustomizeData(Span<byte> customizeData)
|
||||||
{
|
{
|
||||||
bool hasChanges = false;
|
bool hasChanges = false;
|
||||||
@@ -330,7 +360,10 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
|||||||
|
|
||||||
private unsafe bool CompareAndUpdateMainHand(Weapon* weapon)
|
private unsafe bool CompareAndUpdateMainHand(Weapon* weapon)
|
||||||
{
|
{
|
||||||
if ((nint)weapon == nint.Zero) return false;
|
var p = (nint)weapon;
|
||||||
|
if (!PtrGuard.LooksLikePtr(p) || !PtrGuard.IsReadable(p, (nuint)sizeof(Weapon)))
|
||||||
|
return false;
|
||||||
|
|
||||||
bool hasChanges = false;
|
bool hasChanges = false;
|
||||||
hasChanges |= weapon->ModelSetId != MainHandData[0];
|
hasChanges |= weapon->ModelSetId != MainHandData[0];
|
||||||
MainHandData[0] = weapon->ModelSetId;
|
MainHandData[0] = weapon->ModelSetId;
|
||||||
@@ -343,7 +376,10 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
|||||||
|
|
||||||
private unsafe bool CompareAndUpdateOffHand(Weapon* weapon)
|
private unsafe bool CompareAndUpdateOffHand(Weapon* weapon)
|
||||||
{
|
{
|
||||||
if ((nint)weapon == nint.Zero) return false;
|
var p = (nint)weapon;
|
||||||
|
if (!PtrGuard.LooksLikePtr(p) || !PtrGuard.IsReadable(p, (nuint)sizeof(Weapon)))
|
||||||
|
return false;
|
||||||
|
|
||||||
bool hasChanges = false;
|
bool hasChanges = false;
|
||||||
hasChanges |= weapon->ModelSetId != OffHandData[0];
|
hasChanges |= weapon->ModelSetId != OffHandData[0];
|
||||||
OffHandData[0] = weapon->ModelSetId;
|
OffHandData[0] = weapon->ModelSetId;
|
||||||
|
|||||||
@@ -125,6 +125,8 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
services.AddSingleton<FileTransferOrchestrator>();
|
services.AddSingleton<FileTransferOrchestrator>();
|
||||||
services.AddSingleton<LightlessPlugin>();
|
services.AddSingleton<LightlessPlugin>();
|
||||||
services.AddSingleton<LightlessProfileManager>();
|
services.AddSingleton<LightlessProfileManager>();
|
||||||
|
services.AddSingleton<TextureProcessingQueue>();
|
||||||
|
services.AddSingleton<ModelProcessingQueue>();
|
||||||
services.AddSingleton<TextureCompressionService>();
|
services.AddSingleton<TextureCompressionService>();
|
||||||
services.AddSingleton<TextureDownscaleService>();
|
services.AddSingleton<TextureDownscaleService>();
|
||||||
services.AddSingleton<ModelDecimationService>();
|
services.AddSingleton<ModelDecimationService>();
|
||||||
|
|||||||
93
LightlessSync/Services/AssetProcessingQueue.cs
Normal file
93
LightlessSync/Services/AssetProcessingQueue.cs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
|
||||||
|
namespace LightlessSync.Services;
|
||||||
|
|
||||||
|
public sealed class AssetProcessingQueue : IDisposable
|
||||||
|
{
|
||||||
|
private readonly BlockingCollection<WorkItem> _queue = new();
|
||||||
|
private readonly Thread _worker;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
public AssetProcessingQueue(ILogger logger, string name)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_worker = new Thread(Run)
|
||||||
|
{
|
||||||
|
IsBackground = true,
|
||||||
|
Name = string.IsNullOrWhiteSpace(name) ? "LightlessSync.AssetProcessing" : name
|
||||||
|
};
|
||||||
|
_worker.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Enqueue(Func<CancellationToken, Task> work, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (work is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(work));
|
||||||
|
}
|
||||||
|
|
||||||
|
var completion = new TaskCompletionSource<object?>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
|
if (token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
completion.TrySetCanceled(token);
|
||||||
|
return completion.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_queue.IsAddingCompleted || _disposed)
|
||||||
|
{
|
||||||
|
completion.TrySetException(new ObjectDisposedException(nameof(AssetProcessingQueue)));
|
||||||
|
return completion.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
_queue.Add(new WorkItem(work, token, completion));
|
||||||
|
return completion.Task;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Run()
|
||||||
|
{
|
||||||
|
foreach (var item in _queue.GetConsumingEnumerable())
|
||||||
|
{
|
||||||
|
if (item.Token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
item.Completion.TrySetCanceled(item.Token);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
item.Work(item.Token).GetAwaiter().GetResult();
|
||||||
|
item.Completion.TrySetResult(null);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException ex)
|
||||||
|
{
|
||||||
|
var token = ex.CancellationToken.IsCancellationRequested ? ex.CancellationToken : item.Token;
|
||||||
|
item.Completion.TrySetCanceled(token);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Asset processing job failed.");
|
||||||
|
item.Completion.TrySetException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
_queue.CompleteAdding();
|
||||||
|
_worker.Join(TimeSpan.FromSeconds(2));
|
||||||
|
_queue.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly record struct WorkItem(
|
||||||
|
Func<CancellationToken, Task> Work,
|
||||||
|
CancellationToken Token,
|
||||||
|
TaskCompletionSource<object?> Completion);
|
||||||
|
}
|
||||||
@@ -2104,6 +2104,16 @@ internal static class MdlDecimator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (boneWeights != null
|
||||||
|
&& blendWeightEncoding == BlendWeightEncoding.Default
|
||||||
|
&& format.BlendWeightsElement is { } blendWeightsElement
|
||||||
|
&& (MdlFile.VertexType)blendWeightsElement.Type == MdlFile.VertexType.UShort4
|
||||||
|
&& ShouldTreatWeightsAsByteNormalized(boneWeights))
|
||||||
|
{
|
||||||
|
RescaleUShortAsByteWeights(boneWeights);
|
||||||
|
blendWeightEncoding = BlendWeightEncoding.UShortAsByte;
|
||||||
|
}
|
||||||
|
|
||||||
decoded = new DecodedMeshData(positions, normals, tangents, tangents2, colors, boneWeights, uvChannels, positionWs, normalWs, blendWeightEncoding);
|
decoded = new DecodedMeshData(positions, normals, tangents, tangents2, colors, boneWeights, uvChannels, positionWs, normalWs, blendWeightEncoding);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -3413,6 +3423,44 @@ internal static class MdlDecimator
|
|||||||
return ToUShortNormalized(normalized);
|
return ToUShortNormalized(normalized);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool ShouldTreatWeightsAsByteNormalized(BoneWeight[] weights)
|
||||||
|
{
|
||||||
|
const float maxByteUnorm = byte.MaxValue / (float)ushort.MaxValue;
|
||||||
|
var maxWeight = 0f;
|
||||||
|
for (var i = 0; i < weights.Length; i++)
|
||||||
|
{
|
||||||
|
var weight = weights[i];
|
||||||
|
maxWeight = Math.Max(maxWeight, weight.weight0);
|
||||||
|
maxWeight = Math.Max(maxWeight, weight.weight1);
|
||||||
|
maxWeight = Math.Max(maxWeight, weight.weight2);
|
||||||
|
maxWeight = Math.Max(maxWeight, weight.weight3);
|
||||||
|
if (maxWeight > maxByteUnorm)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxWeight > 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RescaleUShortAsByteWeights(BoneWeight[] weights)
|
||||||
|
{
|
||||||
|
var scale = ushort.MaxValue / (float)byte.MaxValue;
|
||||||
|
for (var i = 0; i < weights.Length; i++)
|
||||||
|
{
|
||||||
|
var weight = weights[i];
|
||||||
|
weights[i] = new BoneWeight(
|
||||||
|
weight.index0,
|
||||||
|
weight.index1,
|
||||||
|
weight.index2,
|
||||||
|
weight.index3,
|
||||||
|
weight.weight0 * scale,
|
||||||
|
weight.weight1 * scale,
|
||||||
|
weight.weight2 * scale,
|
||||||
|
weight.weight3 * scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void NormalizeWeights(float[] weights)
|
private static void NormalizeWeights(float[] weights)
|
||||||
{
|
{
|
||||||
var sum = weights.Sum();
|
var sum = weights.Sum();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using LightlessSync.FileCache;
|
using LightlessSync.FileCache;
|
||||||
using LightlessSync.LightlessConfiguration;
|
using LightlessSync.LightlessConfiguration;
|
||||||
using LightlessSync.LightlessConfiguration.Configurations;
|
using LightlessSync.LightlessConfiguration.Configurations;
|
||||||
|
using LightlessSync.Services;
|
||||||
using LightlessSync.Utils;
|
using LightlessSync.Utils;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
@@ -19,6 +20,7 @@ public sealed class ModelDecimationService
|
|||||||
private readonly FileCacheManager _fileCacheManager;
|
private readonly FileCacheManager _fileCacheManager;
|
||||||
private readonly PlayerPerformanceConfigService _performanceConfigService;
|
private readonly PlayerPerformanceConfigService _performanceConfigService;
|
||||||
private readonly XivDataStorageService _xivDataStorageService;
|
private readonly XivDataStorageService _xivDataStorageService;
|
||||||
|
private readonly ModelProcessingQueue _processingQueue;
|
||||||
private readonly SemaphoreSlim _decimationSemaphore = new(MaxConcurrentJobs);
|
private readonly SemaphoreSlim _decimationSemaphore = new(MaxConcurrentJobs);
|
||||||
|
|
||||||
private readonly TaskRegistry<string> _decimationDeduplicator = new();
|
private readonly TaskRegistry<string> _decimationDeduplicator = new();
|
||||||
@@ -30,13 +32,15 @@ public sealed class ModelDecimationService
|
|||||||
LightlessConfigService configService,
|
LightlessConfigService configService,
|
||||||
FileCacheManager fileCacheManager,
|
FileCacheManager fileCacheManager,
|
||||||
PlayerPerformanceConfigService performanceConfigService,
|
PlayerPerformanceConfigService performanceConfigService,
|
||||||
XivDataStorageService xivDataStorageService)
|
XivDataStorageService xivDataStorageService,
|
||||||
|
ModelProcessingQueue processingQueue)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
_fileCacheManager = fileCacheManager;
|
_fileCacheManager = fileCacheManager;
|
||||||
_performanceConfigService = performanceConfigService;
|
_performanceConfigService = performanceConfigService;
|
||||||
_xivDataStorageService = xivDataStorageService;
|
_xivDataStorageService = xivDataStorageService;
|
||||||
|
_processingQueue = processingQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ScheduleDecimation(string hash, string filePath, string? gamePath = null)
|
public void ScheduleDecimation(string hash, string filePath, string? gamePath = null)
|
||||||
@@ -53,9 +57,9 @@ public sealed class ModelDecimationService
|
|||||||
|
|
||||||
_logger.LogDebug("Queued model decimation for {Hash}", hash);
|
_logger.LogDebug("Queued model decimation for {Hash}", hash);
|
||||||
|
|
||||||
_decimationDeduplicator.GetOrStart(hash, async () =>
|
_decimationDeduplicator.GetOrStart(hash, () => _processingQueue.Enqueue(async token =>
|
||||||
{
|
{
|
||||||
await _decimationSemaphore.WaitAsync().ConfigureAwait(false);
|
await _decimationSemaphore.WaitAsync(token).ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await DecimateInternalAsync(hash, filePath).ConfigureAwait(false);
|
await DecimateInternalAsync(hash, filePath).ConfigureAwait(false);
|
||||||
@@ -69,7 +73,7 @@ public sealed class ModelDecimationService
|
|||||||
{
|
{
|
||||||
_decimationSemaphore.Release();
|
_decimationSemaphore.Release();
|
||||||
}
|
}
|
||||||
});
|
}, CancellationToken.None));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ScheduleBatchDecimation(string hash, string filePath, ModelDecimationSettings settings)
|
public void ScheduleBatchDecimation(string hash, string filePath, ModelDecimationSettings settings)
|
||||||
@@ -89,9 +93,9 @@ public sealed class ModelDecimationService
|
|||||||
|
|
||||||
_logger.LogInformation("Queued batch model decimation for {Hash}", hash);
|
_logger.LogInformation("Queued batch model decimation for {Hash}", hash);
|
||||||
|
|
||||||
_decimationDeduplicator.GetOrStart(hash, async () =>
|
_decimationDeduplicator.GetOrStart(hash, () => _processingQueue.Enqueue(async token =>
|
||||||
{
|
{
|
||||||
await _decimationSemaphore.WaitAsync().ConfigureAwait(false);
|
await _decimationSemaphore.WaitAsync(token).ConfigureAwait(false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await DecimateInternalAsync(hash, filePath, settings, allowExisting: false, destinationOverride: filePath, registerDecimatedPath: false).ConfigureAwait(false);
|
await DecimateInternalAsync(hash, filePath, settings, allowExisting: false, destinationOverride: filePath, registerDecimatedPath: false).ConfigureAwait(false);
|
||||||
@@ -105,7 +109,7 @@ public sealed class ModelDecimationService
|
|||||||
{
|
{
|
||||||
_decimationSemaphore.Release();
|
_decimationSemaphore.Release();
|
||||||
}
|
}
|
||||||
});
|
}, CancellationToken.None));
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ShouldScheduleDecimation(string hash, string filePath, string? gamePath = null)
|
public bool ShouldScheduleDecimation(string hash, string filePath, string? gamePath = null)
|
||||||
|
|||||||
19
LightlessSync/Services/ModelProcessingQueue.cs
Normal file
19
LightlessSync/Services/ModelProcessingQueue.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace LightlessSync.Services;
|
||||||
|
|
||||||
|
public sealed class ModelProcessingQueue : IDisposable
|
||||||
|
{
|
||||||
|
private readonly AssetProcessingQueue _queue;
|
||||||
|
|
||||||
|
public ModelProcessingQueue(ILogger<ModelProcessingQueue> logger)
|
||||||
|
{
|
||||||
|
_queue = new AssetProcessingQueue(logger, "LightlessSync.ModelProcessing");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Enqueue(Func<CancellationToken, Task> work, CancellationToken token = default)
|
||||||
|
=> _queue.Enqueue(work, token);
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
=> _queue.Dispose();
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ using System.Threading;
|
|||||||
using OtterTex;
|
using OtterTex;
|
||||||
using OtterImage = OtterTex.Image;
|
using OtterImage = OtterTex.Image;
|
||||||
using LightlessSync.LightlessConfiguration;
|
using LightlessSync.LightlessConfiguration;
|
||||||
|
using LightlessSync.Services;
|
||||||
using LightlessSync.Utils;
|
using LightlessSync.Utils;
|
||||||
using LightlessSync.FileCache;
|
using LightlessSync.FileCache;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -33,6 +34,7 @@ public sealed class TextureDownscaleService
|
|||||||
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
|
private readonly PlayerPerformanceConfigService _playerPerformanceConfigService;
|
||||||
private readonly FileCacheManager _fileCacheManager;
|
private readonly FileCacheManager _fileCacheManager;
|
||||||
private readonly TextureCompressionService _textureCompressionService;
|
private readonly TextureCompressionService _textureCompressionService;
|
||||||
|
private readonly TextureProcessingQueue _processingQueue;
|
||||||
|
|
||||||
private readonly TaskRegistry<string> _downscaleDeduplicator = new();
|
private readonly TaskRegistry<string> _downscaleDeduplicator = new();
|
||||||
private readonly ConcurrentDictionary<string, string> _downscaledPaths = new(StringComparer.OrdinalIgnoreCase);
|
private readonly ConcurrentDictionary<string, string> _downscaledPaths = new(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -73,13 +75,15 @@ public sealed class TextureDownscaleService
|
|||||||
LightlessConfigService configService,
|
LightlessConfigService configService,
|
||||||
PlayerPerformanceConfigService playerPerformanceConfigService,
|
PlayerPerformanceConfigService playerPerformanceConfigService,
|
||||||
FileCacheManager fileCacheManager,
|
FileCacheManager fileCacheManager,
|
||||||
TextureCompressionService textureCompressionService)
|
TextureCompressionService textureCompressionService,
|
||||||
|
TextureProcessingQueue processingQueue)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_configService = configService;
|
_configService = configService;
|
||||||
_playerPerformanceConfigService = playerPerformanceConfigService;
|
_playerPerformanceConfigService = playerPerformanceConfigService;
|
||||||
_fileCacheManager = fileCacheManager;
|
_fileCacheManager = fileCacheManager;
|
||||||
_textureCompressionService = textureCompressionService;
|
_textureCompressionService = textureCompressionService;
|
||||||
|
_processingQueue = processingQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ScheduleDownscale(string hash, string filePath, TextureMapKind mapKind)
|
public void ScheduleDownscale(string hash, string filePath, TextureMapKind mapKind)
|
||||||
@@ -90,7 +94,7 @@ public sealed class TextureDownscaleService
|
|||||||
if (!filePath.EndsWith(".tex", StringComparison.OrdinalIgnoreCase)) return;
|
if (!filePath.EndsWith(".tex", StringComparison.OrdinalIgnoreCase)) return;
|
||||||
if (_downscaleDeduplicator.TryGetExisting(hash, out _)) return;
|
if (_downscaleDeduplicator.TryGetExisting(hash, out _)) return;
|
||||||
|
|
||||||
_downscaleDeduplicator.GetOrStart(hash, async () =>
|
_downscaleDeduplicator.GetOrStart(hash, () => _processingQueue.Enqueue(async token =>
|
||||||
{
|
{
|
||||||
TextureMapKind mapKind;
|
TextureMapKind mapKind;
|
||||||
try
|
try
|
||||||
@@ -104,7 +108,7 @@ public sealed class TextureDownscaleService
|
|||||||
}
|
}
|
||||||
|
|
||||||
await DownscaleInternalAsync(hash, filePath, mapKind).ConfigureAwait(false);
|
await DownscaleInternalAsync(hash, filePath, mapKind).ConfigureAwait(false);
|
||||||
});
|
}, CancellationToken.None));
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ShouldScheduleDownscale(string filePath)
|
public bool ShouldScheduleDownscale(string filePath)
|
||||||
@@ -382,6 +386,12 @@ public sealed class TextureDownscaleService
|
|||||||
{
|
{
|
||||||
var isCompressed = sourceFormat.IsCompressed();
|
var isCompressed = sourceFormat.IsCompressed();
|
||||||
var targetFormat = isCompressed ? sourceFormat : DXGIFormat.B8G8R8A8UNorm;
|
var targetFormat = isCompressed ? sourceFormat : DXGIFormat.B8G8R8A8UNorm;
|
||||||
|
_logger.LogDebug(
|
||||||
|
"Downscale convert target {TargetFormat} (source {SourceFormat}, compressed {IsCompressed}, penumbraFallback {PenumbraFallback})",
|
||||||
|
targetFormat,
|
||||||
|
sourceFormat,
|
||||||
|
isCompressed,
|
||||||
|
attemptPenumbraFallback);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
result = source.Convert(targetFormat);
|
result = source.Convert(targetFormat);
|
||||||
@@ -433,6 +443,7 @@ public sealed class TextureDownscaleService
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_logger.LogDebug("Downscale Penumbra re-encode target {Target} for {Hash}.", target, hash);
|
||||||
using var uncompressed = resizedScratch.Convert(DXGIFormat.B8G8R8A8UNorm);
|
using var uncompressed = resizedScratch.Convert(DXGIFormat.B8G8R8A8UNorm);
|
||||||
TexFileHelper.Save(destination, uncompressed);
|
TexFileHelper.Save(destination, uncompressed);
|
||||||
}
|
}
|
||||||
|
|||||||
19
LightlessSync/Services/TextureProcessingQueue.cs
Normal file
19
LightlessSync/Services/TextureProcessingQueue.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace LightlessSync.Services;
|
||||||
|
|
||||||
|
public sealed class TextureProcessingQueue : IDisposable
|
||||||
|
{
|
||||||
|
private readonly AssetProcessingQueue _queue;
|
||||||
|
|
||||||
|
public TextureProcessingQueue(ILogger<TextureProcessingQueue> logger)
|
||||||
|
{
|
||||||
|
_queue = new AssetProcessingQueue(logger, "LightlessSync.TextureProcessing");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task Enqueue(Func<CancellationToken, Task> work, CancellationToken token = default)
|
||||||
|
=> _queue.Enqueue(work, token);
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
=> _queue.Dispose();
|
||||||
|
}
|
||||||
@@ -56,6 +56,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
private readonly ModelDecimationService _modelDecimationService;
|
private readonly ModelDecimationService _modelDecimationService;
|
||||||
private readonly TextureCompressionService _textureCompressionService;
|
private readonly TextureCompressionService _textureCompressionService;
|
||||||
private readonly TextureMetadataHelper _textureMetadataHelper;
|
private readonly TextureMetadataHelper _textureMetadataHelper;
|
||||||
|
private readonly TextureProcessingQueue _processingQueue;
|
||||||
|
|
||||||
private readonly List<TextureRow> _textureRows = new();
|
private readonly List<TextureRow> _textureRows = new();
|
||||||
private readonly Dictionary<string, TextureCompressionTarget> _textureSelections = new(StringComparer.OrdinalIgnoreCase);
|
private readonly Dictionary<string, TextureCompressionTarget> _textureSelections = new(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -137,7 +138,8 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
LightlessConfigService configService,
|
LightlessConfigService configService,
|
||||||
PlayerPerformanceConfigService playerPerformanceConfig, TransientResourceManager transientResourceManager,
|
PlayerPerformanceConfigService playerPerformanceConfig, TransientResourceManager transientResourceManager,
|
||||||
TransientConfigService transientConfigService, ModelDecimationService modelDecimationService,
|
TransientConfigService transientConfigService, ModelDecimationService modelDecimationService,
|
||||||
TextureCompressionService textureCompressionService, TextureMetadataHelper textureMetadataHelper)
|
TextureCompressionService textureCompressionService, TextureMetadataHelper textureMetadataHelper,
|
||||||
|
TextureProcessingQueue processingQueue)
|
||||||
: base(logger, mediator, "Lightless Character Data Analysis", performanceCollectorService)
|
: base(logger, mediator, "Lightless Character Data Analysis", performanceCollectorService)
|
||||||
{
|
{
|
||||||
_characterAnalyzer = characterAnalyzer;
|
_characterAnalyzer = characterAnalyzer;
|
||||||
@@ -150,6 +152,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
_modelDecimationService = modelDecimationService;
|
_modelDecimationService = modelDecimationService;
|
||||||
_textureCompressionService = textureCompressionService;
|
_textureCompressionService = textureCompressionService;
|
||||||
_textureMetadataHelper = textureMetadataHelper;
|
_textureMetadataHelper = textureMetadataHelper;
|
||||||
|
_processingQueue = processingQueue;
|
||||||
Mediator.Subscribe<CharacterDataAnalyzedMessage>(this, (_) =>
|
Mediator.Subscribe<CharacterDataAnalyzedMessage>(this, (_) =>
|
||||||
{
|
{
|
||||||
_hasUpdate = true;
|
_hasUpdate = true;
|
||||||
@@ -3716,7 +3719,10 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
|||||||
_conversionCurrentFileProgress = 0;
|
_conversionCurrentFileProgress = 0;
|
||||||
_conversionFailed = false;
|
_conversionFailed = false;
|
||||||
|
|
||||||
_conversionTask = RunTextureConversionAsync(requests, _conversionCancellationTokenSource.Token);
|
var conversionToken = _conversionCancellationTokenSource.Token;
|
||||||
|
_conversionTask = _processingQueue.Enqueue(
|
||||||
|
queueToken => RunTextureConversionAsync(requests, queueToken),
|
||||||
|
conversionToken);
|
||||||
_showModal = true;
|
_showModal = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
71
LightlessSync/Utils/PtrGuard.cs
Normal file
71
LightlessSync/Utils/PtrGuard.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using static LightlessSync.Utils.PtrGuardMemory;
|
||||||
|
|
||||||
|
namespace LightlessSync.Utils
|
||||||
|
{
|
||||||
|
public static partial class PtrGuard
|
||||||
|
{
|
||||||
|
private const ulong _aligmentPtr = 0x7UL;
|
||||||
|
private static readonly nuint _minAppAddr = (nuint)GetMinAppAddr();
|
||||||
|
private static readonly nuint _maxAppAddr = (nuint)GetMaxAppAddr();
|
||||||
|
|
||||||
|
private static nint GetMinAppAddr()
|
||||||
|
{
|
||||||
|
GetSystemInfo(out var si);
|
||||||
|
return si.lpMinimumApplicationAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static nint GetMaxAppAddr()
|
||||||
|
{
|
||||||
|
GetSystemInfo(out var si);
|
||||||
|
return si.lpMaximumApplicationAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool LooksLikePtr(nint p)
|
||||||
|
{
|
||||||
|
if (p == 0) return false;
|
||||||
|
nuint u = (nuint)p;
|
||||||
|
|
||||||
|
if (u < _minAppAddr) return false;
|
||||||
|
if (u > _maxAppAddr) return false;
|
||||||
|
if ((u & _aligmentPtr) != 0) return false;
|
||||||
|
if ((uint)u == 0x12345679u) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryReadIntPtr(nint addr, out nint value)
|
||||||
|
{
|
||||||
|
value = 0;
|
||||||
|
|
||||||
|
if (!LooksLikePtr(addr))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return ReadProcessMemory(GetCurrentProcess(), addr, out value, (nuint)IntPtr.Size, out nuint bytesRead)
|
||||||
|
&& bytesRead == (nuint)IntPtr.Size;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsReadable(nint addr, nuint size)
|
||||||
|
{
|
||||||
|
if (addr == 0 || size == 0) return false;
|
||||||
|
|
||||||
|
if (VirtualQuery(addr, out var mbi, (nuint)Marshal.SizeOf<MEMORY_BASIC_INFORMATION>()) == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const uint Commit = 0x1000;
|
||||||
|
const uint NoAccess = 0x01;
|
||||||
|
const uint PageGuard = 0x100;
|
||||||
|
|
||||||
|
if (mbi.State != Commit) return false;
|
||||||
|
if ((mbi.Protect & PageGuard) != 0) return false;
|
||||||
|
if (mbi.Protect == NoAccess) return false;
|
||||||
|
|
||||||
|
ulong start = (ulong)addr;
|
||||||
|
ulong end = start + size - 1;
|
||||||
|
ulong r0 = (ulong)mbi.BaseAddress;
|
||||||
|
ulong r1 = r0 + mbi.RegionSize - 1;
|
||||||
|
|
||||||
|
return start >= r0 && end <= r1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
LightlessSync/Utils/PtrGuardMemory.cs
Normal file
55
LightlessSync/Utils/PtrGuardMemory.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace LightlessSync.Utils
|
||||||
|
{
|
||||||
|
internal static class PtrGuardMemory
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
internal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
internal static extern nuint VirtualQuery(
|
||||||
|
nint lpAddress,
|
||||||
|
out MEMORY_BASIC_INFORMATION lpBuffer,
|
||||||
|
nuint dwLength);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
internal static extern bool ReadProcessMemory(
|
||||||
|
nint hProcess,
|
||||||
|
nint lpBaseAddress,
|
||||||
|
out nint lpBuffer,
|
||||||
|
nuint nSize,
|
||||||
|
out nuint lpNumberOfBytesRead);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
internal static extern nint GetCurrentProcess();
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
internal static extern void GetSystemInfo(out SYSTEM_INFO lpSystemInfo);
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
internal struct SYSTEM_INFO
|
||||||
|
{
|
||||||
|
public ushort wProcessorArchitecture;
|
||||||
|
public ushort wReserved;
|
||||||
|
public uint dwPageSize;
|
||||||
|
public nint lpMinimumApplicationAddress;
|
||||||
|
public nint lpMaximumApplicationAddress;
|
||||||
|
public nint dwActiveProcessorMask;
|
||||||
|
public uint dwNumberOfProcessors;
|
||||||
|
public uint dwProcessorType;
|
||||||
|
public uint dwAllocationGranularity;
|
||||||
|
public ushort wProcessorLevel;
|
||||||
|
public ushort wProcessorRevision;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user