diff --git a/RetainerTrack/Handlers/GameHooks.cs b/RetainerTrack/Handlers/GameHooks.cs index c7af3c6..2c1af26 100644 --- a/RetainerTrack/Handlers/GameHooks.cs +++ b/RetainerTrack/Handlers/GameHooks.cs @@ -95,7 +95,7 @@ internal sealed unsafe class GameHooks : IDisposable if (!string.IsNullOrEmpty(mapping.PlayerName)) { - _logger.LogTrace("Content id {ContentId} belongs to '{Name}'", mapping.ContentId, + _logger.LogDebug("Content id {ContentId} belongs to '{Name}'", mapping.ContentId, mapping.PlayerName); mappings.Add(mapping); } @@ -136,7 +136,7 @@ internal sealed unsafe class GameHooks : IDisposable /// /// Both 1 and 2 are sent to you on login, unprompted. /// - [StructLayout(LayoutKind.Explicit, Size = 0x380)] + [StructLayout(LayoutKind.Explicit, Size = 0x420)] internal struct SocialListResultPage { [FieldOffset(0x10)] private fixed byte Players[10 * 0x58]; @@ -144,7 +144,7 @@ internal sealed unsafe class GameHooks : IDisposable public Span PlayerSpan => new(Unsafe.AsPointer(ref Players[0]), 10); } - [StructLayout(LayoutKind.Explicit, Size = 0x58)] + [StructLayout(LayoutKind.Explicit, Size = 0x68, Pack = 1)] internal struct SocialListPlayer { /// @@ -156,6 +156,6 @@ internal sealed unsafe class GameHooks : IDisposable /// /// This *can* be empty, e.g. if you're querying your friend list, the names are ONLY set for characters on the same world. /// - [FieldOffset(0x31)] public fixed byte CharacterName[32]; + [FieldOffset(0x3C)] public fixed byte CharacterName[32]; } } diff --git a/RetainerTrack/Handlers/PersistenceContext.cs b/RetainerTrack/Handlers/PersistenceContext.cs index 759e0b0..a67ed91 100644 --- a/RetainerTrack/Handlers/PersistenceContext.cs +++ b/RetainerTrack/Handlers/PersistenceContext.cs @@ -89,29 +89,43 @@ internal sealed class PersistenceContext WorldId = worldId, OwnerLocalContentId = l.RetainerOwnerId, }) + .Where(mapping => + { + if (mapping.Name == null) + return true; + + var currentWorldCache = _worldRetainerCache.GetOrAdd(mapping.WorldId, _ => new()); + if (currentWorldCache.TryGetValue(mapping.Name, out ulong playerContentId)) + return mapping.OwnerLocalContentId != playerContentId; + + return true; + }) + .DistinctBy(x => x.LocalContentId) .ToList(); using var scope = _serviceProvider.CreateScope(); using var dbContext = scope.ServiceProvider.GetRequiredService(); - var ids = updates.Select(x => x.LocalContentId).ToList(); - var dbRetainers = dbContext.Retainers.Where(x => ids.Contains(x.LocalContentId)) - .ToDictionary(x => x.LocalContentId, x => x); foreach (var retainer in updates) { - if (dbRetainers.TryGetValue(retainer.LocalContentId, out var dbRetainer)) + Retainer? dbRetainer = dbContext.Retainers.Find(retainer.LocalContentId); + if (dbRetainer != null) { + _logger.LogDebug("Updating retainer {RetainerName} with {LocalContentId}", retainer.Name, retainer.LocalContentId); dbRetainer.Name = retainer.Name; dbRetainer.WorldId = retainer.WorldId; dbRetainer.OwnerLocalContentId = retainer.OwnerLocalContentId; dbContext.Retainers.Update(dbRetainer); } else + { + _logger.LogDebug("Adding retainer {RetainerName} with {LocalContentId}", retainer.Name, retainer.LocalContentId); dbContext.Retainers.Add(retainer); + } if (!_playerNameCache.TryGetValue(retainer.OwnerLocalContentId, out string? ownerName)) ownerName = retainer.OwnerLocalContentId.ToString(CultureInfo.InvariantCulture); - _logger.LogTrace("Retainer {RetainerName} belongs to {OwnerId}", retainer.Name, + _logger.LogDebug(" Retainer {RetainerName} belongs to {OwnerName}", retainer.Name, ownerName); if (retainer.Name != null) @@ -121,7 +135,9 @@ internal sealed class PersistenceContext } } - dbContext.SaveChanges(); + int changeCount = dbContext.SaveChanges(); + if (changeCount > 0) + _logger.LogDebug("Saved {Count} retainer mappings", changeCount); } catch (Exception e) { @@ -153,17 +169,31 @@ internal sealed class PersistenceContext }) .ToList(); + if (updates.Count == 0) + return; using (var scope = _serviceProvider.CreateScope()) { using var dbContext = scope.ServiceProvider.GetRequiredService(); foreach (var update in updates) { - if (!dbContext.Players.Any(x => x.LocalContentId == update.LocalContentId)) - dbContext.Players.AddRange(updates); + var dbPlayer = dbContext.Players.Find(update.LocalContentId); + if (dbPlayer == null) + dbContext.Players.Add(update); + else + { + dbPlayer.Name = update.Name; + dbContext.Players.Update(dbPlayer); + } } - dbContext.SaveChanges(); + int changeCount = dbContext.SaveChanges(); + if (changeCount > 0) + { + _logger.LogDebug("Saved {Count} player mappings", changeCount); + foreach (var update in updates) + _logger.LogTrace(" {ContentId} = {Name}", update.LocalContentId, update.Name); + } } foreach (var player in updates) diff --git a/RetainerTrack/RetainerTrack.csproj b/RetainerTrack/RetainerTrack.csproj index 6aa5fa9..61a604f 100644 --- a/RetainerTrack/RetainerTrack.csproj +++ b/RetainerTrack/RetainerTrack.csproj @@ -2,7 +2,7 @@ net8.0-windows - 3.1 + 3.2 12.0 enable win-x64