Compare commits
	
		
			24 Commits
		
	
	
		
			b41532d5af
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 75f88d5104 | |||
| 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"] | [submodule "MareAPI"] | ||||||
| 	path = MareAPI | 	path = MareAPI | ||||||
| 	url = https://git.lop-sync.com/huggingway/LopAPI.git | 	url = https://git.drgn.rocks/t0w0bi/ClubPenguinApi.git | ||||||
| [submodule "Penumbra.Api"] | [submodule "Penumbra.Api"] | ||||||
| 	path = Penumbra.Api | 	path = Penumbra.Api | ||||||
| 	url = https://github.com/Ottermandias/Penumbra.Api.git | 	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.Hooking; | ||||||
| using Dalamud.Memory; | using Dalamud.Memory; | ||||||
| using Dalamud.Plugin.Services; | using Dalamud.Plugin.Services; | ||||||
|  | using Dalamud.Utility; | ||||||
| using Dalamud.Utility.Signatures; | using Dalamud.Utility.Signatures; | ||||||
| using FFXIVClientStructs.FFXIV.Client.System.String; | using FFXIVClientStructs.FFXIV.Client.System.String; | ||||||
| using FFXIVClientStructs.FFXIV.Client.UI; | 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 |     // Based on https://git.anna.lgbt/anna/ExtraChat/src/branch/main/client/ExtraChat/GameFunctions.cs | ||||||
|  |  | ||||||
|     private readonly ILogger<GameChatHooks> _logger; |     private readonly ILogger<GameChatHooks> _logger; | ||||||
|     private readonly Action<int, byte[]> _ssCommandHandler; |     private readonly Action<int, byte[]> _psCommandHandler; | ||||||
|  |  | ||||||
|     #region signatures |     #region signatures | ||||||
|     #pragma warning disable CS0649 |     #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; |         _logger = logger; | ||||||
|         _ssCommandHandler = ssCommandHandler; |         _psCommandHandler = psCommandHandler; | ||||||
|  |  | ||||||
|         logger.LogInformation("Initializing GameChatHooks"); |         logger.LogInformation("Initializing GameChatHooks"); | ||||||
|         gameInteropProvider.InitializeFromAttributes(this); |         gameInteropProvider.InitializeFromAttributes(this); | ||||||
| @@ -209,20 +210,42 @@ public unsafe sealed class GameChatHooks : IDisposable | |||||||
|             if (isReply) |             if (isReply) | ||||||
|                 _nextMessageIsReply = utcNow + TimeSpan.FromMilliseconds(100); |                 _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 |             // 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) |                 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))) |                     if (messageSpan.StartsWith(System.Text.Encoding.ASCII.GetBytes(cmdString))) | ||||||
|                     { |                     { | ||||||
|                         var ssChatBytes = ProcessChatMessage(message); |                         var psChatBytes = ProcessChatMessage(message); | ||||||
|                         ssChatBytes = ssChatBytes.Skip(cmdString.Length).ToArray(); |                         psChatBytes = psChatBytes.Skip(cmdString.Length).ToArray(); | ||||||
|                         _ssCommandHandler?.Invoke(i, ssChatBytes); |                         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; |                                 return; | ||||||
|                             } |                             } | ||||||
|  |  | ||||||
|  |                             _psCommandHandler?.Invoke(i, psChatBytes); | ||||||
|  |                             return; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -14,6 +14,7 @@ public sealed class IpcCallerHonorific : IIpcCaller | |||||||
|     private readonly ICallGateSubscriber<int, object> _honorificClearCharacterTitle; |     private readonly ICallGateSubscriber<int, object> _honorificClearCharacterTitle; | ||||||
|     private readonly ICallGateSubscriber<object> _honorificDisposing; |     private readonly ICallGateSubscriber<object> _honorificDisposing; | ||||||
|     private readonly ICallGateSubscriber<string> _honorificGetLocalCharacterTitle; |     private readonly ICallGateSubscriber<string> _honorificGetLocalCharacterTitle; | ||||||
|  |     private readonly ICallGateSubscriber<int, string> _honorificGetCharacterTitle; | ||||||
|     private readonly ICallGateSubscriber<string, object> _honorificLocalCharacterTitleChanged; |     private readonly ICallGateSubscriber<string, object> _honorificLocalCharacterTitleChanged; | ||||||
|     private readonly ICallGateSubscriber<object> _honorificReady; |     private readonly ICallGateSubscriber<object> _honorificReady; | ||||||
|     private readonly ICallGateSubscriber<int, string, object> _honorificSetCharacterTitle; |     private readonly ICallGateSubscriber<int, string, object> _honorificSetCharacterTitle; | ||||||
| @@ -29,6 +30,7 @@ public sealed class IpcCallerHonorific : IIpcCaller | |||||||
|         _dalamudUtil = dalamudUtil; |         _dalamudUtil = dalamudUtil; | ||||||
|         _honorificApiVersion = pi.GetIpcSubscriber<(uint, uint)>("Honorific.ApiVersion"); |         _honorificApiVersion = pi.GetIpcSubscriber<(uint, uint)>("Honorific.ApiVersion"); | ||||||
|         _honorificGetLocalCharacterTitle = pi.GetIpcSubscriber<string>("Honorific.GetLocalCharacterTitle"); |         _honorificGetLocalCharacterTitle = pi.GetIpcSubscriber<string>("Honorific.GetLocalCharacterTitle"); | ||||||
|  |         _honorificGetCharacterTitle = pi.GetIpcSubscriber<int, string>("Honorific.GetCharacterTitle"); | ||||||
|         _honorificClearCharacterTitle = pi.GetIpcSubscriber<int, object>("Honorific.ClearCharacterTitle"); |         _honorificClearCharacterTitle = pi.GetIpcSubscriber<int, object>("Honorific.ClearCharacterTitle"); | ||||||
|         _honorificSetCharacterTitle = pi.GetIpcSubscriber<int, string, object>("Honorific.SetCharacterTitle"); |         _honorificSetCharacterTitle = pi.GetIpcSubscriber<int, string, object>("Honorific.SetCharacterTitle"); | ||||||
|         _honorificLocalCharacterTitleChanged = pi.GetIpcSubscriber<string, object>("Honorific.LocalCharacterTitleChanged"); |         _honorificLocalCharacterTitleChanged = pi.GetIpcSubscriber<string, object>("Honorific.LocalCharacterTitleChanged"); | ||||||
| @@ -84,6 +86,23 @@ public sealed class IpcCallerHonorific : IIpcCaller | |||||||
|         return string.IsNullOrEmpty(title) ? string.Empty : Convert.ToBase64String(Encoding.UTF8.GetBytes(title)); |         return string.IsNullOrEmpty(title) ? string.Empty : Convert.ToBase64String(Encoding.UTF8.GetBytes(title)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public async Task<string> GetTitleForPlayer(IntPtr character) | ||||||
|  |     { | ||||||
|  |         if (!APIAvailable) return string.Empty; | ||||||
|  |  | ||||||
|  |         string title = await _dalamudUtil.RunOnFrameworkThread(() => | ||||||
|  |         { | ||||||
|  |             var gameObj = _dalamudUtil.CreateGameObject(character); | ||||||
|  |             if (gameObj is IPlayerCharacter pc) | ||||||
|  |             { | ||||||
|  |                 return _honorificGetCharacterTitle.InvokeFunc(pc.ObjectIndex); | ||||||
|  |             } | ||||||
|  |             return string.Empty; | ||||||
|  |         }).ConfigureAwait(false); | ||||||
|  |  | ||||||
|  |         return string.IsNullOrEmpty(title) ? string.Empty : Convert.ToBase64String(Encoding.UTF8.GetBytes(title)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     public async Task SetTitleAsync(IntPtr character, string honorificDataB64) |     public async Task SetTitleAsync(IntPtr character, string honorificDataB64) | ||||||
|     { |     { | ||||||
|         if (!APIAvailable) return; |         if (!APIAvailable) 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"); |         _moodlesApiVersion = pi.GetIpcSubscriber<int>("Moodles.Version"); | ||||||
|         _moodlesOnChange = pi.GetIpcSubscriber<IPlayerCharacter, object>("Moodles.StatusManagerModified"); |         _moodlesOnChange = pi.GetIpcSubscriber<IPlayerCharacter, object>("Moodles.StatusManagerModified"); | ||||||
|         _moodlesGetStatus = pi.GetIpcSubscriber<nint, string>("Moodles.GetStatusManagerByPtr"); |         _moodlesGetStatus = pi.GetIpcSubscriber<nint, string>("Moodles.GetStatusManagerByPtrV2"); | ||||||
|         _moodlesSetStatus = pi.GetIpcSubscriber<nint, string, object>("Moodles.SetStatusManagerByPtr"); |         _moodlesSetStatus = pi.GetIpcSubscriber<nint, string, object>("Moodles.SetStatusManagerByPtrV2"); | ||||||
|         _moodlesRevertStatus = pi.GetIpcSubscriber<nint, object>("Moodles.ClearStatusManagerByPtr"); |         _moodlesRevertStatus = pi.GetIpcSubscriber<nint, object>("Moodles.ClearStatusManagerByPtrV2"); | ||||||
|  |  | ||||||
|         _moodlesOnChange.Subscribe(OnMoodlesChange); |         _moodlesOnChange.Subscribe(OnMoodlesChange); | ||||||
|  |  | ||||||
| @@ -47,7 +47,7 @@ public sealed class IpcCallerMoodles : IIpcCaller | |||||||
|     { |     { | ||||||
|         try |         try | ||||||
|         { |         { | ||||||
|             APIAvailable = _moodlesApiVersion.InvokeFunc() == 1; |             APIAvailable = _moodlesApiVersion.InvokeFunc() == 3; | ||||||
|         } |         } | ||||||
|         catch |         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(() => |         return await _dalamudUtil.RunOnFrameworkThread(() => | ||||||
|         { |         { | ||||||
|             var collName = identity + "_" + uid; |             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) |             if (ec == PenumbraApiEc.InvalidCredentials) | ||||||
|             { |             { | ||||||
|                 for (int i = 0; i < 5; ++i) |                 for (int i = 0; i < 5; ++i) | ||||||
|   | |||||||
| @@ -30,12 +30,12 @@ public sealed class IpcCallerPetNames : IIpcCaller | |||||||
|         _dalamudUtil = dalamudUtil; |         _dalamudUtil = dalamudUtil; | ||||||
|         _mareMediator = mareMediator; |         _mareMediator = mareMediator; | ||||||
|  |  | ||||||
|         _petnamesReady = pi.GetIpcSubscriber<object>("PetRenamer.Ready"); |         _petnamesReady = pi.GetIpcSubscriber<object>("PetRenamer.OnReady"); | ||||||
|         _petnamesDisposing = pi.GetIpcSubscriber<object>("PetRenamer.Disposing"); |         _petnamesDisposing = pi.GetIpcSubscriber<object>("PetRenamer.OnDisposing"); | ||||||
|         _apiVersion = pi.GetIpcSubscriber<(uint, uint)>("PetRenamer.ApiVersion"); |         _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"); |         _getPlayerData = pi.GetIpcSubscriber<string>("PetRenamer.GetPlayerData"); | ||||||
|         _setPlayerData = pi.GetIpcSubscriber<string, object>("PetRenamer.SetPlayerData"); |         _setPlayerData = pi.GetIpcSubscriber<string, object>("PetRenamer.SetPlayerData"); | ||||||
|         _clearPlayerData = pi.GetIpcSubscriber<ushort, object>("PetRenamer.ClearPlayerData"); |         _clearPlayerData = pi.GetIpcSubscriber<ushort, object>("PetRenamer.ClearPlayerData"); | ||||||
| @@ -56,7 +56,7 @@ public sealed class IpcCallerPetNames : IIpcCaller | |||||||
|             APIAvailable = _enabled?.InvokeFunc() ?? false; |             APIAvailable = _enabled?.InvokeFunc() ?? false; | ||||||
|             if (APIAvailable) |             if (APIAvailable) | ||||||
|             { |             { | ||||||
|                 APIAvailable = _apiVersion?.InvokeFunc() is { Item1: 3, Item2: >= 1 }; |                 APIAvailable = _apiVersion?.InvokeFunc() is { Item1: 4, Item2: >= 0 }; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         catch |         catch | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ using MareSynchronos.MareConfiguration; | |||||||
| using MareSynchronos.PlayerData.Handlers; | using MareSynchronos.PlayerData.Handlers; | ||||||
| using MareSynchronos.Services; | using MareSynchronos.Services; | ||||||
| using MareSynchronos.Services.Mediator; | using MareSynchronos.Services.Mediator; | ||||||
| using MareSynchronos.Utils; |  | ||||||
| using Microsoft.Extensions.Hosting; | using Microsoft.Extensions.Hosting; | ||||||
| using Microsoft.Extensions.Logging; | using Microsoft.Extensions.Logging; | ||||||
|  |  | ||||||
| @@ -15,25 +14,12 @@ public class IpcProvider : IHostedService, IMediatorSubscriber | |||||||
| { | { | ||||||
|     private readonly ILogger<IpcProvider> _logger; |     private readonly ILogger<IpcProvider> _logger; | ||||||
|     private readonly IDalamudPluginInterface _pi; |     private readonly IDalamudPluginInterface _pi; | ||||||
|     private readonly MareConfigService _mareConfig; |  | ||||||
|     private readonly CharaDataManager _charaDataManager; |     private readonly CharaDataManager _charaDataManager; | ||||||
|     private ICallGateProvider<string, IGameObject, bool>? _loadFileProvider; |     private ICallGateProvider<string, IGameObject, bool>? _loadFileProvider; | ||||||
|     private ICallGateProvider<string, IGameObject, Task<bool>>? _loadFileAsyncProvider; |     private ICallGateProvider<string, IGameObject, Task<bool>>? _loadFileAsyncProvider; | ||||||
|     private ICallGateProvider<List<nint>>? _handledGameAddresses; |     private ICallGateProvider<List<nint>>? _handledGameAddresses; | ||||||
|     private readonly List<GameObjectHandler> _activeGameObjectHandlers = []; |     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 MareMediator Mediator { get; init; } | ||||||
|  |  | ||||||
|     public IpcProvider(ILogger<IpcProvider> logger, IDalamudPluginInterface pi, MareConfigService mareConfig, |     public IpcProvider(ILogger<IpcProvider> logger, IDalamudPluginInterface pi, MareConfigService mareConfig, | ||||||
| @@ -41,7 +27,6 @@ public class IpcProvider : IHostedService, IMediatorSubscriber | |||||||
|     { |     { | ||||||
|         _logger = logger; |         _logger = logger; | ||||||
|         _pi = pi; |         _pi = pi; | ||||||
|         _mareConfig = mareConfig; |  | ||||||
|         _charaDataManager = charaDataManager; |         _charaDataManager = charaDataManager; | ||||||
|         Mediator = mareMediator; |         Mediator = mareMediator; | ||||||
|  |  | ||||||
| @@ -55,15 +40,6 @@ public class IpcProvider : IHostedService, IMediatorSubscriber | |||||||
|             if (msg.OwnedObject) return; |             if (msg.OwnedObject) return; | ||||||
|             _activeGameObjectHandlers.Remove(msg.GameObjectHandler); |             _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) |     public Task StartAsync(CancellationToken cancellationToken) | ||||||
| @@ -76,72 +52,10 @@ public class IpcProvider : IHostedService, IMediatorSubscriber | |||||||
|         _handledGameAddresses = _pi.GetIpcProvider<List<nint>>("ClubPenguinSync.GetHandledAddresses"); |         _handledGameAddresses = _pi.GetIpcProvider<List<nint>>("ClubPenguinSync.GetHandledAddresses"); | ||||||
|         _handledGameAddresses.RegisterFunc(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"); |         _logger.LogInformation("Started IpcProviderService"); | ||||||
|         return Task.CompletedTask; |         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) |     public Task StopAsync(CancellationToken cancellationToken) | ||||||
|     { |     { | ||||||
|         _logger.LogDebug("Stopping IpcProvider Service"); |         _logger.LogDebug("Stopping IpcProvider Service"); | ||||||
| @@ -149,14 +63,6 @@ public class IpcProvider : IHostedService, IMediatorSubscriber | |||||||
|         _loadFileAsyncProvider?.UnregisterFunc(); |         _loadFileAsyncProvider?.UnregisterFunc(); | ||||||
|         _handledGameAddresses?.UnregisterFunc(); |         _handledGameAddresses?.UnregisterFunc(); | ||||||
|  |  | ||||||
|         _registerDelayCts.Cancel(); |  | ||||||
|         if (_impersonating) |  | ||||||
|         { |  | ||||||
|             _loadFileProviderMare?.UnregisterFunc(); |  | ||||||
|             _loadFileAsyncProviderMare?.UnregisterFunc(); |  | ||||||
|             _handledGameAddressesMare?.UnregisterFunc(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Mediator.UnsubscribeAll(this); |         Mediator.UnsubscribeAll(this); | ||||||
|         return Task.CompletedTask; |         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(); |         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 LogPerformance { get; set; } = false; | ||||||
|     public bool LogTraceLog { get; set; } = false; |     public bool LogTraceLog { get; set; } = false; | ||||||
|     public bool LogEvents { get; set; } = true; |     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 double MaxLocalCacheInGiB { get; set; } = 20; | ||||||
|     public bool OpenGposeImportOnGposeStart { get; set; } = false; |     public bool OpenGposeImportOnGposeStart { get; set; } = false; | ||||||
|     public bool OpenPopupOnAdd { get; set; } = true; |     public bool OpenPopupOnAdd { get; set; } = true; | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| <?xml version="1.0" encoding="utf-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> |   <PropertyGroup> | ||||||
|     <AssemblyName>ClubPenguinSync</AssemblyName> |     <AssemblyName>ClubPenguinSync</AssemblyName> | ||||||
|     <Version>1.7.0.0</Version> |     <Version>1.7.1.4</Version> | ||||||
|     <PackageProjectUrl>https://github.com/Rawrington/ClubPenguinSync/</PackageProjectUrl> |     <PackageProjectUrl>https://github.com/Rawrington/ClubPenguinSync/</PackageProjectUrl> | ||||||
|   </PropertyGroup> |   </PropertyGroup> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -27,14 +27,13 @@ public class PairHandlerFactory | |||||||
|     private readonly PluginWarningNotificationService _pluginWarningNotificationManager; |     private readonly PluginWarningNotificationService _pluginWarningNotificationManager; | ||||||
|     private readonly PairAnalyzerFactory _pairAnalyzerFactory; |     private readonly PairAnalyzerFactory _pairAnalyzerFactory; | ||||||
|     private readonly VisibilityService _visibilityService; |     private readonly VisibilityService _visibilityService; | ||||||
|     private readonly NoSnapService _noSnapService; |  | ||||||
|  |  | ||||||
|     public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager, |     public PairHandlerFactory(ILoggerFactory loggerFactory, GameObjectHandlerFactory gameObjectHandlerFactory, IpcManager ipcManager, | ||||||
|         FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService, |         FileDownloadManagerFactory fileDownloadManagerFactory, DalamudUtilService dalamudUtilService, | ||||||
|         PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime, |         PluginWarningNotificationService pluginWarningNotificationManager, IHostApplicationLifetime hostApplicationLifetime, | ||||||
|         FileCacheManager fileCacheManager, MareMediator mareMediator, PlayerPerformanceService playerPerformanceService, |         FileCacheManager fileCacheManager, MareMediator mareMediator, PlayerPerformanceService playerPerformanceService, | ||||||
|         ServerConfigurationManager serverConfigManager, PairAnalyzerFactory pairAnalyzerFactory, |         ServerConfigurationManager serverConfigManager, PairAnalyzerFactory pairAnalyzerFactory, | ||||||
|         MareConfigService configService, VisibilityService visibilityService, NoSnapService noSnapService) |         MareConfigService configService, VisibilityService visibilityService) | ||||||
|     { |     { | ||||||
|         _loggerFactory = loggerFactory; |         _loggerFactory = loggerFactory; | ||||||
|         _gameObjectHandlerFactory = gameObjectHandlerFactory; |         _gameObjectHandlerFactory = gameObjectHandlerFactory; | ||||||
| @@ -50,13 +49,12 @@ public class PairHandlerFactory | |||||||
|         _pairAnalyzerFactory = pairAnalyzerFactory; |         _pairAnalyzerFactory = pairAnalyzerFactory; | ||||||
|         _configService = configService; |         _configService = configService; | ||||||
|         _visibilityService = visibilityService; |         _visibilityService = visibilityService; | ||||||
|         _noSnapService = noSnapService; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public PairHandler Create(Pair pair) |     public PairHandler Create(Pair pair) | ||||||
|     { |     { | ||||||
|         return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), pair, _pairAnalyzerFactory.Create(pair), _gameObjectHandlerFactory, |         return new PairHandler(_loggerFactory.CreateLogger<PairHandler>(), pair, _pairAnalyzerFactory.Create(pair), _gameObjectHandlerFactory, | ||||||
|             _ipcManager, _fileDownloadManagerFactory.Create(), _pluginWarningNotificationManager, _dalamudUtilService, _hostApplicationLifetime, |             _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 ServerConfigurationManager _serverConfigManager; | ||||||
|     private readonly PluginWarningNotificationService _pluginWarningNotificationManager; |     private readonly PluginWarningNotificationService _pluginWarningNotificationManager; | ||||||
|     private readonly VisibilityService _visibilityService; |     private readonly VisibilityService _visibilityService; | ||||||
|     private readonly NoSnapService _noSnapService; |  | ||||||
|     private CancellationTokenSource? _applicationCancellationTokenSource = new(); |     private CancellationTokenSource? _applicationCancellationTokenSource = new(); | ||||||
|     private Guid _applicationId; |     private Guid _applicationId; | ||||||
|     private Task? _applicationTask; |     private Task? _applicationTask; | ||||||
| @@ -55,8 +54,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|         FileCacheManager fileDbManager, MareMediator mediator, |         FileCacheManager fileDbManager, MareMediator mediator, | ||||||
|         PlayerPerformanceService playerPerformanceService, |         PlayerPerformanceService playerPerformanceService, | ||||||
|         ServerConfigurationManager serverConfigManager, |         ServerConfigurationManager serverConfigManager, | ||||||
|         MareConfigService configService, VisibilityService visibilityService, |         MareConfigService configService, VisibilityService visibilityService) : base(logger, mediator) | ||||||
|         NoSnapService noSnapService) : base(logger, mediator) |  | ||||||
|     { |     { | ||||||
|         Pair = pair; |         Pair = pair; | ||||||
|         PairAnalyzer = pairAnalyzer; |         PairAnalyzer = pairAnalyzer; | ||||||
| @@ -70,7 +68,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|         _serverConfigManager = serverConfigManager; |         _serverConfigManager = serverConfigManager; | ||||||
|         _configService = configService; |         _configService = configService; | ||||||
|         _visibilityService = visibilityService; |         _visibilityService = visibilityService; | ||||||
|         _noSnapService = noSnapService; |  | ||||||
|  |  | ||||||
|         _visibilityService.StartTracking(Pair.Ident); |         _visibilityService.StartTracking(Pair.Ident); | ||||||
|  |  | ||||||
| @@ -152,7 +149,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|     public string? PlayerName { get; private set; } |     public string? PlayerName { get; private set; } | ||||||
|     public string PlayerNameHash => Pair.Ident; |     public string PlayerNameHash => Pair.Ident; | ||||||
|  |  | ||||||
|     public void ApplyCharacterData(Guid applicationBase, CharacterData characterData, bool forceApplyCustomization = false) |     public async void ApplyCharacterData(Guid applicationBase, CharacterData characterData, bool forceApplyCustomization = false) | ||||||
|     { |     { | ||||||
|         if (_configService.Current.HoldCombatApplication && _dalamudUtil.IsInCombatOrPerforming) |         if (_configService.Current.HoldCombatApplication && _dalamudUtil.IsInCombatOrPerforming) | ||||||
|         { |         { | ||||||
| @@ -178,7 +175,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|             Logger.LogDebug("[BASE-{appBase}] Received data but player was in invalid state, charaHandlerIsNull: {charaIsNull}, playerPointerIsNull: {ptrIsNull}", |             Logger.LogDebug("[BASE-{appBase}] Received data but player was in invalid state, charaHandlerIsNull: {charaIsNull}, playerPointerIsNull: {ptrIsNull}", | ||||||
|                 applicationBase, _charaHandler == null, PlayerCharacter == IntPtr.Zero); |                 applicationBase, _charaHandler == null, PlayerCharacter == IntPtr.Zero); | ||||||
|             var hasDiffMods = characterData.CheckUpdatedData(applicationBase, _cachedData, Logger, |             var hasDiffMods = characterData.CheckUpdatedData(applicationBase, _cachedData, Logger, | ||||||
|                 this, forceApplyCustomization, forceApplyMods: false) |                 this, forceApplyCustomization, forceApplyMods: false, string.Empty) | ||||||
|                 .Any(p => p.Value.Contains(PlayerChanges.ModManip) || p.Value.Contains(PlayerChanges.ModFiles)); |                 .Any(p => p.Value.Contains(PlayerChanges.ModManip) || p.Value.Contains(PlayerChanges.ModFiles)); | ||||||
|             _forceApplyMods = hasDiffMods || _forceApplyMods || (PlayerCharacter == IntPtr.Zero && _cachedData == null); |             _forceApplyMods = hasDiffMods || _forceApplyMods || (PlayerCharacter == IntPtr.Zero && _cachedData == null); | ||||||
|             _cachedData = characterData; |             _cachedData = characterData; | ||||||
| @@ -204,7 +201,7 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|                 $"Not applying character data: {reasons}"))); |                 $"Not applying character data: {reasons}"))); | ||||||
|             Logger.LogDebug("[BASE-{appBase}] Not applying due to hold: {reasons}", applicationBase, reasons); |             Logger.LogDebug("[BASE-{appBase}] Not applying due to hold: {reasons}", applicationBase, reasons); | ||||||
|             var hasDiffMods = characterData.CheckUpdatedData(applicationBase, _cachedData, Logger, |             var hasDiffMods = characterData.CheckUpdatedData(applicationBase, _cachedData, Logger, | ||||||
|                 this, forceApplyCustomization, forceApplyMods: false) |                 this, forceApplyCustomization, forceApplyMods: false, _ipcManager.Honorific.GetTitleForPlayer(PlayerCharacter).GetAwaiter().GetResult()) | ||||||
|                 .Any(p => p.Value.Contains(PlayerChanges.ModManip) || p.Value.Contains(PlayerChanges.ModFiles)); |                 .Any(p => p.Value.Contains(PlayerChanges.ModManip) || p.Value.Contains(PlayerChanges.ModFiles)); | ||||||
|             _forceApplyMods = hasDiffMods || _forceApplyMods || (PlayerCharacter == IntPtr.Zero && _cachedData == null); |             _forceApplyMods = hasDiffMods || _forceApplyMods || (PlayerCharacter == IntPtr.Zero && _cachedData == null); | ||||||
|             _cachedData = characterData; |             _cachedData = characterData; | ||||||
| @@ -231,7 +228,8 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|  |  | ||||||
|         _forceApplyMods |= forceApplyCustomization; |         _forceApplyMods |= forceApplyCustomization; | ||||||
|  |  | ||||||
|         var charaDataToUpdate = characterData.CheckUpdatedData(applicationBase, _cachedData?.DeepClone() ?? new(), Logger, this, forceApplyCustomization, _forceApplyMods); |         string oldHonorificTitle = _ipcManager.Honorific.GetTitleForPlayer(PlayerCharacter).GetAwaiter().GetResult(); | ||||||
|  |         var charaDataToUpdate = characterData.CheckUpdatedData(applicationBase, _cachedData?.DeepClone() ?? new(), Logger, this, forceApplyCustomization, _forceApplyMods, oldHonorificTitle); | ||||||
|  |  | ||||||
|         if (_charaHandler != null && _forceApplyMods) |         if (_charaHandler != null && _forceApplyMods) | ||||||
|         { |         { | ||||||
| @@ -332,7 +330,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|                 var gposeName = actor.Name.TextValue; |                 var gposeName = actor.Name.TextValue; | ||||||
|                 if (!name.Equals(gposeName, StringComparison.Ordinal)) |                 if (!name.Equals(gposeName, StringComparison.Ordinal)) | ||||||
|                     continue; |                     continue; | ||||||
|                 _noSnapService.AddGposer(actor.ObjectIndex); |  | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| @@ -385,10 +382,6 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             else if (_dalamudUtil.IsInCutscene && !string.IsNullOrEmpty(name)) |  | ||||||
|             { |  | ||||||
|                 _noSnapService.AddGposerNamed(name); |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|         catch (Exception ex) |         catch (Exception ex) | ||||||
|         { |         { | ||||||
|   | |||||||
| @@ -133,7 +133,7 @@ public class Pair : DisposableMediatorSubscriberBase | |||||||
|         { |         { | ||||||
|             Name = name, |             Name = name, | ||||||
|             OnClicked = action, |             OnClicked = action, | ||||||
|             PrefixColor = 529, |             PrefixColor = 555, | ||||||
|             PrefixChar = 'C' |             PrefixChar = 'C' | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| @@ -215,11 +215,6 @@ public class Pair : DisposableMediatorSubscriberBase | |||||||
|         if (_serverConfigurationManager.IsUidBlacklisted(UserData.UID)) |         if (_serverConfigurationManager.IsUidBlacklisted(UserData.UID)) | ||||||
|             HoldApplication("Blacklist", maxValue: 1); |             HoldApplication("Blacklist", maxValue: 1); | ||||||
|  |  | ||||||
|         if (NoSnapService.AnyLoaded) |  | ||||||
|             HoldApplication("NoSnap", maxValue: 1); |  | ||||||
|         else |  | ||||||
|             UnholdApplication("NoSnap", skipApplication: true); |  | ||||||
|  |  | ||||||
|         CachedPlayer.ApplyCharacterData(Guid.NewGuid(), RemoveNotSyncedFiles(LastReceivedCharacterData.DeepClone())!, forced); |         CachedPlayer.ApplyCharacterData(Guid.NewGuid(), RemoveNotSyncedFiles(LastReceivedCharacterData.DeepClone())!, forced); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -161,10 +161,10 @@ public sealed class Plugin : IDalamudPlugin | |||||||
|             collection.AddSingleton<CharaDataGposeTogetherManager>(); |             collection.AddSingleton<CharaDataGposeTogetherManager>(); | ||||||
|  |  | ||||||
|             collection.AddSingleton<VfxSpawnManager>(); |             collection.AddSingleton<VfxSpawnManager>(); | ||||||
|  |             collection.AddSingleton<ColorTableHook>(); | ||||||
|             collection.AddSingleton<BlockedCharacterHandler>(); |             collection.AddSingleton<BlockedCharacterHandler>(); | ||||||
|             collection.AddSingleton<IpcProvider>(); |             collection.AddSingleton<IpcProvider>(); | ||||||
|             collection.AddSingleton<VisibilityService>(); |             collection.AddSingleton<VisibilityService>(); | ||||||
|             collection.AddSingleton<RepoChangeService>(); |  | ||||||
|             collection.AddSingleton<EventAggregator>(); |             collection.AddSingleton<EventAggregator>(); | ||||||
|             collection.AddSingleton<DalamudUtilService>(); |             collection.AddSingleton<DalamudUtilService>(); | ||||||
|             collection.AddSingleton<DtrEntry>(); |             collection.AddSingleton<DtrEntry>(); | ||||||
| @@ -178,10 +178,9 @@ public sealed class Plugin : IDalamudPlugin | |||||||
|             collection.AddSingleton<IpcCallerMoodles>(); |             collection.AddSingleton<IpcCallerMoodles>(); | ||||||
|             collection.AddSingleton<IpcCallerPetNames>(); |             collection.AddSingleton<IpcCallerPetNames>(); | ||||||
|             collection.AddSingleton<IpcCallerBrio>(); |             collection.AddSingleton<IpcCallerBrio>(); | ||||||
|             collection.AddSingleton<IpcCallerMare>(); |             collection.AddSingleton<IpcCallerOtherSync>(); | ||||||
|             collection.AddSingleton<IpcManager>(); |             collection.AddSingleton<IpcManager>(); | ||||||
|             collection.AddSingleton<NotificationService>(); |             collection.AddSingleton<NotificationService>(); | ||||||
|             collection.AddSingleton<NoSnapService>(); |  | ||||||
|  |  | ||||||
|             collection.AddSingleton((s) => new MareConfigService(pluginInterface.ConfigDirectory.FullName)); |             collection.AddSingleton((s) => new MareConfigService(pluginInterface.ConfigDirectory.FullName)); | ||||||
|             collection.AddSingleton((s) => new ServerConfigService(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<EventAggregator>()); | ||||||
|             collection.AddHostedService(p => p.GetRequiredService<MarePlugin>()); |             collection.AddHostedService(p => p.GetRequiredService<MarePlugin>()); | ||||||
|             collection.AddHostedService(p => p.GetRequiredService<IpcProvider>()); |             collection.AddHostedService(p => p.GetRequiredService<IpcProvider>()); | ||||||
|             collection.AddHostedService(p => p.GetRequiredService<RepoChangeService>()); |  | ||||||
|             collection.AddHostedService(p => p.GetRequiredService<NoSnapService>()); |  | ||||||
|         }) |         }) | ||||||
|         .Build(); |         .Build(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -13,20 +13,18 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase | |||||||
|     private readonly GameObjectHandlerFactory _gameObjectHandlerFactory; |     private readonly GameObjectHandlerFactory _gameObjectHandlerFactory; | ||||||
|     private readonly DalamudUtilService _dalamudUtilService; |     private readonly DalamudUtilService _dalamudUtilService; | ||||||
|     private readonly IpcManager _ipcManager; |     private readonly IpcManager _ipcManager; | ||||||
|     private readonly NoSnapService _noSnapService; |  | ||||||
|     private readonly Dictionary<string, HandledCharaDataEntry> _handledCharaData = new(StringComparer.Ordinal); |     private readonly Dictionary<string, HandledCharaDataEntry> _handledCharaData = new(StringComparer.Ordinal); | ||||||
|  |  | ||||||
|     public IReadOnlyDictionary<string, HandledCharaDataEntry> HandledCharaData => _handledCharaData; |     public IReadOnlyDictionary<string, HandledCharaDataEntry> HandledCharaData => _handledCharaData; | ||||||
|  |  | ||||||
|     public CharaDataCharacterHandler(ILogger<CharaDataCharacterHandler> logger, MareMediator mediator, |     public CharaDataCharacterHandler(ILogger<CharaDataCharacterHandler> logger, MareMediator mediator, | ||||||
|         GameObjectHandlerFactory gameObjectHandlerFactory, DalamudUtilService dalamudUtilService, |         GameObjectHandlerFactory gameObjectHandlerFactory, DalamudUtilService dalamudUtilService, | ||||||
|         IpcManager ipcManager, NoSnapService noSnapService) |         IpcManager ipcManager) | ||||||
|         : base(logger, mediator) |         : base(logger, mediator) | ||||||
|     { |     { | ||||||
|         _gameObjectHandlerFactory = gameObjectHandlerFactory; |         _gameObjectHandlerFactory = gameObjectHandlerFactory; | ||||||
|         _dalamudUtilService = dalamudUtilService; |         _dalamudUtilService = dalamudUtilService; | ||||||
|         _ipcManager = ipcManager; |         _ipcManager = ipcManager; | ||||||
|         _noSnapService = noSnapService; |  | ||||||
|         mediator.Subscribe<GposeEndMessage>(this, msg => |         mediator.Subscribe<GposeEndMessage>(this, msg => | ||||||
|         { |         { | ||||||
|             foreach (var chara in _handledCharaData) |             foreach (var chara in _handledCharaData) | ||||||
| @@ -94,7 +92,6 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase | |||||||
|         _handledCharaData.Remove(handled.Name); |         _handledCharaData.Remove(handled.Name); | ||||||
|         await _dalamudUtilService.RunOnFrameworkThread(async () => |         await _dalamudUtilService.RunOnFrameworkThread(async () => | ||||||
|         { |         { | ||||||
|             RemoveGposer(handled); |  | ||||||
|             await RevertChara(handled.Name, handled.CustomizePlus).ConfigureAwait(false); |             await RevertChara(handled.Name, handled.CustomizePlus).ConfigureAwait(false); | ||||||
|         }).ConfigureAwait(false); |         }).ConfigureAwait(false); | ||||||
|         return true; |         return true; | ||||||
| @@ -103,7 +100,6 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase | |||||||
|     internal void AddHandledChara(HandledCharaDataEntry handledCharaDataEntry) |     internal void AddHandledChara(HandledCharaDataEntry handledCharaDataEntry) | ||||||
|     { |     { | ||||||
|         _handledCharaData.Add(handledCharaDataEntry.Name, handledCharaDataEntry); |         _handledCharaData.Add(handledCharaDataEntry.Name, handledCharaDataEntry); | ||||||
|         _ = _dalamudUtilService.RunOnFrameworkThread(() => AddGposer(handledCharaDataEntry)); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public void UpdateHandledData(Dictionary<string, CharaDataMetaInfoExtendedDto?> newData) |     public void UpdateHandledData(Dictionary<string, CharaDataMetaInfoExtendedDto?> newData) | ||||||
| @@ -134,23 +130,4 @@ public sealed class CharaDataCharacterHandler : DisposableMediatorSubscriberBase | |||||||
|         if (handler.Address == nint.Zero) return null; |         if (handler.Address == nint.Zero) return null; | ||||||
|         return handler; |         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 chatMsg = message.ChatMsg; | ||||||
|         var prefix = new SeStringBuilder(); |         var prefix = new SeStringBuilder(); | ||||||
|         prefix.AddText("[BnnuyChat] "); |         prefix.AddText("[PenguinChat] "); | ||||||
|         _chatGui.Print(new XivChatEntry{ |         _chatGui.Print(new XivChatEntry{ | ||||||
|             MessageBytes = [..prefix.Build().Encode(), ..message.ChatMsg.PayloadContent], |             MessageBytes = [..prefix.Build().Encode(), ..message.ChatMsg.PayloadContent], | ||||||
|             Name = chatMsg.SenderName, |             Name = chatMsg.SenderName, | ||||||
| @@ -121,7 +121,7 @@ public class ChatService : DisposableMediatorSubscriberBase | |||||||
|         } |         } | ||||||
|         if (color != 0) |         if (color != 0) | ||||||
|             msg.AddUiForeground((ushort)color); |             msg.AddUiForeground((ushort)color); | ||||||
|         msg.AddText($"[SS{shellNumber}]<"); |         msg.AddText($"[PS{shellNumber}]<"); | ||||||
|         if (message.ChatMsg.Sender.UID.Equals(_apiController.UID, StringComparison.Ordinal)) |         if (message.ChatMsg.Sender.UID.Equals(_apiController.UID, StringComparison.Ordinal)) | ||||||
|         { |         { | ||||||
|             // Don't link to your own character |             // Don't link to your own character | ||||||
| @@ -179,7 +179,7 @@ public class ChatService : DisposableMediatorSubscriberBase | |||||||
|                 if (_gameChatHooks.IsValueCreated && _gameChatHooks.Value.ChatChannelOverride != null) |                 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 |                     // 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); |                         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 |                 // BUG: This doesn't always update the chat window e.g. when renaming a group | ||||||
|                 _gameChatHooks.Value.ChatChannelOverride = new() |                 _gameChatHooks.Value.ChatChannelOverride = new() | ||||||
|                 { |                 { | ||||||
|                     ChannelName = $"SS [{shellNumber}]: {name}", |                     ChannelName = $"PS [{shellNumber}]: {name}", | ||||||
|                     ChatMessageHandler = chatBytes => SendChatShell(shellNumber, chatBytes) |                     ChatMessageHandler = chatBytes => SendChatShell(shellNumber, chatBytes) | ||||||
|                 }; |                 }; | ||||||
|                 return; |                 return; | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| using Dalamud.Game.Command; | using Dalamud.Game.Command; | ||||||
| using Dalamud.Plugin.Services; | using Dalamud.Plugin.Services; | ||||||
|  | using Dalamud.Utility; | ||||||
| using MareSynchronos.FileCache; | using MareSynchronos.FileCache; | ||||||
| using MareSynchronos.MareConfiguration; | using MareSynchronos.MareConfiguration; | ||||||
| using MareSynchronos.MareConfiguration.Models; | using MareSynchronos.MareConfiguration.Models; | ||||||
| @@ -17,7 +18,7 @@ public sealed class CommandManagerService : IDisposable | |||||||
|     private const string _commandName = "/sync"; |     private const string _commandName = "/sync"; | ||||||
|     private const string _commandName2 = "/clubpenguin"; |     private const string _commandName2 = "/clubpenguin"; | ||||||
|  |  | ||||||
|     private const string _ssCommandPrefix = "/ss"; |     private const string _psCommandPrefix = "/ps"; | ||||||
|  |  | ||||||
|     private readonly ApiController _apiController; |     private readonly ApiController _apiController; | ||||||
|     private readonly ICommandManager _commandManager; |     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 |         // 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) |         for (int i = 1; i <= ChatService.CommandMaxNumber; ++i) | ||||||
|         { |         { | ||||||
|             _commandManager.AddHandler($"{_ssCommandPrefix}{i}", new CommandInfo(OnChatCommand) |             _commandManager.AddHandler($"{_psCommandPrefix}{i}", new CommandInfo(OnChatCommand) | ||||||
|             { |             { | ||||||
|                 ShowInHelp = false |                 ShowInHelp = false | ||||||
|             }); |             }); | ||||||
| @@ -65,7 +66,7 @@ public sealed class CommandManagerService : IDisposable | |||||||
|         _commandManager.RemoveHandler(_commandName2); |         _commandManager.RemoveHandler(_commandName2); | ||||||
|  |  | ||||||
|         for (int i = 1; i <= ChatService.CommandMaxNumber; ++i) |         for (int i = 1; i <= ChatService.CommandMaxNumber; ++i) | ||||||
|             _commandManager.RemoveHandler($"{_ssCommandPrefix}{i}"); |             _commandManager.RemoveHandler($"{_psCommandPrefix}{i}"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void OnCommand(string command, string args) |     private void OnCommand(string command, string args) | ||||||
| @@ -139,9 +140,9 @@ public sealed class CommandManagerService : IDisposable | |||||||
|         if (_mareConfigService.Current.DisableSyncshellChat) |         if (_mareConfigService.Current.DisableSyncshellChat) | ||||||
|             return; |             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); |             _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); |         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) |     private void LoadConfig(JsonObject jsonDoc) | ||||||
|     { |     { | ||||||
|         var ts = jsonDoc["ts"]!.GetValue<ulong>(); |         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() |     private void EnsureMainExists() | ||||||
|     { |     { | ||||||
|         bool lopExists = false; |         bool clubExists = false; | ||||||
|         for (int i = 0; i < _configService.Current.ServerStorage.Count; ++i) |         for (int i = 0; i < _configService.Current.ServerStorage.Count; ++i) | ||||||
|         { |         { | ||||||
|             var x = _configService.Current.ServerStorage[i]; |             var x = _configService.Current.ServerStorage[i]; | ||||||
|             if (x.ServerUri.Equals(ApiController.ClubPenguinServiceUri, StringComparison.OrdinalIgnoreCase)) |             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); |             _logger.LogDebug("Re-adding missing server {uri}", ApiController.ClubPenguinServiceUri); | ||||||
|             _configService.Current.ServerStorage.Insert(0, new ServerStorage() { ServerUri = ApiController.ClubPenguinServiceUri, ServerName = ApiController.ClubPenguinServer }); |             _configService.Current.ServerStorage.Insert(0, new ServerStorage() { ServerUri = ApiController.ClubPenguinServiceUri, ServerName = ApiController.ClubPenguinServer }); | ||||||
|   | |||||||
| @@ -12,21 +12,21 @@ public class VisibilityService : DisposableMediatorSubscriberBase | |||||||
|     { |     { | ||||||
|         NotVisible, |         NotVisible, | ||||||
|         Visible, |         Visible, | ||||||
|         MareHandled |         OtherSyncHandled | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     private readonly DalamudUtilService _dalamudUtil; |     private readonly DalamudUtilService _dalamudUtil; | ||||||
|     private readonly ConcurrentDictionary<string, TrackedPlayerStatus> _trackedPlayerVisibility = new(StringComparer.Ordinal); |     private readonly ConcurrentDictionary<string, TrackedPlayerStatus> _trackedPlayerVisibility = new(StringComparer.Ordinal); | ||||||
|     private readonly List<string> _makeVisibleNextFrame = new(); |     private readonly List<string> _makeVisibleNextFrame = new(); | ||||||
|     private readonly IpcCallerMare _mare; |     private readonly IpcCallerOtherSync _otherSync; | ||||||
|     private readonly HashSet<nint> cachedMareAddresses = new(); |     private readonly HashSet<nint> cachedOtherSyncAddresses = new(); | ||||||
|     private uint _cachedAddressSum = 0; |     private uint _cachedAddressSum = 0; | ||||||
|     private uint _cachedAddressSumDebounce = 1; |     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) |         : base(logger, mediator) | ||||||
|     { |     { | ||||||
|         _mare = mare; |         _otherSync = otherSync; | ||||||
|         _dalamudUtil = dalamudUtil; |         _dalamudUtil = dalamudUtil; | ||||||
|         Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => FrameworkUpdate()); |         Mediator.Subscribe<FrameworkUpdateMessage>(this, (_) => FrameworkUpdate()); | ||||||
|     } |     } | ||||||
| @@ -44,19 +44,19 @@ public class VisibilityService : DisposableMediatorSubscriberBase | |||||||
|  |  | ||||||
|     private void FrameworkUpdate() |     private void FrameworkUpdate() | ||||||
|     { |     { | ||||||
|         var mareHandledAddresses = _mare.GetHandledGameAddresses(); |         var otherSyncHandledAddresses = _otherSync.GetHandledGameAddresses(); | ||||||
|         uint addressSum = 0; |         uint addressSum = 0; | ||||||
|  |  | ||||||
|         foreach (var addr in mareHandledAddresses) |         foreach (var addr in otherSyncHandledAddresses) | ||||||
|             addressSum ^= (uint)addr.GetHashCode(); |             addressSum ^= (uint)addr.GetHashCode(); | ||||||
|  |  | ||||||
|         if (addressSum != _cachedAddressSum) |         if (addressSum != _cachedAddressSum) | ||||||
|         { |         { | ||||||
|             if (addressSum == _cachedAddressSumDebounce) |             if (addressSum == _cachedAddressSumDebounce) | ||||||
|             { |             { | ||||||
|                 cachedMareAddresses.Clear(); |                 cachedOtherSyncAddresses.Clear(); | ||||||
|                 foreach (var addr in mareHandledAddresses) |                 foreach (var addr in otherSyncHandledAddresses) | ||||||
|                     cachedMareAddresses.Add(addr); |                     cachedOtherSyncAddresses.Add(addr); | ||||||
|                 _cachedAddressSum = addressSum; |                 _cachedAddressSum = addressSum; | ||||||
|             } |             } | ||||||
|             else |             else | ||||||
| @@ -69,11 +69,11 @@ public class VisibilityService : DisposableMediatorSubscriberBase | |||||||
|         { |         { | ||||||
|             string ident = player.Key; |             string ident = player.Key; | ||||||
|             var findResult = _dalamudUtil.FindPlayerByNameHash(ident); |             var findResult = _dalamudUtil.FindPlayerByNameHash(ident); | ||||||
|             var isMareHandled = cachedMareAddresses.Contains(findResult.Address); |             var isOtherSyncHandled = cachedOtherSyncAddresses.Contains(findResult.Address); | ||||||
|             var isVisible = findResult.ObjectId != 0 && !isMareHandled; |             var isVisible = findResult.ObjectId != 0 && !isOtherSyncHandled; | ||||||
|  |  | ||||||
|             if (player.Value == TrackedPlayerStatus.MareHandled && !isMareHandled) |             if (player.Value == TrackedPlayerStatus.OtherSyncHandled && !isOtherSyncHandled) | ||||||
|                 _trackedPlayerVisibility.TryUpdate(ident, newValue: TrackedPlayerStatus.NotVisible, comparisonValue: TrackedPlayerStatus.MareHandled); |                 _trackedPlayerVisibility.TryUpdate(ident, newValue: TrackedPlayerStatus.NotVisible, comparisonValue: TrackedPlayerStatus.OtherSyncHandled); | ||||||
|  |  | ||||||
|             if (player.Value == TrackedPlayerStatus.NotVisible && isVisible) |             if (player.Value == TrackedPlayerStatus.NotVisible && isVisible) | ||||||
|             { |             { | ||||||
| @@ -85,17 +85,17 @@ public class VisibilityService : DisposableMediatorSubscriberBase | |||||||
|                 else |                 else | ||||||
|                     _makeVisibleNextFrame.Add(ident); |                     _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 |                 // 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)); |                     Mediator.Publish<PlayerVisibilityMessage>(new(ident, IsVisible: false, Invalidate: true)); | ||||||
|             } |             } | ||||||
|             else if (player.Value == TrackedPlayerStatus.Visible && !isVisible) |             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)) |                 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) |             if (!isVisible) | ||||||
|   | |||||||
| @@ -105,7 +105,7 @@ public class CompactUi : WindowMediatorSubscriberBase | |||||||
|     protected override void DrawInternal() |     protected override void DrawInternal() | ||||||
|     { |     { | ||||||
|         if (_serverManager.CurrentApiUrl.Equals(ApiController.ClubPenguinServiceUri, StringComparison.Ordinal)) |         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 |         else | ||||||
|             UiSharedService.AccentColor = ImGuiColors.ParsedGreen; |             UiSharedService.AccentColor = ImGuiColors.ParsedGreen; | ||||||
|         ImGui.SetCursorPosY(ImGui.GetCursorPosY() - ImGui.GetStyle().WindowPadding.Y - 1f * ImGuiHelpers.GlobalScale + ImGui.GetStyle().ItemSpacing.Y); |         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) |             if (!_mareConfig.Current.DisableSyncshellChat && shellConfig.Enabled) | ||||||
|             { |             { | ||||||
|                 ImGui.TextUnformatted($"[{shellNumber}]"); |                 ImGui.TextUnformatted($"[{shellNumber}]"); | ||||||
|                 UiSharedService.AttachToolTip("Chat command prefix: /ss" + shellNumber); |                 UiSharedService.AttachToolTip("Chat command prefix: /ps" + shellNumber); | ||||||
|             } |             } | ||||||
|             if (textIsGid) ImGui.PushFont(UiBuilder.MonoFont); |             if (textIsGid) ImGui.PushFont(UiBuilder.MonoFont); | ||||||
|             ImGui.SameLine(); |             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); |             ImGui.BeginDisabled(_registrationInProgress || _uiShared.ApiController.ServerState == ServerState.Connecting || _uiShared.ApiController.ServerState == ServerState.Reconnecting); | ||||||
|             _ = _uiShared.DrawServiceSelection(selectOnChange: true, intro: true); |             _ = _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.BeginDisabled(_registrationInProgress || _registrationSuccess || _secretKey.Length > 0); | ||||||
|                 ImGui.Separator(); |                 ImGui.Separator(); | ||||||
| @@ -297,6 +297,12 @@ This service is provided as-is. | |||||||
|                         ImGui.TextWrapped(_registrationMessage); |                         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(); |             ImGui.Separator(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ using Dalamud.Utility; | |||||||
| using MareSynchronos.API.Data; | using MareSynchronos.API.Data; | ||||||
| using MareSynchronos.API.Data.Comparer; | using MareSynchronos.API.Data.Comparer; | ||||||
| using MareSynchronos.FileCache; | using MareSynchronos.FileCache; | ||||||
|  | using MareSynchronos.Interop; | ||||||
| using MareSynchronos.Interop.Ipc; | using MareSynchronos.Interop.Ipc; | ||||||
| using MareSynchronos.MareConfiguration; | using MareSynchronos.MareConfiguration; | ||||||
| using MareSynchronos.MareConfiguration.Models; | using MareSynchronos.MareConfiguration.Models; | ||||||
| @@ -25,6 +26,7 @@ using System.Collections.Concurrent; | |||||||
| using System.Diagnostics; | using System.Diagnostics; | ||||||
| using System.Globalization; | using System.Globalization; | ||||||
| using System.Numerics; | using System.Numerics; | ||||||
|  | using System.Runtime.CompilerServices; | ||||||
| using System.Text.Json; | using System.Text.Json; | ||||||
|  |  | ||||||
| namespace MareSynchronos.UI; | namespace MareSynchronos.UI; | ||||||
| @@ -50,6 +52,7 @@ public class SettingsUi : WindowMediatorSubscriberBase | |||||||
|     private readonly PlayerPerformanceService _playerPerformanceService; |     private readonly PlayerPerformanceService _playerPerformanceService; | ||||||
|     private readonly AccountRegistrationService _registerService; |     private readonly AccountRegistrationService _registerService; | ||||||
|     private readonly ServerConfigurationManager _serverConfigurationManager; |     private readonly ServerConfigurationManager _serverConfigurationManager; | ||||||
|  |     private readonly ColorTableHook _colorTableHook; | ||||||
|     private readonly UiSharedService _uiShared; |     private readonly UiSharedService _uiShared; | ||||||
|     private bool _deleteAccountPopupModalShown = false; |     private bool _deleteAccountPopupModalShown = false; | ||||||
|     private string _lastTab = string.Empty; |     private string _lastTab = string.Empty; | ||||||
| @@ -77,11 +80,12 @@ public class SettingsUi : WindowMediatorSubscriberBase | |||||||
|         FileCacheManager fileCacheManager, |         FileCacheManager fileCacheManager, | ||||||
|         FileCompactor fileCompactor, ApiController apiController, |         FileCompactor fileCompactor, ApiController apiController, | ||||||
|         IpcManager ipcManager, IpcProvider ipcProvider, CacheMonitor cacheMonitor, |         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; |         _configService = configService; | ||||||
|         _pairManager = pairManager; |         _pairManager = pairManager; | ||||||
|         _chatService = chatService; |         _chatService = chatService; | ||||||
|  |         _colorTableHook = colorTableHook; | ||||||
|         _guiHookService = guiHookService; |         _guiHookService = guiHookService; | ||||||
|         _serverConfigurationManager = serverConfigurationManager; |         _serverConfigurationManager = serverConfigurationManager; | ||||||
|         _playerPerformanceConfigService = playerPerformanceConfigService; |         _playerPerformanceConfigService = playerPerformanceConfigService; | ||||||
| @@ -581,43 +585,6 @@ public class SettingsUi : WindowMediatorSubscriberBase | |||||||
|  |  | ||||||
|         _uiShared.BigText("Advanced"); |         _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; |         bool logEvents = _configService.Current.LogEvents; | ||||||
|         if (ImGui.Checkbox("Log Event Viewer data to disk", ref logEvents)) |         if (ImGui.Checkbox("Log Event Viewer data to disk", ref logEvents)) | ||||||
|         { |         { | ||||||
| @@ -640,6 +607,22 @@ public class SettingsUi : WindowMediatorSubscriberBase | |||||||
|             _configService.Save(); |             _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; |         bool serializedApplications = _configService.Current.SerialApplication; | ||||||
|         if (ImGui.Checkbox("Serialized player applications", ref serializedApplications)) |         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."); |             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; |             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; |         return true; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -56,7 +56,7 @@ public static class VariousExtensions | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     public static Dictionary<ObjectKind, HashSet<PlayerChanges>> CheckUpdatedData(this CharacterData newData, Guid applicationBase, |     public static Dictionary<ObjectKind, HashSet<PlayerChanges>> CheckUpdatedData(this CharacterData newData, Guid applicationBase, | ||||||
|         CharacterData? oldData, ILogger logger, PairHandler cachedPlayer, bool forceApplyCustomization, bool forceApplyMods) |         CharacterData? oldData, ILogger logger, PairHandler cachedPlayer, bool forceApplyCustomization, bool forceApplyMods, string oldHonorificData) | ||||||
|     { |     { | ||||||
|         oldData ??= new(); |         oldData ??= new(); | ||||||
|         var charaDataToUpdate = new Dictionary<ObjectKind, HashSet<PlayerChanges>>(); |         var charaDataToUpdate = new Dictionary<ObjectKind, HashSet<PlayerChanges>>(); | ||||||
| @@ -182,7 +182,7 @@ public static class VariousExtensions | |||||||
|                 charaDataToUpdate[objectKind].Add(PlayerChanges.Heels); |                 charaDataToUpdate[objectKind].Add(PlayerChanges.Heels); | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             bool honorificDataDifferent = !string.Equals(oldData.HonorificData, newData.HonorificData, StringComparison.Ordinal); |             bool honorificDataDifferent = !string.Equals(oldHonorificData, newData.HonorificData, StringComparison.Ordinal); | ||||||
|             if (honorificDataDifferent || (forceApplyCustomization && !string.IsNullOrEmpty(newData.HonorificData))) |             if (honorificDataDifferent || (forceApplyCustomization && !string.IsNullOrEmpty(newData.HonorificData))) | ||||||
|             { |             { | ||||||
|                 logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Diff honorific data) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.Honorific); |                 logger.LogDebug("[BASE-{appBase}] Updating {object}/{kind} (Diff honorific data) => {change}", applicationBase, cachedPlayer, objectKind, PlayerChanges.Honorific); | ||||||
|   | |||||||
| @@ -425,6 +425,18 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase | |||||||
|  |  | ||||||
|     private void PersistFileToStorage(string fileHash, string filePath, long? compressedSize = null) |     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 |         try | ||||||
|         { |         { | ||||||
|             var entry = _fileDbManager.CreateCacheEntry(filePath, fileHash); |             var entry = _fileDbManager.CreateCacheEntry(filePath, fileHash); | ||||||
|   | |||||||
 Submodule Penumbra.Api updated: af41b1787a...dd14131793
									
								
							
		Reference in New Issue
	
	Block a user