Compare commits
16 Commits
clr-fix-at
...
2.0.2.78-D
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac711d9a43 | ||
|
|
b875e0c3a1 | ||
|
|
46e76bbfe6 | ||
|
|
3654365f2a | ||
|
|
9b256dd185 | ||
|
|
223ade39cb | ||
|
|
5aca9e70b2 | ||
|
|
92772cf334 | ||
|
|
0395e81a9f | ||
|
|
7734a7bf7e | ||
|
|
db2d19bb1e | ||
|
|
ab305a249c | ||
|
|
9d104a9dd8 | ||
|
|
bcd3bd5ca2 | ||
|
|
c1829a9837 | ||
|
|
cca23f6e05 |
@@ -1,51 +0,0 @@
|
|||||||
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,
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,11 @@
|
|||||||
using System.Globalization;
|
using LightlessSync.WebAPI;
|
||||||
using System.Text.Json;
|
|
||||||
using System.Text.Json.Nodes;
|
|
||||||
using LightlessSync.LightlessConfiguration.Configurations;
|
|
||||||
using LightlessSync.LightlessConfiguration.Models;
|
|
||||||
using LightlessSync.WebAPI;
|
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace LightlessSync.LightlessConfiguration;
|
namespace LightlessSync.LightlessConfiguration;
|
||||||
|
|
||||||
public class ConfigurationMigrator(ILogger<ConfigurationMigrator> logger, TransientConfigService transientConfigService,
|
public class ConfigurationMigrator(ILogger<ConfigurationMigrator> logger, TransientConfigService transientConfigService,
|
||||||
ServerConfigService serverConfigService, TempCollectionConfigService tempCollectionConfigService,
|
ServerConfigService serverConfigService) : IHostedService
|
||||||
LightlessConfigService lightlessConfigService) : IHostedService
|
|
||||||
{
|
{
|
||||||
private readonly ILogger<ConfigurationMigrator> _logger = logger;
|
private readonly ILogger<ConfigurationMigrator> _logger = logger;
|
||||||
|
|
||||||
@@ -57,8 +51,6 @@ public class ConfigurationMigrator(ILogger<ConfigurationMigrator> logger, Transi
|
|||||||
serverConfigService.Current.Version = 2;
|
serverConfigService.Current.Version = 2;
|
||||||
serverConfigService.Save();
|
serverConfigService.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
MigrateTempCollectionConfig(tempCollectionConfigService, lightlessConfigService);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
@@ -71,273 +63,4 @@ public class ConfigurationMigrator(ILogger<ConfigurationMigrator> logger, Transi
|
|||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MigrateTempCollectionConfig(TempCollectionConfigService tempCollectionConfigService, LightlessConfigService lightlessConfigService)
|
|
||||||
{
|
|
||||||
var now = DateTime.UtcNow;
|
|
||||||
TempCollectionConfig tempConfig = tempCollectionConfigService.Current;
|
|
||||||
var tempChanged = false;
|
|
||||||
var tempNeedsSave = false;
|
|
||||||
|
|
||||||
if (TryReadTempCollectionData(lightlessConfigService.ConfigurationPath, out var root, out var ids, out var entries))
|
|
||||||
{
|
|
||||||
tempChanged |= MergeTempCollectionData(tempConfig, ids, entries, now);
|
|
||||||
var removed = root.Remove("OrphanableTempCollections");
|
|
||||||
removed |= root.Remove("OrphanableTempCollectionEntries");
|
|
||||||
if (removed)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string updatedJson = root.ToJsonString(new JsonSerializerOptions
|
|
||||||
{
|
|
||||||
WriteIndented = true
|
|
||||||
});
|
|
||||||
File.WriteAllText(lightlessConfigService.ConfigurationPath, updatedJson);
|
|
||||||
lightlessConfigService.UpdateLastWriteTime();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "Failed to rewrite {config} after temp collection migration", lightlessConfigService.ConfigurationPath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ids.Count > 0 || entries.Count > 0)
|
|
||||||
{
|
|
||||||
_logger.LogInformation("Migrated {ids} temp collection ids and {entries} entries to {configName}",
|
|
||||||
ids.Count, entries.Count, tempCollectionConfigService.ConfigurationName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TryReadTempCollectionData(tempCollectionConfigService.ConfigurationPath, out var tempRoot, out var tempIds, out var tempEntries))
|
|
||||||
{
|
|
||||||
tempChanged |= MergeTempCollectionData(tempConfig, tempIds, tempEntries, now);
|
|
||||||
if (tempRoot.Remove("OrphanableTempCollections"))
|
|
||||||
{
|
|
||||||
tempNeedsSave = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tempChanged || tempNeedsSave)
|
|
||||||
{
|
|
||||||
tempCollectionConfigService.Save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryReadTempCollectionData(string configPath, out JsonObject root, out HashSet<Guid> ids, out List<OrphanableTempCollectionEntry> entries)
|
|
||||||
{
|
|
||||||
root = new JsonObject();
|
|
||||||
ids = [];
|
|
||||||
entries = [];
|
|
||||||
|
|
||||||
if (!File.Exists(configPath))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
root = JsonNode.Parse(File.ReadAllText(configPath)) as JsonObject ?? new JsonObject();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogWarning(ex, "Failed to read temp collection data from {config}", configPath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
root.TryGetPropertyValue("OrphanableTempCollections", out JsonNode? idsNode);
|
|
||||||
root.TryGetPropertyValue("OrphanableTempCollectionEntries", out JsonNode? entriesNode);
|
|
||||||
|
|
||||||
if (idsNode == null && entriesNode == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ids = ParseGuidSet(idsNode);
|
|
||||||
entries = ParseEntries(entriesNode);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static HashSet<Guid> ParseGuidSet(JsonNode? node)
|
|
||||||
{
|
|
||||||
HashSet<Guid> ids = [];
|
|
||||||
if (node is not JsonArray array)
|
|
||||||
{
|
|
||||||
return ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (JsonNode? item in array)
|
|
||||||
{
|
|
||||||
Guid id = ParseGuid(item);
|
|
||||||
if (id != Guid.Empty)
|
|
||||||
{
|
|
||||||
ids.Add(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<OrphanableTempCollectionEntry> ParseEntries(JsonNode? node)
|
|
||||||
{
|
|
||||||
List<OrphanableTempCollectionEntry> entries = [];
|
|
||||||
if (node is not JsonArray array)
|
|
||||||
{
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (JsonNode? item in array)
|
|
||||||
{
|
|
||||||
if (item is not JsonObject obj)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Guid id = ParseGuid(obj["Id"]);
|
|
||||||
if (id == Guid.Empty)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
DateTime registeredAtUtc = DateTime.MinValue;
|
|
||||||
if (TryParseDateTime(obj["RegisteredAtUtc"], out DateTime parsed))
|
|
||||||
{
|
|
||||||
registeredAtUtc = parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
entries.Add(new OrphanableTempCollectionEntry
|
|
||||||
{
|
|
||||||
Id = id,
|
|
||||||
RegisteredAtUtc = registeredAtUtc
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Guid ParseGuid(JsonNode? node)
|
|
||||||
{
|
|
||||||
if (node is JsonValue value)
|
|
||||||
{
|
|
||||||
if (value.TryGetValue<string>(out string? stringValue) && Guid.TryParse(stringValue, out Guid parsed))
|
|
||||||
{
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Guid.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool TryParseDateTime(JsonNode? node, out DateTime value)
|
|
||||||
{
|
|
||||||
value = DateTime.MinValue;
|
|
||||||
if (node is not JsonValue val)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (val.TryGetValue<DateTime>(out DateTime direct))
|
|
||||||
{
|
|
||||||
value = direct;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (val.TryGetValue<string>(out string? stringValue)
|
|
||||||
&& DateTime.TryParse(stringValue, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTime parsed))
|
|
||||||
{
|
|
||||||
value = parsed;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool MergeTempCollectionData(TempCollectionConfig config, HashSet<Guid> ids, List<OrphanableTempCollectionEntry> entries, DateTime now)
|
|
||||||
{
|
|
||||||
bool changed = false;
|
|
||||||
Dictionary<Guid, OrphanableTempCollectionEntry> entryLookup = new();
|
|
||||||
for (var i = config.OrphanableTempCollectionEntries.Count - 1; i >= 0; i--)
|
|
||||||
{
|
|
||||||
var entry = config.OrphanableTempCollectionEntries[i];
|
|
||||||
if (entry.Id == Guid.Empty)
|
|
||||||
{
|
|
||||||
config.OrphanableTempCollectionEntries.RemoveAt(i);
|
|
||||||
changed = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entryLookup.TryGetValue(entry.Id, out var existing))
|
|
||||||
{
|
|
||||||
if (entry.RegisteredAtUtc != DateTime.MinValue
|
|
||||||
&& (existing.RegisteredAtUtc == DateTime.MinValue || entry.RegisteredAtUtc < existing.RegisteredAtUtc))
|
|
||||||
{
|
|
||||||
existing.RegisteredAtUtc = entry.RegisteredAtUtc;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
config.OrphanableTempCollectionEntries.RemoveAt(i);
|
|
||||||
changed = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
entryLookup[entry.Id] = entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (OrphanableTempCollectionEntry entry in entries)
|
|
||||||
{
|
|
||||||
if (entry.Id == Guid.Empty)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!entryLookup.TryGetValue(entry.Id, out OrphanableTempCollectionEntry? existing))
|
|
||||||
{
|
|
||||||
var added = new OrphanableTempCollectionEntry
|
|
||||||
{
|
|
||||||
Id = entry.Id,
|
|
||||||
RegisteredAtUtc = entry.RegisteredAtUtc
|
|
||||||
};
|
|
||||||
config.OrphanableTempCollectionEntries.Add(added);
|
|
||||||
entryLookup[entry.Id] = added;
|
|
||||||
changed = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.RegisteredAtUtc != DateTime.MinValue
|
|
||||||
&& (existing.RegisteredAtUtc == DateTime.MinValue || entry.RegisteredAtUtc < existing.RegisteredAtUtc))
|
|
||||||
{
|
|
||||||
existing.RegisteredAtUtc = entry.RegisteredAtUtc;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (Guid id in ids)
|
|
||||||
{
|
|
||||||
if (id == Guid.Empty)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!entryLookup.TryGetValue(id, out OrphanableTempCollectionEntry? existing))
|
|
||||||
{
|
|
||||||
var added = new OrphanableTempCollectionEntry
|
|
||||||
{
|
|
||||||
Id = id,
|
|
||||||
RegisteredAtUtc = now
|
|
||||||
};
|
|
||||||
config.OrphanableTempCollectionEntries.Add(added);
|
|
||||||
entryLookup[id] = added;
|
|
||||||
changed = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existing.RegisteredAtUtc == DateTime.MinValue)
|
|
||||||
{
|
|
||||||
existing.RegisteredAtUtc = now;
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return changed;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,41 +72,37 @@ public class ConfigurationSaveService : IHostedService
|
|||||||
{
|
{
|
||||||
_logger.LogTrace("Saving {configName}", config.ConfigurationName);
|
_logger.LogTrace("Saving {configName}", config.ConfigurationName);
|
||||||
var configDir = config.ConfigurationPath.Replace(config.ConfigurationName, string.Empty);
|
var configDir = config.ConfigurationPath.Replace(config.ConfigurationName, string.Empty);
|
||||||
var isTempCollections = string.Equals(config.ConfigurationName, TempCollectionConfigService.ConfigName, StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
if (!isTempCollections)
|
try
|
||||||
{
|
{
|
||||||
try
|
var configBackupFolder = Path.Join(configDir, BackupFolder);
|
||||||
{
|
if (!Directory.Exists(configBackupFolder))
|
||||||
var configBackupFolder = Path.Join(configDir, BackupFolder);
|
Directory.CreateDirectory(configBackupFolder);
|
||||||
if (!Directory.Exists(configBackupFolder))
|
|
||||||
Directory.CreateDirectory(configBackupFolder);
|
|
||||||
|
|
||||||
var configNameSplit = config.ConfigurationName.Split(".");
|
var configNameSplit = config.ConfigurationName.Split(".");
|
||||||
var existingConfigs = Directory.EnumerateFiles(
|
var existingConfigs = Directory.EnumerateFiles(
|
||||||
configBackupFolder,
|
configBackupFolder,
|
||||||
configNameSplit[0] + "*")
|
configNameSplit[0] + "*")
|
||||||
.Select(c => new FileInfo(c))
|
.Select(c => new FileInfo(c))
|
||||||
.OrderByDescending(c => c.LastWriteTime).ToList();
|
.OrderByDescending(c => c.LastWriteTime).ToList();
|
||||||
if (existingConfigs.Skip(10).Any())
|
if (existingConfigs.Skip(10).Any())
|
||||||
|
{
|
||||||
|
foreach (var oldBak in existingConfigs.Skip(10).ToList())
|
||||||
{
|
{
|
||||||
foreach (var oldBak in existingConfigs.Skip(10).ToList())
|
oldBak.Delete();
|
||||||
{
|
|
||||||
oldBak.Delete();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
string backupPath = Path.Combine(configBackupFolder, configNameSplit[0] + "." + DateTime.Now.ToString("yyyyMMddHHmmss") + "." + configNameSplit[1]);
|
string backupPath = Path.Combine(configBackupFolder, configNameSplit[0] + "." + DateTime.Now.ToString("yyyyMMddHHmmss") + "." + configNameSplit[1]);
|
||||||
_logger.LogTrace("Backing up current config to {backupPath}", backupPath);
|
_logger.LogTrace("Backing up current config to {backupPath}", backupPath);
|
||||||
File.Copy(config.ConfigurationPath, backupPath, overwrite: true);
|
File.Copy(config.ConfigurationPath, backupPath, overwrite: true);
|
||||||
FileInfo fi = new(backupPath);
|
FileInfo fi = new(backupPath);
|
||||||
fi.LastWriteTimeUtc = DateTime.UtcNow;
|
fi.LastWriteTimeUtc = DateTime.UtcNow;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// ignore if file cannot be backupped
|
// ignore if file cannot be backupped
|
||||||
_logger.LogWarning(ex, "Could not create backup for {config}", config.ConfigurationPath);
|
_logger.LogWarning(ex, "Could not create backup for {config}", config.ConfigurationPath);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var temp = config.ConfigurationPath + ".tmp";
|
var temp = config.ConfigurationPath + ".tmp";
|
||||||
@@ -114,7 +110,7 @@ public class ConfigurationSaveService : IHostedService
|
|||||||
{
|
{
|
||||||
await File.WriteAllTextAsync(temp, JsonSerializer.Serialize(config.Current, typeof(T), new JsonSerializerOptions()
|
await File.WriteAllTextAsync(temp, JsonSerializer.Serialize(config.Current, typeof(T), new JsonSerializerOptions()
|
||||||
{
|
{
|
||||||
WriteIndented = !isTempCollections
|
WriteIndented = true
|
||||||
})).ConfigureAwait(false);
|
})).ConfigureAwait(false);
|
||||||
File.Move(temp, config.ConfigurationPath, true);
|
File.Move(temp, config.ConfigurationPath, true);
|
||||||
config.UpdateLastWriteTime();
|
config.UpdateLastWriteTime();
|
||||||
|
|||||||
@@ -160,6 +160,8 @@ public class LightlessConfig : ILightlessConfiguration
|
|||||||
public string? SelectedFinderSyncshell { get; set; } = null;
|
public string? SelectedFinderSyncshell { get; set; } = null;
|
||||||
public string LastSeenVersion { get; set; } = string.Empty;
|
public string LastSeenVersion { get; set; } = string.Empty;
|
||||||
public bool EnableParticleEffects { get; set; } = true;
|
public bool EnableParticleEffects { get; set; } = true;
|
||||||
|
public HashSet<Guid> OrphanableTempCollections { get; set; } = [];
|
||||||
|
public List<OrphanableTempCollectionEntry> OrphanableTempCollectionEntries { get; set; } = [];
|
||||||
public AnimationValidationMode AnimationValidationMode { get; set; } = AnimationValidationMode.Unsafe;
|
public AnimationValidationMode AnimationValidationMode { get; set; } = AnimationValidationMode.Unsafe;
|
||||||
public bool AnimationAllowOneBasedShift { get; set; } = false;
|
public bool AnimationAllowOneBasedShift { get; set; } = false;
|
||||||
public bool AnimationAllowNeighborIndexTolerance { get; set; } = false;
|
public bool AnimationAllowNeighborIndexTolerance { get; set; } = false;
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
using LightlessSync.LightlessConfiguration.Models;
|
|
||||||
|
|
||||||
namespace LightlessSync.LightlessConfiguration.Configurations;
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
public sealed class TempCollectionConfig : ILightlessConfiguration
|
|
||||||
{
|
|
||||||
public int Version { get; set; } = 1;
|
|
||||||
public List<OrphanableTempCollectionEntry> OrphanableTempCollectionEntries { get; set; } = [];
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
using LightlessSync.LightlessConfiguration.Configurations;
|
|
||||||
|
|
||||||
namespace LightlessSync.LightlessConfiguration;
|
|
||||||
|
|
||||||
public sealed class TempCollectionConfigService : ConfigurationServiceBase<TempCollectionConfig>
|
|
||||||
{
|
|
||||||
public const string ConfigName = "tempcollections.json";
|
|
||||||
|
|
||||||
public TempCollectionConfigService(string configDir) : base(configDir) { }
|
|
||||||
|
|
||||||
public override string ConfigurationName => ConfigName;
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Authors></Authors>
|
<Authors></Authors>
|
||||||
<Company></Company>
|
<Company></Company>
|
||||||
<Version>2.0.3</Version>
|
<Version>2.0.2.78</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>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
|
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;
|
||||||
@@ -13,7 +14,6 @@ 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,30 +119,46 @@ 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(() =>
|
=> 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
|
||||||
{
|
{
|
||||||
nint basePtr = playerPointer;
|
_ = Marshal.ReadByte(ptr);
|
||||||
|
return true;
|
||||||
if (!PtrGuard.LooksLikePtr(basePtr))
|
}
|
||||||
return true;
|
catch
|
||||||
|
{
|
||||||
nint drawObjAddr = basePtr + _drawObjectOffset;
|
return false;
|
||||||
|
}
|
||||||
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)
|
private static bool IsCacheFresh(CacheEntry entry)
|
||||||
=> (DateTime.UtcNow - entry.CreatedUtc) <= _characterCacheTtl;
|
=> (DateTime.UtcNow - entry.CreatedUtc) <= _characterCacheTtl;
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
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 ObjectKind = LightlessSync.API.Data.Enum.ObjectKind;
|
|
||||||
using VisibilityFlags = FFXIVClientStructs.FFXIV.Client.Game.Object.VisibilityFlags;
|
using VisibilityFlags = FFXIVClientStructs.FFXIV.Client.Game.Object.VisibilityFlags;
|
||||||
|
using ObjectKind = LightlessSync.API.Data.Enum.ObjectKind;
|
||||||
|
|
||||||
namespace LightlessSync.PlayerData.Handlers;
|
namespace LightlessSync.PlayerData.Handlers;
|
||||||
|
|
||||||
@@ -178,43 +177,18 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
|||||||
var prevDrawObj = DrawObjectAddress;
|
var prevDrawObj = DrawObjectAddress;
|
||||||
string? nameString = null;
|
string? nameString = null;
|
||||||
|
|
||||||
var nextAddr = _getAddress();
|
Address = _getAddress();
|
||||||
|
|
||||||
if (nextAddr != IntPtr.Zero && !PtrGuard.LooksLikePtr(nextAddr))
|
|
||||||
{
|
|
||||||
Logger.LogWarning("[{this}] _getAddress returned non-pointer: 0x{addr:X}", this, (ulong)nextAddr);
|
|
||||||
nextAddr = IntPtr.Zero;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextAddr != IntPtr.Zero &&
|
|
||||||
!PtrGuard.IsReadable(nextAddr, (nuint)sizeof(FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject)))
|
|
||||||
{
|
|
||||||
Logger.LogWarning("[{this}] Address not readable: 0x{addr:X}", this, (ulong)nextAddr);
|
|
||||||
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;
|
||||||
|
|
||||||
if (PtrGuard.IsReadable(Address, (nuint)sizeof(Character)))
|
var chara = (Character*)Address;
|
||||||
{
|
nameString = chara->GameObject.NameString;
|
||||||
var chara = (Character*)Address;
|
if (!string.IsNullOrEmpty(nameString) && !string.Equals(nameString, Name, StringComparison.Ordinal))
|
||||||
nameString = chara->GameObject.NameString;
|
Name = nameString;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(nameString) && !string.Equals(nameString, Name, StringComparison.Ordinal))
|
|
||||||
Name = nameString;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -222,27 +196,22 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
|||||||
EntityId = uint.MaxValue;
|
EntityId = uint.MaxValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
CurrentDrawCondition = (Address != IntPtr.Zero && DrawObjectAddress != IntPtr.Zero)
|
CurrentDrawCondition = IsBeingDrawnUnsafe();
|
||||||
? 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 ??= chara->GameObject.NameString;
|
nameString ??= ((Character*)Address)->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;
|
||||||
|
|
||||||
@@ -250,36 +219,32 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
|||||||
|
|
||||||
if (isHuman)
|
if (isHuman)
|
||||||
{
|
{
|
||||||
if (PtrGuard.IsReadable(DrawObjectAddress, (nuint)sizeof(Human)))
|
var classJob = chara->CharacterData.ClassJob;
|
||||||
|
if (classJob != _classJob)
|
||||||
{
|
{
|
||||||
var classJob = chara->CharacterData.ClassJob;
|
Logger.LogTrace("[{this}] classjob changed from {old} to {new}", this, _classJob, classJob);
|
||||||
if (classJob != _classJob)
|
_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)
|
if (equipDiff && !_isOwnedObject) // send the message out immediately and cancel out, no reason to continue if not self
|
||||||
{
|
{
|
||||||
Logger.LogTrace("[{this}] Changed", this);
|
Logger.LogTrace("[{this}] Changed", this);
|
||||||
return;
|
return;
|
||||||
@@ -287,13 +252,11 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
|||||||
|
|
||||||
bool customizeDiff = false;
|
bool customizeDiff = false;
|
||||||
|
|
||||||
if (isHuman && PtrGuard.IsReadable(DrawObjectAddress, (nuint)sizeof(Human)))
|
if (isHuman)
|
||||||
{
|
{
|
||||||
var human = (Human*)drawObj;
|
var gender = ((Human*)drawObj)->Customize.Sex;
|
||||||
|
var raceId = ((Human*)drawObj)->Customize.Race;
|
||||||
var gender = human->Customize.Sex;
|
var tribeId = ((Human*)drawObj)->Customize.Tribe;
|
||||||
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))
|
||||||
@@ -304,11 +267,15 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
|||||||
TribeId = tribeId;
|
TribeId = tribeId;
|
||||||
}
|
}
|
||||||
|
|
||||||
customizeDiff = CompareAndUpdateCustomizeData(human->Customize.Data);
|
customizeDiff = CompareAndUpdateCustomizeData(((Human*)drawObj)->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)
|
||||||
@@ -322,11 +289,12 @@ 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;
|
||||||
@@ -362,10 +330,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
|||||||
|
|
||||||
private unsafe bool CompareAndUpdateMainHand(Weapon* weapon)
|
private unsafe bool CompareAndUpdateMainHand(Weapon* weapon)
|
||||||
{
|
{
|
||||||
var p = (nint)weapon;
|
if ((nint)weapon == nint.Zero) return false;
|
||||||
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;
|
||||||
@@ -378,10 +343,7 @@ public sealed class GameObjectHandler : DisposableMediatorSubscriberBase, IHighP
|
|||||||
|
|
||||||
private unsafe bool CompareAndUpdateOffHand(Weapon* weapon)
|
private unsafe bool CompareAndUpdateOffHand(Weapon* weapon)
|
||||||
{
|
{
|
||||||
var p = (nint)weapon;
|
if ((nint)weapon == nint.Zero) return false;
|
||||||
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;
|
||||||
|
|||||||
@@ -429,7 +429,6 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
LightlessSync.UI.Style.MainStyle.Init(cfg, theme);
|
LightlessSync.UI.Style.MainStyle.Init(cfg, theme);
|
||||||
return cfg;
|
return cfg;
|
||||||
});
|
});
|
||||||
services.AddSingleton(sp => new TempCollectionConfigService(configDir));
|
|
||||||
services.AddSingleton(sp => new ServerConfigService(configDir));
|
services.AddSingleton(sp => new ServerConfigService(configDir));
|
||||||
services.AddSingleton(sp => new NotesConfigService(configDir));
|
services.AddSingleton(sp => new NotesConfigService(configDir));
|
||||||
services.AddSingleton(sp => new PairTagConfigService(configDir));
|
services.AddSingleton(sp => new PairTagConfigService(configDir));
|
||||||
@@ -443,7 +442,6 @@ public sealed class Plugin : IDalamudPlugin
|
|||||||
services.AddSingleton<IConfigService<ILightlessConfiguration>>(sp => sp.GetRequiredService<LightlessConfigService>());
|
services.AddSingleton<IConfigService<ILightlessConfiguration>>(sp => sp.GetRequiredService<LightlessConfigService>());
|
||||||
services.AddSingleton<IConfigService<ILightlessConfiguration>>(sp => sp.GetRequiredService<UiThemeConfigService>());
|
services.AddSingleton<IConfigService<ILightlessConfiguration>>(sp => sp.GetRequiredService<UiThemeConfigService>());
|
||||||
services.AddSingleton<IConfigService<ILightlessConfiguration>>(sp => sp.GetRequiredService<ChatConfigService>());
|
services.AddSingleton<IConfigService<ILightlessConfiguration>>(sp => sp.GetRequiredService<ChatConfigService>());
|
||||||
services.AddSingleton<IConfigService<ILightlessConfiguration>>(sp => sp.GetRequiredService<TempCollectionConfigService>());
|
|
||||||
services.AddSingleton<IConfigService<ILightlessConfiguration>>(sp => sp.GetRequiredService<ServerConfigService>());
|
services.AddSingleton<IConfigService<ILightlessConfiguration>>(sp => sp.GetRequiredService<ServerConfigService>());
|
||||||
services.AddSingleton<IConfigService<ILightlessConfiguration>>(sp => sp.GetRequiredService<NotesConfigService>());
|
services.AddSingleton<IConfigService<ILightlessConfiguration>>(sp => sp.GetRequiredService<NotesConfigService>());
|
||||||
services.AddSingleton<IConfigService<ILightlessConfiguration>>(sp => sp.GetRequiredService<PairTagConfigService>());
|
services.AddSingleton<IConfigService<ILightlessConfiguration>>(sp => sp.GetRequiredService<PairTagConfigService>());
|
||||||
|
|||||||
@@ -701,23 +701,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
|||||||
str += $" Room #{location.RoomId}";
|
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;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
using Lifestream.Enums;
|
|
||||||
using LightlessSync.API.Data;
|
using LightlessSync.API.Data;
|
||||||
using LightlessSync.API.Dto.CharaData;
|
using LightlessSync.API.Dto.CharaData;
|
||||||
using LightlessSync.API.Dto.User;
|
using LightlessSync.API.Dto.User;
|
||||||
@@ -109,144 +108,6 @@ 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)
|
public DateTimeOffset GetSharingStatus(string uid)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -1343,11 +1343,22 @@ internal static class MdlDecimator
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return ModelDecimationFilters.IsBodyMaterial(mdl.Materials[mesh.MaterialIndex]);
|
return IsBodyMaterial(mdl.Materials[mesh.MaterialIndex]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsBodyMaterial(string materialPath)
|
private static bool IsBodyMaterial(string materialPath)
|
||||||
=> ModelDecimationFilters.IsBodyMaterial(materialPath);
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(materialPath))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalized = materialPath.Replace('\\', '/').ToLowerInvariant();
|
||||||
|
var nameStart = normalized.LastIndexOf('/');
|
||||||
|
var fileName = nameStart >= 0 ? normalized[(nameStart + 1)..] : normalized;
|
||||||
|
return fileName.Contains("_bibo", StringComparison.Ordinal)
|
||||||
|
|| fileName.EndsWith("_a.mtrl", StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class BodyCollisionData
|
private sealed class BodyCollisionData
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,132 +0,0 @@
|
|||||||
namespace LightlessSync.Services.ModelDecimation;
|
|
||||||
|
|
||||||
internal static class ModelDecimationFilters
|
|
||||||
{
|
|
||||||
// MODELS ONLY HERE, NOT MATERIALS
|
|
||||||
internal static readonly string[] HairPaths =
|
|
||||||
[
|
|
||||||
"/hair/",
|
|
||||||
"hir.mdl",
|
|
||||||
];
|
|
||||||
|
|
||||||
internal static readonly string[] ClothingPaths =
|
|
||||||
[
|
|
||||||
"chara/equipment/",
|
|
||||||
"/equipment/",
|
|
||||||
|
|
||||||
"met.mdl",
|
|
||||||
"top.mdl",
|
|
||||||
"glv.mdl",
|
|
||||||
"dwn.mdl",
|
|
||||||
"sho.mdl",
|
|
||||||
];
|
|
||||||
|
|
||||||
internal static readonly string[] AccessoryPaths =
|
|
||||||
[
|
|
||||||
"/accessory/",
|
|
||||||
"chara/accessory/",
|
|
||||||
|
|
||||||
"ear.mdl",
|
|
||||||
"nek.mdl",
|
|
||||||
"wrs.mdl",
|
|
||||||
"ril.mdl",
|
|
||||||
"rir.mdl",
|
|
||||||
];
|
|
||||||
|
|
||||||
internal static readonly string[] BodyPaths =
|
|
||||||
[
|
|
||||||
"/body/",
|
|
||||||
"chara/equipment/e0000/model/",
|
|
||||||
"chara/equipment/e9903/model/",
|
|
||||||
"chara/equipment/e9903/model/",
|
|
||||||
"chara/equipment/e0279/model/",
|
|
||||||
];
|
|
||||||
|
|
||||||
internal static readonly string[] FaceHeadPaths =
|
|
||||||
[
|
|
||||||
"/face/",
|
|
||||||
"/obj/face/",
|
|
||||||
"/head/",
|
|
||||||
"fac.mdl",
|
|
||||||
];
|
|
||||||
|
|
||||||
internal static readonly string[] TailOrEarPaths =
|
|
||||||
[
|
|
||||||
"/tail/",
|
|
||||||
"/obj/tail/",
|
|
||||||
"/zear/",
|
|
||||||
"/obj/zear/",
|
|
||||||
|
|
||||||
"til.mdl",
|
|
||||||
"zer.mdl",
|
|
||||||
];
|
|
||||||
|
|
||||||
// BODY MATERIALS ONLY, NOT MESHES
|
|
||||||
internal static readonly string[] BodyMaterials =
|
|
||||||
[
|
|
||||||
"b0001_bibo.mtrl",
|
|
||||||
"b0101_bibo.mtrl",
|
|
||||||
|
|
||||||
"b0001_a.mtrl",
|
|
||||||
"b0001_b.mtrl",
|
|
||||||
|
|
||||||
"b0101_a.mtrl",
|
|
||||||
"b0101_b.mtrl",
|
|
||||||
];
|
|
||||||
|
|
||||||
internal static string NormalizePath(string path)
|
|
||||||
=> path.Replace('\\', '/').ToLowerInvariant();
|
|
||||||
|
|
||||||
internal static bool IsHairPath(string normalizedPath)
|
|
||||||
=> ContainsAny(normalizedPath, HairPaths);
|
|
||||||
|
|
||||||
internal static bool IsClothingPath(string normalizedPath)
|
|
||||||
=> ContainsAny(normalizedPath, ClothingPaths);
|
|
||||||
|
|
||||||
internal static bool IsAccessoryPath(string normalizedPath)
|
|
||||||
=> ContainsAny(normalizedPath, AccessoryPaths);
|
|
||||||
|
|
||||||
|
|
||||||
internal static bool IsBodyPath(string normalizedPath)
|
|
||||||
=> ContainsAny(normalizedPath, BodyPaths);
|
|
||||||
|
|
||||||
internal static bool IsFaceHeadPath(string normalizedPath)
|
|
||||||
=> ContainsAny(normalizedPath, FaceHeadPaths);
|
|
||||||
|
|
||||||
internal static bool IsTailOrEarPath(string normalizedPath)
|
|
||||||
=> ContainsAny(normalizedPath, TailOrEarPaths);
|
|
||||||
|
|
||||||
internal static bool ContainsAny(string normalizedPath, IReadOnlyList<string> markers)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < markers.Count; i++)
|
|
||||||
{
|
|
||||||
if (normalizedPath.Contains(markers[i], StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal static bool IsBodyMaterial(string materialPath)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(materialPath))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var normalized = NormalizePath(materialPath);
|
|
||||||
var nameStart = normalized.LastIndexOf('/');
|
|
||||||
var fileName = nameStart >= 0 ? normalized[(nameStart + 1)..] : normalized;
|
|
||||||
foreach (var marker in BodyMaterials)
|
|
||||||
{
|
|
||||||
if (fileName.Contains(marker, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -348,40 +348,46 @@ public sealed class ModelDecimationService
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var normalized = ModelDecimationFilters.NormalizePath(gamePath);
|
var normalized = NormalizeGamePath(gamePath);
|
||||||
if (ModelDecimationFilters.IsHairPath(normalized))
|
if (normalized.Contains("/hair/", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ModelDecimationFilters.IsClothingPath(normalized))
|
if (normalized.Contains("/chara/equipment/", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return _performanceConfigService.Current.ModelDecimationAllowClothing;
|
return _performanceConfigService.Current.ModelDecimationAllowClothing;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ModelDecimationFilters.IsAccessoryPath(normalized))
|
if (normalized.Contains("/chara/accessory/", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return _performanceConfigService.Current.ModelDecimationAllowAccessories;
|
return _performanceConfigService.Current.ModelDecimationAllowAccessories;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ModelDecimationFilters.IsBodyPath(normalized))
|
if (normalized.Contains("/chara/human/", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return _performanceConfigService.Current.ModelDecimationAllowBody;
|
if (normalized.Contains("/body/", StringComparison.Ordinal))
|
||||||
}
|
{
|
||||||
|
return _performanceConfigService.Current.ModelDecimationAllowBody;
|
||||||
|
}
|
||||||
|
|
||||||
if (ModelDecimationFilters.IsFaceHeadPath(normalized))
|
if (normalized.Contains("/face/", StringComparison.Ordinal) || normalized.Contains("/head/", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return _performanceConfigService.Current.ModelDecimationAllowFaceHead;
|
return _performanceConfigService.Current.ModelDecimationAllowFaceHead;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ModelDecimationFilters.IsTailOrEarPath(normalized))
|
if (normalized.Contains("/tail/", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
return _performanceConfigService.Current.ModelDecimationAllowTail;
|
return _performanceConfigService.Current.ModelDecimationAllowTail;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string NormalizeGamePath(string path)
|
||||||
|
=> path.Replace('\\', '/').ToLowerInvariant();
|
||||||
|
|
||||||
private bool TryGetDecimationSettings(out ModelDecimationSettings settings)
|
private bool TryGetDecimationSettings(out ModelDecimationSettings settings)
|
||||||
{
|
{
|
||||||
settings = new ModelDecimationSettings(
|
settings = new ModelDecimationSettings(
|
||||||
|
|||||||
@@ -10,18 +10,15 @@ namespace LightlessSync.Services;
|
|||||||
public sealed class PenumbraTempCollectionJanitor : DisposableMediatorSubscriberBase
|
public sealed class PenumbraTempCollectionJanitor : DisposableMediatorSubscriberBase
|
||||||
{
|
{
|
||||||
private readonly IpcManager _ipc;
|
private readonly IpcManager _ipc;
|
||||||
private readonly TempCollectionConfigService _config;
|
private readonly LightlessConfigService _config;
|
||||||
private readonly CancellationTokenSource _cleanupCts = new();
|
|
||||||
private int _ran;
|
private int _ran;
|
||||||
private const int CleanupBatchSize = 50;
|
|
||||||
private static readonly TimeSpan CleanupBatchDelay = TimeSpan.FromMilliseconds(50);
|
|
||||||
private static readonly TimeSpan OrphanCleanupDelay = TimeSpan.FromDays(1);
|
private static readonly TimeSpan OrphanCleanupDelay = TimeSpan.FromDays(1);
|
||||||
|
|
||||||
public PenumbraTempCollectionJanitor(
|
public PenumbraTempCollectionJanitor(
|
||||||
ILogger<PenumbraTempCollectionJanitor> logger,
|
ILogger<PenumbraTempCollectionJanitor> logger,
|
||||||
LightlessMediator mediator,
|
LightlessMediator mediator,
|
||||||
IpcManager ipc,
|
IpcManager ipc,
|
||||||
TempCollectionConfigService config) : base(logger, mediator)
|
LightlessConfigService config) : base(logger, mediator)
|
||||||
{
|
{
|
||||||
_ipc = ipc;
|
_ipc = ipc;
|
||||||
_config = config;
|
_config = config;
|
||||||
@@ -34,6 +31,10 @@ public sealed class PenumbraTempCollectionJanitor : DisposableMediatorSubscriber
|
|||||||
if (id == Guid.Empty) return;
|
if (id == Guid.Empty) return;
|
||||||
var changed = false;
|
var changed = false;
|
||||||
var config = _config.Current;
|
var config = _config.Current;
|
||||||
|
if (config.OrphanableTempCollections.Add(id))
|
||||||
|
{
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
var existing = config.OrphanableTempCollectionEntries.FirstOrDefault(entry => entry.Id == id);
|
var existing = config.OrphanableTempCollectionEntries.FirstOrDefault(entry => entry.Id == id);
|
||||||
@@ -62,7 +63,8 @@ public sealed class PenumbraTempCollectionJanitor : DisposableMediatorSubscriber
|
|||||||
{
|
{
|
||||||
if (id == Guid.Empty) return;
|
if (id == Guid.Empty) return;
|
||||||
var config = _config.Current;
|
var config = _config.Current;
|
||||||
var changed = RemoveEntry(config.OrphanableTempCollectionEntries, id) > 0;
|
var changed = config.OrphanableTempCollections.Remove(id);
|
||||||
|
changed |= RemoveEntry(config.OrphanableTempCollectionEntries, id) > 0;
|
||||||
if (changed)
|
if (changed)
|
||||||
{
|
{
|
||||||
_config.Save();
|
_config.Save();
|
||||||
@@ -77,31 +79,14 @@ public sealed class PenumbraTempCollectionJanitor : DisposableMediatorSubscriber
|
|||||||
if (!_ipc.Penumbra.APIAvailable)
|
if (!_ipc.Penumbra.APIAvailable)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await CleanupOrphansOnBootAsync(_cleanupCts.Token).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.LogError(ex, "Error cleaning orphaned temp collections");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task CleanupOrphansOnBootAsync(CancellationToken token)
|
|
||||||
{
|
|
||||||
var config = _config.Current;
|
var config = _config.Current;
|
||||||
|
var ids = config.OrphanableTempCollections;
|
||||||
var entries = config.OrphanableTempCollectionEntries;
|
var entries = config.OrphanableTempCollectionEntries;
|
||||||
if (entries.Count == 0)
|
if (ids.Count == 0 && entries.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
var changed = EnsureEntryTimes(entries, now);
|
var changed = EnsureEntries(ids, entries, now);
|
||||||
var cutoff = now - OrphanCleanupDelay;
|
var cutoff = now - OrphanCleanupDelay;
|
||||||
var expired = entries
|
var expired = entries
|
||||||
.Where(entry => entry.Id != Guid.Empty && entry.RegisteredAtUtc != DateTime.MinValue && entry.RegisteredAtUtc <= cutoff)
|
.Where(entry => entry.Id != Guid.Empty && entry.RegisteredAtUtc != DateTime.MinValue && entry.RegisteredAtUtc <= cutoff)
|
||||||
@@ -120,47 +105,25 @@ public sealed class PenumbraTempCollectionJanitor : DisposableMediatorSubscriber
|
|||||||
var appId = Guid.NewGuid();
|
var appId = Guid.NewGuid();
|
||||||
Logger.LogInformation("Cleaning up {count} orphaned Lightless temp collections older than {delay}", expired.Count, OrphanCleanupDelay);
|
Logger.LogInformation("Cleaning up {count} orphaned Lightless temp collections older than {delay}", expired.Count, OrphanCleanupDelay);
|
||||||
|
|
||||||
List<Guid> removedIds = [];
|
|
||||||
foreach (var id in expired)
|
foreach (var id in expired)
|
||||||
{
|
{
|
||||||
if (token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _ipc.Penumbra.RemoveTemporaryCollectionAsync(Logger, appId, id).ConfigureAwait(false);
|
_ipc.Penumbra.RemoveTemporaryCollectionAsync(Logger, appId, id)
|
||||||
|
.GetAwaiter().GetResult();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logger.LogDebug(ex, "Failed removing orphaned temp collection {id}", id);
|
Logger.LogDebug(ex, "Failed removing orphaned temp collection {id}", id);
|
||||||
}
|
}
|
||||||
|
|
||||||
removedIds.Add(id);
|
|
||||||
if (removedIds.Count % CleanupBatchSize == 0)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Task.Delay(CleanupBatchDelay, token).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (removedIds.Count == 0)
|
foreach (var id in expired)
|
||||||
{
|
{
|
||||||
if (changed)
|
ids.Remove(id);
|
||||||
{
|
|
||||||
_config.Save();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var id in removedIds)
|
foreach (var id in expired)
|
||||||
{
|
{
|
||||||
RemoveEntry(entries, id);
|
RemoveEntry(entries, id);
|
||||||
}
|
}
|
||||||
@@ -168,17 +131,6 @@ public sealed class PenumbraTempCollectionJanitor : DisposableMediatorSubscriber
|
|||||||
_config.Save();
|
_config.Save();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (disposing)
|
|
||||||
{
|
|
||||||
_cleanupCts.Cancel();
|
|
||||||
_cleanupCts.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int RemoveEntry(List<OrphanableTempCollectionEntry> entries, Guid id)
|
private static int RemoveEntry(List<OrphanableTempCollectionEntry> entries, Guid id)
|
||||||
{
|
{
|
||||||
var removed = 0;
|
var removed = 0;
|
||||||
@@ -196,9 +148,29 @@ public sealed class PenumbraTempCollectionJanitor : DisposableMediatorSubscriber
|
|||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool EnsureEntryTimes(List<OrphanableTempCollectionEntry> entries, DateTime now)
|
private static bool EnsureEntries(HashSet<Guid> ids, List<OrphanableTempCollectionEntry> entries, DateTime now)
|
||||||
{
|
{
|
||||||
var changed = false;
|
var changed = false;
|
||||||
|
foreach (var id in ids)
|
||||||
|
{
|
||||||
|
if (id == Guid.Empty)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entries.Any(entry => entry.Id == id))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
entries.Add(new OrphanableTempCollectionEntry
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
RegisteredAtUtc = now
|
||||||
|
});
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var entry in entries)
|
foreach (var entry in entries)
|
||||||
{
|
{
|
||||||
if (entry.Id == Guid.Empty || entry.RegisteredAtUtc != DateTime.MinValue)
|
if (entry.Id == Guid.Empty || entry.RegisteredAtUtc != DateTime.MinValue)
|
||||||
|
|||||||
@@ -4,10 +4,8 @@ using Dalamud.Interface.Utility;
|
|||||||
using Dalamud.Interface.Utility.Raii;
|
using Dalamud.Interface.Utility.Raii;
|
||||||
using LightlessSync.API.Data.Enum;
|
using LightlessSync.API.Data.Enum;
|
||||||
using LightlessSync.API.Data.Extensions;
|
using LightlessSync.API.Data.Extensions;
|
||||||
using LightlessSync.API.Dto.CharaData;
|
|
||||||
using LightlessSync.API.Dto.Group;
|
using LightlessSync.API.Dto.Group;
|
||||||
using LightlessSync.API.Dto.User;
|
using LightlessSync.API.Dto.User;
|
||||||
using LightlessSync.Interop.Ipc;
|
|
||||||
using LightlessSync.LightlessConfiguration;
|
using LightlessSync.LightlessConfiguration;
|
||||||
using LightlessSync.PlayerData.Pairs;
|
using LightlessSync.PlayerData.Pairs;
|
||||||
using LightlessSync.Services;
|
using LightlessSync.Services;
|
||||||
@@ -42,7 +40,6 @@ public class DrawUserPair
|
|||||||
private readonly LocationShareService _locationShareService;
|
private readonly LocationShareService _locationShareService;
|
||||||
private readonly CharaDataManager _charaDataManager;
|
private readonly CharaDataManager _charaDataManager;
|
||||||
private readonly PairLedger _pairLedger;
|
private readonly PairLedger _pairLedger;
|
||||||
private readonly IpcCallerLifestream _lifestreamIpc;
|
|
||||||
private float _menuWidth = -1;
|
private float _menuWidth = -1;
|
||||||
private bool _wasHovered = false;
|
private bool _wasHovered = false;
|
||||||
private TooltipSnapshot _tooltipSnapshot = TooltipSnapshot.Empty;
|
private TooltipSnapshot _tooltipSnapshot = TooltipSnapshot.Empty;
|
||||||
@@ -63,8 +60,7 @@ public class DrawUserPair
|
|||||||
LightlessConfigService configService,
|
LightlessConfigService configService,
|
||||||
LocationShareService locationShareService,
|
LocationShareService locationShareService,
|
||||||
CharaDataManager charaDataManager,
|
CharaDataManager charaDataManager,
|
||||||
PairLedger pairLedger,
|
PairLedger pairLedger)
|
||||||
IpcCallerLifestream lifestreamIpc)
|
|
||||||
{
|
{
|
||||||
_id = id;
|
_id = id;
|
||||||
_uiEntry = uiEntry;
|
_uiEntry = uiEntry;
|
||||||
@@ -83,7 +79,6 @@ public class DrawUserPair
|
|||||||
_locationShareService = locationShareService;
|
_locationShareService = locationShareService;
|
||||||
_charaDataManager = charaDataManager;
|
_charaDataManager = charaDataManager;
|
||||||
_pairLedger = pairLedger;
|
_pairLedger = pairLedger;
|
||||||
_lifestreamIpc = lifestreamIpc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public PairDisplayEntry DisplayEntry => _displayEntry;
|
public PairDisplayEntry DisplayEntry => _displayEntry;
|
||||||
@@ -661,13 +656,6 @@ public class DrawUserPair
|
|||||||
using (ImRaii.PushColor(ImGuiCol.Text, shareColor, shareLocation || shareLocationToOther))
|
using (ImRaii.PushColor(ImGuiCol.Text, shareColor, shareLocation || shareLocationToOther))
|
||||||
_uiSharedService.IconText(shareLocationIcon);
|
_uiSharedService.IconText(shareLocationIcon);
|
||||||
|
|
||||||
var popupId = $"LocationPopup_{_pair.UserData.UID}";
|
|
||||||
|
|
||||||
if (ImGui.IsItemClicked(ImGuiMouseButton.Left) && shareLocation && !string.IsNullOrEmpty(location))
|
|
||||||
{
|
|
||||||
ImGui.OpenPopup(popupId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ImGui.IsItemHovered())
|
if (ImGui.IsItemHovered())
|
||||||
{
|
{
|
||||||
ImGui.BeginTooltip();
|
ImGui.BeginTooltip();
|
||||||
@@ -681,8 +669,6 @@ public class DrawUserPair
|
|||||||
_uiSharedService.IconText(FontAwesomeIcon.LocationArrow);
|
_uiSharedService.IconText(FontAwesomeIcon.LocationArrow);
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
ImGui.TextUnformatted(location);
|
ImGui.TextUnformatted(location);
|
||||||
ImGui.Separator();
|
|
||||||
ImGui.TextUnformatted("Click to teleport to this location");
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -714,62 +700,6 @@ public class DrawUserPair
|
|||||||
}
|
}
|
||||||
ImGui.EndTooltip();
|
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)
|
if (individualAnimDisabled || individualSoundsDisabled || individualVFXDisabled || individualIsSticky)
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ using LightlessSync.UI.Handlers;
|
|||||||
using LightlessSync.UI.Models;
|
using LightlessSync.UI.Models;
|
||||||
using LightlessSync.WebAPI;
|
using LightlessSync.WebAPI;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using LightlessSync.Interop.Ipc;
|
|
||||||
|
|
||||||
namespace LightlessSync.UI;
|
namespace LightlessSync.UI;
|
||||||
|
|
||||||
@@ -41,7 +40,6 @@ public class DrawEntityFactory
|
|||||||
private readonly IdDisplayHandler _uidDisplayHandler;
|
private readonly IdDisplayHandler _uidDisplayHandler;
|
||||||
private readonly PairLedger _pairLedger;
|
private readonly PairLedger _pairLedger;
|
||||||
private readonly PairFactory _pairFactory;
|
private readonly PairFactory _pairFactory;
|
||||||
private readonly IpcCallerLifestream _lifestreamIpc;
|
|
||||||
|
|
||||||
public DrawEntityFactory(
|
public DrawEntityFactory(
|
||||||
ILogger<DrawEntityFactory> logger,
|
ILogger<DrawEntityFactory> logger,
|
||||||
@@ -62,8 +60,7 @@ public class DrawEntityFactory
|
|||||||
RenameSyncshellTagUi renameSyncshellTagUi,
|
RenameSyncshellTagUi renameSyncshellTagUi,
|
||||||
SelectSyncshellForTagUi selectSyncshellForTagUi,
|
SelectSyncshellForTagUi selectSyncshellForTagUi,
|
||||||
PairLedger pairLedger,
|
PairLedger pairLedger,
|
||||||
PairFactory pairFactory,
|
PairFactory pairFactory)
|
||||||
IpcCallerLifestream lifestreamIpc)
|
|
||||||
{
|
{
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_apiController = apiController;
|
_apiController = apiController;
|
||||||
@@ -84,7 +81,6 @@ public class DrawEntityFactory
|
|||||||
_selectSyncshellForTagUi = selectSyncshellForTagUi;
|
_selectSyncshellForTagUi = selectSyncshellForTagUi;
|
||||||
_pairLedger = pairLedger;
|
_pairLedger = pairLedger;
|
||||||
_pairFactory = pairFactory;
|
_pairFactory = pairFactory;
|
||||||
_lifestreamIpc = lifestreamIpc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DrawFolderGroup CreateGroupFolder(
|
public DrawFolderGroup CreateGroupFolder(
|
||||||
@@ -171,8 +167,7 @@ public class DrawEntityFactory
|
|||||||
_configService,
|
_configService,
|
||||||
_locationShareService,
|
_locationShareService,
|
||||||
_charaDataManager,
|
_charaDataManager,
|
||||||
_pairLedger,
|
_pairLedger);
|
||||||
_lifestreamIpc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<PairUiEntry> GetAllEntries()
|
public IReadOnlyList<PairUiEntry> GetAllEntries()
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
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