Compare commits
22 Commits
b41532d5af
...
1.7.1.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 60c1668492 | |||
| 4f07dba409 | |||
| 003fc77628 | |||
| 41b5122e12 | |||
| b9524dbe73 | |||
| 166fc0be91 | |||
| 6519ba6b6c | |||
| c1dbe3663e | |||
| e52fb6a452 | |||
| 14ee5fe6ce | |||
| 6b7f9a6ea0 | |||
| 74c83abe91 | |||
| 00cc07efa4 | |||
| 4853afd1eb | |||
| 5cac38f446 | |||
| 30aab1739d | |||
| a7e8ff9a19 | |||
| 545b06aad6 | |||
| 062d8bf6b2 | |||
| a8a2d8a48f | |||
| 9b0134aa7d | |||
| 243b0b8300 |
97
.gitea/workflows/release-clubpenguin-on-tag.yml
Normal file
97
.gitea/workflows/release-clubpenguin-on-tag.yml
Normal file
@@ -0,0 +1,97 @@
|
||||
name: Build and Update Repo
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
PLUGIN_NAME: ClubPenguinSync
|
||||
DOTNET_VERSION: 9.x
|
||||
|
||||
jobs:
|
||||
build-and-update-repo:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout Club Penguin
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
||||
- name: Setup .NET 9 SDK
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: 9.x
|
||||
|
||||
- name: Download Dalamud
|
||||
run: |
|
||||
cd /
|
||||
mkdir -p ~/.xlcore/dalamud/Hooks/dev
|
||||
curl -O https://goatcorp.github.io/dalamud-distrib/stg/latest.zip
|
||||
unzip latest.zip -d ~/.xlcore/dalamud/Hooks/dev
|
||||
|
||||
- name: Build Club Penguin
|
||||
run: |
|
||||
dotnet restore
|
||||
dotnet build --configuration Release --no-restore
|
||||
|
||||
- name: Prepare Club Penguin Repo
|
||||
run: |
|
||||
mkdir -p output
|
||||
mv MareSynchronos/bin/x64/Release/ClubPenguinSync/latest.zip /repo/download/ClubPenguinSync-${{ gitea.ref_name }}.zip
|
||||
|
||||
- name: Update repo.json with version
|
||||
env:
|
||||
VERSION: ${{ gitea.ref_name }}
|
||||
run: |
|
||||
set -e
|
||||
|
||||
pluginJsonPath="MareSynchronos/bin/x64/Release/${PLUGIN_NAME}.json"
|
||||
repoJsonPath="/repo/repo.json"
|
||||
version="${VERSION}"
|
||||
downloadUrl="https://clubpenguin.drgn.rocks/download/ClubPenguinSync-${{ gitea.ref_name }}.zip"
|
||||
|
||||
# Read plugin JSON
|
||||
pluginJson=$(cat "$pluginJsonPath")
|
||||
internalName=$(jq -r '.InternalName' <<< "$pluginJson")
|
||||
dalamudApiLevel=$(jq -r '.DalamudApiLevel' <<< "$pluginJson")
|
||||
|
||||
# Read repo JSON (force array if not already)
|
||||
repoJsonRaw=$(cat "$repoJsonPath")
|
||||
if echo "$repoJsonRaw" | jq 'type' | grep -q '"array"'; then
|
||||
repoJson="$repoJsonRaw"
|
||||
else
|
||||
repoJson="[$repoJsonRaw]"
|
||||
fi
|
||||
|
||||
# Update matching plugin entry
|
||||
updatedRepoJson=$(jq \
|
||||
--arg internalName "$internalName" \
|
||||
--arg dalamudApiLevel "$dalamudApiLevel" \
|
||||
--arg version "$version" \
|
||||
--arg downloadUrl "$downloadUrl" \
|
||||
'
|
||||
map(
|
||||
if .InternalName == $internalName
|
||||
then
|
||||
.DalamudApiLevel = $dalamudApiLevel
|
||||
| .TestingDalamudApiLevel = $dalamudApiLevel
|
||||
| .AssemblyVersion = $version
|
||||
| .TestingAssemblyVersion = $version
|
||||
| .DownloadLinkInstall = $downloadUrl
|
||||
| .DownloadLinkTesting = $downloadUrl
|
||||
| .DownloadLinkUpdate = $downloadUrl
|
||||
else
|
||||
.
|
||||
end
|
||||
)
|
||||
' <<< "$repoJson")
|
||||
|
||||
# Write back to file
|
||||
echo "$updatedRepoJson" > "$repoJsonPath"
|
||||
# Output the content of the file
|
||||
cat "$repoJsonPath"
|
||||
2
.gitmodules
vendored
2
.gitmodules
vendored
@@ -1,6 +1,6 @@
|
||||
[submodule "MareAPI"]
|
||||
path = MareAPI
|
||||
url = https://git.lop-sync.com/huggingway/LopAPI.git
|
||||
url = https://git.drgn.rocks/t0w0bi/ClubPenguinApi.git
|
||||
[submodule "Penumbra.Api"]
|
||||
path = Penumbra.Api
|
||||
url = https://github.com/Ottermandias/Penumbra.Api.git
|
||||
|
||||
14
MareSynchronos/ClubPenguinSync.json
Normal file
14
MareSynchronos/ClubPenguinSync.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"Author": "tb",
|
||||
"Name": "Club Penguin Sync",
|
||||
"Punchline": "Social modding for cool penguins!",
|
||||
"Description": "This plugin will synchronize your Penumbra mods and current Glamourer state with other paired clients automatically.",
|
||||
"InternalName": "ClubPenguinSync",
|
||||
"ApplicableVersion": "any",
|
||||
"Tags": [
|
||||
"customization"
|
||||
],
|
||||
"IconUrl": "https://clubpenguin.drgn.rocks/icon.png",
|
||||
"RepoUrl": "https://git.drgn.rocks/t0w0bi/ClubPenguinClient/",
|
||||
"CanUnloadAsync": true
|
||||
}
|
||||
106
MareSynchronos/Interop/ColorTableHook.cs
Normal file
106
MareSynchronos/Interop/ColorTableHook.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
using System.Buffers;
|
||||
|
||||
namespace MareSynchronos.Interop;
|
||||
|
||||
public unsafe sealed class ColorTableHook : IDisposable
|
||||
{
|
||||
// Based on https://github.com/Exter-N/MemShield/blob/main/MemShield/Plugin.cs
|
||||
|
||||
private readonly ILogger<ColorTableHook> _logger;
|
||||
private const int ColorTableSize = 2048;
|
||||
private const int ColorDyeTableSize = 128;
|
||||
private readonly MareConfigService _configService;
|
||||
|
||||
#region signatures
|
||||
#pragma warning disable CS0649
|
||||
[Signature("E8 ?? ?? ?? ?? 49 89 04 3E",
|
||||
DetourName = nameof(PrepareColorTableDetour))]
|
||||
private Hook<MaterialResourceHandle.Delegates.PrepareColorTable> _prepareColorTable = null!;
|
||||
#pragma warning restore CS0649
|
||||
#endregion
|
||||
|
||||
public ColorTableHook(ILogger<ColorTableHook> logger, IGameInteropProvider gameInteropProvider, MareConfigService configService)
|
||||
{
|
||||
_logger = logger;
|
||||
_configService = configService;
|
||||
|
||||
logger.LogInformation("Initializing ColorTableHook");
|
||||
gameInteropProvider.InitializeFromAttributes(this);
|
||||
|
||||
if (_configService.Current.AttemptColorTableProtection)
|
||||
{
|
||||
_prepareColorTable.Enable();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_configService.Current.AttemptColorTableProtection)
|
||||
{
|
||||
_prepareColorTable.Disable();
|
||||
}
|
||||
_prepareColorTable.Dispose();
|
||||
}
|
||||
|
||||
public void EnableHooks()
|
||||
{
|
||||
_prepareColorTable.Enable();
|
||||
}
|
||||
|
||||
public void DisableHooks()
|
||||
{
|
||||
_prepareColorTable.Disable();
|
||||
}
|
||||
|
||||
private static int GetDataSetExpectedSize(uint dataFlags)
|
||||
=> (dataFlags & 16) != 0
|
||||
? ColorTableSize + ((dataFlags & 8) != 0 ? ColorDyeTableSize : 0)
|
||||
: 0;
|
||||
|
||||
private Texture* PrepareColorTableDetour(MaterialResourceHandle* @this, byte stain0Id, byte stain1Id)
|
||||
{
|
||||
if(!_configService.Current.AttemptColorTableProtection)
|
||||
{
|
||||
return _prepareColorTable.Original(@this, stain0Id, stain1Id);
|
||||
}
|
||||
|
||||
var expectedSize = GetDataSetExpectedSize(@this->DataFlags);
|
||||
if (@this->DataSetSize >= expectedSize)
|
||||
return _prepareColorTable.Original(@this, stain0Id, stain1Id);
|
||||
var originalDataSetPtr = @this->DataSet;
|
||||
var originalDataSet = new ReadOnlySpan<byte>(originalDataSetPtr, @this->DataSetSize);
|
||||
var pool = ArrayPool<byte>.Shared;
|
||||
var buffer = pool.Rent(expectedSize);
|
||||
try
|
||||
{
|
||||
var bufferSpan = buffer.AsSpan(..expectedSize);
|
||||
originalDataSet.CopyTo(bufferSpan);
|
||||
bufferSpan[@this->DataSetSize..].Clear();
|
||||
fixed (byte* bufferPtr = buffer)
|
||||
{
|
||||
@this->DataSet = bufferPtr;
|
||||
try
|
||||
{
|
||||
return _prepareColorTable.Original(@this, stain0Id, stain1Id);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@this->DataSet = originalDataSetPtr;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
pool.Return(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.Interop.Ipc;
|
||||
|
||||
public sealed class IpcCallerMare : DisposableMediatorSubscriberBase
|
||||
{
|
||||
private readonly ICallGateSubscriber<List<nint>> _mareHandledGameAddresses;
|
||||
private readonly List<nint> _emptyList = [];
|
||||
|
||||
private bool _pluginLoaded;
|
||||
|
||||
public IpcCallerMare(ILogger<IpcCallerMare> logger, IDalamudPluginInterface pi, MareMediator mediator) : base(logger, mediator)
|
||||
{
|
||||
_mareHandledGameAddresses = pi.GetIpcSubscriber<List<nint>>("MareSynchronos.GetHandledAddresses");
|
||||
|
||||
_pluginLoaded = PluginWatcherService.GetInitialPluginState(pi, "MareSynchronos")?.IsLoaded ?? false;
|
||||
|
||||
Mediator.SubscribeKeyed<PluginChangeMessage>(this, "MareSynchronos", (msg) =>
|
||||
{
|
||||
_pluginLoaded = msg.IsLoaded;
|
||||
});
|
||||
}
|
||||
|
||||
public bool APIAvailable { get; private set; } = false;
|
||||
|
||||
// Must be called on framework thread
|
||||
public IReadOnlyList<nint> GetHandledGameAddresses()
|
||||
{
|
||||
if (!_pluginLoaded) return _emptyList;
|
||||
|
||||
try
|
||||
{
|
||||
return _mareHandledGameAddresses.InvokeFunc();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return _emptyList;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,9 +27,9 @@ public sealed class IpcCallerMoodles : IIpcCaller
|
||||
|
||||
_moodlesApiVersion = pi.GetIpcSubscriber<int>("Moodles.Version");
|
||||
_moodlesOnChange = pi.GetIpcSubscriber<IPlayerCharacter, object>("Moodles.StatusManagerModified");
|
||||
_moodlesGetStatus = pi.GetIpcSubscriber<nint, string>("Moodles.GetStatusManagerByPtr");
|
||||
_moodlesSetStatus = pi.GetIpcSubscriber<nint, string, object>("Moodles.SetStatusManagerByPtr");
|
||||
_moodlesRevertStatus = pi.GetIpcSubscriber<nint, object>("Moodles.ClearStatusManagerByPtr");
|
||||
_moodlesGetStatus = pi.GetIpcSubscriber<nint, string>("Moodles.GetStatusManagerByPtrV2");
|
||||
_moodlesSetStatus = pi.GetIpcSubscriber<nint, string, object>("Moodles.SetStatusManagerByPtrV2");
|
||||
_moodlesRevertStatus = pi.GetIpcSubscriber<nint, object>("Moodles.ClearStatusManagerByPtrV2");
|
||||
|
||||
_moodlesOnChange.Subscribe(OnMoodlesChange);
|
||||
|
||||
@@ -47,7 +47,7 @@ public sealed class IpcCallerMoodles : IIpcCaller
|
||||
{
|
||||
try
|
||||
{
|
||||
APIAvailable = _moodlesApiVersion.InvokeFunc() == 1;
|
||||
APIAvailable = _moodlesApiVersion.InvokeFunc() == 3;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
83
MareSynchronos/Interop/Ipc/IpcCallerOtherSync.cs
Normal file
83
MareSynchronos/Interop/Ipc/IpcCallerOtherSync.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Ipc;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.Interop.Ipc;
|
||||
|
||||
public sealed class IpcCallerOtherSync : DisposableMediatorSubscriberBase
|
||||
{
|
||||
private readonly ICallGateSubscriber<List<nint>> _lightlessHandledGameAddresses;
|
||||
private readonly ICallGateSubscriber<List<nint>> _snowcloakHandledGameAddresses;
|
||||
|
||||
private readonly List<nint> _emptyList = [];
|
||||
|
||||
private bool _lightlessLoaded;
|
||||
private bool _snowcloakLoaded;
|
||||
|
||||
public IpcCallerOtherSync(ILogger<IpcCallerOtherSync> logger, IDalamudPluginInterface pi, MareMediator mediator) : base(logger, mediator)
|
||||
{
|
||||
_lightlessHandledGameAddresses = pi.GetIpcSubscriber<List<nint>>("LightlessSync.GetHandledAddresses");
|
||||
_snowcloakHandledGameAddresses = pi.GetIpcSubscriber<List<nint>>("SnowcloakSync.GetHandledAddresses");
|
||||
|
||||
_lightlessLoaded = PluginWatcherService.GetInitialPluginState(pi, "LightlessSync")?.IsLoaded ?? false;
|
||||
|
||||
Mediator.SubscribeKeyed<PluginChangeMessage>(this, "LightlessSync", (msg) =>
|
||||
{
|
||||
_lightlessLoaded = msg.IsLoaded;
|
||||
});
|
||||
|
||||
_snowcloakLoaded = PluginWatcherService.GetInitialPluginState(pi, "Snowcloak")?.IsLoaded ?? false;
|
||||
|
||||
Mediator.SubscribeKeyed<PluginChangeMessage>(this, "Snowcloak", (msg) =>
|
||||
{
|
||||
_snowcloakLoaded = msg.IsLoaded;
|
||||
});
|
||||
}
|
||||
|
||||
public bool APIAvailable { get; private set; } = false;
|
||||
|
||||
// Must be called on framework thread
|
||||
public IReadOnlyList<nint> GetHandledGameAddresses()
|
||||
{
|
||||
if (!_lightlessLoaded && !_snowcloakLoaded) return _emptyList;
|
||||
|
||||
try
|
||||
{
|
||||
return GetLightlessHandledGameAddresses().Concat(GetSnowcloakHandledGameAddresses()).ToList();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return _emptyList;
|
||||
}
|
||||
}
|
||||
|
||||
private List<nint> GetLightlessHandledGameAddresses()
|
||||
{
|
||||
if (!_lightlessLoaded) return _emptyList;
|
||||
|
||||
try
|
||||
{
|
||||
return _lightlessHandledGameAddresses.InvokeFunc();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return _emptyList;
|
||||
}
|
||||
}
|
||||
|
||||
private List<nint> GetSnowcloakHandledGameAddresses()
|
||||
{
|
||||
if (!_snowcloakLoaded) return _emptyList;
|
||||
|
||||
try
|
||||
{
|
||||
return _snowcloakHandledGameAddresses.InvokeFunc();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return _emptyList;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -231,7 +231,7 @@ public sealed class IpcCallerPenumbra : DisposableMediatorSubscriberBase, IIpcCa
|
||||
return await _dalamudUtil.RunOnFrameworkThread(() =>
|
||||
{
|
||||
var collName = identity + "_" + uid;
|
||||
var ec = _penumbraCreateNamedTemporaryCollection.Invoke(identity, collName, out var collId);
|
||||
PenumbraApiEc ec = _penumbraCreateNamedTemporaryCollection.Invoke(identity, collName, out var collId);
|
||||
if (ec == PenumbraApiEc.InvalidCredentials)
|
||||
{
|
||||
for (int i = 0; i < 5; ++i)
|
||||
|
||||
@@ -30,12 +30,12 @@ public sealed class IpcCallerPetNames : IIpcCaller
|
||||
_dalamudUtil = dalamudUtil;
|
||||
_mareMediator = mareMediator;
|
||||
|
||||
_petnamesReady = pi.GetIpcSubscriber<object>("PetRenamer.Ready");
|
||||
_petnamesDisposing = pi.GetIpcSubscriber<object>("PetRenamer.Disposing");
|
||||
_petnamesReady = pi.GetIpcSubscriber<object>("PetRenamer.OnReady");
|
||||
_petnamesDisposing = pi.GetIpcSubscriber<object>("PetRenamer.OnDisposing");
|
||||
_apiVersion = pi.GetIpcSubscriber<(uint, uint)>("PetRenamer.ApiVersion");
|
||||
_enabled = pi.GetIpcSubscriber<bool>("PetRenamer.Enabled");
|
||||
_enabled = pi.GetIpcSubscriber<bool>("PetRenamer.IsEnabled");
|
||||
|
||||
_playerDataChanged = pi.GetIpcSubscriber<string, object>("PetRenamer.PlayerDataChanged");
|
||||
_playerDataChanged = pi.GetIpcSubscriber<string, object>("PetRenamer.OnPlayerDataChanged");
|
||||
_getPlayerData = pi.GetIpcSubscriber<string>("PetRenamer.GetPlayerData");
|
||||
_setPlayerData = pi.GetIpcSubscriber<string, object>("PetRenamer.SetPlayerData");
|
||||
_clearPlayerData = pi.GetIpcSubscriber<ushort, object>("PetRenamer.ClearPlayerData");
|
||||
@@ -56,7 +56,7 @@ public sealed class IpcCallerPetNames : IIpcCaller
|
||||
APIAvailable = _enabled?.InvokeFunc() ?? false;
|
||||
if (APIAvailable)
|
||||
{
|
||||
APIAvailable = _apiVersion?.InvokeFunc() is { Item1: 3, Item2: >= 1 };
|
||||
APIAvailable = _apiVersion?.InvokeFunc() is { Item1: 4, Item2: >= 0 };
|
||||
}
|
||||
}
|
||||
catch
|
||||
|
||||
@@ -5,7 +5,6 @@ using MareSynchronos.MareConfiguration;
|
||||
using MareSynchronos.PlayerData.Handlers;
|
||||
using MareSynchronos.Services;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using MareSynchronos.Utils;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -15,25 +14,12 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
|
||||
{
|
||||
private readonly ILogger<IpcProvider> _logger;
|
||||
private readonly IDalamudPluginInterface _pi;
|
||||
private readonly MareConfigService _mareConfig;
|
||||
private readonly CharaDataManager _charaDataManager;
|
||||
private ICallGateProvider<string, IGameObject, bool>? _loadFileProvider;
|
||||
private ICallGateProvider<string, IGameObject, Task<bool>>? _loadFileAsyncProvider;
|
||||
private ICallGateProvider<List<nint>>? _handledGameAddresses;
|
||||
private readonly List<GameObjectHandler> _activeGameObjectHandlers = [];
|
||||
|
||||
private ICallGateProvider<string, IGameObject, bool>? _loadFileProviderMare;
|
||||
private ICallGateProvider<string, IGameObject, Task<bool>>? _loadFileAsyncProviderMare;
|
||||
private ICallGateProvider<List<nint>>? _handledGameAddressesMare;
|
||||
|
||||
private bool _marePluginEnabled = false;
|
||||
private bool _impersonating = false;
|
||||
private DateTime _unregisterTime = DateTime.UtcNow;
|
||||
private CancellationTokenSource _registerDelayCts = new();
|
||||
|
||||
public bool MarePluginEnabled => _marePluginEnabled;
|
||||
public bool ImpersonationActive => _impersonating;
|
||||
|
||||
public MareMediator Mediator { get; init; }
|
||||
|
||||
public IpcProvider(ILogger<IpcProvider> logger, IDalamudPluginInterface pi, MareConfigService mareConfig,
|
||||
@@ -41,7 +27,6 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
|
||||
{
|
||||
_logger = logger;
|
||||
_pi = pi;
|
||||
_mareConfig = mareConfig;
|
||||
_charaDataManager = charaDataManager;
|
||||
Mediator = mareMediator;
|
||||
|
||||
@@ -55,15 +40,6 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
|
||||
if (msg.OwnedObject) return;
|
||||
_activeGameObjectHandlers.Remove(msg.GameObjectHandler);
|
||||
});
|
||||
|
||||
/*
|
||||
_marePluginEnabled = PluginWatcherService.GetInitialPluginState(pi, "MareSynchronos")?.IsLoaded ?? false;
|
||||
Mediator.SubscribeKeyed<PluginChangeMessage>(this, "MareSynchronos", p => {
|
||||
_marePluginEnabled = p.IsLoaded;
|
||||
HandleMareImpersonation(automatic: true);
|
||||
});
|
||||
*/
|
||||
_marePluginEnabled = true;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
@@ -76,72 +52,10 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
|
||||
_handledGameAddresses = _pi.GetIpcProvider<List<nint>>("ClubPenguinSync.GetHandledAddresses");
|
||||
_handledGameAddresses.RegisterFunc(GetHandledAddresses);
|
||||
|
||||
_loadFileProviderMare = _pi.GetIpcProvider<string, IGameObject, bool>("MareSynchronos.LoadMcdf");
|
||||
_loadFileAsyncProviderMare = _pi.GetIpcProvider<string, IGameObject, Task<bool>>("MareSynchronos.LoadMcdfAsync");
|
||||
_handledGameAddressesMare = _pi.GetIpcProvider<List<nint>>("MareSynchronos.GetHandledAddresses");
|
||||
HandleMareImpersonation(automatic: true);
|
||||
|
||||
_logger.LogInformation("Started IpcProviderService");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void HandleMareImpersonation(bool automatic = false)
|
||||
{
|
||||
if (_marePluginEnabled)
|
||||
{
|
||||
if (_impersonating)
|
||||
{
|
||||
_loadFileProviderMare?.UnregisterFunc();
|
||||
_loadFileAsyncProviderMare?.UnregisterFunc();
|
||||
_handledGameAddressesMare?.UnregisterFunc();
|
||||
_impersonating = false;
|
||||
_unregisterTime = DateTime.UtcNow;
|
||||
_logger.LogDebug("Unregistered MareSynchronos API");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_mareConfig.Current.MareAPI)
|
||||
{
|
||||
var cancelToken = _registerDelayCts.Token;
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
// Wait before registering to reduce the chance of a race condition
|
||||
if (automatic)
|
||||
await Task.Delay(5000).ConfigureAwait(false);
|
||||
|
||||
if (cancelToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
if (_marePluginEnabled)
|
||||
{
|
||||
_logger.LogDebug("Not registering MareSynchronos API: Mare plugin is loaded");
|
||||
return;
|
||||
}
|
||||
|
||||
_loadFileProviderMare?.RegisterFunc(LoadMcdf);
|
||||
_loadFileAsyncProviderMare?.RegisterFunc(LoadMcdfAsync);
|
||||
_handledGameAddressesMare?.RegisterFunc(MareGetHandledAddresses);
|
||||
_impersonating = true;
|
||||
_logger.LogDebug("Registered MareSynchronos API");
|
||||
}, cancelToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
_registerDelayCts = _registerDelayCts.CancelRecreate();
|
||||
if (_impersonating)
|
||||
{
|
||||
_loadFileProviderMare?.UnregisterFunc();
|
||||
_loadFileAsyncProviderMare?.UnregisterFunc();
|
||||
_handledGameAddressesMare?.UnregisterFunc();
|
||||
_impersonating = false;
|
||||
_unregisterTime = DateTime.UtcNow;
|
||||
_logger.LogDebug("Unregistered MareSynchronos API");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogDebug("Stopping IpcProvider Service");
|
||||
@@ -149,14 +63,6 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
|
||||
_loadFileAsyncProvider?.UnregisterFunc();
|
||||
_handledGameAddresses?.UnregisterFunc();
|
||||
|
||||
_registerDelayCts.Cancel();
|
||||
if (_impersonating)
|
||||
{
|
||||
_loadFileProviderMare?.UnregisterFunc();
|
||||
_loadFileAsyncProviderMare?.UnregisterFunc();
|
||||
_handledGameAddressesMare?.UnregisterFunc();
|
||||
}
|
||||
|
||||
Mediator.UnsubscribeAll(this);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
@@ -186,19 +92,4 @@ public class IpcProvider : IHostedService, IMediatorSubscriber
|
||||
{
|
||||
return _activeGameObjectHandlers.Where(g => g.Address != nint.Zero).Select(g => g.Address).Distinct().ToList();
|
||||
}
|
||||
|
||||
private List<nint> MareGetHandledAddresses()
|
||||
{
|
||||
if (!_impersonating)
|
||||
{
|
||||
if ((DateTime.UtcNow - _unregisterTime).TotalSeconds >= 1.0)
|
||||
{
|
||||
_logger.LogWarning("GetHandledAddresses called when it should not be registered");
|
||||
_handledGameAddressesMare?.UnregisterFunc();
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
return GetHandledAddresses();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@ public class MareConfig : IMareConfiguration
|
||||
public bool LogPerformance { get; set; } = false;
|
||||
public bool LogTraceLog { get; set; } = false;
|
||||
public bool LogEvents { get; set; } = true;
|
||||
public bool HoldCombatApplication { get; set; } = false;
|
||||
public bool HoldCombatApplication { get; set; } = true;
|
||||
public bool AttemptColorTableProtection { get; set; } = true;
|
||||
public double MaxLocalCacheInGiB { get; set; } = 20;
|
||||
public bool OpenGposeImportOnGposeStart { get; set; } = false;
|
||||
public bool OpenPopupOnAdd { get; set; } = true;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Dalamud.NET.Sdk/13.0.0">
|
||||
<Project Sdk="Dalamud.NET.Sdk/13.1.0">
|
||||
<PropertyGroup>
|
||||
<AssemblyName>ClubPenguinSync</AssemblyName>
|
||||
<Version>1.7.0.0</Version>
|
||||
<Version>1.7.1.2</Version>
|
||||
<PackageProjectUrl>https://github.com/Rawrington/ClubPenguinSync/</PackageProjectUrl>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -27,14 +27,13 @@ public class PairHandlerFactory
|
||||
private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
|
||||
private readonly PairAnalyzerFactory _pairAnalyzerFactory;
|
||||
private readonly VisibilityService _visibilityService;
|
||||
private readonly NoSnapService _noSnapService;
|
||||
|
||||
public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager,
|
||||
FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService,
|
||||
PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime,
|
||||
FileCacheManager fileCacheManager, MareMediator mareMediator, PlayerPerformanceService playerPerformanceService,
|
||||
ServerConfigurationManager serverConfigManager, PairAnalyzerFactory pairAnalyzerFactory,
|
||||
MareConfigService configService, VisibilityService visibilityService, NoSnapService noSnapService)
|
||||
MareConfigService configService, VisibilityService visibilityService)
|
||||
{
|
||||
_loggerFactory = loggerFactory;
|
||||
_gameObjectHandlerFactory = gameObjectHandlerFactory;
|
||||
@@ -50,13 +49,12 @@ public class PairHandlerFactory
|
||||
_pairAnalyzerFactory = pairAnalyzerFactory;
|
||||
_configService = configService;
|
||||
_visibilityService = visibilityService;
|
||||
_noSnapService = noSnapService;
|
||||
}
|
||||
|
||||
public PairHandler Create(Pair pair)
|
||||
{
|
||||
return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), pair, _pairAnalyzerFactory.Create(pair), _gameObjectHandlerFactory,
|
||||
_ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime,
|
||||
_fileCacheManager, _mareMediator, _playerPerformanceService, _serverConfigManager, _configService, _visibilityService, _noSnapService);
|
||||
_fileCacheManager, _mareMediator, _playerPerformanceService, _serverConfigManager, _configService, _visibilityService);
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
private readonly ServerConfigurationManager _serverConfigManager;
|
||||
private readonly PluginWarningNotificationService _pluginWarningNotificationManager;
|
||||
private readonly VisibilityService _visibilityService;
|
||||
private readonly NoSnapService _noSnapService;
|
||||
private CancellationTokenSource? _applicationCancellationTokenSource = new();
|
||||
private Guid _applicationId;
|
||||
private Task? _applicationTask;
|
||||
@@ -55,8 +54,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
FileCacheManager fileDbManager, MareMediator mediator,
|
||||
PlayerPerformanceService playerPerformanceService,
|
||||
ServerConfigurationManager serverConfigManager,
|
||||
MareConfigService configService, VisibilityService visibilityService,
|
||||
NoSnapService noSnapService) : base(logger, mediator)
|
||||
MareConfigService configService, VisibilityService visibilityService) : base(logger, mediator)
|
||||
{
|
||||
Pair = pair;
|
||||
PairAnalyzer = pairAnalyzer;
|
||||
@@ -70,7 +68,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
_serverConfigManager = serverConfigManager;
|
||||
_configService = configService;
|
||||
_visibilityService = visibilityService;
|
||||
_noSnapService = noSnapService;
|
||||
|
||||
_visibilityService.StartTracking(Pair.Ident);
|
||||
|
||||
@@ -332,7 +329,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
var gposeName = actor.Name.TextValue;
|
||||
if (!name.Equals(gposeName, StringComparison.Ordinal))
|
||||
continue;
|
||||
_noSnapService.AddGposer(actor.ObjectIndex);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -385,10 +381,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (_dalamudUtil.IsInCutscene && !string.IsNullOrEmpty(name))
|
||||
{
|
||||
_noSnapService.AddGposerNamed(name);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -133,7 +133,7 @@ public class Pair : DisposableMediatorSubscriberBase
|
||||
{
|
||||
Name = name,
|
||||
OnClicked = action,
|
||||
PrefixColor = 529,
|
||||
PrefixColor = 555,
|
||||
PrefixChar = 'C'
|
||||
});
|
||||
}
|
||||
@@ -215,11 +215,6 @@ public class Pair : DisposableMediatorSubscriberBase
|
||||
if (_serverConfigurationManager.IsUidBlacklisted(UserData.UID))
|
||||
HoldApplication("Blacklist", maxValue: 1);
|
||||
|
||||
if (NoSnapService.AnyLoaded)
|
||||
HoldApplication("NoSnap", maxValue: 1);
|
||||
else
|
||||
UnholdApplication("NoSnap", skipApplication: true);
|
||||
|
||||
CachedPlayer.ApplyCharacterData(Guid.NewGuid(), RemoveNotSyncedFiles(LastReceivedCharacterData.DeepClone())!, forced);
|
||||
}
|
||||
|
||||
|
||||
@@ -161,10 +161,10 @@ public sealed class Plugin : IDalamudPlugin
|
||||
collection.AddSingleton<CharaDataGposeTogetherManager>();
|
||||
|
||||
collection.AddSingleton<VfxSpawnManager>();
|
||||
collection.AddSingleton<ColorTableHook>();
|
||||
collection.AddSingleton<BlockedCharacterHandler>();
|
||||
collection.AddSingleton<IpcProvider>();
|
||||
collection.AddSingleton<VisibilityService>();
|
||||
collection.AddSingleton<RepoChangeService>();
|
||||
collection.AddSingleton<EventAggregator>();
|
||||
collection.AddSingleton<DalamudUtilService>();
|
||||
collection.AddSingleton<DtrEntry>();
|
||||
@@ -178,10 +178,9 @@ public sealed class Plugin : IDalamudPlugin
|
||||
collection.AddSingleton<IpcCallerMoodles>();
|
||||
collection.AddSingleton<IpcCallerPetNames>();
|
||||
collection.AddSingleton<IpcCallerBrio>();
|
||||
collection.AddSingleton<IpcCallerMare>();
|
||||
collection.AddSingleton<IpcCallerOtherSync>();
|
||||
collection.AddSingleton<IpcManager>();
|
||||
collection.AddSingleton<NotificationService>();
|
||||
collection.AddSingleton<NoSnapService>();
|
||||
|
||||
collection.AddSingleton((s) => new MareConfigService(pluginInterface.ConfigDirectory.FullName));
|
||||
collection.AddSingleton((s) => new ServerConfigService(pluginInterface.ConfigDirectory.FullName));
|
||||
@@ -247,8 +246,6 @@ public sealed class Plugin : IDalamudPlugin
|
||||
collection.AddHostedService(p => p.GetRequiredService<EventAggregator>());
|
||||
collection.AddHostedService(p => p.GetRequiredService<MarePlugin>());
|
||||
collection.AddHostedService(p => p.GetRequiredService<IpcProvider>());
|
||||
collection.AddHostedService(p => p.GetRequiredService<RepoChangeService>());
|
||||
collection.AddHostedService(p => p.GetRequiredService<NoSnapService>());
|
||||
})
|
||||
.Build();
|
||||
|
||||
|
||||
@@ -13,20 +13,18 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase
|
||||
private readonly GameObjectHandlerFactory _gameObjectHandlerFactory;
|
||||
private readonly DalamudUtilService _dalamudUtilService;
|
||||
private readonly IpcManager _ipcManager;
|
||||
private readonly NoSnapService _noSnapService;
|
||||
private readonly Dictionary<string, HandledCharaDataEntry> _handledCharaData = new(StringComparer.Ordinal);
|
||||
|
||||
public IReadOnlyDictionary<string, HandledCharaDataEntry> HandledCharaData => _handledCharaData;
|
||||
|
||||
public CharaDataCharacterHandler(ILogger<CharaDataCharacterHandler> logger, MareMediator mediator,
|
||||
GameObjectHandlerFactory gameObjectHandlerFactory, DalamudUtilService dalamudUtilService,
|
||||
IpcManager ipcManager, NoSnapService noSnapService)
|
||||
IpcManager ipcManager)
|
||||
: base(logger, mediator)
|
||||
{
|
||||
_gameObjectHandlerFactory = gameObjectHandlerFactory;
|
||||
_dalamudUtilService = dalamudUtilService;
|
||||
_ipcManager = ipcManager;
|
||||
_noSnapService = noSnapService;
|
||||
mediator.Subscribe<GposeEndMessage>(this, msg =>
|
||||
{
|
||||
foreach (var chara in _handledCharaData)
|
||||
@@ -94,7 +92,6 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase
|
||||
_handledCharaData.Remove(handled.Name);
|
||||
await _dalamudUtilService.RunOnFrameworkThread(async () =>
|
||||
{
|
||||
RemoveGposer(handled);
|
||||
await RevertChara(handled.Name, handled.CustomizePlus).ConfigureAwait(false);
|
||||
}).ConfigureAwait(false);
|
||||
return true;
|
||||
@@ -103,7 +100,6 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase
|
||||
internal void AddHandledChara(HandledCharaDataEntry handledCharaDataEntry)
|
||||
{
|
||||
_handledCharaData.Add(handledCharaDataEntry.Name, handledCharaDataEntry);
|
||||
_ = _dalamudUtilService.RunOnFrameworkThread(() => AddGposer(handledCharaDataEntry));
|
||||
}
|
||||
|
||||
public void UpdateHandledData(Dictionary<string, CharaDataMetaInfoExtendedDto?> newData)
|
||||
@@ -134,23 +130,4 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase
|
||||
if (handler.Address == nint.Zero) return null;
|
||||
return handler;
|
||||
}
|
||||
|
||||
private int GetGposerObjectIndex(string name)
|
||||
{
|
||||
return _dalamudUtilService.GetGposeCharacterFromObjectTableByName(name, _dalamudUtilService.IsInGpose)?.ObjectIndex ?? -1;
|
||||
}
|
||||
|
||||
private void AddGposer(HandledCharaDataEntry handled)
|
||||
{
|
||||
int objectIndex = GetGposerObjectIndex(handled.Name);
|
||||
if (objectIndex > 0)
|
||||
_noSnapService.AddGposer(objectIndex);
|
||||
}
|
||||
|
||||
private void RemoveGposer(HandledCharaDataEntry handled)
|
||||
{
|
||||
int objectIndex = GetGposerObjectIndex(handled.Name);
|
||||
if (objectIndex > 0)
|
||||
_noSnapService.RemoveGposer(objectIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,226 +0,0 @@
|
||||
using Dalamud.Plugin;
|
||||
using MareSynchronos.Interop.Ipc;
|
||||
using MareSynchronos.MareConfiguration.Models;
|
||||
using MareSynchronos.Services.Mediator;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace MareSynchronos.Services;
|
||||
|
||||
public sealed class NoSnapService : IHostedService, IMediatorSubscriber
|
||||
{
|
||||
private record NoSnapConfig
|
||||
{
|
||||
[JsonPropertyName("listOfPlugins")]
|
||||
public string[]? ListOfPlugins { get; set; }
|
||||
}
|
||||
|
||||
private readonly ILogger<NoSnapService> _logger;
|
||||
private readonly IDalamudPluginInterface _pluginInterface;
|
||||
private readonly Dictionary<string, bool> _listOfPlugins = new(StringComparer.Ordinal)
|
||||
{
|
||||
["QQSnapper"] = false,
|
||||
["QQSnappy"] = false,
|
||||
["QQMeddle.Plugin"] = false,
|
||||
};
|
||||
private static readonly HashSet<int> _gposers = new();
|
||||
private static readonly HashSet<string> _gposersNamed = new(StringComparer.Ordinal);
|
||||
private readonly IHostApplicationLifetime _hostApplicationLifetime;
|
||||
private readonly DalamudUtilService _dalamudUtilService;
|
||||
private readonly IpcManager _ipcManager;
|
||||
private readonly RemoteConfigurationService _remoteConfig;
|
||||
|
||||
public static bool AnyLoaded { get; private set; } = false;
|
||||
public static string ActivePlugins { get; private set; } = string.Empty;
|
||||
|
||||
public MareMediator Mediator { get; init; }
|
||||
|
||||
public NoSnapService(ILogger<NoSnapService> logger, IDalamudPluginInterface pluginInterface, MareMediator mediator,
|
||||
IHostApplicationLifetime hostApplicationLifetime, DalamudUtilService dalamudUtilService, IpcManager ipcManager,
|
||||
RemoteConfigurationService remoteConfig)
|
||||
{
|
||||
_logger = logger;
|
||||
_pluginInterface = pluginInterface;
|
||||
Mediator = mediator;
|
||||
_hostApplicationLifetime = hostApplicationLifetime;
|
||||
_dalamudUtilService = dalamudUtilService;
|
||||
_ipcManager = ipcManager;
|
||||
_remoteConfig = remoteConfig;
|
||||
|
||||
Mediator.Subscribe<GposeEndMessage>(this, msg => ClearGposeList());
|
||||
Mediator.Subscribe<CutsceneEndMessage>(this, msg => ClearGposeList());
|
||||
}
|
||||
|
||||
public void AddGposer(int objectIndex)
|
||||
{
|
||||
if (AnyLoaded || _hostApplicationLifetime.ApplicationStopping.IsCancellationRequested)
|
||||
{
|
||||
_logger.LogTrace("Immediately reverting object index {id}", objectIndex);
|
||||
RevertAndRedraw(objectIndex);
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogTrace("Registering gposer object index {id}", objectIndex);
|
||||
lock (_gposers)
|
||||
_gposers.Add(objectIndex);
|
||||
}
|
||||
|
||||
public void RemoveGposer(int objectIndex)
|
||||
{
|
||||
_logger.LogTrace("Un-registering gposer object index {id}", objectIndex);
|
||||
lock (_gposers)
|
||||
_gposers.Remove(objectIndex);
|
||||
}
|
||||
|
||||
public void AddGposerNamed(string name)
|
||||
{
|
||||
if (AnyLoaded || _hostApplicationLifetime.ApplicationStopping.IsCancellationRequested)
|
||||
{
|
||||
_logger.LogTrace("Immediately reverting {name}", name);
|
||||
RevertAndRedraw(name);
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogTrace("Registering gposer {name}", name);
|
||||
lock (_gposers)
|
||||
_gposersNamed.Add(name);
|
||||
}
|
||||
|
||||
private void ClearGposeList()
|
||||
{
|
||||
if (_gposers.Count > 0 || _gposersNamed.Count > 0)
|
||||
_logger.LogTrace("Clearing gposer list");
|
||||
lock (_gposers)
|
||||
_gposers.Clear();
|
||||
lock (_gposersNamed)
|
||||
_gposersNamed.Clear();
|
||||
}
|
||||
|
||||
private void RevertAndRedraw(int objIndex, Guid applicationId = default)
|
||||
{
|
||||
if (applicationId == default)
|
||||
applicationId = Guid.NewGuid();
|
||||
|
||||
try
|
||||
{
|
||||
_ipcManager.Glamourer.RevertNow(_logger, applicationId, objIndex);
|
||||
_ipcManager.Penumbra.RedrawNow(_logger, applicationId, objIndex);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void RevertAndRedraw(string name, Guid applicationId = default)
|
||||
{
|
||||
if (applicationId == default)
|
||||
applicationId = Guid.NewGuid();
|
||||
|
||||
try
|
||||
{
|
||||
_ipcManager.Glamourer.RevertByNameNow(_logger, applicationId, name);
|
||||
var addr = _dalamudUtilService.GetPlayerCharacterFromCachedTableByName(name);
|
||||
if (addr != 0)
|
||||
{
|
||||
var obj = _dalamudUtilService.CreateGameObject(addr);
|
||||
if (obj != null)
|
||||
_ipcManager.Penumbra.RedrawNow(_logger, applicationId, obj.ObjectIndex);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void RevertGposers()
|
||||
{
|
||||
List<int>? gposersList = null;
|
||||
List<string>? gposersList2 = null;
|
||||
|
||||
lock (_gposers)
|
||||
{
|
||||
if (_gposers.Count > 0)
|
||||
{
|
||||
gposersList = _gposers.ToList();
|
||||
_gposers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
lock (_gposersNamed)
|
||||
{
|
||||
if (_gposersNamed.Count > 0)
|
||||
{
|
||||
gposersList2 = _gposersNamed.ToList();
|
||||
_gposersNamed.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (gposersList == null && gposersList2 == null)
|
||||
return;
|
||||
|
||||
_logger.LogInformation("Reverting gposers");
|
||||
|
||||
_dalamudUtilService.RunOnFrameworkThread(() =>
|
||||
{
|
||||
Guid applicationId = Guid.NewGuid();
|
||||
|
||||
foreach (var gposer in gposersList ?? [])
|
||||
RevertAndRedraw(gposer, applicationId);
|
||||
|
||||
foreach (var gposerName in gposersList2 ?? [])
|
||||
RevertAndRedraw(gposerName, applicationId);
|
||||
}).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var config = await _remoteConfig.GetConfigAsync<NoSnapConfig>("noSnap").ConfigureAwait(false) ?? new();
|
||||
|
||||
if (config.ListOfPlugins != null)
|
||||
{
|
||||
_listOfPlugins.Clear();
|
||||
foreach (var pluginName in config.ListOfPlugins)
|
||||
_listOfPlugins.TryAdd(pluginName, value: false);
|
||||
}
|
||||
|
||||
foreach (var pluginName in _listOfPlugins.Keys)
|
||||
{
|
||||
_listOfPlugins[pluginName] = PluginWatcherService.GetInitialPluginState(_pluginInterface, pluginName)?.IsLoaded ?? false;
|
||||
Mediator.SubscribeKeyed<PluginChangeMessage>(this, pluginName, (msg) =>
|
||||
{
|
||||
_listOfPlugins[pluginName] = msg.IsLoaded;
|
||||
_logger.LogDebug("{pluginName} isLoaded = {isLoaded}", pluginName, msg.IsLoaded);
|
||||
Update();
|
||||
});
|
||||
}
|
||||
|
||||
Update();
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
RevertGposers();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
bool anyLoadedNow = _listOfPlugins.Values.Any(p => p);
|
||||
|
||||
if (AnyLoaded != anyLoadedNow)
|
||||
{
|
||||
AnyLoaded = anyLoadedNow;
|
||||
Mediator.Publish(new RecalculatePerformanceMessage(null));
|
||||
|
||||
if (AnyLoaded)
|
||||
{
|
||||
RevertGposers();
|
||||
var pluginList = string.Join(", ", _listOfPlugins.Where(p => p.Value).Select(p => p.Key));
|
||||
Mediator.Publish(new NotificationMessage("Incompatible plugin loaded", $"Synced player appearances will not apply until incompatible plugins are disabled: {pluginList}.",
|
||||
NotificationType.Error));
|
||||
ActivePlugins = pluginList;
|
||||
}
|
||||
else
|
||||
{
|
||||
ActivePlugins = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,12 +172,6 @@ public sealed class RemoteConfigurationService
|
||||
byte[] pub = Convert.FromBase64String(pubKey);
|
||||
return Ed25519.Verify(sig, msg, pub);
|
||||
}
|
||||
|
||||
private static byte[] SignForMe(string message, ulong ts, byte[] privKey)
|
||||
{
|
||||
byte[] msg = [.. BitConverter.GetBytes(ts), .. Encoding.UTF8.GetBytes(message)];
|
||||
return Ed25519.Sign(msg, privKey);
|
||||
}
|
||||
|
||||
private void LoadConfig(JsonObject jsonDoc)
|
||||
{
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace MareSynchronos.Services;
|
||||
|
||||
public record RepoChangeConfig
|
||||
{
|
||||
[JsonPropertyName("current_repo")]
|
||||
public string? CurrentRepo { get; set; }
|
||||
|
||||
[JsonPropertyName("valid_repos")]
|
||||
public string[]? ValidRepos { get; set; }
|
||||
}
|
||||
@@ -1,401 +0,0 @@
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MareSynchronos.Services;
|
||||
|
||||
/* Reflection code based almost entirely on ECommons DalamudReflector
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 NightmareXIV
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
public sealed class RepoChangeService : IHostedService
|
||||
{
|
||||
#region Reflection Helpers
|
||||
private const BindingFlags AllFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
|
||||
private const BindingFlags StaticFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
|
||||
private const BindingFlags InstanceFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
|
||||
|
||||
private static object GetFoP(object obj, string name)
|
||||
{
|
||||
Type? type = obj.GetType();
|
||||
while (type != null)
|
||||
{
|
||||
var fieldInfo = type.GetField(name, AllFlags);
|
||||
if (fieldInfo != null)
|
||||
{
|
||||
return fieldInfo.GetValue(obj)!;
|
||||
}
|
||||
var propertyInfo = type.GetProperty(name, AllFlags);
|
||||
if (propertyInfo != null)
|
||||
{
|
||||
return propertyInfo.GetValue(obj)!;
|
||||
}
|
||||
type = type.BaseType;
|
||||
}
|
||||
throw new Exception($"Reflection GetFoP failed (not found: {obj.GetType().Name}.{name})");
|
||||
}
|
||||
|
||||
private static T GetFoP<T>(object obj, string name)
|
||||
{
|
||||
return (T)GetFoP(obj, name);
|
||||
}
|
||||
|
||||
private static void SetFoP(object obj, string name, object value)
|
||||
{
|
||||
var type = obj.GetType();
|
||||
var field = type.GetField(name, AllFlags);
|
||||
if (field != null)
|
||||
{
|
||||
field.SetValue(obj, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
var prop = type.GetProperty(name, AllFlags)!;
|
||||
if (prop == null)
|
||||
throw new Exception($"Reflection SetFoP failed (not found: {type.Name}.{name})");
|
||||
prop.SetValue(obj, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static object? Call(object obj, string name, object[] @params, bool matchExactArgumentTypes = false)
|
||||
{
|
||||
MethodInfo? info;
|
||||
var type = obj.GetType();
|
||||
if (!matchExactArgumentTypes)
|
||||
{
|
||||
info = type.GetMethod(name, AllFlags);
|
||||
}
|
||||
else
|
||||
{
|
||||
info = type.GetMethod(name, AllFlags, @params.Select(x => x.GetType()).ToArray());
|
||||
}
|
||||
if (info == null)
|
||||
throw new Exception($"Reflection Call failed (not found: {type.Name}.{name})");
|
||||
return info.Invoke(obj, @params);
|
||||
}
|
||||
|
||||
private static T Call<T>(object obj, string name, object[] @params, bool matchExactArgumentTypes = false)
|
||||
{
|
||||
return (T)Call(obj, name, @params, matchExactArgumentTypes)!;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Dalamud Reflection
|
||||
public object GetService(string serviceFullName)
|
||||
{
|
||||
return _pluginInterface.GetType().Assembly.
|
||||
GetType("Dalamud.Service`1", true)!.MakeGenericType(_pluginInterface.GetType().Assembly.GetType(serviceFullName, true)!).
|
||||
GetMethod("Get")!.Invoke(null, BindingFlags.Default, null, Array.Empty<object>(), null)!;
|
||||
}
|
||||
|
||||
private object GetPluginManager()
|
||||
{
|
||||
return _pluginInterface.GetType().Assembly.
|
||||
GetType("Dalamud.Service`1", true)!.MakeGenericType(_pluginInterface.GetType().Assembly.GetType("Dalamud.Plugin.Internal.PluginManager", true)!).
|
||||
GetMethod("Get")!.Invoke(null, BindingFlags.Default, null, Array.Empty<object>(), null)!;
|
||||
}
|
||||
|
||||
private void ReloadPluginMasters()
|
||||
{
|
||||
var mgr = GetService("Dalamud.Plugin.Internal.PluginManager");
|
||||
var pluginReload = mgr.GetType().GetMethod("SetPluginReposFromConfigAsync", BindingFlags.Instance | BindingFlags.Public)!;
|
||||
pluginReload.Invoke(mgr, [true]);
|
||||
}
|
||||
|
||||
public void SaveDalamudConfig()
|
||||
{
|
||||
var conf = GetService("Dalamud.Configuration.Internal.DalamudConfiguration");
|
||||
var configSave = conf?.GetType().GetMethod("QueueSave", BindingFlags.Instance | BindingFlags.Public);
|
||||
configSave?.Invoke(conf, null);
|
||||
}
|
||||
|
||||
private IEnumerable<object> GetRepoByURL(string repoURL)
|
||||
{
|
||||
var conf = GetService("Dalamud.Configuration.Internal.DalamudConfiguration");
|
||||
var repolist = (System.Collections.IEnumerable)GetFoP(conf, "ThirdRepoList");
|
||||
foreach (var r in repolist)
|
||||
{
|
||||
if (((string)GetFoP(r, "Url")).Equals(repoURL, StringComparison.OrdinalIgnoreCase))
|
||||
yield return r;
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasRepo(string repoURL)
|
||||
{
|
||||
var conf = GetService("Dalamud.Configuration.Internal.DalamudConfiguration");
|
||||
var repolist = (System.Collections.IEnumerable)GetFoP(conf, "ThirdRepoList");
|
||||
foreach (var r in repolist)
|
||||
{
|
||||
if (((string)GetFoP(r, "Url")).Equals(repoURL, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void AddRepo(string repoURL, bool enabled)
|
||||
{
|
||||
var conf = GetService("Dalamud.Configuration.Internal.DalamudConfiguration");
|
||||
var repolist = (System.Collections.IEnumerable)GetFoP(conf, "ThirdRepoList");
|
||||
foreach (var r in repolist)
|
||||
{
|
||||
if (((string)GetFoP(r, "Url")).Equals(repoURL, StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
}
|
||||
var instance = Activator.CreateInstance(_pluginInterface.GetType().Assembly.GetType("Dalamud.Configuration.ThirdPartyRepoSettings")!)!;
|
||||
SetFoP(instance, "Url", repoURL);
|
||||
SetFoP(instance, "IsEnabled", enabled);
|
||||
GetFoP<System.Collections.IList>(conf, "ThirdRepoList").Add(instance!);
|
||||
}
|
||||
|
||||
private void RemoveRepo(string repoURL)
|
||||
{
|
||||
var toRemove = new List<object>();
|
||||
var conf = GetService("Dalamud.Configuration.Internal.DalamudConfiguration");
|
||||
var repolist = (System.Collections.IList)GetFoP(conf, "ThirdRepoList");
|
||||
foreach (var r in repolist)
|
||||
{
|
||||
if (((string)GetFoP(r, "Url")).Equals(repoURL, StringComparison.OrdinalIgnoreCase))
|
||||
toRemove.Add(r);
|
||||
}
|
||||
foreach (var r in toRemove)
|
||||
repolist.Remove(r);
|
||||
}
|
||||
|
||||
public List<(object LocalPlugin, string InstalledFromUrl)> GetLocalPluginsByName(string internalName)
|
||||
{
|
||||
List<(object LocalPlugin, string RepoURL)> result = [];
|
||||
|
||||
var pluginManager = GetPluginManager();
|
||||
var installedPlugins = (System.Collections.IList)pluginManager.GetType().GetProperty("InstalledPlugins")!.GetValue(pluginManager)!;
|
||||
|
||||
foreach (var plugin in installedPlugins)
|
||||
{
|
||||
if (((string)plugin.GetType().GetProperty("InternalName")!.GetValue(plugin)!).Equals(internalName, StringComparison.Ordinal))
|
||||
{
|
||||
var type = plugin.GetType();
|
||||
if (type.Name.Equals("LocalDevPlugin", StringComparison.Ordinal))
|
||||
continue;
|
||||
var manifest = GetFoP(plugin, "manifest");
|
||||
string installedFromUrl = (string)GetFoP(manifest, "InstalledFromUrl");
|
||||
result.Add((plugin, installedFromUrl));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#endregion
|
||||
|
||||
private readonly ILogger<RepoChangeService> _logger;
|
||||
private readonly RemoteConfigurationService _remoteConfig;
|
||||
private readonly IDalamudPluginInterface _pluginInterface;
|
||||
private readonly IFramework _framework;
|
||||
|
||||
public RepoChangeService(ILogger<RepoChangeService> logger, RemoteConfigurationService remoteConfig, IDalamudPluginInterface pluginInterface, IFramework framework)
|
||||
{
|
||||
_logger = logger;
|
||||
_remoteConfig = remoteConfig;
|
||||
_pluginInterface = pluginInterface;
|
||||
_framework = framework;
|
||||
}
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogDebug("Starting RepoChange Service");
|
||||
var repoChangeConfig = await _remoteConfig.GetConfigAsync<RepoChangeConfig>("repoChange").ConfigureAwait(false) ?? new();
|
||||
|
||||
var currentRepo = repoChangeConfig.CurrentRepo;
|
||||
var validRepos = (repoChangeConfig.ValidRepos ?? []).ToList();
|
||||
|
||||
if (!currentRepo.IsNullOrEmpty() && !validRepos.Contains(currentRepo, StringComparer.Ordinal))
|
||||
validRepos.Add(currentRepo);
|
||||
|
||||
if (validRepos.Count == 0)
|
||||
{
|
||||
_logger.LogInformation("No valid repos configured, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
await _framework.RunOnTick(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var internalName = Assembly.GetExecutingAssembly().GetName().Name!;
|
||||
var localPlugins = GetLocalPluginsByName(internalName);
|
||||
|
||||
var suffix = string.Empty;
|
||||
|
||||
if (localPlugins.Count == 0)
|
||||
{
|
||||
_logger.LogInformation("Skipping: No intalled plugin found");
|
||||
return;
|
||||
}
|
||||
|
||||
var hasValidCustomRepoUrl = false;
|
||||
|
||||
foreach (var vr in validRepos)
|
||||
{
|
||||
var vrCN = vr.Replace(".json", "_CN.json", StringComparison.Ordinal);
|
||||
var vrKR = vr.Replace(".json", "_KR.json", StringComparison.Ordinal);
|
||||
if (HasRepo(vr) || HasRepo(vrCN) || HasRepo(vrKR))
|
||||
{
|
||||
hasValidCustomRepoUrl = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
List<string> oldRepos = [];
|
||||
var pluginRepoUrl = localPlugins[0].InstalledFromUrl;
|
||||
|
||||
if (pluginRepoUrl.Contains("_CN.json", StringComparison.Ordinal))
|
||||
suffix = "_CN";
|
||||
else if (pluginRepoUrl.Contains("_KR.json", StringComparison.Ordinal))
|
||||
suffix = "_KR";
|
||||
|
||||
bool hasOldPluginRepoUrl = false;
|
||||
|
||||
foreach (var plugin in localPlugins)
|
||||
{
|
||||
foreach (var vr in validRepos)
|
||||
{
|
||||
var validRepo = vr.Replace(".json", $"{suffix}.json");
|
||||
if (!plugin.InstalledFromUrl.Equals(validRepo, StringComparison.Ordinal))
|
||||
{
|
||||
oldRepos.Add(plugin.InstalledFromUrl);
|
||||
hasOldPluginRepoUrl = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasValidCustomRepoUrl)
|
||||
{
|
||||
if (hasOldPluginRepoUrl)
|
||||
_logger.LogInformation("Result: Repo URL is up to date, but plugin install source is incorrect");
|
||||
else
|
||||
_logger.LogInformation("Result: Repo URL is up to date");
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Result: Repo URL needs to be replaced");
|
||||
}
|
||||
|
||||
if (currentRepo.IsNullOrEmpty())
|
||||
{
|
||||
_logger.LogWarning("No current repo URL configured");
|
||||
return;
|
||||
}
|
||||
|
||||
// Pre-test plugin repo url rewriting to ensure it succeeds before replacing the custom repo URL
|
||||
if (hasOldPluginRepoUrl)
|
||||
{
|
||||
foreach (var plugin in localPlugins)
|
||||
{
|
||||
var manifest = GetFoP(plugin.LocalPlugin, "manifest");
|
||||
if (manifest == null)
|
||||
throw new Exception("Plugin manifest is null");
|
||||
var manifestFile = GetFoP(plugin.LocalPlugin, "manifestFile");
|
||||
if (manifestFile == null)
|
||||
throw new Exception("Plugin manifestFile is null");
|
||||
var repo = GetFoP(manifest, "InstalledFromUrl");
|
||||
if (((string)repo).IsNullOrEmpty())
|
||||
throw new Exception("Plugin repo url is null or empty");
|
||||
SetFoP(manifest, "InstalledFromUrl", repo);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasValidCustomRepoUrl)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var oldRepo in oldRepos)
|
||||
{
|
||||
_logger.LogInformation("* Removing old repo: {r}", oldRepo);
|
||||
RemoveRepo(oldRepo);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_logger.LogInformation("* Adding current repo: {r}", currentRepo);
|
||||
AddRepo(currentRepo, true);
|
||||
}
|
||||
}
|
||||
|
||||
// This time do it for real, and crash the game if we fail, to avoid saving a broken state
|
||||
if (hasOldPluginRepoUrl)
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("* Updating plugins");
|
||||
foreach (var plugin in localPlugins)
|
||||
{
|
||||
var manifest = GetFoP(plugin.LocalPlugin, "manifest");
|
||||
if (manifest == null)
|
||||
throw new Exception("Plugin manifest is null");
|
||||
var manifestFile = GetFoP(plugin.LocalPlugin, "manifestFile");
|
||||
if (manifestFile == null)
|
||||
throw new Exception("Plugin manifestFile is null");
|
||||
var repo = GetFoP(manifest, "InstalledFromUrl");
|
||||
if (((string)repo).IsNullOrEmpty())
|
||||
throw new Exception("Plugin repo url is null or empty");
|
||||
SetFoP(manifest, "InstalledFromUrl", currentRepo);
|
||||
Call(manifest, "Save", [manifestFile, "RepoChange"]);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Exception while changing plugin install repo");
|
||||
foreach (var oldRepo in oldRepos)
|
||||
{
|
||||
_logger.LogInformation("* Restoring old repo: {r}", oldRepo);
|
||||
AddRepo(oldRepo, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasValidCustomRepoUrl || hasOldPluginRepoUrl)
|
||||
{
|
||||
_logger.LogInformation("* Saving dalamud config");
|
||||
SaveDalamudConfig();
|
||||
_logger.LogInformation("* Reloading plugin masters");
|
||||
ReloadPluginMasters();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Exception in RepoChangeService");
|
||||
}
|
||||
}, default, 10, cancellationToken).ConfigureAwait(false);
|
||||
_logger.LogInformation("Started RepoChangeService");
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_ = cancellationToken;
|
||||
_logger.LogDebug("Stopping RepoChange Service");
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
@@ -496,14 +496,14 @@ public class ServerConfigurationManager
|
||||
|
||||
private void EnsureMainExists()
|
||||
{
|
||||
bool lopExists = false;
|
||||
bool clubExists = false;
|
||||
for (int i = 0; i < _configService.Current.ServerStorage.Count; ++i)
|
||||
{
|
||||
var x = _configService.Current.ServerStorage[i];
|
||||
if (x.ServerUri.Equals(ApiController.ClubPenguinServiceUri, StringComparison.OrdinalIgnoreCase))
|
||||
lopExists = true;
|
||||
clubExists = true;
|
||||
}
|
||||
if (!lopExists)
|
||||
if (!clubExists)
|
||||
{
|
||||
_logger.LogDebug("Re-adding missing server {uri}", ApiController.ClubPenguinServiceUri);
|
||||
_configService.Current.ServerStorage.Insert(0, new ServerStorage() { ServerUri = ApiController.ClubPenguinServiceUri, ServerName = ApiController.ClubPenguinServer });
|
||||
|
||||
@@ -12,21 +12,21 @@ public class VisibilityService : DisposableMediatorSubscriberBase
|
||||
{
|
||||
NotVisible,
|
||||
Visible,
|
||||
MareHandled
|
||||
OtherSyncHandled
|
||||
};
|
||||
|
||||
private readonly DalamudUtilService _dalamudUtil;
|
||||
private readonly ConcurrentDictionary<string, TrackedPlayerStatus> _trackedPlayerVisibility = new(StringComparer.Ordinal);
|
||||
private readonly List<string> _makeVisibleNextFrame = new();
|
||||
private readonly IpcCallerMare _mare;
|
||||
private readonly HashSet<nint> cachedMareAddresses = new();
|
||||
private readonly IpcCallerOtherSync _otherSync;
|
||||
private readonly HashSet<nint> cachedOtherSyncAddresses = new();
|
||||
private uint _cachedAddressSum = 0;
|
||||
private uint _cachedAddressSumDebounce = 1;
|
||||
|
||||
public VisibilityService(ILogger<VisibilityService> logger, MareMediator mediator, IpcCallerMare mare, DalamudUtilService dalamudUtil)
|
||||
public VisibilityService(ILogger<VisibilityService> logger, MareMediator mediator, IpcCallerOtherSync otherSync, DalamudUtilService dalamudUtil)
|
||||
: base(logger, mediator)
|
||||
{
|
||||
_mare = mare;
|
||||
_otherSync = otherSync;
|
||||
_dalamudUtil = dalamudUtil;
|
||||
Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => FrameworkUpdate());
|
||||
}
|
||||
@@ -44,19 +44,19 @@ public class VisibilityService : DisposableMediatorSubscriberBase
|
||||
|
||||
private void FrameworkUpdate()
|
||||
{
|
||||
var mareHandledAddresses = _mare.GetHandledGameAddresses();
|
||||
var otherSyncHandledAddresses = _otherSync.GetHandledGameAddresses();
|
||||
uint addressSum = 0;
|
||||
|
||||
foreach (var addr in mareHandledAddresses)
|
||||
foreach (var addr in otherSyncHandledAddresses)
|
||||
addressSum ^= (uint)addr.GetHashCode();
|
||||
|
||||
if (addressSum != _cachedAddressSum)
|
||||
{
|
||||
if (addressSum == _cachedAddressSumDebounce)
|
||||
{
|
||||
cachedMareAddresses.Clear();
|
||||
foreach (var addr in mareHandledAddresses)
|
||||
cachedMareAddresses.Add(addr);
|
||||
cachedOtherSyncAddresses.Clear();
|
||||
foreach (var addr in otherSyncHandledAddresses)
|
||||
cachedOtherSyncAddresses.Add(addr);
|
||||
_cachedAddressSum = addressSum;
|
||||
}
|
||||
else
|
||||
@@ -69,11 +69,11 @@ public class VisibilityService : DisposableMediatorSubscriberBase
|
||||
{
|
||||
string ident = player.Key;
|
||||
var findResult = _dalamudUtil.FindPlayerByNameHash(ident);
|
||||
var isMareHandled = cachedMareAddresses.Contains(findResult.Address);
|
||||
var isVisible = findResult.ObjectId != 0 && !isMareHandled;
|
||||
var isOtherSyncHandled = cachedOtherSyncAddresses.Contains(findResult.Address);
|
||||
var isVisible = findResult.ObjectId != 0 && !isOtherSyncHandled;
|
||||
|
||||
if (player.Value == TrackedPlayerStatus.MareHandled && !isMareHandled)
|
||||
_trackedPlayerVisibility.TryUpdate(ident, newValue: TrackedPlayerStatus.NotVisible, comparisonValue: TrackedPlayerStatus.MareHandled);
|
||||
if (player.Value == TrackedPlayerStatus.OtherSyncHandled && !isOtherSyncHandled)
|
||||
_trackedPlayerVisibility.TryUpdate(ident, newValue: TrackedPlayerStatus.NotVisible, comparisonValue: TrackedPlayerStatus.OtherSyncHandled);
|
||||
|
||||
if (player.Value == TrackedPlayerStatus.NotVisible && isVisible)
|
||||
{
|
||||
@@ -85,17 +85,17 @@ public class VisibilityService : DisposableMediatorSubscriberBase
|
||||
else
|
||||
_makeVisibleNextFrame.Add(ident);
|
||||
}
|
||||
else if (player.Value == TrackedPlayerStatus.NotVisible && isMareHandled)
|
||||
else if (player.Value == TrackedPlayerStatus.NotVisible && isOtherSyncHandled)
|
||||
{
|
||||
// Send a technically redundant visibility update with the added intent of triggering PairHandler to undo the application by name
|
||||
if (_trackedPlayerVisibility.TryUpdate(ident, newValue: TrackedPlayerStatus.MareHandled, comparisonValue: TrackedPlayerStatus.NotVisible))
|
||||
if (_trackedPlayerVisibility.TryUpdate(ident, newValue: TrackedPlayerStatus.OtherSyncHandled, comparisonValue: TrackedPlayerStatus.NotVisible))
|
||||
Mediator.Publish<PlayerVisibilityMessage>(new(ident, IsVisible: false, Invalidate: true));
|
||||
}
|
||||
else if (player.Value == TrackedPlayerStatus.Visible && !isVisible)
|
||||
{
|
||||
var newTrackedStatus = isMareHandled ? TrackedPlayerStatus.MareHandled : TrackedPlayerStatus.NotVisible;
|
||||
var newTrackedStatus = isOtherSyncHandled ? TrackedPlayerStatus.OtherSyncHandled : TrackedPlayerStatus.NotVisible;
|
||||
if (_trackedPlayerVisibility.TryUpdate(ident, newValue: newTrackedStatus, comparisonValue: TrackedPlayerStatus.Visible))
|
||||
Mediator.Publish<PlayerVisibilityMessage>(new(ident, IsVisible: false, Invalidate: isMareHandled));
|
||||
Mediator.Publish<PlayerVisibilityMessage>(new(ident, IsVisible: false, Invalidate: isOtherSyncHandled));
|
||||
}
|
||||
|
||||
if (!isVisible)
|
||||
|
||||
@@ -105,7 +105,7 @@ public class CompactUi : WindowMediatorSubscriberBase
|
||||
protected override void DrawInternal()
|
||||
{
|
||||
if (_serverManager.CurrentApiUrl.Equals(ApiController.ClubPenguinServiceUri, StringComparison.Ordinal))
|
||||
UiSharedService.AccentColor = new Vector4(1.0f, 0.8666f, 0.06666f, 1.0f);
|
||||
UiSharedService.AccentColor = new Vector4(0.70196078431f, 0.54901960784f, 1.0f, 1.0f);
|
||||
else
|
||||
UiSharedService.AccentColor = ImGuiColors.ParsedGreen;
|
||||
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - ImGui.GetStyle().WindowPadding.Y - 1f * ImGuiHelpers.GlobalScale + ImGui.GetStyle().ItemSpacing.Y);
|
||||
|
||||
@@ -247,7 +247,7 @@ This service is provided as-is.
|
||||
ImGui.BeginDisabled(_registrationInProgress || _uiShared.ApiController.ServerState == ServerState.Connecting || _uiShared.ApiController.ServerState == ServerState.Reconnecting);
|
||||
_ = _uiShared.DrawServiceSelection(selectOnChange: true, intro: true);
|
||||
|
||||
if (true) // Enable registration button for all servers
|
||||
if (_serverConfigurationManager.CurrentApiUrl == null || !_serverConfigurationManager.CurrentApiUrl.Equals(ApiController.ClubPenguinServiceUri, StringComparison.Ordinal)) // Enable registration button for all servers
|
||||
{
|
||||
ImGui.BeginDisabled(_registrationInProgress || _registrationSuccess || _secretKey.Length > 0);
|
||||
ImGui.Separator();
|
||||
@@ -297,6 +297,12 @@ This service is provided as-is.
|
||||
ImGui.TextWrapped(_registrationMessage);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.Separator();
|
||||
UiSharedService.TextWrapped("You must join the discord to register a Club Penguin Sync account on the main server.");
|
||||
UiSharedService.TextWrapped("Use the /signup command to register an account within the discord follow the instructiosn in the #how-to-setup channel.");
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using Dalamud.Utility;
|
||||
using MareSynchronos.API.Data;
|
||||
using MareSynchronos.API.Data.Comparer;
|
||||
using MareSynchronos.FileCache;
|
||||
using MareSynchronos.Interop;
|
||||
using MareSynchronos.Interop.Ipc;
|
||||
using MareSynchronos.MareConfiguration;
|
||||
using MareSynchronos.MareConfiguration.Models;
|
||||
@@ -25,6 +26,7 @@ using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace MareSynchronos.UI;
|
||||
@@ -50,6 +52,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
private readonly PlayerPerformanceService _playerPerformanceService;
|
||||
private readonly AccountRegistrationService _registerService;
|
||||
private readonly ServerConfigurationManager _serverConfigurationManager;
|
||||
private readonly ColorTableHook _colorTableHook;
|
||||
private readonly UiSharedService _uiShared;
|
||||
private bool _deleteAccountPopupModalShown = false;
|
||||
private string _lastTab = string.Empty;
|
||||
@@ -77,11 +80,12 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
FileCacheManager fileCacheManager,
|
||||
FileCompactor fileCompactor, ApiController apiController,
|
||||
IpcManager ipcManager, IpcProvider ipcProvider, CacheMonitor cacheMonitor,
|
||||
DalamudUtilService dalamudUtilService, AccountRegistrationService registerService) : base(logger, mediator, "Club Penguin Sync Settings", performanceCollector)
|
||||
DalamudUtilService dalamudUtilService, AccountRegistrationService registerService, ColorTableHook colorTableHook) : base(logger, mediator, "Club Penguin Sync Settings", performanceCollector)
|
||||
{
|
||||
_configService = configService;
|
||||
_pairManager = pairManager;
|
||||
_chatService = chatService;
|
||||
_colorTableHook = colorTableHook;
|
||||
_guiHookService = guiHookService;
|
||||
_serverConfigurationManager = serverConfigurationManager;
|
||||
_playerPerformanceConfigService = playerPerformanceConfigService;
|
||||
@@ -581,43 +585,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
|
||||
_uiShared.BigText("Advanced");
|
||||
|
||||
bool mareApi = _configService.Current.MareAPI;
|
||||
using (var disabled = ImRaii.Disabled(true))
|
||||
{
|
||||
bool dummyFalse = false;
|
||||
if (ImGui.Checkbox("Enable Mare Synchronos API", ref dummyFalse))
|
||||
{
|
||||
_configService.Current.MareAPI = mareApi;
|
||||
_configService.Save();
|
||||
_ipcProvider.HandleMareImpersonation();
|
||||
}
|
||||
_uiShared.DrawHelpText("Enables handling of the Mare Synchronos API. This currently includes:\n\n" +
|
||||
" - MCDF loading support for other plugins\n" +
|
||||
" - Blocking Moodles applications to paired users\n\n" +
|
||||
"If the Mare Synchronos plugin is loaded while this option is enabled, control of its API will be relinquished.");
|
||||
}
|
||||
|
||||
using (_ = ImRaii.PushIndent())
|
||||
{
|
||||
ImGui.SameLine(300.0f * ImGuiHelpers.GlobalScale);
|
||||
UiSharedService.ColorTextWrapped("Mare API impersonation is currently unavailable", ImGuiColors.DalamudGrey2);
|
||||
/*
|
||||
if (_ipcProvider.ImpersonationActive)
|
||||
{
|
||||
UiSharedService.ColorTextWrapped("Mare API active!", ImGuiColors.HealerGreen);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!mareApi)
|
||||
UiSharedService.ColorTextWrapped("Mare API inactive: Option is disabled", ImGuiColors.DalamudYellow);
|
||||
else if (_ipcProvider.MarePluginEnabled)
|
||||
UiSharedService.ColorTextWrapped("Mare API inactive: Mare plugin is loaded", ImGuiColors.DalamudYellow);
|
||||
else
|
||||
UiSharedService.ColorTextWrapped("Mare API inactive: Unknown reason", ImGuiColors.DalamudRed);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
bool logEvents = _configService.Current.LogEvents;
|
||||
if (ImGui.Checkbox("Log Event Viewer data to disk", ref logEvents))
|
||||
{
|
||||
@@ -640,6 +607,22 @@ public class SettingsUi : WindowMediatorSubscriberBase
|
||||
_configService.Save();
|
||||
}
|
||||
|
||||
bool attemptColorTableProtection = _configService.Current.AttemptColorTableProtection;
|
||||
if (ImGui.Checkbox("[EXPERIMENTAL] Attempt to fix potential ColorTable crashes.", ref attemptColorTableProtection))
|
||||
{
|
||||
if (attemptColorTableProtection)
|
||||
{
|
||||
_colorTableHook.EnableHooks();
|
||||
}
|
||||
else
|
||||
{
|
||||
_colorTableHook.DisableHooks();
|
||||
}
|
||||
//this shouldnt need the mediator as this is the ONLY source of truth.
|
||||
_configService.Current.AttemptColorTableProtection = attemptColorTableProtection;
|
||||
_configService.Save();
|
||||
}
|
||||
|
||||
bool serializedApplications = _configService.Current.SerialApplication;
|
||||
if (ImGui.Checkbox("Serialized player applications", ref serializedApplications))
|
||||
{
|
||||
|
||||
@@ -852,16 +852,6 @@ public partial class UiSharedService : DisposableMediatorSubscriberBase
|
||||
ImGui.TextColored(ImGuiColors.DalamudRed, "You need to install both Penumbra and Glamourer and keep them up to date to use Club Penguin Sync.");
|
||||
return false;
|
||||
}
|
||||
else if (NoSnapService.AnyLoaded)
|
||||
{
|
||||
IconText(FontAwesomeIcon.ExclamationTriangle, ImGuiColors.DalamudYellow);
|
||||
ImGui.SameLine();
|
||||
var cursorX = ImGui.GetCursorPosX();
|
||||
ImGui.TextColored(ImGuiColors.DalamudYellow, "Synced player appearances will not apply until incompatible plugins are disabled:");
|
||||
ImGui.SetCursorPosX(cursorX + 16.0f);
|
||||
ImGui.TextColored(ImGuiColors.DalamudYellow, NoSnapService.ActivePlugins);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -425,6 +425,18 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
||||
|
||||
private void PersistFileToStorage(string fileHash, string filePath, long? compressedSize = null)
|
||||
{
|
||||
var fi = new FileInfo(filePath);
|
||||
Func<DateTime> RandomDayInThePast()
|
||||
{
|
||||
DateTime start = new(1995, 1, 1, 1, 1, 1, DateTimeKind.Local);
|
||||
Random gen = new();
|
||||
int range = (DateTime.Today - start).Days;
|
||||
return () => start.AddDays(gen.Next(range));
|
||||
}
|
||||
|
||||
fi.CreationTime = RandomDayInThePast().Invoke();
|
||||
fi.LastAccessTime = DateTime.Today;
|
||||
fi.LastWriteTime = RandomDayInThePast().Invoke();
|
||||
try
|
||||
{
|
||||
var entry = _fileDbManager.CreateCacheEntry(filePath, fileHash);
|
||||
|
||||
Submodule Penumbra.Api updated: af41b1787a...dd14131793
Reference in New Issue
Block a user