Compare commits
	
		
			23 Commits
		
	
	
		
			b41532d5af
			...
			1.7.1.3
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 51004f3017 | |||
| 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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -3,6 +3,7 @@ using Dalamud.Game.Text.SeStringHandling.Payloads; | ||||
| using Dalamud.Hooking; | ||||
| using Dalamud.Memory; | ||||
| using Dalamud.Plugin.Services; | ||||
| using Dalamud.Utility; | ||||
| using Dalamud.Utility.Signatures; | ||||
| using FFXIVClientStructs.FFXIV.Client.System.String; | ||||
| using FFXIVClientStructs.FFXIV.Client.UI; | ||||
| @@ -26,7 +27,7 @@ public unsafe sealed class GameChatHooks : IDisposable | ||||
|     // Based on https://git.anna.lgbt/anna/ExtraChat/src/branch/main/client/ExtraChat/GameFunctions.cs | ||||
|  | ||||
|     private readonly ILogger<GameChatHooks> _logger; | ||||
|     private readonly Action<int, byte[]> _ssCommandHandler; | ||||
|     private readonly Action<int, byte[]> _psCommandHandler; | ||||
|  | ||||
|     #region signatures | ||||
|     #pragma warning disable CS0649 | ||||
| @@ -130,10 +131,10 @@ public unsafe sealed class GameChatHooks : IDisposable | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public GameChatHooks(ILogger<GameChatHooks> logger, IGameInteropProvider gameInteropProvider, Action<int, byte[]> ssCommandHandler) | ||||
|     public GameChatHooks(ILogger<GameChatHooks> logger, IGameInteropProvider gameInteropProvider, Action<int, byte[]> psCommandHandler) | ||||
|     { | ||||
|         _logger = logger; | ||||
|         _ssCommandHandler = ssCommandHandler; | ||||
|         _psCommandHandler = psCommandHandler; | ||||
|  | ||||
|         logger.LogInformation("Initializing GameChatHooks"); | ||||
|         gameInteropProvider.InitializeFromAttributes(this); | ||||
| @@ -209,20 +210,42 @@ public unsafe sealed class GameChatHooks : IDisposable | ||||
|             if (isReply) | ||||
|                 _nextMessageIsReply = utcNow + TimeSpan.FromMilliseconds(100); | ||||
|  | ||||
|             // If it is a command, check if it begins with /ss first so we can handle the message directly | ||||
|             // If it is a command, check if it begins with /ps first so we can handle the message directly | ||||
|             // Letting Dalamud handle the commands causes all of the special payloads to be dropped | ||||
|             if (isCommand && messageSpan.StartsWith(System.Text.Encoding.ASCII.GetBytes("/ss"))) | ||||
|             if (isCommand && messageSpan.StartsWith(System.Text.Encoding.ASCII.GetBytes("/ps"))) | ||||
|             { | ||||
|                  | ||||
|                 for (int i = 1; i <= ChatService.CommandMaxNumber; ++i) | ||||
|                 { | ||||
|                     var cmdString = $"/ss{i} "; | ||||
|                     var cmdString = $"/ps{i} "; | ||||
|                     if (messageSpan.StartsWith(System.Text.Encoding.ASCII.GetBytes(cmdString))) | ||||
|                     { | ||||
|                         var ssChatBytes = ProcessChatMessage(message); | ||||
|                         ssChatBytes = ssChatBytes.Skip(cmdString.Length).ToArray(); | ||||
|                         _ssCommandHandler?.Invoke(i, ssChatBytes); | ||||
|                         var psChatBytes = ProcessChatMessage(message); | ||||
|                         psChatBytes = psChatBytes.Skip(cmdString.Length).ToArray(); | ||||
|                         if (psChatBytes.Length > 0) | ||||
|                         { | ||||
|                             bool isBlank = true; | ||||
|                             int j; | ||||
|  | ||||
|                             for (j = 0; j < psChatBytes.Length; j++) | ||||
|                             { | ||||
|                                 if(!char.IsWhiteSpace((char)psChatBytes[j])) | ||||
|                                 { | ||||
|                                     isBlank = false; | ||||
|                                     break; | ||||
|                                 } | ||||
|                             } | ||||
|  | ||||
|                             if (isBlank) | ||||
|                             { | ||||
|                                 SendMessageHook!.OriginalDisposeSafe(thisPtr, message, uiModule); | ||||
|                                 return; | ||||
|                             } | ||||
|  | ||||
|                             _psCommandHandler?.Invoke(i, psChatBytes); | ||||
|                             return; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|   | ||||
| @@ -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.3</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); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -72,7 +72,7 @@ public class ChatService : DisposableMediatorSubscriberBase | ||||
|     { | ||||
|         var chatMsg = message.ChatMsg; | ||||
|         var prefix = new SeStringBuilder(); | ||||
|         prefix.AddText("[BnnuyChat] "); | ||||
|         prefix.AddText("[PenguinChat] "); | ||||
|         _chatGui.Print(new XivChatEntry{ | ||||
|             MessageBytes = [..prefix.Build().Encode(), ..message.ChatMsg.PayloadContent], | ||||
|             Name = chatMsg.SenderName, | ||||
| @@ -121,7 +121,7 @@ public class ChatService : DisposableMediatorSubscriberBase | ||||
|         } | ||||
|         if (color != 0) | ||||
|             msg.AddUiForeground((ushort)color); | ||||
|         msg.AddText($"[SS{shellNumber}]<"); | ||||
|         msg.AddText($"[PS{shellNumber}]<"); | ||||
|         if (message.ChatMsg.Sender.UID.Equals(_apiController.UID, StringComparison.Ordinal)) | ||||
|         { | ||||
|             // Don't link to your own character | ||||
| @@ -179,7 +179,7 @@ public class ChatService : DisposableMediatorSubscriberBase | ||||
|                 if (_gameChatHooks.IsValueCreated && _gameChatHooks.Value.ChatChannelOverride != null) | ||||
|                 { | ||||
|                     // Very dumb and won't handle re-numbering -- need to identify the active chat channel more reliably later | ||||
|                     if (_gameChatHooks.Value.ChatChannelOverride.ChannelName.StartsWith($"SS [{shellNumber}]", StringComparison.Ordinal)) | ||||
|                     if (_gameChatHooks.Value.ChatChannelOverride.ChannelName.StartsWith($"PS [{shellNumber}]", StringComparison.Ordinal)) | ||||
|                         SwitchChatShell(shellNumber); | ||||
|                 } | ||||
|             } | ||||
| @@ -200,7 +200,7 @@ public class ChatService : DisposableMediatorSubscriberBase | ||||
|                 // BUG: This doesn't always update the chat window e.g. when renaming a group | ||||
|                 _gameChatHooks.Value.ChatChannelOverride = new() | ||||
|                 { | ||||
|                     ChannelName = $"SS [{shellNumber}]: {name}", | ||||
|                     ChannelName = $"PS [{shellNumber}]: {name}", | ||||
|                     ChatMessageHandler = chatBytes => SendChatShell(shellNumber, chatBytes) | ||||
|                 }; | ||||
|                 return; | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| using Dalamud.Game.Command; | ||||
| using Dalamud.Plugin.Services; | ||||
| using Dalamud.Utility; | ||||
| using MareSynchronos.FileCache; | ||||
| using MareSynchronos.MareConfiguration; | ||||
| using MareSynchronos.MareConfiguration.Models; | ||||
| @@ -17,7 +18,7 @@ public sealed class CommandManagerService : IDisposable | ||||
|     private const string _commandName = "/sync"; | ||||
|     private const string _commandName2 = "/clubpenguin"; | ||||
|  | ||||
|     private const string _ssCommandPrefix = "/ss"; | ||||
|     private const string _psCommandPrefix = "/ps"; | ||||
|  | ||||
|     private readonly ApiController _apiController; | ||||
|     private readonly ICommandManager _commandManager; | ||||
| @@ -52,7 +53,7 @@ public sealed class CommandManagerService : IDisposable | ||||
|         // Lazy registration of all possible /ss# commands which tbf is what the game does for linkshells anyway | ||||
|         for (int i = 1; i <= ChatService.CommandMaxNumber; ++i) | ||||
|         { | ||||
|             _commandManager.AddHandler($"{_ssCommandPrefix}{i}", new CommandInfo(OnChatCommand) | ||||
|             _commandManager.AddHandler($"{_psCommandPrefix}{i}", new CommandInfo(OnChatCommand) | ||||
|             { | ||||
|                 ShowInHelp = false | ||||
|             }); | ||||
| @@ -65,7 +66,7 @@ public sealed class CommandManagerService : IDisposable | ||||
|         _commandManager.RemoveHandler(_commandName2); | ||||
|  | ||||
|         for (int i = 1; i <= ChatService.CommandMaxNumber; ++i) | ||||
|             _commandManager.RemoveHandler($"{_ssCommandPrefix}{i}"); | ||||
|             _commandManager.RemoveHandler($"{_psCommandPrefix}{i}"); | ||||
|     } | ||||
|  | ||||
|     private void OnCommand(string command, string args) | ||||
| @@ -139,9 +140,9 @@ public sealed class CommandManagerService : IDisposable | ||||
|         if (_mareConfigService.Current.DisableSyncshellChat) | ||||
|             return; | ||||
|  | ||||
|         int shellNumber = int.Parse(command[_ssCommandPrefix.Length..]); | ||||
|         int shellNumber = int.Parse(command[_psCommandPrefix.Length..]); | ||||
|  | ||||
|         if (args.Length == 0) | ||||
|         if (args.Length == 0 || args.IsNullOrWhitespace()) | ||||
|         { | ||||
|             _chatService.SwitchChatShell(shellNumber); | ||||
|         } | ||||
|   | ||||
| @@ -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; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -173,12 +173,6 @@ public sealed class RemoteConfigurationService | ||||
|         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) | ||||
|     { | ||||
|         var ts = jsonDoc["ts"]!.GetValue<ulong>(); | ||||
|   | ||||
| @@ -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); | ||||
|   | ||||
| @@ -246,7 +246,7 @@ internal sealed class GroupPanel | ||||
|             if (!_mareConfig.Current.DisableSyncshellChat && shellConfig.Enabled) | ||||
|             { | ||||
|                 ImGui.TextUnformatted($"[{shellNumber}]"); | ||||
|                 UiSharedService.AttachToolTip("Chat command prefix: /ss" + shellNumber); | ||||
|                 UiSharedService.AttachToolTip("Chat command prefix: /ps" + shellNumber); | ||||
|             } | ||||
|             if (textIsGid) ImGui.PushFont(UiBuilder.MonoFont); | ||||
|             ImGui.SameLine(); | ||||
|   | ||||
| @@ -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