adjust to send only one packet for all changes at once
This commit is contained in:
2
MareAPI
2
MareAPI
Submodule MareAPI updated: 96d17b006e...a649b36495
@@ -7,6 +7,7 @@ using Dalamud.Utility;
|
|||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
|
using MareSynchronos.API;
|
||||||
using MareSynchronos.Interop;
|
using MareSynchronos.Interop;
|
||||||
using MareSynchronos.Managers;
|
using MareSynchronos.Managers;
|
||||||
using MareSynchronos.Models;
|
using MareSynchronos.Models;
|
||||||
@@ -30,7 +31,7 @@ public class CharacterDataFactory
|
|||||||
_ipcManager = ipcManager;
|
_ipcManager = ipcManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CharacterData? BuildCharacterData(IntPtr playerPointer)
|
public CharacterData BuildCharacterData(CharacterData previousData, ObjectKind objectKind, IntPtr playerPointer)
|
||||||
{
|
{
|
||||||
if (!_ipcManager.Initialized)
|
if (!_ipcManager.Initialized)
|
||||||
{
|
{
|
||||||
@@ -39,19 +40,22 @@ public class CharacterDataFactory
|
|||||||
|
|
||||||
if (playerPointer == IntPtr.Zero)
|
if (playerPointer == IntPtr.Zero)
|
||||||
{
|
{
|
||||||
return null;
|
Logger.Verbose("Pointer was zero for " + objectKind);
|
||||||
|
previousData.FileReplacements.Remove(objectKind);
|
||||||
|
previousData.GlamourerString.Remove(objectKind);
|
||||||
|
return previousData;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return CreateCharacterData(playerPointer);
|
return CreateCharacterData(previousData, objectKind, playerPointer);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Warn("Failed to create character data");
|
Logger.Warn("Failed to create " + objectKind + " data");
|
||||||
Logger.Warn(e.Message);
|
Logger.Warn(e.Message);
|
||||||
Logger.Warn(e.StackTrace ?? string.Empty);
|
Logger.Warn(e.StackTrace ?? string.Empty);
|
||||||
return null;
|
return previousData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,16 +64,15 @@ public class CharacterDataFactory
|
|||||||
return (string.Join("", Enumerable.Repeat("\t", inheritanceLevel)), string.Join("", Enumerable.Repeat("\t", inheritanceLevel + 2)));
|
return (string.Join("", Enumerable.Repeat("\t", inheritanceLevel)), string.Join("", Enumerable.Repeat("\t", inheritanceLevel + 2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DebugPrint(FileReplacement fileReplacement, string objectKind, string resourceType, int inheritanceLevel)
|
private void DebugPrint(FileReplacement fileReplacement, ObjectKind objectKind, string resourceType, int inheritanceLevel)
|
||||||
{
|
{
|
||||||
var indentation = GetIndentationForInheritanceLevel(inheritanceLevel);
|
var indentation = GetIndentationForInheritanceLevel(inheritanceLevel);
|
||||||
objectKind += string.IsNullOrEmpty(objectKind) ? "" : " ";
|
|
||||||
|
|
||||||
Logger.Verbose(indentation.Item1 + objectKind + resourceType + " [" + string.Join(", ", fileReplacement.GamePaths) + "]");
|
Logger.Verbose(indentation.Item1 + objectKind + resourceType + " [" + string.Join(", ", fileReplacement.GamePaths) + "]");
|
||||||
Logger.Verbose(indentation.Item2 + "=> " + fileReplacement.ResolvedPath);
|
Logger.Verbose(indentation.Item2 + "=> " + fileReplacement.ResolvedPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void AddReplacementsFromRenderModel(RenderModel* mdl, CharacterData cache, int inheritanceLevel = 0, string objectKind = "")
|
private unsafe void AddReplacementsFromRenderModel(RenderModel* mdl, ObjectKind objectKind, CharacterData cache, int inheritanceLevel = 0)
|
||||||
{
|
{
|
||||||
if (mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara)
|
if (mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara)
|
||||||
{
|
{
|
||||||
@@ -91,18 +94,18 @@ public class CharacterDataFactory
|
|||||||
FileReplacement mdlFileReplacement = CreateFileReplacement(mdlPath);
|
FileReplacement mdlFileReplacement = CreateFileReplacement(mdlPath);
|
||||||
DebugPrint(mdlFileReplacement, objectKind, "Model", inheritanceLevel);
|
DebugPrint(mdlFileReplacement, objectKind, "Model", inheritanceLevel);
|
||||||
|
|
||||||
cache.AddFileReplacement(mdlFileReplacement);
|
cache.AddFileReplacement(objectKind, mdlFileReplacement);
|
||||||
|
|
||||||
for (var mtrlIdx = 0; mtrlIdx < mdl->MaterialCount; mtrlIdx++)
|
for (var mtrlIdx = 0; mtrlIdx < mdl->MaterialCount; mtrlIdx++)
|
||||||
{
|
{
|
||||||
var mtrl = (Material*)mdl->Materials[mtrlIdx];
|
var mtrl = (Material*)mdl->Materials[mtrlIdx];
|
||||||
if (mtrl == null) continue;
|
if (mtrl == null) continue;
|
||||||
|
|
||||||
AddReplacementsFromMaterial(mtrl, cache, inheritanceLevel + 1, objectKind);
|
AddReplacementsFromMaterial(mtrl, objectKind, cache, inheritanceLevel + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void AddReplacementsFromMaterial(Material* mtrl, CharacterData cache, int inheritanceLevel = 0, string objectKind = "")
|
private unsafe void AddReplacementsFromMaterial(Material* mtrl, ObjectKind objectKind, CharacterData cache, int inheritanceLevel = 0)
|
||||||
{
|
{
|
||||||
string fileName;
|
string fileName;
|
||||||
try
|
try
|
||||||
@@ -122,7 +125,7 @@ public class CharacterDataFactory
|
|||||||
var mtrlFileReplacement = CreateFileReplacement(mtrlPath);
|
var mtrlFileReplacement = CreateFileReplacement(mtrlPath);
|
||||||
DebugPrint(mtrlFileReplacement, objectKind, "Material", inheritanceLevel);
|
DebugPrint(mtrlFileReplacement, objectKind, "Material", inheritanceLevel);
|
||||||
|
|
||||||
cache.AddFileReplacement(mtrlFileReplacement);
|
cache.AddFileReplacement(objectKind, mtrlFileReplacement);
|
||||||
|
|
||||||
var mtrlResourceHandle = (MtrlResource*)mtrl->ResourceHandle;
|
var mtrlResourceHandle = (MtrlResource*)mtrl->ResourceHandle;
|
||||||
for (var resIdx = 0; resIdx < mtrlResourceHandle->NumTex; resIdx++)
|
for (var resIdx = 0; resIdx < mtrlResourceHandle->NumTex; resIdx++)
|
||||||
@@ -131,11 +134,11 @@ public class CharacterDataFactory
|
|||||||
|
|
||||||
if (string.IsNullOrEmpty(texPath)) continue;
|
if (string.IsNullOrEmpty(texPath)) continue;
|
||||||
|
|
||||||
AddReplacementsFromTexture(texPath, cache, inheritanceLevel + 1, objectKind);
|
AddReplacementsFromTexture(texPath, objectKind, cache, inheritanceLevel + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddReplacementsFromTexture(string texPath, CharacterData cache, int inheritanceLevel = 0, string objectKind = "", bool doNotReverseResolve = true)
|
private void AddReplacementsFromTexture(string texPath, ObjectKind objectKind, CharacterData cache, int inheritanceLevel = 0, bool doNotReverseResolve = true)
|
||||||
{
|
{
|
||||||
if (texPath.IsNullOrEmpty()) return;
|
if (texPath.IsNullOrEmpty()) return;
|
||||||
|
|
||||||
@@ -144,7 +147,7 @@ public class CharacterDataFactory
|
|||||||
var texFileReplacement = CreateFileReplacement(texPath, doNotReverseResolve);
|
var texFileReplacement = CreateFileReplacement(texPath, doNotReverseResolve);
|
||||||
DebugPrint(texFileReplacement, objectKind, "Texture", inheritanceLevel);
|
DebugPrint(texFileReplacement, objectKind, "Texture", inheritanceLevel);
|
||||||
|
|
||||||
cache.AddFileReplacement(texFileReplacement);
|
cache.AddFileReplacement(objectKind, texFileReplacement);
|
||||||
|
|
||||||
if (texPath.Contains("/--")) return;
|
if (texPath.Contains("/--")) return;
|
||||||
|
|
||||||
@@ -153,10 +156,10 @@ public class CharacterDataFactory
|
|||||||
|
|
||||||
DebugPrint(texDx11Replacement, objectKind, "Texture (DX11)", inheritanceLevel);
|
DebugPrint(texDx11Replacement, objectKind, "Texture (DX11)", inheritanceLevel);
|
||||||
|
|
||||||
cache.AddFileReplacement(texDx11Replacement);
|
cache.AddFileReplacement(objectKind, texDx11Replacement);
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe CharacterData CreateCharacterData(IntPtr charaPointer)
|
private unsafe CharacterData CreateCharacterData(CharacterData previousData, ObjectKind objectKind, IntPtr charaPointer)
|
||||||
{
|
{
|
||||||
Stopwatch st = Stopwatch.StartNew();
|
Stopwatch st = Stopwatch.StartNew();
|
||||||
var chara = _dalamudUtil.CreateGameObject(charaPointer)!;
|
var chara = _dalamudUtil.CreateGameObject(charaPointer)!;
|
||||||
@@ -166,20 +169,18 @@ public class CharacterDataFactory
|
|||||||
Thread.Sleep(50);
|
Thread.Sleep(50);
|
||||||
}
|
}
|
||||||
_dalamudUtil.WaitWhileCharacterIsDrawing(charaPointer);
|
_dalamudUtil.WaitWhileCharacterIsDrawing(charaPointer);
|
||||||
var cache = new CharacterData()
|
|
||||||
{
|
|
||||||
ManipulationString = _ipcManager.PenumbraGetMetaManipulations(),
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
if (previousData.FileReplacements.ContainsKey(objectKind))
|
||||||
{
|
{
|
||||||
cache.GlamourerString = _ipcManager.GlamourerGetCharacterCustomization(chara);
|
previousData.FileReplacements[objectKind].Clear();
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// might not have glamourer data
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
previousData.ManipulationString = _ipcManager.PenumbraGetMetaManipulations();
|
||||||
|
|
||||||
|
if (objectKind is not ObjectKind.Mount)
|
||||||
|
{
|
||||||
|
previousData.GlamourerString[objectKind] = _ipcManager.GlamourerGetCharacterCustomization(chara);
|
||||||
|
}
|
||||||
|
|
||||||
var human = (Human*)((Character*)charaPointer)->GameObject.GetDrawObject();
|
var human = (Human*)((Character*)charaPointer)->GameObject.GetDrawObject();
|
||||||
for (var mdlIdx = 0; mdlIdx < human->CharacterBase.SlotCount; ++mdlIdx)
|
for (var mdlIdx = 0; mdlIdx < human->CharacterBase.SlotCount; ++mdlIdx)
|
||||||
@@ -190,54 +191,50 @@ public class CharacterDataFactory
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
AddReplacementsFromRenderModel(mdl, cache, 0, "Character");
|
AddReplacementsFromRenderModel(mdl, objectKind, previousData, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
var weaponObject = (Weapon*)((Object*)human)->ChildObject;
|
if (objectKind == ObjectKind.Player)
|
||||||
|
|
||||||
if ((IntPtr)weaponObject != IntPtr.Zero)
|
|
||||||
{
|
{
|
||||||
var mainHandWeapon = weaponObject->WeaponRenderModel->RenderModel;
|
var weaponObject = (Weapon*)((Object*)human)->ChildObject;
|
||||||
|
|
||||||
AddReplacementsFromRenderModel(mainHandWeapon, cache, 0, "Weapon");
|
if ((IntPtr)weaponObject != IntPtr.Zero)
|
||||||
|
|
||||||
if (weaponObject->NextSibling != (IntPtr)weaponObject)
|
|
||||||
{
|
{
|
||||||
var offHandWeapon = ((Weapon*)weaponObject->NextSibling)->WeaponRenderModel->RenderModel;
|
var mainHandWeapon = weaponObject->WeaponRenderModel->RenderModel;
|
||||||
|
|
||||||
AddReplacementsFromRenderModel(offHandWeapon, cache, 1, "OffHand Weapon");
|
AddReplacementsFromRenderModel(mainHandWeapon, objectKind, previousData, 0);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(cache.GlamourerString))
|
if (weaponObject->NextSibling != (IntPtr)weaponObject)
|
||||||
{
|
{
|
||||||
try
|
var offHandWeapon = ((Weapon*)weaponObject->NextSibling)->WeaponRenderModel->RenderModel;
|
||||||
{
|
|
||||||
AddReplacementSkeleton(((HumanExt*)human)->Human.RaceSexId, cache);
|
AddReplacementsFromRenderModel(offHandWeapon, objectKind, previousData, 1);
|
||||||
AddReplacementsFromTexture(new Utf8String(((HumanExt*)human)->Decal->FileName()).ToString(), cache, 0, "Decal", false);
|
}
|
||||||
AddReplacementsFromTexture(new Utf8String(((HumanExt*)human)->LegacyBodyDecal->FileName()).ToString(), cache, 0, "Legacy Decal", false);
|
|
||||||
}
|
}
|
||||||
catch { }
|
|
||||||
|
AddReplacementSkeleton(((HumanExt*)human)->Human.RaceSexId, objectKind, previousData);
|
||||||
|
AddReplacementsFromTexture(new Utf8String(((HumanExt*)human)->Decal->FileName()).ToString(), objectKind, previousData, 0, false);
|
||||||
|
AddReplacementsFromTexture(new Utf8String(((HumanExt*)human)->LegacyBodyDecal->FileName()).ToString(), objectKind, previousData, 0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
st.Stop();
|
st.Stop();
|
||||||
Logger.Verbose("Building Character Data took " + st.Elapsed);
|
Logger.Verbose("Building " + objectKind + " Data took " + st.Elapsed);
|
||||||
|
|
||||||
return cache;
|
return previousData;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddReplacementSkeleton(ushort raceSexId, CharacterData cache)
|
private void AddReplacementSkeleton(ushort raceSexId, ObjectKind objectKind, CharacterData cache)
|
||||||
{
|
{
|
||||||
string raceSexIdString = raceSexId.ToString("0000");
|
string raceSexIdString = raceSexId.ToString("0000");
|
||||||
|
|
||||||
string skeletonPath = $"chara/human/c{raceSexIdString}/skeleton/base/b0001/skl_c{raceSexIdString}b0001.sklb";
|
string skeletonPath = $"chara/human/c{raceSexIdString}/skeleton/base/b0001/skl_c{raceSexIdString}b0001.sklb";
|
||||||
|
|
||||||
Logger.Verbose("Adding File Replacement for Skeleton " + skeletonPath);
|
//Logger.Verbose("Adding File Replacement for Skeleton " + skeletonPath);
|
||||||
|
|
||||||
var replacement = CreateFileReplacement(skeletonPath, true);
|
var replacement = CreateFileReplacement(skeletonPath, true);
|
||||||
cache.AddFileReplacement(replacement);
|
cache.AddFileReplacement(objectKind, replacement);
|
||||||
|
|
||||||
DebugPrint(replacement, "Skeleton", "SKLB", 0);
|
DebugPrint(replacement, objectKind, "SKLB", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private FileReplacement CreateFileReplacement(string path, bool doNotReverseResolve = false)
|
private FileReplacement CreateFileReplacement(string path, bool doNotReverseResolve = false)
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public class CachedPlayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
private bool _isDisposed = false;
|
private bool _isDisposed = false;
|
||||||
private Dictionary<ObjectKind, CancellationTokenSource?> _downloadCancellationTokenSources = new();
|
private CancellationTokenSource? _downloadCancellationTokenSource = new();
|
||||||
|
|
||||||
private string _lastGlamourerData = string.Empty;
|
private string _lastGlamourerData = string.Empty;
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ public class CachedPlayer
|
|||||||
|
|
||||||
public bool WasVisible { get; private set; }
|
public bool WasVisible { get; private set; }
|
||||||
|
|
||||||
private readonly Dictionary<ObjectKind, CharacterCacheDto> _cache = new();
|
private CharacterCacheDto _cachedData = new();
|
||||||
|
|
||||||
private CharacterEquipment? _currentCharacterEquipment;
|
private CharacterEquipment? _currentCharacterEquipment;
|
||||||
|
|
||||||
@@ -67,33 +67,41 @@ public class CachedPlayer
|
|||||||
Logger.Debug("Received data for " + this);
|
Logger.Debug("Received data for " + this);
|
||||||
|
|
||||||
Logger.Debug("Checking for files to download for player " + PlayerName);
|
Logger.Debug("Checking for files to download for player " + PlayerName);
|
||||||
Logger.Debug("Hash for data is " + characterData.Hash);
|
Logger.Debug("Hash for data is " + characterData.GetHashCode());
|
||||||
|
|
||||||
if (!_cache.ContainsKey(characterData.ObjectKind))
|
if (characterData.GetHashCode() == _cachedData.GetHashCode()) return;
|
||||||
|
|
||||||
|
List<ObjectKind> charaDataToUpdate = new List<ObjectKind>();
|
||||||
|
foreach (var objectKind in Enum.GetValues<ObjectKind>())
|
||||||
{
|
{
|
||||||
_cache.Add(characterData.ObjectKind, new());
|
bool doesntContainKey = !_cachedData.FileReplacements.ContainsKey(objectKind)
|
||||||
_downloadCancellationTokenSources.Add(characterData.ObjectKind, null);
|
|| (_cachedData.FileReplacements.ContainsKey(objectKind) && !characterData.FileReplacements.ContainsKey(objectKind));
|
||||||
|
if (doesntContainKey)
|
||||||
|
{
|
||||||
|
charaDataToUpdate.Add(objectKind);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool listsAreEqual = Enumerable.SequenceEqual(_cachedData.FileReplacements[objectKind], characterData.FileReplacements[objectKind]);
|
||||||
|
bool glamourerDataDifferent = _cachedData.GlamourerData[objectKind] != characterData.GlamourerData[objectKind];
|
||||||
|
if (!listsAreEqual || glamourerDataDifferent)
|
||||||
|
{
|
||||||
|
Logger.Debug("Updating " + objectKind);
|
||||||
|
|
||||||
|
charaDataToUpdate.Add(objectKind);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_cache.TryGetValue(characterData.ObjectKind, out var cachedDto);
|
_cachedData = characterData;
|
||||||
if ((cachedDto?.Hash ?? string.Empty) != characterData.Hash)
|
|
||||||
{
|
|
||||||
Logger.Debug("Received total " + characterData.FileReplacements.Count + " file replacement data");
|
|
||||||
_cache[characterData.ObjectKind] = characterData;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Logger.Debug("Had valid local cache for " + PlayerName);
|
|
||||||
}
|
|
||||||
|
|
||||||
DownloadAndApplyCharacter(characterData.ObjectKind);
|
DownloadAndApplyCharacter(charaDataToUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DownloadAndApplyCharacter(ObjectKind objectKind)
|
private void DownloadAndApplyCharacter(List<ObjectKind> objectKind)
|
||||||
{
|
{
|
||||||
_downloadCancellationTokenSources[objectKind]?.Cancel();
|
_downloadCancellationTokenSource?.Cancel();
|
||||||
_downloadCancellationTokenSources[objectKind] = new CancellationTokenSource();
|
_downloadCancellationTokenSource = new CancellationTokenSource();
|
||||||
var downloadToken = _downloadCancellationTokenSources[objectKind]!.Token;
|
var downloadToken = _downloadCancellationTokenSource.Token;
|
||||||
var downloadId = _apiController.GetDownloadId();
|
var downloadId = _apiController.GetDownloadId();
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
@@ -103,7 +111,7 @@ public class CachedPlayer
|
|||||||
int attempts = 0;
|
int attempts = 0;
|
||||||
while ((toDownloadReplacements = TryCalculateModdedDictionary(out moddedPaths)).Count > 0 && attempts++ <= 10)
|
while ((toDownloadReplacements = TryCalculateModdedDictionary(out moddedPaths)).Count > 0 && attempts++ <= 10)
|
||||||
{
|
{
|
||||||
Logger.Debug("Downloading missing files for player " + PlayerName);
|
Logger.Debug("Downloading missing files for player " + PlayerName + ", kind: " + objectKind);
|
||||||
await _apiController.DownloadFiles(downloadId, toDownloadReplacements, downloadToken);
|
await _apiController.DownloadFiles(downloadId, toDownloadReplacements, downloadToken);
|
||||||
if (downloadToken.IsCancellationRequested)
|
if (downloadToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@@ -127,7 +135,12 @@ public class CachedPlayer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplyCharacterData(objectKind, moddedPaths);
|
ApplyBaseData(moddedPaths);
|
||||||
|
|
||||||
|
foreach (var kind in objectKind)
|
||||||
|
{
|
||||||
|
ApplyCustomizationData(kind);
|
||||||
|
}
|
||||||
}, downloadToken).ContinueWith(task =>
|
}, downloadToken).ContinueWith(task =>
|
||||||
{
|
{
|
||||||
if (!task.IsCanceled) return;
|
if (!task.IsCanceled) return;
|
||||||
@@ -144,7 +157,7 @@ public class CachedPlayer
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var db = new FileCacheContext();
|
using var db = new FileCacheContext();
|
||||||
foreach (var item in _cache.SelectMany(c => c.Value.FileReplacements).ToList())
|
foreach (var item in _cachedData.FileReplacements.SelectMany(k => k.Value).ToList())
|
||||||
{
|
{
|
||||||
foreach (var gamePath in item.GamePaths)
|
foreach (var gamePath in item.GamePaths)
|
||||||
{
|
{
|
||||||
@@ -168,14 +181,16 @@ public class CachedPlayer
|
|||||||
return missingFiles;
|
return missingFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void ApplyCharacterData(ObjectKind objectKind, Dictionary<string, string> moddedPaths)
|
private void ApplyBaseData(Dictionary<string, string> moddedPaths)
|
||||||
{
|
{
|
||||||
if (PlayerCharacter is null) return;
|
|
||||||
var cache = _cache[objectKind];
|
|
||||||
|
|
||||||
_ipcManager.PenumbraRemoveTemporaryCollection(PlayerName!);
|
_ipcManager.PenumbraRemoveTemporaryCollection(PlayerName!);
|
||||||
var tempCollection = _ipcManager.PenumbraCreateTemporaryCollection(PlayerName!);
|
var tempCollection = _ipcManager.PenumbraCreateTemporaryCollection(PlayerName!);
|
||||||
_ipcManager.PenumbraSetTemporaryMods(tempCollection, moddedPaths, _cache.First().Value.ManipulationData);
|
_ipcManager.PenumbraSetTemporaryMods(tempCollection, moddedPaths, _cachedData.ManipulationData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void ApplyCustomizationData(ObjectKind objectKind)
|
||||||
|
{
|
||||||
|
if (PlayerCharacter is null) return;
|
||||||
|
|
||||||
if (objectKind == ObjectKind.Player)
|
if (objectKind == ObjectKind.Player)
|
||||||
{
|
{
|
||||||
@@ -183,7 +198,7 @@ public class CachedPlayer
|
|||||||
RequestedPenumbraRedraw = true;
|
RequestedPenumbraRedraw = true;
|
||||||
Logger.Debug(
|
Logger.Debug(
|
||||||
$"Request Redraw for {PlayerName}");
|
$"Request Redraw for {PlayerName}");
|
||||||
_ipcManager.GlamourerApplyAll(cache.GlamourerData, PlayerCharacter!);
|
_ipcManager.GlamourerApplyAll(_cachedData.GlamourerData[objectKind], PlayerCharacter!);
|
||||||
}
|
}
|
||||||
else if (objectKind == ObjectKind.Minion)
|
else if (objectKind == ObjectKind.Minion)
|
||||||
{
|
{
|
||||||
@@ -191,7 +206,7 @@ public class CachedPlayer
|
|||||||
if (minion != null)
|
if (minion != null)
|
||||||
{
|
{
|
||||||
Logger.Debug($"Request Redraw for Minion");
|
Logger.Debug($"Request Redraw for Minion");
|
||||||
_ipcManager.GlamourerApplyAll(cache.GlamourerData, obj: (IntPtr)minion);
|
_ipcManager.GlamourerApplyAll(_cachedData.GlamourerData[objectKind], obj: (IntPtr)minion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (objectKind == ObjectKind.Pet)
|
else if (objectKind == ObjectKind.Pet)
|
||||||
@@ -200,7 +215,7 @@ public class CachedPlayer
|
|||||||
if (pet != IntPtr.Zero)
|
if (pet != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
Logger.Debug("Request Redraw for Pet");
|
Logger.Debug("Request Redraw for Pet");
|
||||||
_ipcManager.GlamourerApplyAll(cache.GlamourerData, pet);
|
_ipcManager.GlamourerApplyAll(_cachedData.GlamourerData[objectKind], pet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (objectKind == ObjectKind.Companion)
|
else if (objectKind == ObjectKind.Companion)
|
||||||
@@ -209,7 +224,7 @@ public class CachedPlayer
|
|||||||
if (companion != IntPtr.Zero)
|
if (companion != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
Logger.Debug("Request Redraw for Companion");
|
Logger.Debug("Request Redraw for Companion");
|
||||||
_ipcManager.GlamourerApplyAll(cache.GlamourerData, companion);
|
_ipcManager.GlamourerApplyAll(_cachedData.GlamourerData[objectKind], companion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (objectKind == ObjectKind.Mount)
|
else if (objectKind == ObjectKind.Mount)
|
||||||
@@ -223,6 +238,49 @@ public class CachedPlayer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private unsafe void RevertCustomizationData(ObjectKind objectKind)
|
||||||
|
{
|
||||||
|
if (PlayerCharacter is null) return;
|
||||||
|
|
||||||
|
if (objectKind == ObjectKind.Player)
|
||||||
|
{
|
||||||
|
_ipcManager.GlamourerApplyOnlyCustomization(_originalGlamourerData, PlayerCharacter);
|
||||||
|
_ipcManager.GlamourerApplyOnlyEquipment(_lastGlamourerData, PlayerCharacter);
|
||||||
|
}
|
||||||
|
else if (objectKind == ObjectKind.Minion)
|
||||||
|
{
|
||||||
|
var minion = ((Character*)PlayerCharacter.Address)->CompanionObject;
|
||||||
|
if (minion != null)
|
||||||
|
{
|
||||||
|
_ipcManager.PenumbraRedraw((IntPtr)minion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (objectKind == ObjectKind.Pet)
|
||||||
|
{
|
||||||
|
var pet = _dalamudUtil.GetPet(PlayerCharacter.Address);
|
||||||
|
if (pet != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
_ipcManager.PenumbraRedraw(pet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (objectKind == ObjectKind.Companion)
|
||||||
|
{
|
||||||
|
var companion = _dalamudUtil.GetCompanion(PlayerCharacter.Address);
|
||||||
|
if (companion != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
_ipcManager.PenumbraRedraw(companion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (objectKind == ObjectKind.Mount)
|
||||||
|
{
|
||||||
|
var mount = ((CharaExt*)PlayerCharacter.Address)->Mount;
|
||||||
|
if (mount != null)
|
||||||
|
{
|
||||||
|
_ipcManager.PenumbraRedraw((IntPtr)mount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void DisposePlayer()
|
public void DisposePlayer()
|
||||||
{
|
{
|
||||||
if (_isDisposed) return;
|
if (_isDisposed) return;
|
||||||
@@ -234,16 +292,15 @@ public class CachedPlayer
|
|||||||
Logger.Verbose("Restoring state for " + PlayerName);
|
Logger.Verbose("Restoring state for " + PlayerName);
|
||||||
_dalamudUtil.FrameworkUpdate -= DalamudUtilOnFrameworkUpdate;
|
_dalamudUtil.FrameworkUpdate -= DalamudUtilOnFrameworkUpdate;
|
||||||
_ipcManager.PenumbraRedrawEvent -= IpcManagerOnPenumbraRedrawEvent;
|
_ipcManager.PenumbraRedrawEvent -= IpcManagerOnPenumbraRedrawEvent;
|
||||||
foreach (var token in _downloadCancellationTokenSources)
|
_downloadCancellationTokenSource?.Cancel();
|
||||||
{
|
_downloadCancellationTokenSource?.Dispose();
|
||||||
token.Value?.Cancel();
|
|
||||||
token.Value?.Dispose();
|
|
||||||
}
|
|
||||||
_ipcManager.PenumbraRemoveTemporaryCollection(PlayerName);
|
_ipcManager.PenumbraRemoveTemporaryCollection(PlayerName);
|
||||||
if (PlayerCharacter != null && PlayerCharacter.IsValid())
|
if (PlayerCharacter != null && PlayerCharacter.IsValid())
|
||||||
{
|
{
|
||||||
_ipcManager.GlamourerApplyOnlyCustomization(_originalGlamourerData, PlayerCharacter);
|
foreach (var item in _cachedData.FileReplacements)
|
||||||
_ipcManager.GlamourerApplyOnlyEquipment(_lastGlamourerData, PlayerCharacter);
|
{
|
||||||
|
RevertCustomizationData(item.Key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -315,8 +372,7 @@ public class CachedPlayer
|
|||||||
if (RequestedPenumbraRedraw == false)
|
if (RequestedPenumbraRedraw == false)
|
||||||
{
|
{
|
||||||
Logger.Warn("Unauthorized character change detected");
|
Logger.Warn("Unauthorized character change detected");
|
||||||
// todo for everything I guess
|
ApplyCustomizationData(ObjectKind.Player);
|
||||||
DownloadAndApplyCharacter(ObjectKind.Player);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -129,7 +129,6 @@ namespace MareSynchronos.Managers
|
|||||||
public void GlamourerApplyAll(string customization, GameObject character)
|
public void GlamourerApplyAll(string customization, GameObject character)
|
||||||
{
|
{
|
||||||
if (!CheckGlamourerApi()) return;
|
if (!CheckGlamourerApi()) return;
|
||||||
Logger.Verbose("Glamourer apply all to " + character);
|
|
||||||
_glamourerApplyAll!.InvokeAction(customization, character);
|
_glamourerApplyAll!.InvokeAction(customization, character);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +149,12 @@ namespace MareSynchronos.Managers
|
|||||||
public string GlamourerGetCharacterCustomization(GameObject character)
|
public string GlamourerGetCharacterCustomization(GameObject character)
|
||||||
{
|
{
|
||||||
if (!CheckGlamourerApi()) return string.Empty;
|
if (!CheckGlamourerApi()) return string.Empty;
|
||||||
return _glamourerGetAllCustomization!.InvokeFunc(character);
|
var glamourerString = _glamourerGetAllCustomization!.InvokeFunc(character);
|
||||||
|
byte[] bytes = Convert.FromBase64String(glamourerString);
|
||||||
|
// ignore transparency
|
||||||
|
bytes[88] = 128;
|
||||||
|
bytes[89] = 63;
|
||||||
|
return Convert.ToBase64String(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void GlamourerRevertCharacterCustomization(GameObject character)
|
public void GlamourerRevertCharacterCustomization(GameObject character)
|
||||||
@@ -206,7 +210,7 @@ namespace MareSynchronos.Managers
|
|||||||
{
|
{
|
||||||
if (!CheckPenumbraApi()) return null;
|
if (!CheckPenumbraApi()) return null;
|
||||||
var resolvedPath = _penumbraResolvePlayer!.InvokeFunc(path);
|
var resolvedPath = _penumbraResolvePlayer!.InvokeFunc(path);
|
||||||
Logger.Verbose("Resolved " + path + "=>" + string.Join(", ", resolvedPath));
|
//Logger.Verbose("Resolved " + path + "=>" + string.Join(", ", resolvedPath));
|
||||||
return resolvedPath;
|
return resolvedPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +222,7 @@ namespace MareSynchronos.Managers
|
|||||||
{
|
{
|
||||||
resolvedPaths = new[] { path };
|
resolvedPaths = new[] { path };
|
||||||
}
|
}
|
||||||
Logger.Verbose("Reverse Resolved " + path + "=>" + string.Join(", ", resolvedPaths));
|
//Logger.Verbose("Reverse Resolved " + path + "=>" + string.Join(", ", resolvedPaths));
|
||||||
return resolvedPaths;
|
return resolvedPaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,9 +71,9 @@ public class OnlinePlayerManager : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PlayerManagerOnPlayerHasChanged(CharacterCacheDto characterCache, ObjectKind objectKind)
|
private void PlayerManagerOnPlayerHasChanged(CharacterCacheDto characterCache)
|
||||||
{
|
{
|
||||||
PushCharacterData(OnlineVisiblePlayerHashes, objectKind);
|
PushCharacterData(OnlineVisiblePlayerHashes);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApiControllerOnConnected()
|
private void ApiControllerOnConnected()
|
||||||
@@ -173,7 +173,7 @@ public class OnlinePlayerManager : IDisposable
|
|||||||
{
|
{
|
||||||
if (_onlineCachedPlayers.Any(p => p.PlayerNameHash == characterNameHash))
|
if (_onlineCachedPlayers.Any(p => p.PlayerNameHash == characterNameHash))
|
||||||
{
|
{
|
||||||
PushCharacterData(new List<string>() { characterNameHash }, ObjectKind.Player);
|
PushCharacterData(new List<string>() { characterNameHash });
|
||||||
_playerTokenDisposal.TryGetValue(_onlineCachedPlayers.Single(p => p.PlayerNameHash == characterNameHash), out var cancellationTokenSource);
|
_playerTokenDisposal.TryGetValue(_onlineCachedPlayers.Single(p => p.PlayerNameHash == characterNameHash), out var cancellationTokenSource);
|
||||||
cancellationTokenSource?.Cancel();
|
cancellationTokenSource?.Cancel();
|
||||||
return;
|
return;
|
||||||
@@ -242,25 +242,19 @@ public class OnlinePlayerManager : IDisposable
|
|||||||
if (newlyVisiblePlayers.Any())
|
if (newlyVisiblePlayers.Any())
|
||||||
{
|
{
|
||||||
Logger.Verbose("Has new visible players, pushing character data");
|
Logger.Verbose("Has new visible players, pushing character data");
|
||||||
PushCharacterData(newlyVisiblePlayers, ObjectKind.Player);
|
PushCharacterData(newlyVisiblePlayers);
|
||||||
PushCharacterData(newlyVisiblePlayers, ObjectKind.Pet);
|
|
||||||
PushCharacterData(newlyVisiblePlayers, ObjectKind.Minion);
|
|
||||||
PushCharacterData(newlyVisiblePlayers, ObjectKind.Companion);
|
|
||||||
PushCharacterData(newlyVisiblePlayers, ObjectKind.Mount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_lastPlayerObjectCheck = DateTime.Now;
|
_lastPlayerObjectCheck = DateTime.Now;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PushCharacterData(List<string> visiblePlayers, ObjectKind objectKind)
|
private void PushCharacterData(List<string> visiblePlayers)
|
||||||
{
|
{
|
||||||
_playerManager.LastSentCharacterData.TryGetValue(objectKind, out var characterData);
|
if (visiblePlayers.Any() && _playerManager.LastCreatedCharacterData != null)
|
||||||
if (visiblePlayers.Any() && characterData != null)
|
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
Logger.Verbose(JsonConvert.SerializeObject(_playerManager.LastSentCharacterData[objectKind], Formatting.Indented));
|
await _apiController.PushCharacterData(_playerManager.LastCreatedCharacterData!,
|
||||||
await _apiController.PushCharacterData(_playerManager.LastSentCharacterData[objectKind]!,
|
|
||||||
visiblePlayers);
|
visiblePlayers);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
using MareSynchronos.Factories;
|
using MareSynchronos.Factories;
|
||||||
using MareSynchronos.Utils;
|
using MareSynchronos.Utils;
|
||||||
using MareSynchronos.WebAPI;
|
using MareSynchronos.WebAPI;
|
||||||
using Newtonsoft.Json;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using MareSynchronos.API;
|
using MareSynchronos.API;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MareSynchronos.Models;
|
using MareSynchronos.Models;
|
||||||
@@ -17,7 +14,7 @@ using MareSynchronos.Interop;
|
|||||||
|
|
||||||
namespace MareSynchronos.Managers
|
namespace MareSynchronos.Managers
|
||||||
{
|
{
|
||||||
public delegate void PlayerHasChanged(CharacterCacheDto characterCache, ObjectKind objectKind);
|
public delegate void PlayerHasChanged(CharacterCacheDto characterCache);
|
||||||
|
|
||||||
public class PlayerManager : IDisposable
|
public class PlayerManager : IDisposable
|
||||||
{
|
{
|
||||||
@@ -26,17 +23,15 @@ namespace MareSynchronos.Managers
|
|||||||
private readonly DalamudUtil _dalamudUtil;
|
private readonly DalamudUtil _dalamudUtil;
|
||||||
private readonly IpcManager _ipcManager;
|
private readonly IpcManager _ipcManager;
|
||||||
public event PlayerHasChanged? PlayerHasChanged;
|
public event PlayerHasChanged? PlayerHasChanged;
|
||||||
public bool SendingData { get; private set; }
|
public CharacterCacheDto? LastCreatedCharacterData { get; private set; }
|
||||||
public Dictionary<ObjectKind, CharacterCacheDto?> LastSentCharacterData { get; private set; } = new();
|
public CharacterData PermanentDataCache { get; private set; } = new();
|
||||||
|
private readonly Dictionary<ObjectKind, Func<bool>> objectKindsToUpdate = new();
|
||||||
|
|
||||||
private Dictionary<ObjectKind, CancellationTokenSource?> _playerChangedCts = new();
|
private CancellationTokenSource? _playerChangedCts = new();
|
||||||
private DateTime _lastPlayerObjectCheck;
|
private DateTime _lastPlayerObjectCheck;
|
||||||
private Dictionary<ObjectKind, CharacterEquipment?> _currentCharacterEquipment = new();
|
private CharacterEquipment? _currentCharacterEquipment = new();
|
||||||
|
|
||||||
private PlayerAttachedObject Minion;
|
private List<PlayerOrRelatedObject> playerAttachedObjects = new List<PlayerOrRelatedObject>();
|
||||||
private PlayerAttachedObject Pet;
|
|
||||||
private PlayerAttachedObject Companion;
|
|
||||||
private PlayerAttachedObject Mount;
|
|
||||||
|
|
||||||
public unsafe PlayerManager(ApiController apiController, IpcManager ipcManager,
|
public unsafe PlayerManager(ApiController apiController, IpcManager ipcManager,
|
||||||
CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil)
|
CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil)
|
||||||
@@ -50,6 +45,7 @@ namespace MareSynchronos.Managers
|
|||||||
|
|
||||||
_apiController.Connected += ApiControllerOnConnected;
|
_apiController.Connected += ApiControllerOnConnected;
|
||||||
_apiController.Disconnected += ApiController_Disconnected;
|
_apiController.Disconnected += ApiController_Disconnected;
|
||||||
|
_dalamudUtil.FrameworkUpdate += DalamudUtilOnFrameworkUpdate;
|
||||||
|
|
||||||
Logger.Debug("Watching Player, ApiController is Connected: " + _apiController.IsConnected);
|
Logger.Debug("Watching Player, ApiController is Connected: " + _apiController.IsConnected);
|
||||||
if (_apiController.IsConnected)
|
if (_apiController.IsConnected)
|
||||||
@@ -57,10 +53,14 @@ namespace MareSynchronos.Managers
|
|||||||
ApiControllerOnConnected();
|
ApiControllerOnConnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
Minion = new PlayerAttachedObject(ObjectKind.Minion, IntPtr.Zero, IntPtr.Zero, () => (IntPtr)((Character*)_dalamudUtil.PlayerPointer)->CompanionObject);
|
playerAttachedObjects = new List<PlayerOrRelatedObject>()
|
||||||
Pet = new PlayerAttachedObject(ObjectKind.Pet, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.GetPet());
|
{
|
||||||
Companion = new PlayerAttachedObject(ObjectKind.Companion, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.GetCompanion());
|
new PlayerOrRelatedObject(ObjectKind.Player, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.PlayerPointer),
|
||||||
Mount = new PlayerAttachedObject(ObjectKind.Mount, IntPtr.Zero, IntPtr.Zero, () => (IntPtr)((CharaExt*)_dalamudUtil.PlayerPointer)->Mount);
|
new PlayerOrRelatedObject(ObjectKind.Minion, IntPtr.Zero, IntPtr.Zero, () => (IntPtr)((Character*)_dalamudUtil.PlayerPointer)->CompanionObject),
|
||||||
|
new PlayerOrRelatedObject(ObjectKind.Pet, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.GetPet()),
|
||||||
|
new PlayerOrRelatedObject(ObjectKind.Companion, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.GetCompanion()),
|
||||||
|
new PlayerOrRelatedObject(ObjectKind.Mount, IntPtr.Zero, IntPtr.Zero, () => (IntPtr)((CharaExt*)_dalamudUtil.PlayerPointer)->Mount),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
@@ -74,49 +74,18 @@ namespace MareSynchronos.Managers
|
|||||||
_dalamudUtil.FrameworkUpdate -= DalamudUtilOnFrameworkUpdate;
|
_dalamudUtil.FrameworkUpdate -= DalamudUtilOnFrameworkUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
private unsafe void CheckAndUpdateObject(PlayerAttachedObject attachedObject)
|
|
||||||
{
|
|
||||||
var curPtr = attachedObject.CurrentAddress;
|
|
||||||
if (curPtr != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
var chara = (Character*)curPtr;
|
|
||||||
if (attachedObject.Address == IntPtr.Zero || attachedObject.Address != curPtr
|
|
||||||
|| attachedObject.CompareAndUpdateEquipment(chara->EquipSlotData, chara->CustomizeData)
|
|
||||||
|| (chara->GameObject.DrawObject != null && (IntPtr)chara->GameObject.DrawObject != attachedObject.DrawObjectAddress))
|
|
||||||
{
|
|
||||||
Logger.Verbose(attachedObject.ObjectKind + " Changed " + curPtr);
|
|
||||||
|
|
||||||
attachedObject.Address = curPtr;
|
|
||||||
attachedObject.DrawObjectAddress = (IntPtr)chara->GameObject.DrawObject;
|
|
||||||
attachedObject.CompareAndUpdateEquipment(chara->EquipSlotData, chara->CustomizeData);
|
|
||||||
OnPlayerChanged(attachedObject.ObjectKind);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
attachedObject.Address = IntPtr.Zero;
|
|
||||||
LastSentCharacterData[attachedObject.ObjectKind] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private unsafe void DalamudUtilOnFrameworkUpdate()
|
private unsafe void DalamudUtilOnFrameworkUpdate()
|
||||||
{
|
{
|
||||||
if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized || !_apiController.IsConnected) return;
|
if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized) return;
|
||||||
//_dalamudUtil.DebugPrintRenderFlags(_dalamudUtil.PlayerPointer);
|
|
||||||
|
|
||||||
if (DateTime.Now < _lastPlayerObjectCheck.AddSeconds(0.25)) return;
|
if (DateTime.Now < _lastPlayerObjectCheck.AddSeconds(0.25)) return;
|
||||||
|
|
||||||
|
playerAttachedObjects.ForEach(k => k.CheckAndUpdateObject());
|
||||||
if (_dalamudUtil.IsPlayerPresent && !_currentCharacterEquipment[ObjectKind.Player]!.CompareAndUpdate(_dalamudUtil.PlayerCharacter))
|
if (playerAttachedObjects.Any(c => c.HasUnprocessedUpdate && !c.IsProcessing))
|
||||||
{
|
{
|
||||||
OnPlayerChanged(ObjectKind.Player);
|
OnPlayerOrAttachedObjectsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckAndUpdateObject(Minion);
|
|
||||||
CheckAndUpdateObject(Pet);
|
|
||||||
CheckAndUpdateObject(Companion);
|
|
||||||
CheckAndUpdateObject(Mount);
|
|
||||||
|
|
||||||
_lastPlayerObjectCheck = DateTime.Now;
|
_lastPlayerObjectCheck = DateTime.Now;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,10 +94,6 @@ namespace MareSynchronos.Managers
|
|||||||
Logger.Debug("ApiController Connected");
|
Logger.Debug("ApiController Connected");
|
||||||
|
|
||||||
_ipcManager.PenumbraRedrawEvent += IpcManager_PenumbraRedrawEvent;
|
_ipcManager.PenumbraRedrawEvent += IpcManager_PenumbraRedrawEvent;
|
||||||
_dalamudUtil.FrameworkUpdate += DalamudUtilOnFrameworkUpdate;
|
|
||||||
|
|
||||||
_currentCharacterEquipment[ObjectKind.Player] = new CharacterEquipment(_dalamudUtil.PlayerCharacter);
|
|
||||||
PlayerChanged(ObjectKind.Player);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApiController_Disconnected()
|
private void ApiController_Disconnected()
|
||||||
@@ -136,82 +101,63 @@ namespace MareSynchronos.Managers
|
|||||||
Logger.Debug(nameof(ApiController_Disconnected));
|
Logger.Debug(nameof(ApiController_Disconnected));
|
||||||
|
|
||||||
_ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent;
|
_ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent;
|
||||||
_dalamudUtil.FrameworkUpdate -= DalamudUtilOnFrameworkUpdate;
|
|
||||||
LastSentCharacterData.Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<CharacterCacheDto?> CreateFullCharacterCache(CancellationToken token, ObjectKind objectKind)
|
private async Task<CharacterCacheDto?> CreateFullCharacterCacheDto(CancellationToken token)
|
||||||
{
|
{
|
||||||
IntPtr pointer = objectKind switch
|
foreach (var unprocessedObject in playerAttachedObjects.Where(c => c.HasUnprocessedUpdate).ToList())
|
||||||
{
|
{
|
||||||
ObjectKind.Player => _dalamudUtil.PlayerPointer,
|
Logger.Verbose("Building Cache for " + unprocessedObject.ObjectKind);
|
||||||
ObjectKind.Minion => Minion.Address,
|
PermanentDataCache = _characterDataFactory.BuildCharacterData(PermanentDataCache, unprocessedObject.ObjectKind, unprocessedObject.Address);
|
||||||
ObjectKind.Pet => Pet.Address,
|
unprocessedObject.HasUnprocessedUpdate = false;
|
||||||
ObjectKind.Companion => Companion.Address,
|
unprocessedObject.IsProcessing = false;
|
||||||
ObjectKind.Mount => Mount.Address,
|
token.ThrowIfCancellationRequested();
|
||||||
_ => throw new NotImplementedException()
|
}
|
||||||
};
|
|
||||||
|
|
||||||
var cache = _characterDataFactory.BuildCharacterData(pointer);
|
while (!PermanentDataCache.IsReady && !token.IsCancellationRequested)
|
||||||
if (cache == null) return null;
|
|
||||||
CharacterCacheDto? cacheDto = null;
|
|
||||||
|
|
||||||
await Task.Run(async () =>
|
|
||||||
{
|
{
|
||||||
while (!cache.IsReady && !token.IsCancellationRequested)
|
await Task.Delay(50, token);
|
||||||
{
|
}
|
||||||
await Task.Delay(50, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token.IsCancellationRequested) return;
|
if (token.IsCancellationRequested) return null;
|
||||||
cache.Kind = objectKind;
|
|
||||||
cacheDto = cache.ToCharacterCacheDto();
|
|
||||||
var json = JsonConvert.SerializeObject(cacheDto);
|
|
||||||
|
|
||||||
cacheDto.Hash = Crypto.GetHash(json);
|
Logger.Verbose("Cache creation complete");
|
||||||
}, token);
|
|
||||||
|
|
||||||
return cacheDto;
|
return PermanentDataCache.ToCharacterCacheDto();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void IpcManager_PenumbraRedrawEvent(IntPtr address, int idx)
|
private void IpcManager_PenumbraRedrawEvent(IntPtr address, int idx)
|
||||||
{
|
{
|
||||||
Logger.Verbose("RedrawEvent for addr " + address);
|
Logger.Verbose("RedrawEvent for addr " + address);
|
||||||
|
|
||||||
if (address == _dalamudUtil.PlayerPointer)
|
foreach (var item in playerAttachedObjects)
|
||||||
{
|
{
|
||||||
Logger.Debug("Penumbra Redraw Event for " + _dalamudUtil.PlayerName);
|
if (address == item.Address)
|
||||||
PlayerChanged(ObjectKind.Player);
|
{
|
||||||
|
Logger.Debug("Penumbra redraw Event for " + item.ObjectKind);
|
||||||
|
item.HasUnprocessedUpdate = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (address == Minion.Address)
|
if (playerAttachedObjects.Any(c => c.HasUnprocessedUpdate))
|
||||||
{
|
{
|
||||||
Logger.Debug("Penumbra Redraw Event for Minion");
|
OnPlayerOrAttachedObjectsChanged();
|
||||||
PlayerChanged(ObjectKind.Minion);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (address == Pet.Address)
|
|
||||||
{
|
|
||||||
Logger.Debug("Penumbra Redraw Event for Pet");
|
|
||||||
PlayerChanged(ObjectKind.Pet);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (address == Companion.Address)
|
|
||||||
{
|
|
||||||
Logger.Debug("Penumbra Redraw Event for Companion");
|
|
||||||
PlayerChanged(ObjectKind.Companion);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PlayerChanged(ObjectKind objectKind)
|
private void OnPlayerOrAttachedObjectsChanged()
|
||||||
{
|
{
|
||||||
if (_dalamudUtil.IsInGpose) return;
|
if (_dalamudUtil.IsInGpose) return;
|
||||||
|
|
||||||
Logger.Debug("Object changed: " + objectKind.ToString());
|
var unprocessedObjects = playerAttachedObjects.Where(c => c.HasUnprocessedUpdate);
|
||||||
_playerChangedCts.TryGetValue(objectKind, out var cts);
|
foreach (var unprocessedObject in unprocessedObjects)
|
||||||
cts?.Cancel();
|
{
|
||||||
_playerChangedCts[objectKind] = new CancellationTokenSource();
|
unprocessedObject.IsProcessing = true;
|
||||||
var token = _playerChangedCts[objectKind]!.Token;
|
}
|
||||||
|
Logger.Debug("Object(s) changed: " + string.Join(", ", unprocessedObjects.Select(c => c.ObjectKind)));
|
||||||
|
_playerChangedCts?.Cancel();
|
||||||
|
_playerChangedCts = new CancellationTokenSource();
|
||||||
|
var token = _playerChangedCts.Token;
|
||||||
|
|
||||||
// fix for redraw from anamnesis
|
// fix for redraw from anamnesis
|
||||||
while ((!_dalamudUtil.IsPlayerPresent || _dalamudUtil.PlayerName == "--") && !token.IsCancellationRequested)
|
while ((!_dalamudUtil.IsPlayerPresent || _dalamudUtil.PlayerName == "--") && !token.IsCancellationRequested)
|
||||||
@@ -234,45 +180,27 @@ namespace MareSynchronos.Managers
|
|||||||
|
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
SendingData = true;
|
|
||||||
int attempts = 0;
|
|
||||||
while (!_apiController.IsConnected && attempts < 10 && !token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
Logger.Warn("No connection to the API");
|
|
||||||
await Task.Delay(TimeSpan.FromSeconds(1), token);
|
|
||||||
attempts++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attempts == 10 || token.IsCancellationRequested) return;
|
|
||||||
|
|
||||||
_dalamudUtil.WaitWhileSelfIsDrawing(token);
|
_dalamudUtil.WaitWhileSelfIsDrawing(token);
|
||||||
|
|
||||||
var characterCache = (await CreateFullCharacterCache(token, objectKind));
|
CharacterCacheDto? cacheDto = (await CreateFullCharacterCacheDto(token));
|
||||||
|
if (cacheDto == null || token.IsCancellationRequested) return;
|
||||||
|
|
||||||
if (characterCache == null || token.IsCancellationRequested) return;
|
if ((LastCreatedCharacterData?.GetHashCode() ?? 0) == cacheDto.GetHashCode())
|
||||||
|
|
||||||
LastSentCharacterData.TryGetValue(objectKind, out var lastSentPlayerData);
|
|
||||||
if (characterCache.Hash == (lastSentPlayerData?.Hash ?? string.Empty))
|
|
||||||
{
|
{
|
||||||
Logger.Debug("Not sending data, already sent");
|
Logger.Debug("Not sending data, already sent");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LastCreatedCharacterData = cacheDto;
|
||||||
|
}
|
||||||
|
|
||||||
LastSentCharacterData[objectKind] = characterCache;
|
if (_apiController.IsConnected)
|
||||||
PlayerHasChanged?.Invoke(characterCache, objectKind);
|
{
|
||||||
SendingData = false;
|
Logger.Verbose("Invoking PlayerHasChanged");
|
||||||
|
PlayerHasChanged?.Invoke(cacheDto);
|
||||||
|
}
|
||||||
}, token);
|
}, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPlayerChanged(ObjectKind objectKind)
|
|
||||||
{
|
|
||||||
Task.Run(() =>
|
|
||||||
{
|
|
||||||
Logger.Debug("Watcher: PlayerChanged");
|
|
||||||
PlayerChanged(objectKind);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using MareSynchronos.API;
|
using MareSynchronos.API;
|
||||||
using MareSynchronos.Factories;
|
|
||||||
|
|
||||||
namespace MareSynchronos.Models
|
namespace MareSynchronos.Models
|
||||||
{
|
{
|
||||||
@@ -11,28 +10,30 @@ namespace MareSynchronos.Models
|
|||||||
public class CharacterData
|
public class CharacterData
|
||||||
{
|
{
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public ObjectKind Kind { get; set; }
|
public Dictionary<ObjectKind, List<FileReplacement>> FileReplacements { get; set; } = new();
|
||||||
public List<FileReplacement> FileReplacements { get; set; } = new();
|
|
||||||
|
|
||||||
[JsonProperty]
|
[JsonProperty]
|
||||||
public string GlamourerString { get; set; } = string.Empty;
|
public Dictionary<ObjectKind, string> GlamourerString { get; set; } = new();
|
||||||
|
|
||||||
public bool IsReady => FileReplacements.All(f => f.Computed);
|
public bool IsReady => FileReplacements.SelectMany(k => k.Value).All(f => f.Computed);
|
||||||
|
|
||||||
|
[JsonProperty]
|
||||||
public string ManipulationString { get; set; } = string.Empty;
|
public string ManipulationString { get; set; } = string.Empty;
|
||||||
|
|
||||||
public void AddFileReplacement(FileReplacement fileReplacement)
|
public void AddFileReplacement(ObjectKind objectKind, FileReplacement fileReplacement)
|
||||||
{
|
{
|
||||||
if (!fileReplacement.HasFileReplacement) return;
|
if (!fileReplacement.HasFileReplacement) return;
|
||||||
|
|
||||||
var existingReplacement = FileReplacements.SingleOrDefault(f => f.ResolvedPath == fileReplacement.ResolvedPath);
|
if (!FileReplacements.ContainsKey(objectKind)) FileReplacements.Add(objectKind, new List<FileReplacement>());
|
||||||
|
|
||||||
|
var existingReplacement = FileReplacements[objectKind].SingleOrDefault(f => f.ResolvedPath == fileReplacement.ResolvedPath);
|
||||||
if (existingReplacement != null)
|
if (existingReplacement != null)
|
||||||
{
|
{
|
||||||
existingReplacement.GamePaths.AddRange(fileReplacement.GamePaths.Where(e => !existingReplacement.GamePaths.Contains(e)));
|
existingReplacement.GamePaths.AddRange(fileReplacement.GamePaths.Where(e => !existingReplacement.GamePaths.Contains(e)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FileReplacements.Add(fileReplacement);
|
FileReplacements[objectKind].Add(fileReplacement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,15 +41,14 @@ namespace MareSynchronos.Models
|
|||||||
{
|
{
|
||||||
return new CharacterCacheDto()
|
return new CharacterCacheDto()
|
||||||
{
|
{
|
||||||
ObjectKind = Kind,
|
FileReplacements = FileReplacements.ToDictionary(k => k.Key, k => k.Value.Where(f => f.HasFileReplacement).GroupBy(f => f.Hash).Select(g =>
|
||||||
FileReplacements = FileReplacements.Where(f => f.HasFileReplacement).GroupBy(f => f.Hash).Select(g =>
|
|
||||||
{
|
{
|
||||||
return new FileReplacementDto()
|
return new FileReplacementDto()
|
||||||
{
|
{
|
||||||
GamePaths = g.SelectMany(g => g.GamePaths).Distinct().ToArray(),
|
GamePaths = g.SelectMany(g => g.GamePaths).Distinct().ToArray(),
|
||||||
Hash = g.First().Hash
|
Hash = g.First().Hash
|
||||||
};
|
};
|
||||||
}).ToList(),
|
}).ToList()),
|
||||||
GlamourerData = GlamourerString,
|
GlamourerData = GlamourerString,
|
||||||
ManipulationData = ManipulationString
|
ManipulationData = ManipulationString
|
||||||
};
|
};
|
||||||
@@ -57,7 +57,7 @@ namespace MareSynchronos.Models
|
|||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
StringBuilder stringBuilder = new();
|
StringBuilder stringBuilder = new();
|
||||||
foreach (var fileReplacement in FileReplacements.OrderBy(a => a.GamePaths[0]))
|
foreach (var fileReplacement in FileReplacements.SelectMany(k => k.Value).OrderBy(a => a.GamePaths[0]))
|
||||||
{
|
{
|
||||||
stringBuilder.AppendLine(fileReplacement.ToString());
|
stringBuilder.AppendLine(fileReplacement.ToString());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,33 +2,76 @@
|
|||||||
using MareSynchronos.API;
|
using MareSynchronos.API;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using MareSynchronos.Utils;
|
||||||
|
using Penumbra.GameData.ByteString;
|
||||||
|
|
||||||
namespace MareSynchronos.Models
|
namespace MareSynchronos.Models
|
||||||
{
|
{
|
||||||
internal class PlayerAttachedObject
|
internal class PlayerOrRelatedObject
|
||||||
{
|
{
|
||||||
private readonly Func<IntPtr> getAddress;
|
private readonly Func<IntPtr> getAddress;
|
||||||
|
|
||||||
public unsafe Character* Character => (Character*)Address;
|
public unsafe Character* Character => (Character*)Address;
|
||||||
|
|
||||||
|
private string _name;
|
||||||
|
|
||||||
public ObjectKind ObjectKind { get; }
|
public ObjectKind ObjectKind { get; }
|
||||||
public IntPtr Address { get; set; }
|
public IntPtr Address { get; set; }
|
||||||
public IntPtr DrawObjectAddress { get; set; }
|
public IntPtr DrawObjectAddress { get; set; }
|
||||||
|
|
||||||
public IntPtr CurrentAddress => getAddress.Invoke();
|
private IntPtr CurrentAddress => getAddress.Invoke();
|
||||||
|
|
||||||
public PlayerAttachedObject(ObjectKind objectKind, IntPtr address, IntPtr drawObjectAddress, Func<IntPtr> getAddress)
|
public PlayerOrRelatedObject(ObjectKind objectKind, IntPtr address, IntPtr drawObjectAddress, Func<IntPtr> getAddress)
|
||||||
{
|
{
|
||||||
ObjectKind = objectKind;
|
ObjectKind = objectKind;
|
||||||
Address = address;
|
Address = address;
|
||||||
DrawObjectAddress = drawObjectAddress;
|
DrawObjectAddress = drawObjectAddress;
|
||||||
this.getAddress = getAddress;
|
this.getAddress = getAddress;
|
||||||
|
_name = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] EquipSlotData { get; set; } = new byte[40];
|
public byte[] EquipSlotData { get; set; } = new byte[40];
|
||||||
public byte[] CustomizeData { get; set; } = new byte[26];
|
public byte[] CustomizeData { get; set; } = new byte[26];
|
||||||
|
|
||||||
public unsafe bool CompareAndUpdateEquipment(byte* equipSlotData, byte* customizeData)
|
public bool HasUnprocessedUpdate { get; set; } = false;
|
||||||
|
public bool IsProcessing { get; set; } = false;
|
||||||
|
|
||||||
|
public unsafe void CheckAndUpdateObject()
|
||||||
|
{
|
||||||
|
var curPtr = CurrentAddress;
|
||||||
|
if (curPtr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
var chara = (Character*)curPtr;
|
||||||
|
bool addr = Address == IntPtr.Zero || Address != curPtr;
|
||||||
|
bool equip = CompareAndUpdateByteData(chara->EquipSlotData, chara->CustomizeData);
|
||||||
|
bool drawObj = (chara->GameObject.DrawObject != null && (IntPtr)chara->GameObject.DrawObject != DrawObjectAddress);
|
||||||
|
var name = new Utf8String(chara->GameObject.Name).ToString();
|
||||||
|
bool nameChange = (name != _name);
|
||||||
|
if (addr || equip || drawObj || nameChange)
|
||||||
|
{
|
||||||
|
_name = name;
|
||||||
|
Logger.Verbose(ObjectKind + " Changed: " + _name + ", now: " + curPtr + ", " + (IntPtr)chara->GameObject.DrawObject);
|
||||||
|
|
||||||
|
Address = curPtr;
|
||||||
|
DrawObjectAddress = (IntPtr)chara->GameObject.DrawObject;
|
||||||
|
HasUnprocessedUpdate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Address != IntPtr.Zero || DrawObjectAddress != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Address = IntPtr.Zero;
|
||||||
|
DrawObjectAddress = IntPtr.Zero;
|
||||||
|
HasUnprocessedUpdate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Address = IntPtr.Zero;
|
||||||
|
DrawObjectAddress = IntPtr.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe bool CompareAndUpdateByteData(byte* equipSlotData, byte* customizeData)
|
||||||
{
|
{
|
||||||
bool hasChanges = false;
|
bool hasChanges = false;
|
||||||
for (int i = 0; i < EquipSlotData.Length; i++)
|
for (int i = 0; i < EquipSlotData.Length; i++)
|
||||||
|
|||||||
@@ -17,13 +17,12 @@ namespace MareSynchronos.WebAPI
|
|||||||
public partial class ApiController
|
public partial class ApiController
|
||||||
{
|
{
|
||||||
private int _downloadId = 0;
|
private int _downloadId = 0;
|
||||||
public void CancelUpload(ObjectKind objectKind)
|
public void CancelUpload()
|
||||||
{
|
{
|
||||||
_uploadTokens.TryGetValue(objectKind, out var cts);
|
if (_uploadToken != null)
|
||||||
if (cts != null)
|
|
||||||
{
|
{
|
||||||
Logger.Warn("Cancelling upload");
|
Logger.Debug("Cancelling upload");
|
||||||
cts?.Cancel();
|
_uploadToken?.Cancel();
|
||||||
_mareHub!.SendAsync(Api.SendFileAbortUpload);
|
_mareHub!.SendAsync(Api.SendFileAbortUpload);
|
||||||
CurrentUploads.Clear();
|
CurrentUploads.Clear();
|
||||||
}
|
}
|
||||||
@@ -41,7 +40,6 @@ namespace MareSynchronos.WebAPI
|
|||||||
await using var fs = File.OpenWrite(fileName);
|
await using var fs = File.OpenWrite(fileName);
|
||||||
await foreach (var data in reader.WithCancellation(ct))
|
await foreach (var data in reader.WithCancellation(ct))
|
||||||
{
|
{
|
||||||
//Logger.Debug("Getting chunk of " + hash);
|
|
||||||
CurrentDownloads[downloadId].Single(f => f.Hash == hash).Transferred += data.Length;
|
CurrentDownloads[downloadId].Single(f => f.Hash == hash).Transferred += data.Length;
|
||||||
await fs.WriteAsync(data, ct);
|
await fs.WriteAsync(data, ct);
|
||||||
}
|
}
|
||||||
@@ -124,12 +122,12 @@ namespace MareSynchronos.WebAPI
|
|||||||
if (!IsConnected || SecretKey == "-") return;
|
if (!IsConnected || SecretKey == "-") return;
|
||||||
Logger.Debug("Sending Character data to service " + ApiUri);
|
Logger.Debug("Sending Character data to service " + ApiUri);
|
||||||
|
|
||||||
CancelUpload(character.ObjectKind);
|
CancelUpload();
|
||||||
_uploadTokens[character.ObjectKind] = new CancellationTokenSource();
|
_uploadToken = new CancellationTokenSource();
|
||||||
var uploadToken = _uploadTokens[character.ObjectKind]!.Token;
|
var uploadToken = _uploadToken.Token;
|
||||||
Logger.Verbose("New Token Created");
|
Logger.Verbose("New Token Created");
|
||||||
|
|
||||||
var filesToUpload = await _mareHub!.InvokeAsync<List<UploadFileDto>>(Api.InvokeFileSendFiles, character.FileReplacements.Select(c => c.Hash).Distinct(), uploadToken);
|
var filesToUpload = await _mareHub!.InvokeAsync<List<UploadFileDto>>(Api.InvokeFileSendFiles, character.FileReplacements.SelectMany(c => c.Value.Select(v => v.Hash)).Distinct(), uploadToken);
|
||||||
|
|
||||||
foreach (var file in filesToUpload.Where(f => !f.IsForbidden))
|
foreach (var file in filesToUpload.Where(f => !f.IsForbidden))
|
||||||
{
|
{
|
||||||
@@ -205,8 +203,8 @@ namespace MareSynchronos.WebAPI
|
|||||||
Logger.Warn("=== Upload operation was cancelled ===");
|
Logger.Warn("=== Upload operation was cancelled ===");
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Verbose("Upload complete for " + character.Hash);
|
Logger.Verbose("Upload complete for " + character.GetHashCode());
|
||||||
_uploadTokens.Remove(character.ObjectKind);
|
_uploadToken = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<(string, byte[])> GetCompressedFileData(string fileHash, CancellationToken uploadToken)
|
private async Task<(string, byte[])> GetCompressedFileData(string fileHash, CancellationToken uploadToken)
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ namespace MareSynchronos.WebAPI
|
|||||||
|
|
||||||
private HubConnection? _mareHub;
|
private HubConnection? _mareHub;
|
||||||
|
|
||||||
private Dictionary<ObjectKind, CancellationTokenSource?> _uploadTokens = new();
|
private CancellationTokenSource? _uploadToken = new();
|
||||||
|
|
||||||
private ConnectionDto? _connectionDto;
|
private ConnectionDto? _connectionDto;
|
||||||
public SystemInfoDto SystemInfoDto { get; private set; } = new();
|
public SystemInfoDto SystemInfoDto { get; private set; } = new();
|
||||||
@@ -268,10 +268,7 @@ namespace MareSynchronos.WebAPI
|
|||||||
{
|
{
|
||||||
CurrentUploads.Clear();
|
CurrentUploads.Clear();
|
||||||
CurrentDownloads.Clear();
|
CurrentDownloads.Clear();
|
||||||
foreach(var token in _uploadTokens.Values)
|
_uploadToken?.Cancel();
|
||||||
{
|
|
||||||
token?.Cancel();
|
|
||||||
}
|
|
||||||
Logger.Debug("Connection closed");
|
Logger.Debug("Connection closed");
|
||||||
Disconnected?.Invoke();
|
Disconnected?.Invoke();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
@@ -289,10 +286,7 @@ namespace MareSynchronos.WebAPI
|
|||||||
{
|
{
|
||||||
CurrentUploads.Clear();
|
CurrentUploads.Clear();
|
||||||
CurrentDownloads.Clear();
|
CurrentDownloads.Clear();
|
||||||
foreach (var token in _uploadTokens.Values)
|
_uploadToken?.Cancel();
|
||||||
{
|
|
||||||
token?.Cancel();
|
|
||||||
}
|
|
||||||
Logger.Debug("Connection closed... Reconnecting");
|
Logger.Debug("Connection closed... Reconnecting");
|
||||||
Disconnected?.Invoke();
|
Disconnected?.Invoke();
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|||||||
Reference in New Issue
Block a user