Compare commits
35 Commits
update-dec
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abc324bf4f | ||
|
|
eee0e072bd | ||
|
|
d8335eb04f | ||
|
|
994335c6b0 | ||
|
|
172288c755 | ||
|
|
1c17be53d0 | ||
| 68b4863f52 | |||
|
|
22fe9901a4 | ||
|
|
cff866dcc2 | ||
|
|
e8f598e695 | ||
|
|
861a337029 | ||
| 06f89955d3 | |||
|
|
367af2c3d0 | ||
|
|
19a238c808 | ||
|
|
c7a2b679f2 | ||
|
|
bec69074a5 | ||
| 7d86b41cee | |||
| 0185e6b534 | |||
|
|
90bf84f8eb | ||
|
|
ac711d9a43 | ||
|
|
b875e0c3a1 | ||
|
|
46e76bbfe6 | ||
|
|
3654365f2a | ||
|
|
9b256dd185 | ||
|
|
223ade39cb | ||
|
|
5aca9e70b2 | ||
|
|
92772cf334 | ||
|
|
0395e81a9f | ||
|
|
7734a7bf7e | ||
|
|
db2d19bb1e | ||
|
|
ab305a249c | ||
|
|
9d104a9dd8 | ||
|
|
bcd3bd5ca2 | ||
|
|
c1829a9837 | ||
|
|
cca23f6e05 |
@@ -0,0 +1,51 @@
|
||||
namespace Lifestream.Enums;
|
||||
|
||||
public enum TerritoryTypeIdHousing
|
||||
{
|
||||
None = -1,
|
||||
|
||||
// Mist (Limsa Lominsa)
|
||||
Mist = 339,
|
||||
MistSmall = 282,
|
||||
MistMedium = 283,
|
||||
MistLarge = 284,
|
||||
MistFCRoom = 384,
|
||||
MistFCWorkshop = 423,
|
||||
MistApartment = 608,
|
||||
|
||||
// Lavender Beds (Gridania)
|
||||
Lavender = 340,
|
||||
LavenderSmall = 342,
|
||||
LavenderMedium = 343,
|
||||
LavenderLarge = 344,
|
||||
LavenderFCRoom = 385,
|
||||
LavenderFCWorkshop = 425,
|
||||
LavenderApartment = 609,
|
||||
|
||||
// Goblet (Ul'dah)
|
||||
Goblet = 341,
|
||||
GobletSmall = 345,
|
||||
GobletMedium = 346,
|
||||
GobletLarge = 347,
|
||||
GobletFCRoom = 386,
|
||||
GobletFCWorkshop = 424,
|
||||
GobletApartment = 610,
|
||||
|
||||
// Shirogane (Kugane)
|
||||
Shirogane = 641,
|
||||
ShiroganeSmall = 649,
|
||||
ShiroganeMedium = 650,
|
||||
ShiroganeLarge = 651,
|
||||
ShiroganeFCRoom = 652,
|
||||
ShiroganeFCWorkshop = 653,
|
||||
ShiroganeApartment = 655,
|
||||
|
||||
// Empyreum (Ishgard)
|
||||
Empyream = 979,
|
||||
EmpyreamSmall = 980,
|
||||
EmpyreamMedium = 981,
|
||||
EmpyreamLarge = 982,
|
||||
EmpyreamFCRoom = 983,
|
||||
EmpyreamFCWorkshop = 984,
|
||||
EmpyreamApartment = 999,
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<Authors></Authors>
|
||||
<Company></Company>
|
||||
<Version>2.0.3</Version>
|
||||
<Version>2.0.2.82</Version>
|
||||
<Description></Description>
|
||||
<Copyright></Copyright>
|
||||
<PackageProjectUrl>https://github.com/Light-Public-Syncshells/LightlessClient</PackageProjectUrl>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Dalamud.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using LightlessSync.API.Data.Enum;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using LightlessSync.FileCache;
|
||||
using LightlessSync.Interop.Ipc;
|
||||
using LightlessSync.LightlessConfiguration;
|
||||
@@ -14,6 +13,7 @@ using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using ObjectKind = LightlessSync.API.Data.Enum.ObjectKind;
|
||||
|
||||
namespace LightlessSync.PlayerData.Factories;
|
||||
|
||||
@@ -119,46 +119,30 @@ public class PlayerDataFactory
|
||||
return null;
|
||||
}
|
||||
|
||||
private static readonly int _drawObjectOffset =
|
||||
(int)Marshal.OffsetOf<GameObject>(nameof(GameObject.DrawObject));
|
||||
|
||||
private async Task<bool> CheckForNullDrawObject(IntPtr playerPointer)
|
||||
=> await _dalamudUtil.RunOnFrameworkThread(() => CheckForNullDrawObjectUnsafe(playerPointer)).ConfigureAwait(false);
|
||||
|
||||
private unsafe static bool CheckForNullDrawObjectUnsafe(IntPtr playerPointer)
|
||||
{
|
||||
if (playerPointer == IntPtr.Zero)
|
||||
return true;
|
||||
|
||||
if (!IsPointerValid(playerPointer))
|
||||
return true;
|
||||
|
||||
var character = (Character*)playerPointer;
|
||||
if (character == null)
|
||||
return true;
|
||||
|
||||
var gameObject = &character->GameObject;
|
||||
if (gameObject == null)
|
||||
return true;
|
||||
|
||||
if (!IsPointerValid((IntPtr)gameObject))
|
||||
return true;
|
||||
|
||||
return gameObject->DrawObject == null;
|
||||
}
|
||||
|
||||
private static bool IsPointerValid(IntPtr ptr)
|
||||
{
|
||||
if (ptr == IntPtr.Zero)
|
||||
return false;
|
||||
|
||||
try
|
||||
=> await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
_ = Marshal.ReadByte(ptr);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
nint basePtr = playerPointer;
|
||||
|
||||
if (!PtrGuard.LooksLikePtr(basePtr))
|
||||
return true;
|
||||
|
||||
nint drawObjAddr = basePtr + _drawObjectOffset;
|
||||
|
||||
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 drawObj == 0;
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
private static bool IsCacheFresh(CacheEntry entry)
|
||||
=> (DateTime.UtcNow - entry.CreatedUtc) <= _characterCacheTtl;
|
||||
|
||||
@@ -2,11 +2,12 @@
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using LightlessSync.Services;
|
||||
using LightlessSync.Services.Mediator;
|
||||
using LightlessSync.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Runtime.CompilerServices;
|
||||
using static FFXIVClientStructs.FFXIV.Client.Game.Character.DrawDataContainer;
|
||||
using VisibilityFlags = FFXIVClientStructs.FFXIV.Client.Game.Object.VisibilityFlags;
|
||||
using ObjectKind = LightlessSync.API.Data.Enum.ObjectKind;
|
||||
using VisibilityFlags = FFXIVClientStructs.FFXIV.Client.Game.Object.VisibilityFlags;
|
||||
|
||||
namespace LightlessSync.PlayerData.Handlers;
|
||||
|
||||
@@ -177,18 +178,41 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
||||
var prevDrawObj = DrawObjectAddress;
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
||||
var chara = (Character*)Address;
|
||||
nameString = chara->GameObject.NameString;
|
||||
if (!string.IsNullOrEmpty(nameString) && !string.Equals(nameString, Name, StringComparison.Ordinal))
|
||||
Name = nameString;
|
||||
if (PtrGuard.IsReadable(Address, (nuint)sizeof(Character)))
|
||||
{
|
||||
var chara = (Character*)Address;
|
||||
nameString = chara->GameObject.NameString;
|
||||
|
||||
if (!string.IsNullOrEmpty(nameString) && !string.Equals(nameString, Name, StringComparison.Ordinal))
|
||||
Name = nameString;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -196,22 +220,27 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
||||
EntityId = uint.MaxValue;
|
||||
}
|
||||
|
||||
CurrentDrawCondition = IsBeingDrawnUnsafe();
|
||||
CurrentDrawCondition = (Address != IntPtr.Zero && DrawObjectAddress != IntPtr.Zero)
|
||||
? IsBeingDrawnUnsafe()
|
||||
: DrawCondition.DrawObjectZero;
|
||||
|
||||
if (_haltProcessing || !allowPublish) return;
|
||||
|
||||
bool drawObjDiff = DrawObjectAddress != prevDrawObj;
|
||||
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 drawObj = (DrawObject*)DrawObjectAddress;
|
||||
|
||||
var objType = drawObj->Object.GetObjectType();
|
||||
var isHuman = objType == ObjectType.CharacterBase
|
||||
&& ((CharacterBase*)drawObj)->GetModelType() == CharacterBase.ModelType.Human;
|
||||
|
||||
nameString ??= ((Character*)Address)->GameObject.NameString;
|
||||
nameString ??= chara->GameObject.NameString;
|
||||
var nameChange = !string.Equals(nameString, Name, StringComparison.Ordinal);
|
||||
if (nameChange) Name = nameString;
|
||||
|
||||
@@ -219,32 +248,36 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
||||
|
||||
if (isHuman)
|
||||
{
|
||||
var classJob = chara->CharacterData.ClassJob;
|
||||
if (classJob != _classJob)
|
||||
if (PtrGuard.IsReadable(DrawObjectAddress, (nuint)sizeof(Human)))
|
||||
{
|
||||
Logger.LogTrace("[{this}] classjob changed from {old} to {new}", this, _classJob, classJob);
|
||||
_classJob = classJob;
|
||||
Mediator.Publish(new ClassJobChangedMessage(this));
|
||||
var classJob = chara->CharacterData.ClassJob;
|
||||
if (classJob != _classJob)
|
||||
{
|
||||
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]));
|
||||
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);
|
||||
return;
|
||||
@@ -252,11 +285,13 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
||||
|
||||
bool customizeDiff = false;
|
||||
|
||||
if (isHuman)
|
||||
if (isHuman && PtrGuard.IsReadable(DrawObjectAddress, (nuint)sizeof(Human)))
|
||||
{
|
||||
var gender = ((Human*)drawObj)->Customize.Sex;
|
||||
var raceId = ((Human*)drawObj)->Customize.Race;
|
||||
var tribeId = ((Human*)drawObj)->Customize.Tribe;
|
||||
var human = (Human*)drawObj;
|
||||
|
||||
var gender = human->Customize.Sex;
|
||||
var raceId = human->Customize.Race;
|
||||
var tribeId = human->Customize.Tribe;
|
||||
|
||||
if (_isOwnedObject && ObjectKind == ObjectKind.Player
|
||||
&& (gender != Gender || raceId != RaceId || tribeId != TribeId))
|
||||
@@ -267,15 +302,11 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
||||
TribeId = tribeId;
|
||||
}
|
||||
|
||||
customizeDiff = CompareAndUpdateCustomizeData(((Human*)drawObj)->Customize.Data);
|
||||
if (customizeDiff)
|
||||
Logger.LogTrace("Checking [{this}] customize data as human from draw obj, result: {diff}", this, customizeDiff);
|
||||
customizeDiff = CompareAndUpdateCustomizeData(human->Customize.Data);
|
||||
}
|
||||
else
|
||||
{
|
||||
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)
|
||||
@@ -289,12 +320,11 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
||||
CurrentDrawCondition = DrawCondition.DrawObjectZero;
|
||||
Logger.LogTrace("[{this}] Changed", this);
|
||||
if (_isOwnedObject && ObjectKind != ObjectKind.Player)
|
||||
{
|
||||
Mediator.Publish(new ClearCacheForObjectMessage(this));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private unsafe bool CompareAndUpdateCustomizeData(Span<byte> customizeData)
|
||||
{
|
||||
bool hasChanges = false;
|
||||
@@ -330,7 +360,10 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
||||
|
||||
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;
|
||||
hasChanges |= weapon->ModelSetId != MainHandData[0];
|
||||
MainHandData[0] = weapon->ModelSetId;
|
||||
@@ -343,7 +376,10 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
||||
|
||||
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;
|
||||
hasChanges |= weapon->ModelSetId != OffHandData[0];
|
||||
OffHandData[0] = weapon->ModelSetId;
|
||||
|
||||
@@ -701,7 +701,23 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
||||
str += $" Room #{location.RoomId}";
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
public string LocationToLifestream(LocationInfo location)
|
||||
{
|
||||
if (location.ServerId is 0 || location.TerritoryId is 0 || ContentFinderData.Value.ContainsKey(location.TerritoryId)) return String.Empty;
|
||||
var str = WorldData.Value[(ushort)location.ServerId];
|
||||
if (location.HouseId is 0 && location.MapId is not 0)
|
||||
{
|
||||
var mapName = MapData.Value[(ushort)location.MapId].MapName;
|
||||
var parts = mapName.Split(" - ", StringSplitOptions.RemoveEmptyEntries);
|
||||
var locationName = parts.Length > 0 ? parts[^1] : mapName;
|
||||
str += $", tp {locationName}";
|
||||
string message = $"LocationToLifestream: {str}";
|
||||
_logger.LogInformation(message);
|
||||
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Lifestream.Enums;
|
||||
using LightlessSync.API.Data;
|
||||
using LightlessSync.API.Dto.CharaData;
|
||||
using LightlessSync.API.Dto.User;
|
||||
@@ -108,6 +109,144 @@ namespace LightlessSync.Services
|
||||
}
|
||||
}
|
||||
|
||||
public LocationInfo? GetLocationForLifestreamByUid(string uid)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_locations.TryGetValue<LocationInfo>(uid, out var location))
|
||||
{
|
||||
return location;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError(e,"GetLocationInfoByUid error : ");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public AddressBookEntryTuple? GetAddressBookEntryByLocation(LocationInfo location)
|
||||
{
|
||||
if (location.ServerId is 0 || location.TerritoryId is 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var territoryHousing = (TerritoryTypeIdHousing)location.TerritoryId;
|
||||
|
||||
if (territoryHousing == TerritoryTypeIdHousing.None || !Enum.IsDefined(typeof(TerritoryTypeIdHousing), territoryHousing))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var city = GetResidentialAetheryteKind(territoryHousing);
|
||||
|
||||
if (city == ResidentialAetheryteKind.None)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (location.HouseId is not 0 and not 100)
|
||||
{
|
||||
AddressBookEntryTuple addressEntry = (
|
||||
Name: "",
|
||||
World: (int)location.ServerId,
|
||||
City: (int)city,
|
||||
Ward: (int)location.WardId,
|
||||
PropertyType: 0,
|
||||
Plot: (int)location.HouseId,
|
||||
Apartment: 0,
|
||||
ApartmentSubdivision: location.DivisionId == 2,
|
||||
AliasEnabled: false,
|
||||
Alias: ""
|
||||
);
|
||||
return addressEntry;
|
||||
}
|
||||
else if (location.HouseId is 100)
|
||||
{
|
||||
AddressBookEntryTuple addressEntry = (
|
||||
Name: "",
|
||||
World: (int)location.ServerId,
|
||||
City: (int)city,
|
||||
Ward: (int)location.WardId,
|
||||
PropertyType: 1,
|
||||
Plot: 0,
|
||||
Apartment: (int)location.RoomId,
|
||||
ApartmentSubdivision: location.DivisionId == 2,
|
||||
AliasEnabled: false,
|
||||
Alias: ""
|
||||
);
|
||||
return addressEntry;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private ResidentialAetheryteKind GetResidentialAetheryteKind(TerritoryTypeIdHousing territoryHousing)
|
||||
{
|
||||
return territoryHousing switch
|
||||
{
|
||||
TerritoryTypeIdHousing.Shirogane or
|
||||
TerritoryTypeIdHousing.ShiroganeApartment or
|
||||
TerritoryTypeIdHousing.ShiroganeSmall or
|
||||
TerritoryTypeIdHousing.ShiroganeMedium or
|
||||
TerritoryTypeIdHousing.ShiroganeLarge or
|
||||
TerritoryTypeIdHousing.ShiroganeFCRoom or
|
||||
TerritoryTypeIdHousing.ShiroganeFCWorkshop
|
||||
=> ResidentialAetheryteKind.Kugane,
|
||||
|
||||
TerritoryTypeIdHousing.Lavender or
|
||||
TerritoryTypeIdHousing.LavenderSmall or
|
||||
TerritoryTypeIdHousing.LavenderMedium or
|
||||
TerritoryTypeIdHousing.LavenderLarge or
|
||||
TerritoryTypeIdHousing.LavenderApartment or
|
||||
TerritoryTypeIdHousing.LavenderFCRoom or
|
||||
TerritoryTypeIdHousing.LavenderFCWorkshop
|
||||
=> ResidentialAetheryteKind.Gridania,
|
||||
|
||||
TerritoryTypeIdHousing.Mist or
|
||||
TerritoryTypeIdHousing.MistSmall or
|
||||
TerritoryTypeIdHousing.MistMedium or
|
||||
TerritoryTypeIdHousing.MistLarge or
|
||||
TerritoryTypeIdHousing.MistApartment or
|
||||
TerritoryTypeIdHousing.MistFCRoom or
|
||||
TerritoryTypeIdHousing.MistFCWorkshop
|
||||
=> ResidentialAetheryteKind.Limsa,
|
||||
|
||||
TerritoryTypeIdHousing.Goblet or
|
||||
TerritoryTypeIdHousing.GobletSmall or
|
||||
TerritoryTypeIdHousing.GobletMedium or
|
||||
TerritoryTypeIdHousing.GobletLarge or
|
||||
TerritoryTypeIdHousing.GobletApartment or
|
||||
TerritoryTypeIdHousing.GobletFCRoom or
|
||||
TerritoryTypeIdHousing.GobletFCWorkshop
|
||||
=> ResidentialAetheryteKind.Uldah,
|
||||
|
||||
TerritoryTypeIdHousing.Empyream or
|
||||
TerritoryTypeIdHousing.EmpyreamSmall or
|
||||
TerritoryTypeIdHousing.EmpyreamMedium or
|
||||
TerritoryTypeIdHousing.EmpyreamLarge or
|
||||
TerritoryTypeIdHousing.EmpyreamApartment or
|
||||
TerritoryTypeIdHousing.EmpyreamFCRoom or
|
||||
TerritoryTypeIdHousing.EmpyreamFCWorkshop
|
||||
=> ResidentialAetheryteKind.Foundation,
|
||||
|
||||
_ => ResidentialAetheryteKind.None
|
||||
};
|
||||
}
|
||||
|
||||
public string? GetMapAddressByLocation(LocationInfo location)
|
||||
{
|
||||
string? liString = null;
|
||||
var territoryHousing = (TerritoryTypeIdHousing)location.TerritoryId;
|
||||
if (GetResidentialAetheryteKind(territoryHousing) == ResidentialAetheryteKind.None)
|
||||
{
|
||||
liString = _dalamudUtilService.LocationToLifestream(location);
|
||||
}
|
||||
return liString;
|
||||
}
|
||||
|
||||
public DateTimeOffset GetSharingStatus(string uid)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -4,8 +4,10 @@ using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using LightlessSync.API.Data.Enum;
|
||||
using LightlessSync.API.Data.Extensions;
|
||||
using LightlessSync.API.Dto.CharaData;
|
||||
using LightlessSync.API.Dto.Group;
|
||||
using LightlessSync.API.Dto.User;
|
||||
using LightlessSync.Interop.Ipc;
|
||||
using LightlessSync.LightlessConfiguration;
|
||||
using LightlessSync.PlayerData.Pairs;
|
||||
using LightlessSync.Services;
|
||||
@@ -40,6 +42,7 @@ public class DrawUserPair
|
||||
private readonly LocationShareService _locationShareService;
|
||||
private readonly CharaDataManager _charaDataManager;
|
||||
private readonly PairLedger _pairLedger;
|
||||
private readonly IpcCallerLifestream _lifestreamIpc;
|
||||
private float _menuWidth = -1;
|
||||
private bool _wasHovered = false;
|
||||
private TooltipSnapshot _tooltipSnapshot = TooltipSnapshot.Empty;
|
||||
@@ -60,7 +63,8 @@ public class DrawUserPair
|
||||
LightlessConfigService configService,
|
||||
LocationShareService locationShareService,
|
||||
CharaDataManager charaDataManager,
|
||||
PairLedger pairLedger)
|
||||
PairLedger pairLedger,
|
||||
IpcCallerLifestream lifestreamIpc)
|
||||
{
|
||||
_id = id;
|
||||
_uiEntry = uiEntry;
|
||||
@@ -79,6 +83,7 @@ public class DrawUserPair
|
||||
_locationShareService = locationShareService;
|
||||
_charaDataManager = charaDataManager;
|
||||
_pairLedger = pairLedger;
|
||||
_lifestreamIpc = lifestreamIpc;
|
||||
}
|
||||
|
||||
public PairDisplayEntry DisplayEntry => _displayEntry;
|
||||
@@ -656,6 +661,13 @@ public class DrawUserPair
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, shareColor, shareLocation || shareLocationToOther))
|
||||
_uiSharedService.IconText(shareLocationIcon);
|
||||
|
||||
var popupId = $"LocationPopup_{_pair.UserData.UID}";
|
||||
|
||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Left) && shareLocation && !string.IsNullOrEmpty(location))
|
||||
{
|
||||
ImGui.OpenPopup(popupId);
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.BeginTooltip();
|
||||
@@ -669,6 +681,8 @@ public class DrawUserPair
|
||||
_uiSharedService.IconText(FontAwesomeIcon.LocationArrow);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(location);
|
||||
ImGui.Separator();
|
||||
ImGui.TextUnformatted("Click to teleport to this location");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -700,6 +714,62 @@ public class DrawUserPair
|
||||
}
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
|
||||
if (ImGui.BeginPopup(popupId))
|
||||
{
|
||||
|
||||
var locationInfo = _locationShareService.GetLocationForLifestreamByUid(_pair.UserData.UID);
|
||||
if (locationInfo != null)
|
||||
{
|
||||
var locationLi = locationInfo.Value;
|
||||
var housingAddress = _locationShareService.GetAddressBookEntryByLocation(locationLi);
|
||||
var mapAddress = _locationShareService.GetMapAddressByLocation(locationLi);
|
||||
ImGui.TextUnformatted("Teleport to user?");
|
||||
ImGui.Separator();
|
||||
if (!_lifestreamIpc.APIAvailable)
|
||||
{
|
||||
ImGui.TextUnformatted("Lifestream IPC is not available. Please ensure Lifestream is enabled");
|
||||
}
|
||||
else if (housingAddress != null || mapAddress != null)
|
||||
{
|
||||
ImGui.TextUnformatted($"Go to {location}?");
|
||||
ImGui.TextUnformatted($"NOTE: Teleporting to maps with multiple aetherytes or instances may not be accurate currently. (ie. Thavnair, Yanxia)");
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextUnformatted("Lifestream cannot teleport here. If you are in a residential area, please make sure you're inside a plot.");
|
||||
}
|
||||
ImGui.Separator();
|
||||
if (_lifestreamIpc.APIAvailable && (housingAddress != null || mapAddress != null))
|
||||
{
|
||||
if (locationLi.HouseId is not 0 && housingAddress != null)
|
||||
{
|
||||
if (ImGui.Button("Navigate"))
|
||||
{
|
||||
_lifestreamIpc.GoToHousingAddress(housingAddress.Value);
|
||||
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
else if (mapAddress != null && locationLi.HouseId is 0)
|
||||
{
|
||||
if (ImGui.Button("Navigate"))
|
||||
{
|
||||
_lifestreamIpc.ExecuteLifestreamCommand(mapAddress);
|
||||
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
}
|
||||
if (ImGui.Button("Close"))
|
||||
{
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
ImGui.EndPopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled || individualIsSticky)
|
||||
|
||||
@@ -16,6 +16,7 @@ using LightlessSync.UI.Handlers;
|
||||
using LightlessSync.UI.Models;
|
||||
using LightlessSync.WebAPI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using LightlessSync.Interop.Ipc;
|
||||
|
||||
namespace LightlessSync.UI;
|
||||
|
||||
@@ -40,6 +41,7 @@ public class DrawEntityFactory
|
||||
private readonly IdDisplayHandler _uidDisplayHandler;
|
||||
private readonly PairLedger _pairLedger;
|
||||
private readonly PairFactory _pairFactory;
|
||||
private readonly IpcCallerLifestream _lifestreamIpc;
|
||||
|
||||
public DrawEntityFactory(
|
||||
ILogger<DrawEntityFactory> logger,
|
||||
@@ -60,7 +62,8 @@ public class DrawEntityFactory
|
||||
RenameSyncshellTagUi renameSyncshellTagUi,
|
||||
SelectSyncshellForTagUi selectSyncshellForTagUi,
|
||||
PairLedger pairLedger,
|
||||
PairFactory pairFactory)
|
||||
PairFactory pairFactory,
|
||||
IpcCallerLifestream lifestreamIpc)
|
||||
{
|
||||
_logger = logger;
|
||||
_apiController = apiController;
|
||||
@@ -81,6 +84,7 @@ public class DrawEntityFactory
|
||||
_selectSyncshellForTagUi = selectSyncshellForTagUi;
|
||||
_pairLedger = pairLedger;
|
||||
_pairFactory = pairFactory;
|
||||
_lifestreamIpc = lifestreamIpc;
|
||||
}
|
||||
|
||||
public DrawFolderGroup CreateGroupFolder(
|
||||
@@ -167,7 +171,8 @@ public class DrawEntityFactory
|
||||
_configService,
|
||||
_locationShareService,
|
||||
_charaDataManager,
|
||||
_pairLedger);
|
||||
_pairLedger,
|
||||
_lifestreamIpc);
|
||||
}
|
||||
|
||||
public IReadOnlyList<PairUiEntry> GetAllEntries()
|
||||
|
||||
55
LightlessSync/Utils/PtrGuard.cs
Normal file
55
LightlessSync/Utils/PtrGuard.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using static LightlessSync.Utils.PtrGuardMemory;
|
||||
|
||||
namespace LightlessSync.Utils
|
||||
{
|
||||
public static partial class PtrGuard
|
||||
{
|
||||
private const ulong _minLikelyPtr = 0x0000_0001_0000_0000UL;
|
||||
private const ulong _maxUserPtr = 0x0000_7FFF_FFFF_FFFFUL;
|
||||
private const ulong _aligmentPtr = 0x7UL;
|
||||
|
||||
public static bool LooksLikePtr(nint p)
|
||||
{
|
||||
if (p == 0) return false;
|
||||
var u = (ulong)p;
|
||||
if (u < _minLikelyPtr) return false;
|
||||
if (u > _maxUserPtr) return false;
|
||||
if ((u & _aligmentPtr) != 0) 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
LightlessSync/Utils/PtrGuardMemory.cs
Normal file
36
LightlessSync/Utils/PtrGuardMemory.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user