diff --git a/.editorconfig b/.editorconfig
index f0328fd7..0bbaa114 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,31 +1,21 @@
-# Standard properties
-charset = utf-8
-end_of_line = lf
-insert_final_newline = true
-csharp_indent_labels = one_less_than_current
-csharp_prefer_simple_using_statement = true:suggestion
-csharp_prefer_braces = true:silent
-csharp_style_prefer_method_group_conversion = true:silent
-csharp_style_expression_bodied_methods = false:silent
-csharp_style_expression_bodied_constructors = false:silent
-csharp_style_expression_bodied_operators = false:silent
-csharp_style_expression_bodied_properties = true:silent
-csharp_style_expression_bodied_indexers = true:silent
-csharp_style_expression_bodied_accessors = true:silent
-csharp_style_expression_bodied_lambdas = true:silent
-csharp_style_expression_bodied_local_functions = false:silent
-csharp_style_throw_expression = true:suggestion
-csharp_style_prefer_null_check_over_type_check = true:suggestion
-csharp_prefer_simple_default_expression = true:suggestion
-csharp_style_prefer_local_over_anonymous_function = true:suggestion
-csharp_style_prefer_index_operator = true:suggestion
-csharp_style_prefer_range_operator = true:suggestion
-csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
-csharp_style_prefer_tuple_swap = true:suggestion
-csharp_style_inlined_variable_declaration = true:suggestion
-csharp_style_prefer_top_level_statements = true:silent
+
+[*.proto]
+indent_style=tab
+indent_size=tab
+tab_width=4
+
+[*.{asax,ascx,aspx,axaml,cs,cshtml,css,htm,html,js,jsx,master,paml,razor,skin,ts,tsx,vb,xaml,xamlx,xoml}]
+indent_style=space
+indent_size=4
+tab_width=4
+
+[*.{appxmanifest,axml,build,config,csproj,dbml,discomap,dtd,json,jsproj,lsproj,njsproj,nuspec,proj,props,resjson,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}]
+indent_style=space
+indent_size=2
+tab_width=2
[*]
+
# Microsoft .NET properties
csharp_indent_braces=false
csharp_indent_switch_labels=true
@@ -3576,18 +3566,30 @@ resharper_xaml_xaml_xamarin_forms_data_type_and_binding_context_type_mismatched_
resharper_xaml_x_key_attribute_disallowed_highlighting=error
resharper_xml_doc_comment_syntax_problem_highlighting=warning
resharper_xunit_xunit_test_with_console_output_highlighting=warning
-csharp_style_prefer_implicitly_typed_lambda_expression = true:suggestion
-csharp_style_expression_bodied_methods = true:silent
-csharp_style_prefer_tuple_swap = true:suggestion
-csharp_style_prefer_unbound_generic_type_in_nameof = true:suggestion
-csharp_style_prefer_utf8_string_literals = true:suggestion
-csharp_style_inlined_variable_declaration = true:suggestion
-csharp_style_expression_bodied_constructors = true:silent
-csharp_style_expression_bodied_operators = true:silent
-csharp_style_deconstructed_variable_declaration = true:suggestion
-csharp_style_unused_value_assignment_preference = discard_variable:suggestion
-csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+
+# Standard properties
+end_of_line= crlf
+csharp_indent_labels = one_less_than_current
+csharp_prefer_simple_using_statement = true:suggestion
+csharp_prefer_braces = true:silent
+csharp_style_prefer_method_group_conversion = true:silent
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_lambdas = true:silent
+csharp_style_expression_bodied_local_functions = false:silent
+csharp_style_throw_expression = true:suggestion
+csharp_style_prefer_null_check_over_type_check = true:suggestion
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_prefer_local_over_anonymous_function = true:suggestion
+csharp_style_prefer_index_operator = true:suggestion
+csharp_style_prefer_range_operator = true:suggestion
+csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
+csharp_style_prefer_tuple_swap = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
[*.{cshtml,htm,html,proto,razor}]
indent_style=tab
@@ -3599,21 +3601,6 @@ indent_style=space
indent_size=4
tab_width=4
-[ "*.proto" ]
-indent_style=tab
-indent_size=tab
-tab_width=4
-
-[*.{asax,ascx,aspx,axaml,cs,cshtml,css,htm,html,js,jsx,master,paml,razor,skin,ts,tsx,vb,xaml,xamlx,xoml}]
-indent_style=space
-indent_size=4
-tab_width=4
-
-[*.{appxmanifest,axml,build,config,csproj,dbml,discomap,dtd,json,jsproj,lsproj,njsproj,nuspec,proj,props,resjson,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}]
-indent_style=space
-indent_size=2
-tab_width=2
-
[*.{appxmanifest,asax,ascx,aspx,axaml,axml,build,c,c++,cc,cginc,compute,config,cp,cpp,cs,cshtml,csproj,css,cu,cuh,cxx,dbml,discomap,dtd,h,hh,hlsl,hlsli,hlslinc,hpp,htm,html,hxx,inc,inl,ino,ipp,js,json,jsproj,jsx,lsproj,master,mpp,mq4,mq5,mqh,njsproj,nuspec,paml,proj,props,proto,razor,resjson,resw,resx,skin,StyleCop,targets,tasks,tpp,ts,tsx,usf,ush,vb,vbproj,xaml,xamlx,xml,xoml,xsd}]
indent_style=space
indent_size= 4
@@ -3634,4 +3621,3 @@ dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_simplified_interpolation = true:suggestion
dotnet_style_namespace_match_folder = true:suggestion
-insert_final_newline = true
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1a61439e..a317f236 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -10,15 +10,13 @@ jobs:
build:
runs-on: windows-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v2
with:
- submodules: recursive
+ submodules: true
- name: Setup .NET
- uses: actions/setup-dotnet@v5
+ uses: actions/setup-dotnet@v1
with:
- dotnet-version: |
- 10.x.x
- 9.x.x
+ dotnet-version: '7.x.x'
- name: Restore dependencies
run: dotnet restore
- name: Download Dalamud
@@ -31,7 +29,7 @@ jobs:
- name: Archive
run: Compress-Archive -Path Penumbra/bin/Release/* -DestinationPath Penumbra.zip
- name: Upload a Build Artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v2.2.1
with:
path: |
./Penumbra/bin/Release/*
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c72b4800..44f9fd2f 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -9,15 +9,13 @@ jobs:
build:
runs-on: windows-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v2
with:
- submodules: recursive
+ submodules: true
- name: Setup .NET
- uses: actions/setup-dotnet@v5
+ uses: actions/setup-dotnet@v1
with:
- dotnet-version: |
- 10.x.x
- 9.x.x
+ dotnet-version: '7.x.x'
- name: Restore dependencies
run: dotnet restore
- name: Download Dalamud
@@ -39,7 +37,7 @@ jobs:
- name: Archive
run: Compress-Archive -Path Penumbra/bin/Release/* -DestinationPath Penumbra.zip
- name: Upload a Build Artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v2.2.1
with:
path: |
./Penumbra/bin/Release/*
diff --git a/.github/workflows/test_release.yml b/.github/workflows/test_release.yml
index c6b4e459..80c0ce8f 100644
--- a/.github/workflows/test_release.yml
+++ b/.github/workflows/test_release.yml
@@ -9,15 +9,13 @@ jobs:
build:
runs-on: windows-latest
steps:
- - uses: actions/checkout@v5
+ - uses: actions/checkout@v2
with:
- submodules: recursive
+ submodules: true
- name: Setup .NET
- uses: actions/setup-dotnet@v5
+ uses: actions/setup-dotnet@v1
with:
- dotnet-version: |
- 10.x.x
- 9.x.x
+ dotnet-version: '7.x.x'
- name: Restore dependencies
run: dotnet restore
- name: Download Dalamud
@@ -39,7 +37,7 @@ jobs:
- name: Archive
run: Compress-Archive -Path Penumbra/bin/Debug/* -DestinationPath Penumbra.zip
- name: Upload a Build Artifact
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v2.2.1
with:
path: |
./Penumbra/bin/Debug/*
diff --git a/.gitmodules b/.gitmodules
index ea1199ad..c03525eb 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,16 +1,16 @@
[submodule "OtterGui"]
path = OtterGui
- url = https://github.com/Ottermandias/OtterGui.git
+ url = git@github.com:Ottermandias/OtterGui.git
branch = main
[submodule "Penumbra.Api"]
path = Penumbra.Api
- url = https://github.com/Ottermandias/Penumbra.Api.git
+ url = git@github.com:Ottermandias/Penumbra.Api.git
branch = main
[submodule "Penumbra.String"]
path = Penumbra.String
- url = https://github.com/Ottermandias/Penumbra.String.git
+ url = git@github.com:Ottermandias/Penumbra.String.git
branch = main
[submodule "Penumbra.GameData"]
path = Penumbra.GameData
- url = https://github.com/Ottermandias/Penumbra.GameData.git
+ url = git@github.com:Ottermandias/Penumbra.GameData.git
branch = main
diff --git a/OtterGui b/OtterGui
index ff1e6543..e3d26f16 160000
--- a/OtterGui
+++ b/OtterGui
@@ -1 +1 @@
-Subproject commit ff1e6543845e3b8c53a5f8b240bc38faffb1b3bf
+Subproject commit e3d26f16234a4295bf3c7802d87ce43293c6ffe0
diff --git a/Penumbra.Api b/Penumbra.Api
index 52a3216a..983c98f7 160000
--- a/Penumbra.Api
+++ b/Penumbra.Api
@@ -1 +1 @@
-Subproject commit 52a3216a525592205198303df2844435e382cf87
+Subproject commit 983c98f74e7cd052b21f6ca35ef0ceaa9b388964
diff --git a/Penumbra.CrashHandler/Buffers/AnimationInvocationBuffer.cs b/Penumbra.CrashHandler/Buffers/AnimationInvocationBuffer.cs
deleted file mode 100644
index 292be2ff..00000000
--- a/Penumbra.CrashHandler/Buffers/AnimationInvocationBuffer.cs
+++ /dev/null
@@ -1,123 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text.Json.Nodes;
-
-namespace Penumbra.CrashHandler.Buffers;
-
-/// The types of currently hooked and relevant animation loading functions.
-public enum AnimationInvocationType : int
-{
- PapLoad,
- ActionLoad,
- ScheduleClipUpdate,
- LoadTimelineResources,
- LoadCharacterVfx,
- LoadCharacterSound,
- ApricotSoundPlay,
- LoadAreaVfx,
- CharacterBaseLoadAnimation,
-}
-
-/// The full crash entry for an invoked vfx function.
-public record struct VfxFuncInvokedEntry(
- double Age,
- DateTimeOffset Timestamp,
- int ThreadId,
- string InvocationType,
- string CharacterName,
- string CharacterAddress,
- Guid CollectionId) : ICrashDataEntry;
-
-/// Only expose the write interface for the buffer.
-public interface IAnimationInvocationBufferWriter
-{
- /// Write a line into the buffer with the given data.
- /// The address of the related character, if known.
- /// The name of the related character, anonymized or relying on index if unavailable, if known.
- /// The GUID of the associated collection.
- /// The type of VFX func called.
- public void WriteLine(nint characterAddress, ReadOnlySpan characterName, Guid collectionId, AnimationInvocationType type);
-}
-
-internal sealed class AnimationInvocationBuffer : MemoryMappedBuffer, IAnimationInvocationBufferWriter, IBufferReader
-{
- private const int _version = 1;
- private const int _lineCount = 64;
- private const int _lineCapacity = 128;
- private const string _name = "Penumbra.AnimationInvocation";
-
- public void WriteLine(nint characterAddress, ReadOnlySpan characterName, Guid collectionId, AnimationInvocationType type)
- {
- var accessor = GetCurrentLineLocking();
- lock (accessor)
- {
- accessor.Write(0, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
- accessor.Write(8, Environment.CurrentManagedThreadId);
- accessor.Write(12, (int)type);
- accessor.Write(16, characterAddress);
- var span = GetSpan(accessor, 24, 16);
- collectionId.TryWriteBytes(span);
- accessor.SafeMemoryMappedViewHandle.ReleasePointer();
- span = GetSpan(accessor, 40);
- WriteSpan(characterName, span);
- accessor.SafeMemoryMappedViewHandle.ReleasePointer();
- }
- }
-
- public uint TotalCount
- => TotalWrittenLines;
-
- public IEnumerable GetLines(DateTimeOffset crashTime)
- {
- var lineCount = (int)CurrentLineCount;
- for (var i = lineCount - 1; i >= 0; --i)
- {
- var line = GetLine(i);
- var timestamp = DateTimeOffset.FromUnixTimeMilliseconds(BitConverter.ToInt64(line));
- var thread = BitConverter.ToInt32(line[8..]);
- var type = (AnimationInvocationType)BitConverter.ToInt32(line[12..]);
- var address = BitConverter.ToUInt64(line[16..]);
- var collectionId = new Guid(line[24..40]);
- var characterName = ReadString(line[40..]);
- yield return new JsonObject()
- {
- [nameof(VfxFuncInvokedEntry.Age)] = (crashTime - timestamp).TotalSeconds,
- [nameof(VfxFuncInvokedEntry.Timestamp)] = timestamp,
- [nameof(VfxFuncInvokedEntry.ThreadId)] = thread,
- [nameof(VfxFuncInvokedEntry.InvocationType)] = ToName(type),
- [nameof(VfxFuncInvokedEntry.CharacterName)] = characterName,
- [nameof(VfxFuncInvokedEntry.CharacterAddress)] = address.ToString("X"),
- [nameof(VfxFuncInvokedEntry.CollectionId)] = collectionId,
- };
- }
- }
-
- public static IBufferReader CreateReader(int pid)
- => new AnimationInvocationBuffer(false, pid);
-
- public static IAnimationInvocationBufferWriter CreateWriter(int pid)
- => new AnimationInvocationBuffer(pid);
-
- private AnimationInvocationBuffer(bool writer, int pid)
- : base($"{_name}_{pid}_{_version}", _version)
- { }
-
- private AnimationInvocationBuffer(int pid)
- : base($"{_name}_{pid}_{_version}", _version, _lineCount, _lineCapacity)
- { }
-
- private static string ToName(AnimationInvocationType type)
- => type switch
- {
- AnimationInvocationType.PapLoad => "PAP Load",
- AnimationInvocationType.ActionLoad => "Action Load",
- AnimationInvocationType.ScheduleClipUpdate => "Schedule Clip Update",
- AnimationInvocationType.LoadTimelineResources => "Load Timeline Resources",
- AnimationInvocationType.LoadCharacterVfx => "Load Character VFX",
- AnimationInvocationType.LoadCharacterSound => "Load Character Sound",
- AnimationInvocationType.ApricotSoundPlay => "Apricot Sound Play",
- AnimationInvocationType.LoadAreaVfx => "Load Area VFX",
- AnimationInvocationType.CharacterBaseLoadAnimation => "Load Animation (CharacterBase)",
- _ => $"Unknown ({(int)type})",
- };
-}
diff --git a/Penumbra.CrashHandler/Buffers/CharacterBaseBuffer.cs b/Penumbra.CrashHandler/Buffers/CharacterBaseBuffer.cs
deleted file mode 100644
index 89fea29d..00000000
--- a/Penumbra.CrashHandler/Buffers/CharacterBaseBuffer.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text.Json.Nodes;
-
-namespace Penumbra.CrashHandler.Buffers;
-
-/// Only expose the write interface for the buffer.
-public interface ICharacterBaseBufferWriter
-{
- /// Write a line into the buffer with the given data.
- /// The address of the related character, if known.
- /// The name of the related character, anonymized or relying on index if unavailable, if known.
- /// The GUID of the associated collection.
- public void WriteLine(nint characterAddress, ReadOnlySpan characterName, Guid collectionId);
-}
-
-/// The full crash entry for a loaded character base.
-public record struct CharacterLoadedEntry(
- double Age,
- DateTimeOffset Timestamp,
- int ThreadId,
- string CharacterName,
- string CharacterAddress,
- Guid CollectionId) : ICrashDataEntry;
-
-internal sealed class CharacterBaseBuffer : MemoryMappedBuffer, ICharacterBaseBufferWriter, IBufferReader
-{
- private const int _version = 1;
- private const int _lineCount = 10;
- private const int _lineCapacity = 128;
- private const string _name = "Penumbra.CharacterBase";
-
- public void WriteLine(nint characterAddress, ReadOnlySpan characterName, Guid collectionId)
- {
- var accessor = GetCurrentLineLocking();
- lock (accessor)
- {
- accessor.Write(0, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
- accessor.Write(8, Environment.CurrentManagedThreadId);
- accessor.Write(12, characterAddress);
- var span = GetSpan(accessor, 20, 16);
- collectionId.TryWriteBytes(span);
- accessor.SafeMemoryMappedViewHandle.ReleasePointer();
- span = GetSpan(accessor, 36);
- WriteSpan(characterName, span);
- accessor.SafeMemoryMappedViewHandle.ReleasePointer();
- }
- }
-
- public IEnumerable GetLines(DateTimeOffset crashTime)
- {
- var lineCount = (int)CurrentLineCount;
- for (var i = lineCount - 1; i >= 0; --i)
- {
- var line = GetLine(i);
- var timestamp = DateTimeOffset.FromUnixTimeMilliseconds(BitConverter.ToInt64(line));
- var thread = BitConverter.ToInt32(line[8..]);
- var address = BitConverter.ToUInt64(line[12..]);
- var collectionId = new Guid(line[20..36]);
- var characterName = ReadString(line[36..]);
- yield return new JsonObject
- {
- [nameof(CharacterLoadedEntry.Age)] = (crashTime - timestamp).TotalSeconds,
- [nameof(CharacterLoadedEntry.Timestamp)] = timestamp,
- [nameof(CharacterLoadedEntry.ThreadId)] = thread,
- [nameof(CharacterLoadedEntry.CharacterName)] = characterName,
- [nameof(CharacterLoadedEntry.CharacterAddress)] = address.ToString("X"),
- [nameof(CharacterLoadedEntry.CollectionId)] = collectionId,
- };
- }
- }
-
- public uint TotalCount
- => TotalWrittenLines;
-
- public static IBufferReader CreateReader(int pid)
- => new CharacterBaseBuffer(false, pid);
-
- public static ICharacterBaseBufferWriter CreateWriter(int pid)
- => new CharacterBaseBuffer(pid);
-
- private CharacterBaseBuffer(bool writer, int pid)
- : base($"{_name}_{pid}_{_version}", _version)
- { }
-
- private CharacterBaseBuffer(int pid)
- : base($"{_name}_{pid}_{_version}", _version, _lineCount, _lineCapacity)
- { }
-}
diff --git a/Penumbra.CrashHandler/Buffers/MemoryMappedBuffer.cs b/Penumbra.CrashHandler/Buffers/MemoryMappedBuffer.cs
deleted file mode 100644
index e2ffcebe..00000000
--- a/Penumbra.CrashHandler/Buffers/MemoryMappedBuffer.cs
+++ /dev/null
@@ -1,220 +0,0 @@
-using System;
-using System.Diagnostics.CodeAnalysis;
-using System.IO;
-using System.IO.MemoryMappedFiles;
-using System.Linq;
-using System.Numerics;
-using System.Text;
-
-namespace Penumbra.CrashHandler.Buffers;
-
-public class MemoryMappedBuffer : IDisposable
-{
- private const int MinHeaderLength = 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4;
-
- private readonly MemoryMappedFile _file;
- private readonly MemoryMappedViewAccessor _header;
- private readonly MemoryMappedViewAccessor[] _lines = [];
-
- public readonly int Version;
- public readonly uint LineCount;
- public readonly uint LineCapacity;
- private readonly uint _lineMask;
- private bool _disposed;
-
- protected uint CurrentLineCount
- {
- get => _header.ReadUInt32(16);
- set => _header.Write(16, value);
- }
-
- protected uint CurrentLinePosition
- {
- get => _header.ReadUInt32(20);
- set => _header.Write(20, value);
- }
-
- public uint TotalWrittenLines
- {
- get => _header.ReadUInt32(24);
- protected set => _header.Write(24, value);
- }
-
- public MemoryMappedBuffer(string mapName, int version, uint lineCount, uint lineCapacity)
- {
- Version = version;
- LineCount = BitOperations.RoundUpToPowerOf2(Math.Clamp(lineCount, 2, int.MaxValue >> 3));
- LineCapacity = BitOperations.RoundUpToPowerOf2(Math.Clamp(lineCapacity, 2, int.MaxValue >> 3));
- _lineMask = LineCount - 1;
- var fileName = Encoding.UTF8.GetBytes(mapName);
- var headerLength = (uint)(4 + 4 + 4 + 4 + 4 + 4 + 4 + fileName.Length + 1);
- headerLength = (headerLength & 0b111) > 0 ? (headerLength & ~0b111u) + 0b1000 : headerLength;
- var capacity = LineCount * LineCapacity + headerLength;
- _file = MemoryMappedFile.CreateNew(mapName, capacity, MemoryMappedFileAccess.ReadWrite, MemoryMappedFileOptions.None,
- HandleInheritability.Inheritable);
- _header = _file.CreateViewAccessor(0, headerLength);
- _header.Write(0, headerLength);
- _header.Write(4, Version);
- _header.Write(8, LineCount);
- _header.Write(12, LineCapacity);
- _header.WriteArray(28, fileName, 0, fileName.Length);
- _header.Write(fileName.Length + 28, (byte)0);
- _lines = Enumerable.Range(0, (int)LineCount).Select(i
- => _file.CreateViewAccessor(headerLength + i * LineCapacity, LineCapacity, MemoryMappedFileAccess.ReadWrite))
- .ToArray();
- }
-
- public MemoryMappedBuffer(string mapName, int? expectedVersion = null, uint? expectedMinLineCount = null,
- uint? expectedMinLineCapacity = null)
- {
- _lines = [];
- _file = MemoryMappedFile.OpenExisting(mapName, MemoryMappedFileRights.ReadWrite, HandleInheritability.Inheritable);
- using var headerLine = _file.CreateViewAccessor(0, 4, MemoryMappedFileAccess.Read);
- var headerLength = headerLine.ReadUInt32(0);
- if (headerLength < MinHeaderLength)
- Throw($"Map {mapName} did not contain a valid header.");
-
- _header = _file.CreateViewAccessor(0, headerLength, MemoryMappedFileAccess.ReadWrite);
- Version = _header.ReadInt32(4);
- LineCount = _header.ReadUInt32(8);
- LineCapacity = _header.ReadUInt32(12);
- _lineMask = LineCount - 1;
- if (expectedVersion.HasValue && expectedVersion.Value != Version)
- Throw($"Map {mapName} has version {Version} instead of {expectedVersion.Value}.");
-
- if (LineCount < expectedMinLineCount)
- Throw($"Map {mapName} has line count {LineCount} but line count >= {expectedMinLineCount.Value} is required.");
-
- if (LineCapacity < expectedMinLineCapacity)
- Throw($"Map {mapName} has line capacity {LineCapacity} but line capacity >= {expectedMinLineCapacity.Value} is required.");
-
- var name = ReadString(GetSpan(_header, 28));
- if (name != mapName)
- Throw($"Map {mapName} does not contain its map name at the expected location.");
-
- _lines = Enumerable.Range(0, (int)LineCount).Select(i
- => _file.CreateViewAccessor(headerLength + i * LineCapacity, LineCapacity, MemoryMappedFileAccess.ReadWrite))
- .ToArray();
-
- [DoesNotReturn]
- void Throw(string text)
- {
- _file.Dispose();
- _header?.Dispose();
- _disposed = true;
- throw new Exception(text);
- }
- }
-
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- _disposed = true;
- }
-
- protected static string ReadString(Span span)
- {
- if (span.IsEmpty)
- throw new Exception("String from empty span requested.");
-
- var termination = span.IndexOf((byte)0);
- if (termination < 0)
- throw new Exception("String in span is not terminated.");
-
- return Encoding.UTF8.GetString(span[..termination]);
- }
-
- protected static int WriteString(string text, Span span)
- {
- var bytes = Encoding.UTF8.GetBytes(text);
- var source = (Span)bytes;
- var length = source.Length + 1;
- if (length > span.Length)
- source = source[..(span.Length - 1)];
- source.CopyTo(span);
- span[bytes.Length] = 0;
- return source.Length + 1;
- }
-
- protected static int WriteSpan(ReadOnlySpan input, Span span)
- {
- var length = input.Length + 1;
- if (length > span.Length)
- input = input[..(span.Length - 1)];
-
- input.CopyTo(span);
- span[input.Length] = 0;
- return input.Length + 1;
- }
-
- protected Span GetLine(int i)
- {
- if (i < 0 || i > LineCount)
- return null;
-
- lock (_header)
- {
- var lineIdx = (CurrentLinePosition + i) & _lineMask;
- if (lineIdx > CurrentLineCount)
- return null;
-
- return GetSpan(_lines[lineIdx]);
- }
- }
-
-
- protected MemoryMappedViewAccessor GetCurrentLineLocking()
- {
- MemoryMappedViewAccessor view;
- lock (_header)
- {
- var currentLineCount = CurrentLineCount;
- if (currentLineCount == LineCount)
- {
- var currentLinePos = CurrentLinePosition;
- view = _lines[currentLinePos]!;
- CurrentLinePosition = (currentLinePos + 1) & _lineMask;
- }
- else
- {
- view = _lines[currentLineCount];
- ++CurrentLineCount;
- }
-
- ++TotalWrittenLines;
- _header.Flush();
- }
-
- return view;
- }
-
- protected static Span GetSpan(MemoryMappedViewAccessor accessor, int offset = 0)
- => GetSpan(accessor, offset, (int)accessor.Capacity - offset);
-
- protected static unsafe Span GetSpan(MemoryMappedViewAccessor accessor, int offset, int size)
- {
- byte* ptr = null;
- accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr);
- size = Math.Min(size, (int)accessor.Capacity - offset);
- if (size < 0)
- return [];
-
- var span = new Span(ptr + offset + accessor.PointerOffset, size);
- return span;
- }
-
- protected void Dispose(bool disposing)
- {
- if (_disposed)
- return;
-
- _header?.Dispose();
- foreach (var line in _lines)
- line?.Dispose();
- _file?.Dispose();
- }
-
- ~MemoryMappedBuffer()
- => Dispose(false);
-}
diff --git a/Penumbra.CrashHandler/Buffers/ModdedFileBuffer.cs b/Penumbra.CrashHandler/Buffers/ModdedFileBuffer.cs
deleted file mode 100644
index e4ee66d0..00000000
--- a/Penumbra.CrashHandler/Buffers/ModdedFileBuffer.cs
+++ /dev/null
@@ -1,105 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Text.Json.Nodes;
-
-namespace Penumbra.CrashHandler.Buffers;
-
-/// Only expose the write interface for the buffer.
-public interface IModdedFileBufferWriter
-{
- /// Write a line into the buffer with the given data.
- /// The address of the related character, if known.
- /// The name of the related character, anonymized or relying on index if unavailable, if known.
- /// The GUID of the associated collection.
- /// The file name as requested by the game.
- /// The actual modded file name loaded.
- public void WriteLine(nint characterAddress, ReadOnlySpan characterName, Guid collectionId, ReadOnlySpan requestedFileName,
- ReadOnlySpan actualFileName);
-}
-
-/// The full crash entry for a loaded modded file.
-public record struct ModdedFileLoadedEntry(
- double Age,
- DateTimeOffset Timestamp,
- int ThreadId,
- string CharacterName,
- string CharacterAddress,
- Guid CollectionId,
- string RequestedFileName,
- string ActualFileName) : ICrashDataEntry;
-
-internal sealed class ModdedFileBuffer : MemoryMappedBuffer, IModdedFileBufferWriter, IBufferReader
-{
- private const int _version = 1;
- private const int _lineCount = 128;
- private const int _lineCapacity = 1024;
- private const string _name = "Penumbra.ModdedFile";
-
- public void WriteLine(nint characterAddress, ReadOnlySpan characterName, Guid collectionId, ReadOnlySpan requestedFileName,
- ReadOnlySpan actualFileName)
- {
- var accessor = GetCurrentLineLocking();
- lock (accessor)
- {
- accessor.Write(0, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
- accessor.Write(8, Environment.CurrentManagedThreadId);
- accessor.Write(12, characterAddress);
- var span = GetSpan(accessor, 20, 16);
- collectionId.TryWriteBytes(span);
- accessor.SafeMemoryMappedViewHandle.ReleasePointer();
- span = GetSpan(accessor, 36, 80);
- WriteSpan(characterName, span);
- accessor.SafeMemoryMappedViewHandle.ReleasePointer();
- span = GetSpan(accessor, 116, 260);
- WriteSpan(requestedFileName, span);
- accessor.SafeMemoryMappedViewHandle.ReleasePointer();
- span = GetSpan(accessor, 376);
- WriteSpan(actualFileName, span);
- accessor.SafeMemoryMappedViewHandle.ReleasePointer();
- }
- }
-
- public uint TotalCount
- => TotalWrittenLines;
-
- public IEnumerable GetLines(DateTimeOffset crashTime)
- {
- var lineCount = (int)CurrentLineCount;
- for (var i = lineCount - 1; i >= 0; --i)
- {
- var line = GetLine(i);
- var timestamp = DateTimeOffset.FromUnixTimeMilliseconds(BitConverter.ToInt64(line));
- var thread = BitConverter.ToInt32(line[8..]);
- var address = BitConverter.ToUInt64(line[12..]);
- var collectionId = new Guid(line[20..36]);
- var characterName = ReadString(line[36..]);
- var requestedFileName = ReadString(line[116..]);
- var actualFileName = ReadString(line[376..]);
- yield return new JsonObject()
- {
- [nameof(ModdedFileLoadedEntry.Age)] = (crashTime - timestamp).TotalSeconds,
- [nameof(ModdedFileLoadedEntry.Timestamp)] = timestamp,
- [nameof(ModdedFileLoadedEntry.ThreadId)] = thread,
- [nameof(ModdedFileLoadedEntry.CharacterName)] = characterName,
- [nameof(ModdedFileLoadedEntry.CharacterAddress)] = address.ToString("X"),
- [nameof(ModdedFileLoadedEntry.CollectionId)] = collectionId,
- [nameof(ModdedFileLoadedEntry.RequestedFileName)] = requestedFileName,
- [nameof(ModdedFileLoadedEntry.ActualFileName)] = actualFileName,
- };
- }
- }
-
- public static IBufferReader CreateReader(int pid)
- => new ModdedFileBuffer(false, pid);
-
- public static IModdedFileBufferWriter CreateWriter(int pid)
- => new ModdedFileBuffer(pid);
-
- private ModdedFileBuffer(bool writer, int pid)
- : base($"{_name}_{pid}_{_version}", _version)
- { }
-
- private ModdedFileBuffer(int pid)
- : base($"{_name}_{pid}_{_version}", _version, _lineCount, _lineCapacity)
- { }
-}
diff --git a/Penumbra.CrashHandler/CrashData.cs b/Penumbra.CrashHandler/CrashData.cs
deleted file mode 100644
index 55460548..00000000
--- a/Penumbra.CrashHandler/CrashData.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using System;
-using System.Collections.Generic;
-using Penumbra.CrashHandler.Buffers;
-
-namespace Penumbra.CrashHandler;
-
-/// A base entry for crash data.
-public interface ICrashDataEntry
-{
- /// The timestamp of the event.
- DateTimeOffset Timestamp { get; }
-
- /// The thread invoking the event.
- int ThreadId { get; }
-
- /// The age of the event compared to the crash. (Redundantly with the timestamp)
- double Age { get; }
-}
-
-/// A full set of crash data.
-public class CrashData
-{
- /// The mode this data was obtained - manually or from a crash.
- public string Mode { get; set; } = "Unknown";
-
- /// The time this crash data was generated.
- public DateTimeOffset CrashTime { get; set; } = DateTimeOffset.UnixEpoch;
-
- /// Penumbra's Version when this crash data was created.
- public string Version { get; set; } = string.Empty;
-
- /// The Game's Version when this crash data was created.
- public string GameVersion { get; set; } = string.Empty;
-
- /// The FFXIV process ID when this data was generated.
- public int ProcessId { get; set; } = 0;
-
- /// The FFXIV Exit Code (if any) when this data was generated.
- public int ExitCode { get; set; } = 0;
-
- /// The total amount of characters loaded during this session.
- public int TotalCharactersLoaded { get; set; } = 0;
-
- /// The total amount of modded files loaded during this session.
- public int TotalModdedFilesLoaded { get; set; } = 0;
-
- /// The total amount of vfx functions invoked during this session.
- public int TotalVFXFuncsInvoked { get; set; } = 0;
-
- /// The last character loaded before this crash data was generated.
- public CharacterLoadedEntry? LastCharacterLoaded
- => LastCharactersLoaded.Count == 0 ? default : LastCharactersLoaded[0];
-
- /// The last modded file loaded before this crash data was generated.
- public ModdedFileLoadedEntry? LastModdedFileLoaded
- => LastModdedFilesLoaded.Count == 0 ? default : LastModdedFilesLoaded[0];
-
- /// The last vfx function invoked before this crash data was generated.
- public VfxFuncInvokedEntry? LastVfxFuncInvoked
- => LastVFXFuncsInvoked.Count == 0 ? default : LastVFXFuncsInvoked[0];
-
- /// A collection of the last few characters loaded before this crash data was generated.
- public List LastCharactersLoaded { get; set; } = [];
-
- /// A collection of the last few modded files loaded before this crash data was generated.
- public List LastModdedFilesLoaded { get; set; } = [];
-
- /// A collection of the last few vfx functions invoked before this crash data was generated.
- public List LastVFXFuncsInvoked { get; set; } = [];
-}
diff --git a/Penumbra.CrashHandler/GameEventLogReader.cs b/Penumbra.CrashHandler/GameEventLogReader.cs
deleted file mode 100644
index 8a7f53f8..00000000
--- a/Penumbra.CrashHandler/GameEventLogReader.cs
+++ /dev/null
@@ -1,58 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.Json.Nodes;
-using Penumbra.CrashHandler.Buffers;
-
-namespace Penumbra.CrashHandler;
-
-public interface IBufferReader
-{
- public uint TotalCount { get; }
- public IEnumerable GetLines(DateTimeOffset crashTime);
-}
-
-public sealed class GameEventLogReader(int pid) : IDisposable
-{
- public readonly (IBufferReader Reader, string TypeSingular, string TypePlural)[] Readers =
- [
- (CharacterBaseBuffer.CreateReader(pid), "CharacterLoaded", "CharactersLoaded"),
- (ModdedFileBuffer.CreateReader(pid), "ModdedFileLoaded", "ModdedFilesLoaded"),
- (AnimationInvocationBuffer.CreateReader(pid), "VFXFuncInvoked", "VFXFuncsInvoked"),
- ];
-
- public void Dispose()
- {
- foreach (var (reader, _, _) in Readers)
- (reader as IDisposable)?.Dispose();
- }
-
-
- public JsonObject Dump(string mode, int processId, int exitCode, string version, string gameVersion)
- {
- var crashTime = DateTimeOffset.UtcNow;
- var obj = new JsonObject
- {
- [nameof(CrashData.Mode)] = mode,
- [nameof(CrashData.CrashTime)] = DateTimeOffset.UtcNow,
- [nameof(CrashData.ProcessId)] = processId,
- [nameof(CrashData.ExitCode)] = exitCode,
- [nameof(CrashData.Version)] = version,
- [nameof(CrashData.GameVersion)] = gameVersion,
- };
-
- foreach (var (reader, singular, _) in Readers)
- obj["Last" + singular] = reader.GetLines(crashTime).FirstOrDefault();
-
- foreach (var (reader, _, plural) in Readers)
- {
- obj["Total" + plural] = reader.TotalCount;
- var array = new JsonArray();
- foreach (var file in reader.GetLines(crashTime))
- array.Add(file);
- obj["Last" + plural] = array;
- }
-
- return obj;
- }
-}
diff --git a/Penumbra.CrashHandler/GameEventLogWriter.cs b/Penumbra.CrashHandler/GameEventLogWriter.cs
deleted file mode 100644
index 915c59a2..00000000
--- a/Penumbra.CrashHandler/GameEventLogWriter.cs
+++ /dev/null
@@ -1,18 +0,0 @@
-using System;
-using Penumbra.CrashHandler.Buffers;
-
-namespace Penumbra.CrashHandler;
-
-public sealed class GameEventLogWriter(int pid) : IDisposable
-{
- public readonly ICharacterBaseBufferWriter CharacterBase = CharacterBaseBuffer.CreateWriter(pid);
- public readonly IModdedFileBufferWriter FileLoaded = ModdedFileBuffer.CreateWriter(pid);
- public readonly IAnimationInvocationBufferWriter AnimationFuncInvoked = AnimationInvocationBuffer.CreateWriter(pid);
-
- public void Dispose()
- {
- (CharacterBase as IDisposable)?.Dispose();
- (FileLoaded as IDisposable)?.Dispose();
- (AnimationFuncInvoked as IDisposable)?.Dispose();
- }
-}
diff --git a/Penumbra.CrashHandler/Penumbra.CrashHandler.csproj b/Penumbra.CrashHandler/Penumbra.CrashHandler.csproj
deleted file mode 100644
index e07bb745..00000000
--- a/Penumbra.CrashHandler/Penumbra.CrashHandler.csproj
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
- Exe
-
-
-
- embedded
-
-
-
- embedded
-
-
-
- false
-
-
-
diff --git a/Penumbra.CrashHandler/Program.cs b/Penumbra.CrashHandler/Program.cs
deleted file mode 100644
index 38c176a6..00000000
--- a/Penumbra.CrashHandler/Program.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.IO;
-using System.Text.Json;
-
-namespace Penumbra.CrashHandler;
-
-public class CrashHandler
-{
- public static void Main(string[] args)
- {
- if (args.Length < 4 || !int.TryParse(args[1], out var pid))
- return;
-
- try
- {
- using var reader = new GameEventLogReader(pid);
- var parent = Process.GetProcessById(pid);
- using var handle = parent.SafeHandle;
- parent.WaitForExit();
- int exitCode;
- try
- {
- exitCode = parent.ExitCode;
- }
- catch
- {
- exitCode = -1;
- }
-
- var obj = reader.Dump("Crash", pid, exitCode, args[2], args[3]);
- using var fs = File.Open(args[0], FileMode.Create);
- using var w = new Utf8JsonWriter(fs, new JsonWriterOptions { Indented = true });
- obj.WriteTo(w, new JsonSerializerOptions() { WriteIndented = true });
- }
- catch (Exception ex)
- {
- File.WriteAllText(args[0], $"{DateTime.UtcNow} {pid} {ex}");
- }
- }
-}
diff --git a/Penumbra.CrashHandler/packages.lock.json b/Penumbra.CrashHandler/packages.lock.json
deleted file mode 100644
index 0a160ea5..00000000
--- a/Penumbra.CrashHandler/packages.lock.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "version": 1,
- "dependencies": {
- "net10.0-windows7.0": {
- "DotNet.ReproducibleBuilds": {
- "type": "Direct",
- "requested": "[1.2.39, )",
- "resolved": "1.2.39",
- "contentHash": "fcFN01tDTIQqDuTwr1jUQK/geofiwjG5DycJQOnC72i1SsLAk1ELe+apBOuZ11UMQG8YKFZG1FgvjZPbqHyatg=="
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Penumbra.GameData b/Penumbra.GameData
index 0e973ed6..1e65d3fd 160000
--- a/Penumbra.GameData
+++ b/Penumbra.GameData
@@ -1 +1 @@
-Subproject commit 0e973ed6eace6afd31cd298f8c58f76fa8d5ef60
+Subproject commit 1e65d3fd028d3ac58090a8c886f351acbd9f3a2a
diff --git a/Penumbra.String b/Penumbra.String
index 9bd016fb..5d9f36c5 160000
--- a/Penumbra.String
+++ b/Penumbra.String
@@ -1 +1 @@
-Subproject commit 9bd016fbef5fb2de467dd42165267fdd93cd9592
+Subproject commit 5d9f36c5b57685b07354460e225e65759ef9996e
diff --git a/Penumbra.sln b/Penumbra.sln
index fbcd6080..5c11aaea 100644
--- a/Penumbra.sln
+++ b/Penumbra.sln
@@ -8,11 +8,6 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F89C9EAE-25C8-43BE-8108-5921E5A93502}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
- .github\workflows\build.yml = .github\workflows\build.yml
- Penumbra\Penumbra.json = Penumbra\Penumbra.json
- .github\workflows\release.yml = .github\workflows\release.yml
- repo.json = repo.json
- .github\workflows\test_release.yml = .github\workflows\test_release.yml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "Penumbra.GameData\Penumbra.GameData.csproj", "{EE551E87-FDB3-4612-B500-DC870C07C605}"
@@ -23,76 +18,36 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.Api", "Penumbra.Ap
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.String", "Penumbra.String\Penumbra.String.csproj", "{5549BAFD-6357-4B1A-800C-75AC36E5B76D}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.CrashHandler", "Penumbra.CrashHandler\Penumbra.CrashHandler.csproj", "{EE834491-A98F-4395-BE0D-6861AE5AD953}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Schemas", "Schemas", "{BFEA7504-1210-4F79-A7FE-BF03B6567E33}"
- ProjectSection(SolutionItems) = preProject
- schemas\default_mod.json = schemas\default_mod.json
- schemas\group.json = schemas\group.json
- schemas\local_mod_data-v3.json = schemas\local_mod_data-v3.json
- schemas\mod_meta-v3.json = schemas\mod_meta-v3.json
- EndProjectSection
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "structs", "structs", "{B03F276A-0572-4F62-AF86-EF62F6B80463}"
- ProjectSection(SolutionItems) = preProject
- schemas\structs\container.json = schemas\structs\container.json
- schemas\structs\group_combining.json = schemas\structs\group_combining.json
- schemas\structs\group_imc.json = schemas\structs\group_imc.json
- schemas\structs\group_multi.json = schemas\structs\group_multi.json
- schemas\structs\group_single.json = schemas\structs\group_single.json
- schemas\structs\manipulation.json = schemas\structs\manipulation.json
- schemas\structs\meta_atch.json = schemas\structs\meta_atch.json
- schemas\structs\meta_atr.json = schemas\structs\meta_atr.json
- schemas\structs\meta_enums.json = schemas\structs\meta_enums.json
- schemas\structs\meta_eqdp.json = schemas\structs\meta_eqdp.json
- schemas\structs\meta_eqp.json = schemas\structs\meta_eqp.json
- schemas\structs\meta_est.json = schemas\structs\meta_est.json
- schemas\structs\meta_geqp.json = schemas\structs\meta_geqp.json
- schemas\structs\meta_gmp.json = schemas\structs\meta_gmp.json
- schemas\structs\meta_imc.json = schemas\structs\meta_imc.json
- schemas\structs\meta_rsp.json = schemas\structs\meta_rsp.json
- schemas\structs\meta_shp.json = schemas\structs\meta_shp.json
- schemas\structs\option.json = schemas\structs\option.json
- EndProjectSection
-EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|x64 = Debug|x64
- Release|x64 = Release|x64
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.ActiveCfg = Debug|x64
- {13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.Build.0 = Debug|x64
- {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.ActiveCfg = Release|x64
- {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.Build.0 = Release|x64
- {EE551E87-FDB3-4612-B500-DC870C07C605}.Debug|x64.ActiveCfg = Debug|x64
- {EE551E87-FDB3-4612-B500-DC870C07C605}.Debug|x64.Build.0 = Debug|x64
- {EE551E87-FDB3-4612-B500-DC870C07C605}.Release|x64.ActiveCfg = Release|x64
- {EE551E87-FDB3-4612-B500-DC870C07C605}.Release|x64.Build.0 = Release|x64
- {87750518-1A20-40B4-9FC1-22F906EFB290}.Debug|x64.ActiveCfg = Debug|x64
- {87750518-1A20-40B4-9FC1-22F906EFB290}.Debug|x64.Build.0 = Debug|x64
- {87750518-1A20-40B4-9FC1-22F906EFB290}.Release|x64.ActiveCfg = Release|x64
- {87750518-1A20-40B4-9FC1-22F906EFB290}.Release|x64.Build.0 = Release|x64
- {1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Debug|x64.ActiveCfg = Debug|x64
- {1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Debug|x64.Build.0 = Debug|x64
- {1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Release|x64.ActiveCfg = Release|x64
- {1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Release|x64.Build.0 = Release|x64
- {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Debug|x64.ActiveCfg = Debug|x64
- {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Debug|x64.Build.0 = Debug|x64
- {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Release|x64.ActiveCfg = Release|x64
- {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Release|x64.Build.0 = Release|x64
- {EE834491-A98F-4395-BE0D-6861AE5AD953}.Debug|x64.ActiveCfg = Debug|x64
- {EE834491-A98F-4395-BE0D-6861AE5AD953}.Debug|x64.Build.0 = Debug|x64
- {EE834491-A98F-4395-BE0D-6861AE5AD953}.Release|x64.ActiveCfg = Release|x64
- {EE834491-A98F-4395-BE0D-6861AE5AD953}.Release|x64.Build.0 = Release|x64
+ {13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EE551E87-FDB3-4612-B500-DC870C07C605}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {EE551E87-FDB3-4612-B500-DC870C07C605}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EE551E87-FDB3-4612-B500-DC870C07C605}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {EE551E87-FDB3-4612-B500-DC870C07C605}.Release|Any CPU.Build.0 = Release|Any CPU
+ {87750518-1A20-40B4-9FC1-22F906EFB290}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {87750518-1A20-40B4-9FC1-22F906EFB290}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {87750518-1A20-40B4-9FC1-22F906EFB290}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {87750518-1A20-40B4-9FC1-22F906EFB290}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {BFEA7504-1210-4F79-A7FE-BF03B6567E33} = {F89C9EAE-25C8-43BE-8108-5921E5A93502}
- {B03F276A-0572-4F62-AF86-EF62F6B80463} = {BFEA7504-1210-4F79-A7FE-BF03B6567E33}
- EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B17E85B1-5F60-4440-9F9A-3DDE877E8CDF}
EndGlobalSection
diff --git a/Penumbra/Api/Api/ApiHelpers.cs b/Penumbra/Api/Api/ApiHelpers.cs
deleted file mode 100644
index 92a30bce..00000000
--- a/Penumbra/Api/Api/ApiHelpers.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-using OtterGui.Log;
-using OtterGui.Services;
-using Penumbra.Api.Enums;
-using Penumbra.Collections;
-using Penumbra.Collections.Manager;
-using Penumbra.GameData.Actors;
-using Penumbra.GameData.Interop;
-using Penumbra.Interop.PathResolving;
-
-namespace Penumbra.Api.Api;
-
-public class ApiHelpers(
- CollectionManager collectionManager,
- ObjectManager objects,
- CollectionResolver collectionResolver,
- ActorManager actors) : IApiService
-{
- /// Return the associated identifier for an object given by its index.
- [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
- internal ActorIdentifier AssociatedIdentifier(int gameObjectIdx)
- {
- if (gameObjectIdx < 0 || gameObjectIdx >= objects.TotalCount)
- return ActorIdentifier.Invalid;
-
- var ptr = objects[gameObjectIdx];
- return actors.FromObject(ptr, out _, false, true, true);
- }
-
- ///
- /// Return the collection associated to a current game object. If it does not exist, return the default collection.
- /// If the index is invalid, returns false and the default collection.
- ///
- [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
- internal unsafe bool AssociatedCollection(int gameObjectIdx, out ModCollection collection)
- {
- collection = collectionManager.Active.Default;
- if (gameObjectIdx < 0 || gameObjectIdx >= objects.TotalCount)
- return false;
-
- var ptr = objects[gameObjectIdx];
- var data = collectionResolver.IdentifyCollection(ptr.AsObject, false);
- if (data.Valid)
- collection = data.ModCollection;
-
- return true;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
- internal static PenumbraApiEc Return(PenumbraApiEc ec, LazyString args, [CallerMemberName] string name = "Unknown")
- {
- if (ec is PenumbraApiEc.Success or PenumbraApiEc.NothingChanged)
- Penumbra.Log.Verbose($"[{name}] Called with {args}, returned {ec}.");
- else
- Penumbra.Log.Debug($"[{name}] Called with {args}, returned {ec}.");
- return ec;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
- internal static LazyString Args(params object[] arguments)
- {
- if (arguments.Length == 0)
- return new LazyString(() => "no arguments");
-
- return new LazyString(() =>
- {
- var sb = new StringBuilder();
- for (var i = 0; i < arguments.Length / 2; ++i)
- {
- sb.Append(arguments[2 * i]);
- sb.Append(" = ");
- sb.Append(arguments[2 * i + 1]);
- sb.Append(", ");
- }
-
- return sb.ToString(0, sb.Length - 2);
- });
- }
-}
diff --git a/Penumbra/Api/Api/CollectionApi.cs b/Penumbra/Api/Api/CollectionApi.cs
deleted file mode 100644
index c40feb12..00000000
--- a/Penumbra/Api/Api/CollectionApi.cs
+++ /dev/null
@@ -1,177 +0,0 @@
-using OtterGui.Services;
-using Penumbra.Api.Enums;
-using Penumbra.Collections;
-using Penumbra.Collections.Manager;
-using Penumbra.Mods;
-
-namespace Penumbra.Api.Api;
-
-public class CollectionApi(CollectionManager collections, ApiHelpers helpers) : IPenumbraApiCollection, IApiService
-{
- public Dictionary GetCollections()
- => collections.Storage.ToDictionary(c => c.Identity.Id, c => c.Identity.Name);
-
- public List<(Guid Id, string Name)> GetCollectionsByIdentifier(string identifier)
- {
- if (identifier.Length == 0)
- return [];
-
- var list = new List<(Guid Id, string Name)>(4);
- if (Guid.TryParse(identifier, out var guid) && collections.Storage.ById(guid, out var collection) && collection != ModCollection.Empty)
- list.Add((collection.Identity.Id, collection.Identity.Name));
- else if (identifier.Length >= 8)
- list.AddRange(collections.Storage.Where(c => c.Identity.Identifier.StartsWith(identifier, StringComparison.OrdinalIgnoreCase))
- .Select(c => (c.Identity.Id, c.Identity.Name)));
-
- list.AddRange(collections.Storage
- .Where(c => string.Equals(c.Identity.Name, identifier, StringComparison.OrdinalIgnoreCase)
- && !list.Contains((c.Identity.Id, c.Identity.Name)))
- .Select(c => (c.Identity.Id, c.Identity.Name)));
- return list;
- }
-
- public Func CheckCurrentChangedItemFunc()
- {
- var weakRef = new WeakReference(collections);
- return s =>
- {
- if (!weakRef.TryGetTarget(out var c))
- throw new ObjectDisposedException("The underlying collection storage of this IPC container was disposed.");
-
- if (!c.Active.Current.ChangedItems.TryGetValue(s, out var d))
- return [];
-
- return d.Item1.Select(m => (m is Mod mod ? mod.Identifier : string.Empty, m.Name.Text)).ToArray();
- };
- }
-
- public Dictionary GetChangedItemsForCollection(Guid collectionId)
- {
- try
- {
- if (!collections.Storage.ById(collectionId, out var collection))
- collection = ModCollection.Empty;
-
- if (collection.HasCache)
- return collection.ChangedItems.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.Item2?.ToInternalObject());
-
- Penumbra.Log.Warning($"Collection {collectionId} does not exist or is not loaded.");
- return [];
- }
- catch (Exception e)
- {
- Penumbra.Log.Error($"Could not obtain Changed Items for {collectionId}:\n{e}");
- throw;
- }
- }
-
- public (Guid Id, string Name)? GetCollection(ApiCollectionType type)
- {
- if (!Enum.IsDefined(type))
- return null;
-
- var collection = collections.Active.ByType((CollectionType)type);
- return collection == null ? null : (collection.Identity.Id, collection.Identity.Name);
- }
-
- internal (Guid Id, string Name)? GetCollection(byte type)
- => GetCollection((ApiCollectionType)type);
-
- public (bool ObjectValid, bool IndividualSet, (Guid Id, string Name) EffectiveCollection) GetCollectionForObject(int gameObjectIdx)
- {
- var id = helpers.AssociatedIdentifier(gameObjectIdx);
- if (!id.IsValid)
- return (false, false, (collections.Active.Default.Identity.Id, collections.Active.Default.Identity.Name));
-
- if (collections.Active.Individuals.TryGetValue(id, out var collection))
- return (true, true, (collection.Identity.Id, collection.Identity.Name));
-
- helpers.AssociatedCollection(gameObjectIdx, out collection);
- return (true, false, (collection.Identity.Id, collection.Identity.Name));
- }
-
- public Guid[] GetCollectionByName(string name)
- => collections.Storage.Where(c => string.Equals(name, c.Identity.Name, StringComparison.OrdinalIgnoreCase)).Select(c => c.Identity.Id)
- .ToArray();
-
- public (PenumbraApiEc, (Guid Id, string Name)? OldCollection) SetCollection(ApiCollectionType type, Guid? collectionId,
- bool allowCreateNew, bool allowDelete)
- {
- if (!Enum.IsDefined(type))
- return (PenumbraApiEc.InvalidArgument, null);
-
- var oldCollection = collections.Active.ByType((CollectionType)type);
- var old = oldCollection != null ? (oldCollection.Identity.Id, oldCollection.Identity.Name) : new ValueTuple?();
- if (collectionId == null)
- {
- if (old == null)
- return (PenumbraApiEc.NothingChanged, old);
-
- if (!allowDelete || type is ApiCollectionType.Current or ApiCollectionType.Default or ApiCollectionType.Interface)
- return (PenumbraApiEc.AssignmentDeletionDisallowed, old);
-
- collections.Active.RemoveSpecialCollection((CollectionType)type);
- return (PenumbraApiEc.Success, old);
- }
-
- if (!collections.Storage.ById(collectionId.Value, out var collection))
- return (PenumbraApiEc.CollectionMissing, old);
-
- if (old == null)
- {
- if (!allowCreateNew)
- return (PenumbraApiEc.AssignmentCreationDisallowed, old);
-
- collections.Active.CreateSpecialCollection((CollectionType)type);
- }
- else if (old.Value.Item1 == collection.Identity.Id)
- {
- return (PenumbraApiEc.NothingChanged, old);
- }
-
- collections.Active.SetCollection(collection, (CollectionType)type);
- return (PenumbraApiEc.Success, old);
- }
-
- public (PenumbraApiEc, (Guid Id, string Name)? OldCollection) SetCollectionForObject(int gameObjectIdx, Guid? collectionId,
- bool allowCreateNew, bool allowDelete)
- {
- var id = helpers.AssociatedIdentifier(gameObjectIdx);
- if (!id.IsValid)
- return (PenumbraApiEc.InvalidIdentifier, (collections.Active.Default.Identity.Id, collections.Active.Default.Identity.Name));
-
- var oldCollection = collections.Active.Individuals.TryGetValue(id, out var c) ? c : null;
- var old = oldCollection != null ? (oldCollection.Identity.Id, oldCollection.Identity.Name) : new ValueTuple?();
- if (collectionId == null)
- {
- if (old == null)
- return (PenumbraApiEc.NothingChanged, old);
-
- if (!allowDelete)
- return (PenumbraApiEc.AssignmentDeletionDisallowed, old);
-
- var idx = collections.Active.Individuals.Index(id);
- collections.Active.RemoveIndividualCollection(idx);
- return (PenumbraApiEc.Success, old);
- }
-
- if (!collections.Storage.ById(collectionId.Value, out var collection))
- return (PenumbraApiEc.CollectionMissing, old);
-
- if (old == null)
- {
- if (!allowCreateNew)
- return (PenumbraApiEc.AssignmentCreationDisallowed, old);
-
- var ids = collections.Active.Individuals.GetGroup(id);
- collections.Active.CreateIndividualCollection(ids);
- }
- else if (old.Value.Item1 == collection.Identity.Id)
- {
- return (PenumbraApiEc.NothingChanged, old);
- }
-
- collections.Active.SetCollection(collection, CollectionType.Individual, collections.Active.Individuals.Index(id));
- return (PenumbraApiEc.Success, old);
- }
-}
diff --git a/Penumbra/Api/Api/EditingApi.cs b/Penumbra/Api/Api/EditingApi.cs
deleted file mode 100644
index 5a1fc347..00000000
--- a/Penumbra/Api/Api/EditingApi.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-using OtterGui.Services;
-using Penumbra.Import.Textures;
-using TextureType = Penumbra.Api.Enums.TextureType;
-
-namespace Penumbra.Api.Api;
-
-public class EditingApi(TextureManager textureManager) : IPenumbraApiEditing, IApiService
-{
- public Task ConvertTextureFile(string inputFile, string outputFile, TextureType textureType, bool mipMaps)
- => textureType switch
- {
- TextureType.Png => textureManager.SavePng(inputFile, outputFile),
- TextureType.Targa => textureManager.SaveTga(inputFile, outputFile),
- TextureType.AsIsTex => textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, true, inputFile, outputFile),
- TextureType.AsIsDds => textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, false, inputFile, outputFile),
- TextureType.RgbaTex => textureManager.SaveAs(CombinedTexture.TextureSaveType.Bitmap, mipMaps, true, inputFile, outputFile),
- TextureType.RgbaDds => textureManager.SaveAs(CombinedTexture.TextureSaveType.Bitmap, mipMaps, false, inputFile, outputFile),
- TextureType.Bc3Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC3, mipMaps, true, inputFile, outputFile),
- TextureType.Bc3Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC3, mipMaps, false, inputFile, outputFile),
- TextureType.Bc7Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC7, mipMaps, true, inputFile, outputFile),
- TextureType.Bc7Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC7, mipMaps, false, inputFile, outputFile),
- TextureType.Bc1Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC1, mipMaps, true, inputFile, outputFile),
- TextureType.Bc1Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC1, mipMaps, false, inputFile, outputFile),
- TextureType.Bc4Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC4, mipMaps, true, inputFile, outputFile),
- TextureType.Bc4Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC4, mipMaps, false, inputFile, outputFile),
- TextureType.Bc5Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC5, mipMaps, true, inputFile, outputFile),
- TextureType.Bc5Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC5, mipMaps, false, inputFile, outputFile),
- _ => Task.FromException(new Exception($"Invalid input value {textureType}.")),
- };
-
- // @formatter:off
- public Task ConvertTextureData(byte[] rgbaData, int width, string outputFile, TextureType textureType, bool mipMaps)
- => textureType switch
- {
- TextureType.Png => textureManager.SavePng(new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
- TextureType.Targa => textureManager.SaveTga(new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
- TextureType.AsIsTex => textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
- TextureType.AsIsDds => textureManager.SaveAs(CombinedTexture.TextureSaveType.AsIs, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
- TextureType.RgbaTex => textureManager.SaveAs(CombinedTexture.TextureSaveType.Bitmap, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
- TextureType.RgbaDds => textureManager.SaveAs(CombinedTexture.TextureSaveType.Bitmap, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
- TextureType.Bc3Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC3, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
- TextureType.Bc3Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC3, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
- TextureType.Bc7Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC7, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
- TextureType.Bc7Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC7, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
- TextureType.Bc1Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC1, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
- TextureType.Bc1Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC1, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
- TextureType.Bc4Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC4, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
- TextureType.Bc4Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC4, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
- TextureType.Bc5Tex => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC5, mipMaps, true, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
- TextureType.Bc5Dds => textureManager.SaveAs(CombinedTexture.TextureSaveType.BC5, mipMaps, false, new BaseImage(), outputFile, rgbaData, width, rgbaData.Length / 4 / width),
- _ => Task.FromException(new Exception($"Invalid input value {textureType}.")),
- };
- // @formatter:on
-}
diff --git a/Penumbra/Api/Api/GameStateApi.cs b/Penumbra/Api/Api/GameStateApi.cs
deleted file mode 100644
index 74cde3a0..00000000
--- a/Penumbra/Api/Api/GameStateApi.cs
+++ /dev/null
@@ -1,123 +0,0 @@
-using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
-using OtterGui.Services;
-using Penumbra.Api.Enums;
-using Penumbra.Collections;
-using Penumbra.Interop.Hooks.ResourceLoading;
-using Penumbra.Interop.PathResolving;
-using Penumbra.Interop.Structs;
-using Penumbra.Services;
-using Penumbra.String.Classes;
-
-namespace Penumbra.Api.Api;
-
-public class GameStateApi : IPenumbraApiGameState, IApiService, IDisposable
-{
- private readonly CommunicatorService _communicator;
- private readonly CollectionResolver _collectionResolver;
- private readonly DrawObjectState _drawObjectState;
- private readonly CutsceneService _cutsceneService;
- private readonly ResourceLoader _resourceLoader;
-
- public unsafe GameStateApi(CommunicatorService communicator, CollectionResolver collectionResolver, CutsceneService cutsceneService,
- ResourceLoader resourceLoader, DrawObjectState drawObjectState)
- {
- _communicator = communicator;
- _collectionResolver = collectionResolver;
- _cutsceneService = cutsceneService;
- _resourceLoader = resourceLoader;
- _drawObjectState = drawObjectState;
- _resourceLoader.ResourceLoaded += OnResourceLoaded;
- _resourceLoader.PapRequested += OnPapRequested;
- _communicator.CreatedCharacterBase.Subscribe(OnCreatedCharacterBase, Communication.CreatedCharacterBase.Priority.Api);
- }
-
- public unsafe void Dispose()
- {
- _resourceLoader.ResourceLoaded -= OnResourceLoaded;
- _resourceLoader.PapRequested -= OnPapRequested;
- _communicator.CreatedCharacterBase.Unsubscribe(OnCreatedCharacterBase);
- }
-
- public event CreatedCharacterBaseDelegate? CreatedCharacterBase;
- public event GameObjectResourceResolvedDelegate? GameObjectResourceResolved;
-
- public event CreatingCharacterBaseDelegate? CreatingCharacterBase
- {
- add
- {
- if (value == null)
- return;
-
- _communicator.CreatingCharacterBase.Subscribe(new Action(value),
- Communication.CreatingCharacterBase.Priority.Api);
- }
- remove
- {
- if (value == null)
- return;
-
- _communicator.CreatingCharacterBase.Unsubscribe(new Action(value));
- }
- }
-
- public unsafe (nint GameObject, (Guid Id, string Name) Collection) GetDrawObjectInfo(nint drawObject)
- {
- var data = _collectionResolver.IdentifyCollection((DrawObject*)drawObject, true);
- return (data.AssociatedGameObject, (Id: data.ModCollection.Identity.Id, Name: data.ModCollection.Identity.Name));
- }
-
- public int GetCutsceneParentIndex(int actorIdx)
- => _cutsceneService.GetParentIndex(actorIdx);
-
- public Func GetCutsceneParentIndexFunc()
- {
- var weakRef = new WeakReference(_cutsceneService);
- return idx =>
- {
- if (!weakRef.TryGetTarget(out var c))
- throw new ObjectDisposedException("The underlying cutscene state storage of this IPC container was disposed.");
-
- return c.GetParentIndex(idx);
- };
- }
-
- public Func GetGameObjectFromDrawObjectFunc()
- {
- var weakRef = new WeakReference(_drawObjectState);
- return model =>
- {
- if (!weakRef.TryGetTarget(out var c))
- throw new ObjectDisposedException("The underlying draw object state storage of this IPC container was disposed.");
-
- return c.TryGetValue(model, out var data) ? data.Item1.Address : nint.Zero;
- };
- }
-
- public PenumbraApiEc SetCutsceneParentIndex(int copyIdx, int newParentIdx)
- => _cutsceneService.SetParentIndex(copyIdx, newParentIdx)
- ? PenumbraApiEc.Success
- : PenumbraApiEc.InvalidArgument;
-
- private unsafe void OnResourceLoaded(ResourceHandle* handle, Utf8GamePath originalPath, FullPath? manipulatedPath, ResolveData resolveData)
- {
- if (resolveData.AssociatedGameObject != nint.Zero && GameObjectResourceResolved != null)
- {
- var original = originalPath.ToString();
- GameObjectResourceResolved.Invoke(resolveData.AssociatedGameObject, original,
- manipulatedPath?.ToString() ?? original);
- }
- }
-
- private void OnPapRequested(Utf8GamePath originalPath, FullPath? manipulatedPath, ResolveData resolveData)
- {
- if (resolveData.AssociatedGameObject != nint.Zero && GameObjectResourceResolved != null)
- {
- var original = originalPath.ToString();
- GameObjectResourceResolved.Invoke(resolveData.AssociatedGameObject, original,
- manipulatedPath?.ToString() ?? original);
- }
- }
-
- private void OnCreatedCharacterBase(nint gameObject, ModCollection collection, nint drawObject)
- => CreatedCharacterBase?.Invoke(gameObject, collection.Identity.Id, drawObject);
-}
diff --git a/Penumbra/Api/Api/IdentityChecker.cs b/Penumbra/Api/Api/IdentityChecker.cs
deleted file mode 100644
index e090053e..00000000
--- a/Penumbra/Api/Api/IdentityChecker.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Penumbra.Api.Api;
-
-public static class IdentityChecker
-{
- public static bool Check(string identity)
- => true;
-}
diff --git a/Penumbra/Api/Api/MetaApi.cs b/Penumbra/Api/Api/MetaApi.cs
deleted file mode 100644
index 5cffc811..00000000
--- a/Penumbra/Api/Api/MetaApi.cs
+++ /dev/null
@@ -1,544 +0,0 @@
-using Dalamud.Plugin.Services;
-using Newtonsoft.Json;
-using Newtonsoft.Json.Linq;
-using OtterGui;
-using OtterGui.Services;
-using Penumbra.Collections;
-using Penumbra.Collections.Cache;
-using Penumbra.GameData.Files.AtchStructs;
-using Penumbra.GameData.Files.Utility;
-using Penumbra.GameData.Structs;
-using Penumbra.Interop.PathResolving;
-using Penumbra.Meta.Manipulations;
-
-namespace Penumbra.Api.Api;
-
-public class MetaApi(IFramework framework, CollectionResolver collectionResolver, ApiHelpers helpers)
- : IPenumbraApiMeta, IApiService
-{
- public string GetPlayerMetaManipulations()
- {
- var collection = collectionResolver.PlayerCollection();
- return CompressMetaManipulations(collection);
- }
-
- public string GetMetaManipulations(int gameObjectIdx)
- {
- helpers.AssociatedCollection(gameObjectIdx, out var collection);
- return CompressMetaManipulations(collection);
- }
-
- public Task GetPlayerMetaManipulationsAsync()
- {
- return Task.Run(async () =>
- {
- var playerCollection = await framework.RunOnFrameworkThread(collectionResolver.PlayerCollection).ConfigureAwait(false);
- return CompressMetaManipulations(playerCollection);
- });
- }
-
- public Task GetMetaManipulationsAsync(int gameObjectIdx)
- {
- return Task.Run(async () =>
- {
- var playerCollection = await framework.RunOnFrameworkThread(() =>
- {
- helpers.AssociatedCollection(gameObjectIdx, out var collection);
- return collection;
- }).ConfigureAwait(false);
- return CompressMetaManipulations(playerCollection);
- });
- }
-
- internal static string CompressMetaManipulations(ModCollection collection)
- => CompressMetaManipulationsV1(collection);
-
- private static string CompressMetaManipulationsV0(ModCollection collection)
- {
- var array = new JArray();
- if (collection.MetaCache is { } cache)
- {
- MetaDictionary.SerializeTo(array, cache.GlobalEqp.Select(kvp => kvp.Key));
- MetaDictionary.SerializeTo(array, cache.Imc.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Entry)));
- MetaDictionary.SerializeTo(array, cache.Eqp.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Entry)));
- MetaDictionary.SerializeTo(array, cache.Eqdp.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Entry)));
- MetaDictionary.SerializeTo(array, cache.Est.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Entry)));
- MetaDictionary.SerializeTo(array, cache.Rsp.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Entry)));
- MetaDictionary.SerializeTo(array, cache.Gmp.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Entry)));
- MetaDictionary.SerializeTo(array, cache.Atch.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Entry)));
- MetaDictionary.SerializeTo(array, cache.Shp.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Entry)));
- MetaDictionary.SerializeTo(array, cache.Atr.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value.Entry)));
- }
-
- return Functions.ToCompressedBase64(array, 0);
- }
-
- private static unsafe string CompressMetaManipulationsV1(ModCollection? collection)
- {
- using var ms = new MemoryStream();
- ms.Capacity = 1024;
- using (var zipStream = new GZipStream(ms, CompressionMode.Compress, true))
- {
- zipStream.Write((byte)1);
- zipStream.Write("META0001"u8);
- if (collection?.MetaCache is not { } cache)
- {
- zipStream.Write(0);
- zipStream.Write(0);
- zipStream.Write(0);
- zipStream.Write(0);
- zipStream.Write(0);
- zipStream.Write(0);
- zipStream.Write(0);
- }
- else
- {
- WriteCache(zipStream, cache.Imc);
- WriteCache(zipStream, cache.Eqp);
- WriteCache(zipStream, cache.Eqdp);
- WriteCache(zipStream, cache.Est);
- WriteCache(zipStream, cache.Rsp);
- WriteCache(zipStream, cache.Gmp);
- cache.GlobalEqp.EnterReadLock();
-
- try
- {
- zipStream.Write(cache.GlobalEqp.Count);
- foreach (var (globalEqp, _) in cache.GlobalEqp)
- zipStream.Write(new ReadOnlySpan(&globalEqp, sizeof(GlobalEqpManipulation)));
- }
- finally
- {
- cache.GlobalEqp.ExitReadLock();
- }
-
- WriteCache(zipStream, cache.Atch);
- WriteCache(zipStream, cache.Shp);
- WriteCache(zipStream, cache.Atr);
- }
- }
-
- ms.Flush();
- ms.Position = 0;
- var data = ms.GetBuffer().AsSpan(0, (int)ms.Length);
- return Convert.ToBase64String(data);
-
- void WriteCache(Stream stream, MetaCacheBase metaCache)
- where TKey : unmanaged, IMetaIdentifier
- where TValue : unmanaged
- {
- metaCache.EnterReadLock();
- try
- {
- stream.Write(metaCache.Count);
- foreach (var (identifier, (_, value)) in metaCache)
- {
- stream.Write(identifier);
- stream.Write(value);
- }
- }
- finally
- {
- metaCache.ExitReadLock();
- }
- }
- }
-
- public const uint ImcKey = ((uint)'I' << 24) | ((uint)'M' << 16) | ((uint)'C' << 8);
- public const uint EqpKey = ((uint)'E' << 24) | ((uint)'Q' << 16) | ((uint)'P' << 8);
- public const uint EqdpKey = ((uint)'E' << 24) | ((uint)'Q' << 16) | ((uint)'D' << 8) | 'P';
- public const uint EstKey = ((uint)'E' << 24) | ((uint)'S' << 16) | ((uint)'T' << 8);
- public const uint RspKey = ((uint)'R' << 24) | ((uint)'S' << 16) | ((uint)'P' << 8);
- public const uint GmpKey = ((uint)'G' << 24) | ((uint)'M' << 16) | ((uint)'P' << 8);
- public const uint GeqpKey = ((uint)'G' << 24) | ((uint)'E' << 16) | ((uint)'Q' << 8) | 'P';
- public const uint AtchKey = ((uint)'A' << 24) | ((uint)'T' << 16) | ((uint)'C' << 8) | 'H';
- public const uint ShpKey = ((uint)'S' << 24) | ((uint)'H' << 16) | ((uint)'P' << 8);
- public const uint AtrKey = ((uint)'A' << 24) | ((uint)'T' << 16) | ((uint)'R' << 8);
-
- private static unsafe string CompressMetaManipulationsV2(ModCollection? collection)
- {
- using var ms = new MemoryStream();
- ms.Capacity = 1024;
- using (var zipStream = new GZipStream(ms, CompressionMode.Compress, true))
- {
- zipStream.Write((byte)2);
- zipStream.Write("META0002"u8);
- if (collection?.MetaCache is { } cache)
- {
- WriteCache(zipStream, cache.Imc, ImcKey);
- WriteCache(zipStream, cache.Eqp, EqpKey);
- WriteCache(zipStream, cache.Eqdp, EqdpKey);
- WriteCache(zipStream, cache.Est, EstKey);
- WriteCache(zipStream, cache.Rsp, RspKey);
- WriteCache(zipStream, cache.Gmp, GmpKey);
- cache.GlobalEqp.EnterReadLock();
-
- try
- {
- if (cache.GlobalEqp.Count > 0)
- {
- zipStream.Write(GeqpKey);
- zipStream.Write(cache.GlobalEqp.Count);
- foreach (var (globalEqp, _) in cache.GlobalEqp)
- zipStream.Write(new ReadOnlySpan(&globalEqp, sizeof(GlobalEqpManipulation)));
- }
- }
- finally
- {
- cache.GlobalEqp.ExitReadLock();
- }
-
- WriteCache(zipStream, cache.Atch, AtchKey);
- WriteCache(zipStream, cache.Shp, ShpKey);
- WriteCache(zipStream, cache.Atr, AtrKey);
- }
- }
-
- ms.Flush();
- ms.Position = 0;
- var data = ms.GetBuffer().AsSpan(0, (int)ms.Length);
- return Convert.ToBase64String(data);
-
- void WriteCache(Stream stream, MetaCacheBase metaCache, uint label)
- where TKey : unmanaged, IMetaIdentifier
- where TValue : unmanaged
- {
- metaCache.EnterReadLock();
- try
- {
- if (metaCache.Count <= 0)
- return;
-
- stream.Write(label);
- stream.Write(metaCache.Count);
- foreach (var (identifier, (_, value)) in metaCache)
- {
- stream.Write(identifier);
- stream.Write(value);
- }
- }
- finally
- {
- metaCache.ExitReadLock();
- }
- }
- }
-
- ///
- /// Convert manipulations from a transmitted base64 string to actual manipulations.
- /// The empty string is treated as an empty set.
- /// Only returns true if all conversions are successful and distinct.
- ///
- internal static bool ConvertManips(string manipString, [NotNullWhen(true)] out MetaDictionary? manips, out byte version)
- {
- if (manipString.Length == 0)
- {
- manips = new MetaDictionary();
- version = byte.MaxValue;
- return true;
- }
-
- try
- {
- var bytes = Convert.FromBase64String(manipString);
- using var compressedStream = new MemoryStream(bytes);
- using var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress);
- using var resultStream = new MemoryStream();
- zipStream.CopyTo(resultStream);
- resultStream.Flush();
- resultStream.Position = 0;
- var data = resultStream.GetBuffer().AsSpan(0, (int)resultStream.Length);
- version = data[0];
- data = data[1..];
- switch (version)
- {
- case 0: return ConvertManipsV0(data, out manips);
- case 1: return ConvertManipsV1(data, out manips);
- case 2: return ConvertManipsV2(data, out manips);
- default:
- Penumbra.Log.Debug($"Invalid version for manipulations: {version}.");
- manips = null;
- return false;
- }
- }
- catch (Exception ex)
- {
- Penumbra.Log.Debug($"Error decompressing manipulations:\n{ex}");
- manips = null;
- version = byte.MaxValue;
- return false;
- }
- }
-
- private static bool ConvertManipsV2(ReadOnlySpan data, [NotNullWhen(true)] out MetaDictionary? manips)
- {
- if (!data.StartsWith("META0002"u8))
- {
- Penumbra.Log.Debug("Invalid manipulations of version 2, does not start with valid prefix.");
- manips = null;
- return false;
- }
-
- manips = new MetaDictionary();
- var r = new SpanBinaryReader(data[8..]);
- while (r.Remaining > 4)
- {
- var prefix = r.ReadUInt32();
- var count = r.Remaining > 4 ? r.ReadInt32() : 0;
- if (count is 0)
- continue;
-
- switch (prefix)
- {
- case ImcKey:
- for (var i = 0; i < count; ++i)
- {
- var identifier = r.Read();
- var value = r.Read();
- if (!identifier.Validate() || !manips.TryAdd(identifier, value))
- return false;
- }
-
- break;
- case EqpKey:
- for (var i = 0; i < count; ++i)
- {
- var identifier = r.Read();
- var value = r.Read();
- if (!identifier.Validate() || !manips.TryAdd(identifier, value))
- return false;
- }
-
- break;
- case EqdpKey:
- for (var i = 0; i < count; ++i)
- {
- var identifier = r.Read();
- var value = r.Read();
- if (!identifier.Validate() || !manips.TryAdd(identifier, value))
- return false;
- }
-
- break;
- case EstKey:
- for (var i = 0; i < count; ++i)
- {
- var identifier = r.Read();
- var value = r.Read();
- if (!identifier.Validate() || !manips.TryAdd(identifier, value))
- return false;
- }
-
- break;
- case RspKey:
- for (var i = 0; i < count; ++i)
- {
- var identifier = r.Read();
- var value = r.Read();
- if (!identifier.Validate() || !manips.TryAdd(identifier, value))
- return false;
- }
-
- break;
- case GmpKey:
- for (var i = 0; i < count; ++i)
- {
- var identifier = r.Read();
- var value = r.Read();
- if (!identifier.Validate() || !manips.TryAdd(identifier, value))
- return false;
- }
-
- break;
- case GeqpKey:
- for (var i = 0; i < count; ++i)
- {
- var identifier = r.Read();
- if (!identifier.Validate() || !manips.TryAdd(identifier))
- return false;
- }
-
- break;
- case AtchKey:
- for (var i = 0; i < count; ++i)
- {
- var identifier = r.Read();
- var value = r.Read();
- if (!identifier.Validate() || !manips.TryAdd(identifier, value))
- return false;
- }
-
- break;
- case ShpKey:
- for (var i = 0; i < count; ++i)
- {
- var identifier = r.Read();
- var value = r.Read();
- if (!identifier.Validate() || !manips.TryAdd(identifier, value))
- return false;
- }
-
- break;
- case AtrKey:
- for (var i = 0; i < count; ++i)
- {
- var identifier = r.Read();
- var value = r.Read();
- if (!identifier.Validate() || !manips.TryAdd(identifier, value))
- return false;
- }
-
- break;
- }
- }
-
- return true;
- }
-
- private static bool ConvertManipsV1(ReadOnlySpan data, [NotNullWhen(true)] out MetaDictionary? manips)
- {
- if (!data.StartsWith("META0001"u8))
- {
- Penumbra.Log.Debug($"Invalid manipulations of version 1, does not start with valid prefix.");
- manips = null;
- return false;
- }
-
- manips = new MetaDictionary();
- var r = new SpanBinaryReader(data[8..]);
- var imcCount = r.ReadInt32();
- for (var i = 0; i < imcCount; ++i)
- {
- var identifier = r.Read();
- var value = r.Read();
- if (!identifier.Validate() || !manips.TryAdd(identifier, value))
- return false;
- }
-
- var eqpCount = r.ReadInt32();
- for (var i = 0; i < eqpCount; ++i)
- {
- var identifier = r.Read();
- var value = r.Read();
- if (!identifier.Validate() || !manips.TryAdd(identifier, value))
- return false;
- }
-
- var eqdpCount = r.ReadInt32();
- for (var i = 0; i < eqdpCount; ++i)
- {
- var identifier = r.Read();
- var value = r.Read();
- if (!identifier.Validate() || !manips.TryAdd(identifier, value))
- return false;
- }
-
- var estCount = r.ReadInt32();
- for (var i = 0; i < estCount; ++i)
- {
- var identifier = r.Read();
- var value = r.Read();
- if (!identifier.Validate() || !manips.TryAdd(identifier, value))
- return false;
- }
-
- var rspCount = r.ReadInt32();
- for (var i = 0; i < rspCount; ++i)
- {
- var identifier = r.Read();
- var value = r.Read();
- if (!identifier.Validate() || !manips.TryAdd(identifier, value))
- return false;
- }
-
- var gmpCount = r.ReadInt32();
- for (var i = 0; i < gmpCount; ++i)
- {
- var identifier = r.Read();
- var value = r.Read();
- if (!identifier.Validate() || !manips.TryAdd(identifier, value))
- return false;
- }
-
- var globalEqpCount = r.ReadInt32();
- for (var i = 0; i < globalEqpCount; ++i)
- {
- var manip = r.Read();
- if (!manip.Validate() || !manips.TryAdd(manip))
- return false;
- }
-
- // Atch was added after there were already some V1 around, so check for size here.
- if (r.Position < r.Count)
- {
- var atchCount = r.ReadInt32();
- for (var i = 0; i < atchCount; ++i)
- {
- var identifier = r.Read();
- var value = r.Read();
- if (!identifier.Validate() || !manips.TryAdd(identifier, value))
- return false;
- }
-
- // Shp and Atr was added later
- if (r.Position < r.Count)
- {
- var shpCount = r.ReadInt32();
- for (var i = 0; i < shpCount; ++i)
- {
- var identifier = r.Read();
- var value = r.Read();
- if (!identifier.Validate() || !manips.TryAdd(identifier, value))
- return false;
- }
-
- var atrCount = r.ReadInt32();
- for (var i = 0; i < atrCount; ++i)
- {
- var identifier = r.Read();
- var value = r.Read();
- if (!identifier.Validate() || !manips.TryAdd(identifier, value))
- return false;
- }
- }
- }
-
- return true;
- }
-
- private static bool ConvertManipsV0(ReadOnlySpan data, [NotNullWhen(true)] out MetaDictionary? manips)
- {
- var json = Encoding.UTF8.GetString(data);
- manips = JsonConvert.DeserializeObject(json);
- return manips != null;
- }
-
- internal void TestMetaManipulations()
- {
- var collection = collectionResolver.PlayerCollection();
- var dict = new MetaDictionary(collection.MetaCache);
- var count = dict.Count;
-
- var watch = Stopwatch.StartNew();
- var v0 = CompressMetaManipulationsV0(collection);
- var v0Time = watch.ElapsedMilliseconds;
-
- watch.Restart();
- var v1 = CompressMetaManipulationsV1(collection);
- var v1Time = watch.ElapsedMilliseconds;
-
- watch.Restart();
- var v1Success = ConvertManips(v1, out var v1Roundtrip, out _);
- var v1RoundtripTime = watch.ElapsedMilliseconds;
-
- watch.Restart();
- var v0Success = ConvertManips(v0, out var v0Roundtrip, out _);
- var v0RoundtripTime = watch.ElapsedMilliseconds;
-
- Penumbra.Log.Information($"Version | Count | Time | Length | Success | ReCount | ReTime | Equal");
- Penumbra.Log.Information(
- $"0 | {count} | {v0Time} | {v0.Length} | {v0Success} | {v0Roundtrip?.Count} | {v0RoundtripTime} | {v0Roundtrip?.Equals(dict)}");
- Penumbra.Log.Information(
- $"1 | {count} | {v1Time} | {v1.Length} | {v1Success} | {v1Roundtrip?.Count} | {v1RoundtripTime} | {v0Roundtrip?.Equals(dict)}");
- }
-}
diff --git a/Penumbra/Api/Api/ModSettingsApi.cs b/Penumbra/Api/Api/ModSettingsApi.cs
deleted file mode 100644
index d49c2904..00000000
--- a/Penumbra/Api/Api/ModSettingsApi.cs
+++ /dev/null
@@ -1,362 +0,0 @@
-using OtterGui.Extensions;
-using OtterGui.Services;
-using Penumbra.Api.Enums;
-using Penumbra.Api.Helpers;
-using Penumbra.Collections;
-using Penumbra.Collections.Manager;
-using Penumbra.Communication;
-using Penumbra.Interop.PathResolving;
-using Penumbra.Mods;
-using Penumbra.Mods.Editor;
-using Penumbra.Mods.Groups;
-using Penumbra.Mods.Manager;
-using Penumbra.Mods.Manager.OptionEditor;
-using Penumbra.Mods.Settings;
-using Penumbra.Mods.SubMods;
-using Penumbra.Services;
-
-namespace Penumbra.Api.Api;
-
-public class ModSettingsApi : IPenumbraApiModSettings, IApiService, IDisposable
-{
- private readonly CollectionResolver _collectionResolver;
- private readonly ModManager _modManager;
- private readonly CollectionManager _collectionManager;
- private readonly CollectionEditor _collectionEditor;
- private readonly CommunicatorService _communicator;
-
- public ModSettingsApi(CollectionResolver collectionResolver,
- ModManager modManager,
- CollectionManager collectionManager,
- CollectionEditor collectionEditor,
- CommunicatorService communicator)
- {
- _collectionResolver = collectionResolver;
- _modManager = modManager;
- _collectionManager = collectionManager;
- _collectionEditor = collectionEditor;
- _communicator = communicator;
- _communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ApiModSettings);
- _communicator.ModSettingChanged.Subscribe(OnModSettingChange, Communication.ModSettingChanged.Priority.Api);
- _communicator.ModOptionChanged.Subscribe(OnModOptionEdited, ModOptionChanged.Priority.Api);
- _communicator.ModFileChanged.Subscribe(OnModFileChanged, ModFileChanged.Priority.Api);
- }
-
- public void Dispose()
- {
- _communicator.ModPathChanged.Unsubscribe(OnModPathChange);
- _communicator.ModSettingChanged.Unsubscribe(OnModSettingChange);
- _communicator.ModOptionChanged.Unsubscribe(OnModOptionEdited);
- _communicator.ModFileChanged.Unsubscribe(OnModFileChanged);
- }
-
- public event ModSettingChangedDelegate? ModSettingChanged;
-
- public AvailableModSettings? GetAvailableModSettings(string modDirectory, string modName)
- {
- if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
- return null;
-
- var dict = new Dictionary(mod.Groups.Count);
- foreach (var g in mod.Groups)
- dict.Add(g.Name, (g.Options.Select(o => o.Name).ToArray(), (int)g.Type));
- return new AvailableModSettings(dict);
- }
-
- public (PenumbraApiEc, (bool, int, Dictionary>, bool)?) GetCurrentModSettings(Guid collectionId, string modDirectory,
- string modName, bool ignoreInheritance)
- {
- var ret = GetCurrentModSettingsWithTemp(collectionId, modDirectory, modName, ignoreInheritance, true, 0);
- if (ret.Item2 is null)
- return (ret.Item1, null);
-
- return (ret.Item1, (ret.Item2.Value.Item1, ret.Item2.Value.Item2, ret.Item2.Value.Item3, ret.Item2.Value.Item4));
- }
-
- public PenumbraApiEc GetSettingsInAllCollections(string modDirectory, string modName,
- out Dictionary>, bool, bool)> settings,
- bool ignoreTemporaryCollections = false)
- {
- settings = [];
- if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
- return PenumbraApiEc.ModMissing;
-
- var collections = ignoreTemporaryCollections
- ? _collectionManager.Storage.Where(c => c != ModCollection.Empty)
- : _collectionManager.Storage.Where(c => c != ModCollection.Empty).Concat(_collectionManager.Temp.Values);
- settings = [];
- foreach (var collection in collections)
- {
- if (GetCurrentSettings(collection, mod, false, false, 0) is { } s)
- settings.Add(collection.Identity.Id, s);
- }
-
- return PenumbraApiEc.Success;
- }
-
- public (PenumbraApiEc, (bool, int, Dictionary>, bool, bool)?) GetCurrentModSettingsWithTemp(Guid collectionId,
- string modDirectory, string modName, bool ignoreInheritance, bool ignoreTemporary, int key)
- {
- if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
- return (PenumbraApiEc.ModMissing, null);
-
- if (!_collectionManager.Storage.ById(collectionId, out var collection))
- return (PenumbraApiEc.CollectionMissing, null);
-
- if (collection.Identity.Id == Guid.Empty)
- return (PenumbraApiEc.Success, null);
-
- if (GetCurrentSettings(collection, mod, ignoreInheritance, ignoreTemporary, key) is { } settings)
- return (PenumbraApiEc.Success, settings);
-
- return (PenumbraApiEc.Success, null);
- }
-
- public (PenumbraApiEc, Dictionary>, bool, bool)>?) GetAllModSettings(Guid collectionId,
- bool ignoreInheritance, bool ignoreTemporary, int key)
- {
- if (!_collectionManager.Storage.ById(collectionId, out var collection))
- return (PenumbraApiEc.CollectionMissing, null);
-
- if (collection.Identity.Id == Guid.Empty)
- return (PenumbraApiEc.Success, []);
-
- var ret = new Dictionary>, bool, bool)>(_modManager.Count);
- foreach (var mod in _modManager)
- {
- if (GetCurrentSettings(collection, mod, ignoreInheritance, ignoreTemporary, key) is { } settings)
- ret[mod.Identifier] = settings;
- }
-
- return (PenumbraApiEc.Success, ret);
- }
-
- public PenumbraApiEc TryInheritMod(Guid collectionId, string modDirectory, string modName, bool inherit)
- {
- var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "Inherit",
- inherit.ToString());
-
- if (collectionId == Guid.Empty)
- return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
-
- if (!_collectionManager.Storage.ById(collectionId, out var collection))
- return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
-
- if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
- return ApiHelpers.Return(PenumbraApiEc.ModMissing, args);
-
- var ret = _collectionEditor.SetModInheritance(collection, mod, inherit)
- ? PenumbraApiEc.Success
- : PenumbraApiEc.NothingChanged;
- return ApiHelpers.Return(ret, args);
- }
-
- public PenumbraApiEc TrySetMod(Guid collectionId, string modDirectory, string modName, bool enabled)
- {
- var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "Enabled", enabled);
- if (!_collectionManager.Storage.ById(collectionId, out var collection))
- return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
-
- if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
- return ApiHelpers.Return(PenumbraApiEc.ModMissing, args);
-
- var ret = _collectionEditor.SetModState(collection, mod, enabled)
- ? PenumbraApiEc.Success
- : PenumbraApiEc.NothingChanged;
- return ApiHelpers.Return(ret, args);
- }
-
- public PenumbraApiEc TrySetModPriority(Guid collectionId, string modDirectory, string modName, int priority)
- {
- var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "Priority", priority);
-
- if (!_collectionManager.Storage.ById(collectionId, out var collection))
- return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
-
- if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
- return ApiHelpers.Return(PenumbraApiEc.ModMissing, args);
-
- var ret = _collectionEditor.SetModPriority(collection, mod, new ModPriority(priority))
- ? PenumbraApiEc.Success
- : PenumbraApiEc.NothingChanged;
- return ApiHelpers.Return(ret, args);
- }
-
- public PenumbraApiEc TrySetModSetting(Guid collectionId, string modDirectory, string modName, string optionGroupName, string optionName)
- {
- var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "OptionGroupName",
- optionGroupName, "OptionName", optionName);
-
- if (!_collectionManager.Storage.ById(collectionId, out var collection))
- return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
-
- if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
- return ApiHelpers.Return(PenumbraApiEc.ModMissing, args);
-
- var groupIdx = mod.Groups.IndexOf(g => g.Name == optionGroupName);
- if (groupIdx < 0)
- return ApiHelpers.Return(PenumbraApiEc.OptionGroupMissing, args);
-
- var optionIdx = mod.Groups[groupIdx].Options.IndexOf(o => o.Name == optionName);
- if (optionIdx < 0)
- return ApiHelpers.Return(PenumbraApiEc.OptionMissing, args);
-
- var setting = mod.Groups[groupIdx].Behaviour switch
- {
- GroupDrawBehaviour.MultiSelection => Setting.Multi(optionIdx),
- GroupDrawBehaviour.SingleSelection => Setting.Single(optionIdx),
- _ => Setting.Zero,
- };
- var ret = _collectionEditor.SetModSetting(collection, mod, groupIdx, setting)
- ? PenumbraApiEc.Success
- : PenumbraApiEc.NothingChanged;
- return ApiHelpers.Return(ret, args);
- }
-
- public PenumbraApiEc TrySetModSettings(Guid collectionId, string modDirectory, string modName, string optionGroupName,
- IReadOnlyList optionNames)
- {
- var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "OptionGroupName",
- optionGroupName, "#optionNames", optionNames.Count);
-
- if (!_collectionManager.Storage.ById(collectionId, out var collection))
- return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
-
- if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
- return ApiHelpers.Return(PenumbraApiEc.ModMissing, args);
-
- var settingSuccess = ConvertModSetting(mod, optionGroupName, optionNames, out var groupIdx, out var setting);
- if (settingSuccess is not PenumbraApiEc.Success)
- return ApiHelpers.Return(settingSuccess, args);
-
- var ret = _collectionEditor.SetModSetting(collection, mod, groupIdx, setting)
- ? PenumbraApiEc.Success
- : PenumbraApiEc.NothingChanged;
- return ApiHelpers.Return(ret, args);
- }
-
- public PenumbraApiEc CopyModSettings(Guid? collectionId, string modDirectoryFrom, string modDirectoryTo)
- {
- var args = ApiHelpers.Args("CollectionId", collectionId.HasValue ? collectionId.Value.ToString() : "NULL",
- "From", modDirectoryFrom, "To", modDirectoryTo);
- var sourceMod = _modManager.FirstOrDefault(m => string.Equals(m.ModPath.Name, modDirectoryFrom, StringComparison.OrdinalIgnoreCase));
- var targetMod = _modManager.FirstOrDefault(m => string.Equals(m.ModPath.Name, modDirectoryTo, StringComparison.OrdinalIgnoreCase));
- if (collectionId == null)
- foreach (var collection in _collectionManager.Storage)
- _collectionEditor.CopyModSettings(collection, sourceMod, modDirectoryFrom, targetMod, modDirectoryTo);
- else if (_collectionManager.Storage.ById(collectionId.Value, out var collection))
- _collectionEditor.CopyModSettings(collection, sourceMod, modDirectoryFrom, targetMod, modDirectoryTo);
- else
- return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
-
- return ApiHelpers.Return(PenumbraApiEc.Success, args);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
- private (bool, int, Dictionary>, bool, bool)? GetCurrentSettings(ModCollection collection, Mod mod,
- bool ignoreInheritance, bool ignoreTemporary, int key)
- {
- var settings = collection.Settings.Settings[mod.Index];
- if (!ignoreTemporary && settings.TempSettings is { } tempSettings && (tempSettings.Lock <= 0 || tempSettings.Lock == key))
- {
- if (!tempSettings.ForceInherit)
- return (tempSettings.Enabled, tempSettings.Priority.Value, tempSettings.ConvertToShareable(mod).Settings,
- false, true);
- if (!ignoreInheritance && collection.GetActualSettings(mod.Index).Settings is { } actualSettingsTemp)
- return (actualSettingsTemp.Enabled, actualSettingsTemp.Priority.Value,
- actualSettingsTemp.ConvertToShareable(mod).Settings, true, true);
- }
-
- if (settings.Settings is { } ownSettings)
- return (ownSettings.Enabled, ownSettings.Priority.Value, ownSettings.ConvertToShareable(mod).Settings, false,
- false);
- if (!ignoreInheritance && collection.GetInheritedSettings(mod.Index).Settings is { } actualSettings)
- return (actualSettings.Enabled, actualSettings.Priority.Value,
- actualSettings.ConvertToShareable(mod).Settings, true, false);
-
- return null;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
- private void TriggerSettingEdited(Mod mod)
- {
- var collection = _collectionResolver.PlayerCollection();
- var (settings, parent) = collection.GetActualSettings(mod.Index);
- if (settings is { Enabled: true })
- ModSettingChanged?.Invoke(ModSettingChange.Edited, collection.Identity.Id, mod.Identifier, parent != collection);
- }
-
- private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? _1, DirectoryInfo? _2)
- {
- if (type == ModPathChangeType.Reloaded)
- TriggerSettingEdited(mod);
- }
-
- private void OnModSettingChange(ModCollection collection, ModSettingChange type, Mod? mod, Setting _1, int _2, bool inherited)
- => ModSettingChanged?.Invoke(type, collection.Identity.Id, mod?.ModPath.Name ?? string.Empty, inherited);
-
- private void OnModOptionEdited(ModOptionChangeType type, Mod mod, IModGroup? group, IModOption? option, IModDataContainer? container,
- int moveIndex)
- {
- switch (type)
- {
- case ModOptionChangeType.GroupDeleted:
- case ModOptionChangeType.GroupMoved:
- case ModOptionChangeType.GroupTypeChanged:
- case ModOptionChangeType.PriorityChanged:
- case ModOptionChangeType.OptionDeleted:
- case ModOptionChangeType.OptionMoved:
- case ModOptionChangeType.OptionFilesChanged:
- case ModOptionChangeType.OptionFilesAdded:
- case ModOptionChangeType.OptionSwapsChanged:
- case ModOptionChangeType.OptionMetaChanged:
- TriggerSettingEdited(mod);
- break;
- }
- }
-
- private void OnModFileChanged(Mod mod, FileRegistry file)
- {
- if (file.CurrentUsage == 0)
- return;
-
- TriggerSettingEdited(mod);
- }
-
- public static PenumbraApiEc ConvertModSetting(Mod mod, string groupName, IReadOnlyList optionNames, out int groupIndex,
- out Setting setting)
- {
- groupIndex = mod.Groups.IndexOf(g => g.Name.Equals(groupName, StringComparison.OrdinalIgnoreCase));
- setting = Setting.Zero;
- if (groupIndex < 0)
- return PenumbraApiEc.OptionGroupMissing;
-
- switch (mod.Groups[groupIndex])
- {
- case { Behaviour: GroupDrawBehaviour.SingleSelection } single:
- {
- var optionIdx = optionNames.Count == 0 ? -1 : single.Options.IndexOf(o => o.Name == optionNames[^1]);
- if (optionIdx < 0)
- return PenumbraApiEc.OptionMissing;
-
- setting = Setting.Single(optionIdx);
- break;
- }
- case { Behaviour: GroupDrawBehaviour.MultiSelection } multi:
- {
- foreach (var name in optionNames)
- {
- var optionIdx = multi.Options.IndexOf(o => o.Name == name);
- if (optionIdx < 0)
- return PenumbraApiEc.OptionMissing;
-
- setting |= Setting.Multi(optionIdx);
- }
-
- break;
- }
- }
-
- return PenumbraApiEc.Success;
- }
-}
diff --git a/Penumbra/Api/Api/ModsApi.cs b/Penumbra/Api/Api/ModsApi.cs
deleted file mode 100644
index 1f4f1cf4..00000000
--- a/Penumbra/Api/Api/ModsApi.cs
+++ /dev/null
@@ -1,165 +0,0 @@
-using Newtonsoft.Json.Linq;
-using OtterGui.Compression;
-using OtterGui.Services;
-using Penumbra.Api.Enums;
-using Penumbra.Communication;
-using Penumbra.Mods;
-using Penumbra.Mods.Manager;
-using Penumbra.Services;
-
-namespace Penumbra.Api.Api;
-
-public class ModsApi : IPenumbraApiMods, IApiService, IDisposable
-{
- private readonly CommunicatorService _communicator;
- private readonly ModManager _modManager;
- private readonly ModImportManager _modImportManager;
- private readonly Configuration _config;
- private readonly ModFileSystem _modFileSystem;
- private readonly MigrationManager _migrationManager;
-
- public ModsApi(ModManager modManager, ModImportManager modImportManager, Configuration config, ModFileSystem modFileSystem,
- CommunicatorService communicator, MigrationManager migrationManager)
- {
- _modManager = modManager;
- _modImportManager = modImportManager;
- _config = config;
- _modFileSystem = modFileSystem;
- _communicator = communicator;
- _migrationManager = migrationManager;
- _communicator.ModPathChanged.Subscribe(OnModPathChanged, ModPathChanged.Priority.ApiMods);
- }
-
- private void OnModPathChanged(ModPathChangeType type, Mod mod, DirectoryInfo? oldDirectory, DirectoryInfo? newDirectory)
- {
- switch (type)
- {
- case ModPathChangeType.Deleted when oldDirectory != null: ModDeleted?.Invoke(oldDirectory.Name); break;
- case ModPathChangeType.Added when newDirectory != null: ModAdded?.Invoke(newDirectory.Name); break;
- case ModPathChangeType.Moved when newDirectory != null && oldDirectory != null:
- ModMoved?.Invoke(oldDirectory.Name, newDirectory.Name);
- break;
- }
- }
-
- public void Dispose()
- {
- _communicator.ModPathChanged.Unsubscribe(OnModPathChanged);
- }
-
- public Dictionary GetModList()
- => _modManager.ToDictionary(m => m.ModPath.Name, m => m.Name.Text);
-
- public PenumbraApiEc InstallMod(string modFilePackagePath)
- {
- if (!File.Exists(modFilePackagePath))
- return ApiHelpers.Return(PenumbraApiEc.FileMissing, ApiHelpers.Args("ModFilePackagePath", modFilePackagePath));
-
- _modImportManager.AddUnpack(modFilePackagePath);
- return ApiHelpers.Return(PenumbraApiEc.Success, ApiHelpers.Args("ModFilePackagePath", modFilePackagePath));
- }
-
- public PenumbraApiEc ReloadMod(string modDirectory, string modName)
- {
- if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
- return ApiHelpers.Return(PenumbraApiEc.ModMissing, ApiHelpers.Args("ModDirectory", modDirectory, "ModName", modName));
-
- _modManager.ReloadMod(mod);
- return ApiHelpers.Return(PenumbraApiEc.Success, ApiHelpers.Args("ModDirectory", modDirectory, "ModName", modName));
- }
-
- public PenumbraApiEc AddMod(string modDirectory)
- {
- var args = ApiHelpers.Args("ModDirectory", modDirectory);
-
- var dir = new DirectoryInfo(Path.Join(_modManager.BasePath.FullName, Path.GetFileName(modDirectory)));
- if (!dir.Exists)
- return ApiHelpers.Return(PenumbraApiEc.FileMissing, args);
-
- if (dir.Parent == null
- || Path.TrimEndingDirectorySeparator(Path.GetFullPath(_modManager.BasePath.FullName))
- != Path.TrimEndingDirectorySeparator(Path.GetFullPath(dir.Parent.FullName)))
- return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
-
- _modManager.AddMod(dir, true);
- if (_config.MigrateImportedModelsToV6)
- {
- _migrationManager.MigrateMdlDirectory(dir.FullName, false);
- _migrationManager.Await();
- }
-
- if (_config.UseFileSystemCompression)
- new FileCompactor(Penumbra.Log).StartMassCompact(dir.EnumerateFiles("*.*", SearchOption.AllDirectories),
- CompressionAlgorithm.Xpress8K, false);
-
- return ApiHelpers.Return(PenumbraApiEc.Success, args);
- }
-
- public PenumbraApiEc DeleteMod(string modDirectory, string modName)
- {
- if (!_modManager.TryGetMod(modDirectory, modName, out var mod))
- return ApiHelpers.Return(PenumbraApiEc.NothingChanged, ApiHelpers.Args("ModDirectory", modDirectory, "ModName", modName));
-
- _modManager.DeleteMod(mod);
- return ApiHelpers.Return(PenumbraApiEc.Success, ApiHelpers.Args("ModDirectory", modDirectory, "ModName", modName));
- }
-
- public event Action? ModDeleted;
- public event Action? ModAdded;
- public event Action? ModMoved;
-
- public event Action? CreatingPcp
- {
- add => _communicator.PcpCreation.Subscribe(value!, PcpCreation.Priority.ModsApi);
- remove => _communicator.PcpCreation.Unsubscribe(value!);
- }
-
- public event Action? ParsingPcp
- {
- add => _communicator.PcpParsing.Subscribe(value!, PcpParsing.Priority.ModsApi);
- remove => _communicator.PcpParsing.Unsubscribe(value!);
- }
-
- public (PenumbraApiEc, string, bool, bool) GetModPath(string modDirectory, string modName)
- {
- if (!_modManager.TryGetMod(modDirectory, modName, out var mod)
- || !_modFileSystem.TryGetValue(mod, out var leaf))
- return (PenumbraApiEc.ModMissing, string.Empty, false, false);
-
- var fullPath = leaf.FullName();
- var isDefault = ModFileSystem.ModHasDefaultPath(mod, fullPath);
- var isNameDefault = isDefault || ModFileSystem.ModHasDefaultPath(mod, leaf.Name);
- return (PenumbraApiEc.Success, fullPath, !isDefault, !isNameDefault);
- }
-
- public PenumbraApiEc SetModPath(string modDirectory, string modName, string newPath)
- {
- if (newPath.Length == 0)
- return PenumbraApiEc.InvalidArgument;
-
- if (!_modManager.TryGetMod(modDirectory, modName, out var mod)
- || !_modFileSystem.TryGetValue(mod, out var leaf))
- return PenumbraApiEc.ModMissing;
-
- try
- {
- _modFileSystem.RenameAndMove(leaf, newPath);
- return PenumbraApiEc.Success;
- }
- catch
- {
- return PenumbraApiEc.PathRenameFailed;
- }
- }
-
- public Dictionary GetChangedItems(string modDirectory, string modName)
- => _modManager.TryGetMod(modDirectory, modName, out var mod)
- ? mod.ChangedItems.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToInternalObject())
- : [];
-
- public IReadOnlyDictionary> GetChangedItemAdapterDictionary()
- => new ModChangedItemAdapter(new WeakReference(_modManager));
-
- public IReadOnlyList<(string ModDirectory, IReadOnlyDictionary ChangedItems)> GetChangedItemAdapterList()
- => new ModChangedItemAdapter(new WeakReference(_modManager));
-}
diff --git a/Penumbra/Api/Api/PenumbraApi.cs b/Penumbra/Api/Api/PenumbraApi.cs
deleted file mode 100644
index c4026c72..00000000
--- a/Penumbra/Api/Api/PenumbraApi.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using OtterGui.Services;
-
-namespace Penumbra.Api.Api;
-
-public class PenumbraApi(
- CollectionApi collection,
- EditingApi editing,
- GameStateApi gameState,
- MetaApi meta,
- ModsApi mods,
- ModSettingsApi modSettings,
- PluginStateApi pluginState,
- RedrawApi redraw,
- ResolveApi resolve,
- ResourceTreeApi resourceTree,
- TemporaryApi temporary,
- UiApi ui) : IDisposable, IApiService, IPenumbraApi
-{
- public const int BreakingVersion = 5;
- public const int FeatureVersion = 13;
-
- public void Dispose()
- {
- Valid = false;
- }
-
- public (int Breaking, int Feature) ApiVersion
- => (BreakingVersion, FeatureVersion);
-
- public bool Valid { get; private set; } = true;
- public IPenumbraApiCollection Collection { get; } = collection;
- public IPenumbraApiEditing Editing { get; } = editing;
- public IPenumbraApiGameState GameState { get; } = gameState;
- public IPenumbraApiMeta Meta { get; } = meta;
- public IPenumbraApiMods Mods { get; } = mods;
- public IPenumbraApiModSettings ModSettings { get; } = modSettings;
- public IPenumbraApiPluginState PluginState { get; } = pluginState;
- public IPenumbraApiRedraw Redraw { get; } = redraw;
- public IPenumbraApiResolve Resolve { get; } = resolve;
- public IPenumbraApiResourceTree ResourceTree { get; } = resourceTree;
- public IPenumbraApiTemporary Temporary { get; } = temporary;
- public IPenumbraApiUi Ui { get; } = ui;
-}
diff --git a/Penumbra/Api/Api/PluginStateApi.cs b/Penumbra/Api/Api/PluginStateApi.cs
deleted file mode 100644
index f74553d1..00000000
--- a/Penumbra/Api/Api/PluginStateApi.cs
+++ /dev/null
@@ -1,38 +0,0 @@
-using System.Collections.Frozen;
-using Newtonsoft.Json;
-using OtterGui.Services;
-using Penumbra.Communication;
-using Penumbra.Mods;
-using Penumbra.Services;
-
-namespace Penumbra.Api.Api;
-
-public class PluginStateApi(Configuration config, CommunicatorService communicator) : IPenumbraApiPluginState, IApiService
-{
- public string GetModDirectory()
- => config.ModDirectory;
-
- public string GetConfiguration()
- => JsonConvert.SerializeObject(config, Formatting.Indented);
-
- public event Action? ModDirectoryChanged
- {
- add => communicator.ModDirectoryChanged.Subscribe(value!, Communication.ModDirectoryChanged.Priority.Api);
- remove => communicator.ModDirectoryChanged.Unsubscribe(value!);
- }
-
- public bool GetEnabledState()
- => config.EnableMods;
-
- public event Action? EnabledChange
- {
- add => communicator.EnabledChanged.Subscribe(value!, EnabledChanged.Priority.Api);
- remove => communicator.EnabledChanged.Unsubscribe(value!);
- }
-
- public FrozenSet SupportedFeatures
- => FeatureChecker.SupportedFeatures.ToFrozenSet();
-
- public string[] CheckSupportedFeatures(IEnumerable requiredFeatures)
- => requiredFeatures.Where(f => !FeatureChecker.Supported(f)).ToArray();
-}
diff --git a/Penumbra/Api/Api/RedrawApi.cs b/Penumbra/Api/Api/RedrawApi.cs
deleted file mode 100644
index 08f1f9df..00000000
--- a/Penumbra/Api/Api/RedrawApi.cs
+++ /dev/null
@@ -1,57 +0,0 @@
-using Dalamud.Game.ClientState.Objects.Types;
-using Dalamud.Plugin.Services;
-using OtterGui.Services;
-using Penumbra.Api.Enums;
-using Penumbra.Collections;
-using Penumbra.Collections.Manager;
-using Penumbra.GameData.Interop;
-using Penumbra.Interop.Services;
-
-namespace Penumbra.Api.Api;
-
-public class RedrawApi(RedrawService redrawService, IFramework framework, CollectionManager collections, ObjectManager objects, ApiHelpers helpers) : IPenumbraApiRedraw, IApiService
-{
- public void RedrawObject(int gameObjectIndex, RedrawType setting)
- {
- framework.RunOnFrameworkThread(() => redrawService.RedrawObject(gameObjectIndex, setting));
- }
-
- public void RedrawObject(string name, RedrawType setting)
- {
- framework.RunOnFrameworkThread(() => redrawService.RedrawObject(name, setting));
- }
-
- public void RedrawObject(IGameObject? gameObject, RedrawType setting)
- {
- framework.RunOnFrameworkThread(() => redrawService.RedrawObject(gameObject, setting));
- }
-
- public void RedrawAll(RedrawType setting)
- {
- framework.RunOnFrameworkThread(() => redrawService.RedrawAll(setting));
- }
-
- public void RedrawCollectionMembers(Guid collectionId, RedrawType setting)
- {
-
- if (!collections.Storage.ById(collectionId, out var collection))
- collection = ModCollection.Empty;
- framework.RunOnFrameworkThread(() =>
- {
- foreach (var actor in objects.Objects)
- {
- helpers.AssociatedCollection(actor.ObjectIndex, out var modCollection);
- if (collection == modCollection)
- {
- redrawService.RedrawObject(actor.ObjectIndex, setting);
- }
- }
- });
- }
-
- public event GameObjectRedrawnDelegate? GameObjectRedrawn
- {
- add => redrawService.GameObjectRedrawn += value;
- remove => redrawService.GameObjectRedrawn -= value;
- }
-}
diff --git a/Penumbra/Api/Api/ResolveApi.cs b/Penumbra/Api/Api/ResolveApi.cs
deleted file mode 100644
index 00a0c86f..00000000
--- a/Penumbra/Api/Api/ResolveApi.cs
+++ /dev/null
@@ -1,135 +0,0 @@
-using Dalamud.Plugin.Services;
-using OtterGui.Services;
-using Penumbra.Api.Enums;
-using Penumbra.Collections;
-using Penumbra.Collections.Manager;
-using Penumbra.Interop.PathResolving;
-using Penumbra.Mods.Manager;
-using Penumbra.String.Classes;
-
-namespace Penumbra.Api.Api;
-
-public class ResolveApi(
- ModManager modManager,
- CollectionManager collectionManager,
- Configuration config,
- CollectionResolver collectionResolver,
- ApiHelpers helpers,
- IFramework framework) : IPenumbraApiResolve, IApiService
-{
- public string ResolveDefaultPath(string gamePath)
- => ResolvePath(gamePath, modManager, collectionManager.Active.Default);
-
- public string ResolveInterfacePath(string gamePath)
- => ResolvePath(gamePath, modManager, collectionManager.Active.Interface);
-
- public string ResolveGameObjectPath(string gamePath, int gameObjectIdx)
- {
- helpers.AssociatedCollection(gameObjectIdx, out var collection);
- return ResolvePath(gamePath, modManager, collection);
- }
-
- public string ResolvePlayerPath(string gamePath)
- => ResolvePath(gamePath, modManager, collectionResolver.PlayerCollection());
-
- public string[] ReverseResolveGameObjectPath(string moddedPath, int gameObjectIdx)
- {
- if (!config.EnableMods)
- return [moddedPath];
-
- helpers.AssociatedCollection(gameObjectIdx, out var collection);
- var ret = collection.ReverseResolvePath(new FullPath(moddedPath));
- return ret.Select(r => r.ToString()).ToArray();
- }
-
- public PenumbraApiEc ResolvePath(Guid collectionId, string gamePath, out string resolvedPath)
- {
- resolvedPath = gamePath;
- if (!collectionManager.Storage.ById(collectionId, out var collection))
- return PenumbraApiEc.CollectionMissing;
-
- if (!collection.HasCache)
- return PenumbraApiEc.CollectionInactive;
-
- resolvedPath = ResolvePath(gamePath, modManager, collection);
- return PenumbraApiEc.Success;
- }
-
- public string[] ReverseResolvePlayerPath(string moddedPath)
- {
- if (!config.EnableMods)
- return [moddedPath];
-
- var ret = collectionResolver.PlayerCollection().ReverseResolvePath(new FullPath(moddedPath));
- return ret.Select(r => r.ToString()).ToArray();
- }
-
- public (string[], string[][]) ResolvePlayerPaths(string[] forward, string[] reverse)
- {
- if (!config.EnableMods)
- return (forward, reverse.Select(p => new[]
- {
- p,
- }).ToArray());
-
- var playerCollection = collectionResolver.PlayerCollection();
- var resolved = forward.Select(p => ResolvePath(p, modManager, playerCollection)).ToArray();
- var reverseResolved = playerCollection.ReverseResolvePaths(reverse);
- return (resolved, reverseResolved.Select(a => a.Select(p => p.ToString()).ToArray()).ToArray());
- }
-
- public PenumbraApiEc ResolvePaths(Guid collectionId, string[] forward, string[] reverse, out string[] resolvedForward,
- out string[][] resolvedReverse)
- {
- resolvedForward = forward;
- resolvedReverse = [];
- if (!config.EnableMods)
- return PenumbraApiEc.Success;
-
- if (!collectionManager.Storage.ById(collectionId, out var collection))
- return PenumbraApiEc.CollectionMissing;
-
- if (!collection.HasCache)
- return PenumbraApiEc.CollectionInactive;
-
- resolvedForward = forward.Select(p => ResolvePath(p, modManager, collection)).ToArray();
- var reverseResolved = collection.ReverseResolvePaths(reverse);
- resolvedReverse = reverseResolved.Select(a => a.Select(p => p.ToString()).ToArray()).ToArray();
- return PenumbraApiEc.Success;
- }
-
- public async Task<(string[], string[][])> ResolvePlayerPathsAsync(string[] forward, string[] reverse)
- {
- if (!config.EnableMods)
- return (forward, reverse.Select(p => new[]
- {
- p,
- }).ToArray());
-
- return await Task.Run(async () =>
- {
- var playerCollection = await framework.RunOnFrameworkThread(collectionResolver.PlayerCollection).ConfigureAwait(false);
- var forwardTask = Task.Run(() =>
- {
- var forwardRet = new string[forward.Length];
- Parallel.For(0, forward.Length, idx => forwardRet[idx] = ResolvePath(forward[idx], modManager, playerCollection));
- return forwardRet;
- }).ConfigureAwait(false);
- var reverseTask = Task.Run(() => playerCollection.ReverseResolvePaths(reverse)).ConfigureAwait(false);
- var reverseResolved = (await reverseTask).Select(a => a.Select(p => p.ToString()).ToArray()).ToArray();
- return (await forwardTask, reverseResolved);
- }).ConfigureAwait(false);
- }
-
- /// Resolve a path given by string for a specific collection.
- [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
- private string ResolvePath(string path, ModManager _, ModCollection collection)
- {
- if (!config.EnableMods)
- return path;
-
- var gamePath = Utf8GamePath.FromString(path, out var p) ? p : Utf8GamePath.Empty;
- var ret = collection.ResolvePath(gamePath);
- return ret?.ToString() ?? path;
- }
-}
diff --git a/Penumbra/Api/Api/ResourceTreeApi.cs b/Penumbra/Api/Api/ResourceTreeApi.cs
deleted file mode 100644
index dcec99bf..00000000
--- a/Penumbra/Api/Api/ResourceTreeApi.cs
+++ /dev/null
@@ -1,63 +0,0 @@
-using Dalamud.Game.ClientState.Objects.Types;
-using Newtonsoft.Json.Linq;
-using OtterGui.Services;
-using Penumbra.Api.Enums;
-using Penumbra.Api.Helpers;
-using Penumbra.GameData.Interop;
-using Penumbra.Interop.ResourceTree;
-
-namespace Penumbra.Api.Api;
-
-public class ResourceTreeApi(ResourceTreeFactory resourceTreeFactory, ObjectManager objects) : IPenumbraApiResourceTree, IApiService
-{
- public Dictionary>?[] GetGameObjectResourcePaths(params ushort[] gameObjects)
- {
- var characters = gameObjects.Select(index => objects.GetDalamudObject((int)index)).OfType();
- var resourceTrees = resourceTreeFactory.FromCharacters(characters, 0);
- var pathDictionaries = ResourceTreeApiHelper.GetResourcePathDictionaries(resourceTrees);
-
- return Array.ConvertAll(gameObjects, obj => pathDictionaries.GetValueOrDefault(obj));
- }
-
- public Dictionary>> GetPlayerResourcePaths()
- {
- var resourceTrees = resourceTreeFactory.FromObjectTable(ResourceTreeFactory.Flags.LocalPlayerRelatedOnly);
- return ResourceTreeApiHelper.GetResourcePathDictionaries(resourceTrees);
- }
-
- public GameResourceDict?[] GetGameObjectResourcesOfType(ResourceType type, bool withUiData,
- params ushort[] gameObjects)
- {
- var characters = gameObjects.Select(index => objects.GetDalamudObject((int)index)).OfType();
- var resourceTrees = resourceTreeFactory.FromCharacters(characters, withUiData ? ResourceTreeFactory.Flags.WithUiData : 0);
- var resDictionaries = ResourceTreeApiHelper.GetResourcesOfType(resourceTrees, type);
-
- return Array.ConvertAll(gameObjects, obj => resDictionaries.GetValueOrDefault(obj));
- }
-
- public Dictionary GetPlayerResourcesOfType(ResourceType type,
- bool withUiData)
- {
- var resourceTrees = resourceTreeFactory.FromObjectTable(ResourceTreeFactory.Flags.LocalPlayerRelatedOnly
- | (withUiData ? ResourceTreeFactory.Flags.WithUiData : 0));
- return ResourceTreeApiHelper.GetResourcesOfType(resourceTrees, type);
- }
-
- public JObject?[] GetGameObjectResourceTrees(bool withUiData, params ushort[] gameObjects)
- {
- var characters = gameObjects.Select(index => objects.GetDalamudObject((int)index)).OfType();
- var resourceTrees = resourceTreeFactory.FromCharacters(characters, withUiData ? ResourceTreeFactory.Flags.WithUiData : 0);
- var resDictionary = ResourceTreeApiHelper.EncapsulateResourceTrees(resourceTrees);
-
- return Array.ConvertAll(gameObjects, obj => resDictionary.GetValueOrDefault(obj));
- }
-
- public Dictionary GetPlayerResourceTrees(bool withUiData)
- {
- var resourceTrees = resourceTreeFactory.FromObjectTable(ResourceTreeFactory.Flags.LocalPlayerRelatedOnly
- | (withUiData ? ResourceTreeFactory.Flags.WithUiData : 0));
- var resDictionary = ResourceTreeApiHelper.EncapsulateResourceTrees(resourceTrees);
-
- return resDictionary;
- }
-}
diff --git a/Penumbra/Api/Api/TemporaryApi.cs b/Penumbra/Api/Api/TemporaryApi.cs
deleted file mode 100644
index 7567acd3..00000000
--- a/Penumbra/Api/Api/TemporaryApi.cs
+++ /dev/null
@@ -1,338 +0,0 @@
-using OtterGui.Log;
-using OtterGui.Services;
-using Penumbra.Api.Enums;
-using Penumbra.Collections;
-using Penumbra.Collections.Manager;
-using Penumbra.GameData.Actors;
-using Penumbra.GameData.Interop;
-using Penumbra.Mods.Manager;
-using Penumbra.Mods.Settings;
-using Penumbra.String.Classes;
-
-namespace Penumbra.Api.Api;
-
-public class TemporaryApi(
- TempCollectionManager tempCollections,
- ObjectManager objects,
- ActorManager actors,
- CollectionManager collectionManager,
- TempModManager tempMods,
- ApiHelpers apiHelpers,
- ModManager modManager) : IPenumbraApiTemporary, IApiService
-{
- public (PenumbraApiEc, Guid) CreateTemporaryCollection(string identity, string name)
- {
- if (!IdentityChecker.Check(identity))
- return (PenumbraApiEc.InvalidCredentials, Guid.Empty);
-
- var collection = tempCollections.CreateTemporaryCollection(name);
- if (collection == Guid.Empty)
- return (PenumbraApiEc.UnknownError, collection);
- return (PenumbraApiEc.Success, collection);
- }
-
- public PenumbraApiEc DeleteTemporaryCollection(Guid collectionId)
- => tempCollections.RemoveTemporaryCollection(collectionId)
- ? PenumbraApiEc.Success
- : PenumbraApiEc.CollectionMissing;
-
- public PenumbraApiEc AssignTemporaryCollection(Guid collectionId, int actorIndex, bool forceAssignment)
- {
- var args = ApiHelpers.Args("CollectionId", collectionId, "ActorIndex", actorIndex, "Forced", forceAssignment);
- if (actorIndex < 0 || actorIndex >= objects.TotalCount)
- return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
-
- var identifier = actors.FromObject(objects[actorIndex], out _, false, false, true);
- if (!identifier.IsValid)
- return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
-
- if (!tempCollections.CollectionById(collectionId, out var collection))
- return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
-
- if (forceAssignment)
- {
- if (tempCollections.Collections.ContainsKey(identifier) && !tempCollections.Collections.Delete(identifier))
- return ApiHelpers.Return(PenumbraApiEc.AssignmentDeletionFailed, args);
- }
- else if (tempCollections.Collections.ContainsKey(identifier)
- || collectionManager.Active.Individuals.ContainsKey(identifier))
- {
- return ApiHelpers.Return(PenumbraApiEc.CharacterCollectionExists, args);
- }
-
- var group = tempCollections.Collections.GetGroup(identifier);
- var ret = tempCollections.AddIdentifier(collection, group)
- ? PenumbraApiEc.Success
- : PenumbraApiEc.UnknownError;
- return ApiHelpers.Return(ret, args);
- }
-
- public PenumbraApiEc AddTemporaryModAll(string tag, Dictionary paths, string manipString, int priority)
- {
- var args = ApiHelpers.Args("Tag", tag, "#Paths", paths.Count, "ManipString", manipString, "Priority", priority);
- if (!ConvertPaths(paths, out var p))
- return ApiHelpers.Return(PenumbraApiEc.InvalidGamePath, args);
-
- if (!MetaApi.ConvertManips(manipString, out var m, out _))
- return ApiHelpers.Return(PenumbraApiEc.InvalidManipulation, args);
-
- var ret = tempMods.Register(tag, null, p, m, new ModPriority(priority)) switch
- {
- RedirectResult.Success => PenumbraApiEc.Success,
- _ => PenumbraApiEc.UnknownError,
- };
- return ApiHelpers.Return(ret, args);
- }
-
- public PenumbraApiEc AddTemporaryMod(string tag, Guid collectionId, Dictionary paths, string manipString, int priority)
- {
- var args = ApiHelpers.Args("Tag", tag, "CollectionId", collectionId, "#Paths", paths.Count, "ManipString",
- manipString, "Priority", priority);
-
- if (collectionId == Guid.Empty)
- return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
-
- if (!tempCollections.CollectionById(collectionId, out var collection)
- && !collectionManager.Storage.ById(collectionId, out collection))
- return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
-
- if (!ConvertPaths(paths, out var p))
- return ApiHelpers.Return(PenumbraApiEc.InvalidGamePath, args);
-
- if (!MetaApi.ConvertManips(manipString, out var m, out _))
- return ApiHelpers.Return(PenumbraApiEc.InvalidManipulation, args);
-
- var ret = tempMods.Register(tag, collection, p, m, new ModPriority(priority)) switch
- {
- RedirectResult.Success => PenumbraApiEc.Success,
- _ => PenumbraApiEc.UnknownError,
- };
- return ApiHelpers.Return(ret, args);
- }
-
- public PenumbraApiEc RemoveTemporaryModAll(string tag, int priority)
- {
- var ret = tempMods.Unregister(tag, null, new ModPriority(priority)) switch
- {
- RedirectResult.Success => PenumbraApiEc.Success,
- RedirectResult.NotRegistered => PenumbraApiEc.NothingChanged,
- _ => PenumbraApiEc.UnknownError,
- };
- return ApiHelpers.Return(ret, ApiHelpers.Args("Tag", tag, "Priority", priority));
- }
-
- public PenumbraApiEc RemoveTemporaryMod(string tag, Guid collectionId, int priority)
- {
- var args = ApiHelpers.Args("Tag", tag, "CollectionId", collectionId, "Priority", priority);
-
- if (!tempCollections.CollectionById(collectionId, out var collection)
- && !collectionManager.Storage.ById(collectionId, out collection))
- return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
-
- var ret = tempMods.Unregister(tag, collection, new ModPriority(priority)) switch
- {
- RedirectResult.Success => PenumbraApiEc.Success,
- RedirectResult.NotRegistered => PenumbraApiEc.NothingChanged,
- _ => PenumbraApiEc.UnknownError,
- };
- return ApiHelpers.Return(ret, args);
- }
-
- public (PenumbraApiEc, (bool, bool, int, Dictionary>)?, string) QueryTemporaryModSettings(Guid collectionId,
- string modDirectory, string modName, int key)
- {
- var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName);
- if (!collectionManager.Storage.ById(collectionId, out var collection))
- return (ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args), null, string.Empty);
-
- return QueryTemporaryModSettings(args, collection, modDirectory, modName, key);
- }
-
- public (PenumbraApiEc ErrorCode, (bool, bool, int, Dictionary>)? Settings, string Source)
- QueryTemporaryModSettingsPlayer(int objectIndex,
- string modDirectory, string modName, int key)
- {
- var args = ApiHelpers.Args("ObjectIndex", objectIndex, "ModDirectory", modDirectory, "ModName", modName);
- if (!apiHelpers.AssociatedCollection(objectIndex, out var collection))
- return (ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args), null, string.Empty);
-
- return QueryTemporaryModSettings(args, collection, modDirectory, modName, key);
- }
-
- private (PenumbraApiEc ErrorCode, (bool, bool, int, Dictionary>)? Settings, string Source) QueryTemporaryModSettings(
- in LazyString args, ModCollection collection, string modDirectory, string modName, int key)
- {
- if (!modManager.TryGetMod(modDirectory, modName, out var mod))
- return (ApiHelpers.Return(PenumbraApiEc.ModMissing, args), null, string.Empty);
-
- if (collection.Identity.Index <= 0)
- return (ApiHelpers.Return(PenumbraApiEc.Success, args), null, string.Empty);
-
- var settings = collection.GetTempSettings(mod.Index);
- if (settings == null)
- return (ApiHelpers.Return(PenumbraApiEc.Success, args), null, string.Empty);
-
- if (settings.Lock > 0 && settings.Lock != key)
- return (ApiHelpers.Return(PenumbraApiEc.TemporarySettingDisallowed, args), null, settings.Source);
-
- return (ApiHelpers.Return(PenumbraApiEc.Success, args),
- (settings.ForceInherit, settings.Enabled, settings.Priority.Value, settings.ConvertToShareable(mod).Settings), settings.Source);
- }
-
-
- public PenumbraApiEc SetTemporaryModSettings(Guid collectionId, string modDirectory, string modName, bool inherit, bool enabled,
- int priority,
- IReadOnlyDictionary> options, string source, int key)
- {
- var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "Inherit", inherit,
- "Enabled", enabled,
- "Priority", priority, "Options", options, "Source", source, "Key", key);
- if (!collectionManager.Storage.ById(collectionId, out var collection))
- return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
-
- return SetTemporaryModSettings(args, collection, modDirectory, modName, inherit, enabled, priority, options, source, key);
- }
-
- public PenumbraApiEc SetTemporaryModSettingsPlayer(int objectIndex, string modDirectory, string modName, bool inherit, bool enabled,
- int priority,
- IReadOnlyDictionary> options, string source, int key)
- {
- var args = ApiHelpers.Args("ObjectIndex", objectIndex, "ModDirectory", modDirectory, "ModName", modName, "Inherit", inherit, "Enabled",
- enabled,
- "Priority", priority, "Options", options, "Source", source, "Key", key);
- if (!apiHelpers.AssociatedCollection(objectIndex, out var collection))
- return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
-
- return SetTemporaryModSettings(args, collection, modDirectory, modName, inherit, enabled, priority, options, source, key);
- }
-
- private PenumbraApiEc SetTemporaryModSettings(in LazyString args, ModCollection collection, string modDirectory, string modName,
- bool inherit, bool enabled, int priority, IReadOnlyDictionary> options, string source, int key)
- {
- if (collection.Identity.Index <= 0)
- return ApiHelpers.Return(PenumbraApiEc.TemporarySettingImpossible, args);
-
- if (!modManager.TryGetMod(modDirectory, modName, out var mod))
- return ApiHelpers.Return(PenumbraApiEc.ModMissing, args);
-
- if (!collectionManager.Editor.CanSetTemporarySettings(collection, mod, key))
- if (collection.GetTempSettings(mod.Index) is { Lock: > 0 } oldSettings && oldSettings.Lock != key)
- return ApiHelpers.Return(PenumbraApiEc.TemporarySettingDisallowed, args);
-
- var newSettings = new TemporaryModSettings()
- {
- ForceInherit = inherit,
- Enabled = enabled,
- Priority = new ModPriority(priority),
- Lock = key,
- Source = source,
- Settings = SettingList.Default(mod),
- };
-
-
- foreach (var (groupName, optionNames) in options)
- {
- var ec = ModSettingsApi.ConvertModSetting(mod, groupName, optionNames, out var groupIdx, out var setting);
- if (ec != PenumbraApiEc.Success)
- return ApiHelpers.Return(ec, args);
-
- newSettings.Settings[groupIdx] = setting;
- }
-
- if (collectionManager.Editor.SetTemporarySettings(collection, mod, newSettings, key))
- return ApiHelpers.Return(PenumbraApiEc.Success, args);
-
- // This should not happen since all error cases had been checked before.
- return ApiHelpers.Return(PenumbraApiEc.UnknownError, args);
- }
-
- public PenumbraApiEc RemoveTemporaryModSettings(Guid collectionId, string modDirectory, string modName, int key)
- {
- var args = ApiHelpers.Args("CollectionId", collectionId, "ModDirectory", modDirectory, "ModName", modName, "Key", key);
- if (!collectionManager.Storage.ById(collectionId, out var collection))
- return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
-
- return RemoveTemporaryModSettings(args, collection, modDirectory, modName, key);
- }
-
- public PenumbraApiEc RemoveTemporaryModSettingsPlayer(int objectIndex, string modDirectory, string modName, int key)
- {
- var args = ApiHelpers.Args("ObjectIndex", objectIndex, "ModDirectory", modDirectory, "ModName", modName, "Key", key);
- if (!apiHelpers.AssociatedCollection(objectIndex, out var collection))
- return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
-
- return RemoveTemporaryModSettings(args, collection, modDirectory, modName, key);
- }
-
- private PenumbraApiEc RemoveTemporaryModSettings(in LazyString args, ModCollection collection, string modDirectory, string modName, int key)
- {
- if (collection.Identity.Index <= 0)
- return ApiHelpers.Return(PenumbraApiEc.NothingChanged, args);
-
- if (!modManager.TryGetMod(modDirectory, modName, out var mod))
- return ApiHelpers.Return(PenumbraApiEc.ModMissing, args);
-
- if (collection.GetTempSettings(mod.Index) is null)
- return ApiHelpers.Return(PenumbraApiEc.NothingChanged, args);
-
- if (!collectionManager.Editor.SetTemporarySettings(collection, mod, null, key))
- return ApiHelpers.Return(PenumbraApiEc.TemporarySettingDisallowed, args);
-
- return ApiHelpers.Return(PenumbraApiEc.Success, args);
- }
-
- public PenumbraApiEc RemoveAllTemporaryModSettings(Guid collectionId, int key)
- {
- var args = ApiHelpers.Args("CollectionId", collectionId, "Key", key);
- if (!collectionManager.Storage.ById(collectionId, out var collection))
- return ApiHelpers.Return(PenumbraApiEc.CollectionMissing, args);
-
- return RemoveAllTemporaryModSettings(args, collection, key);
- }
-
- public PenumbraApiEc RemoveAllTemporaryModSettingsPlayer(int objectIndex, int key)
- {
- var args = ApiHelpers.Args("ObjectIndex", objectIndex, "Key", key);
- if (!apiHelpers.AssociatedCollection(objectIndex, out var collection))
- return ApiHelpers.Return(PenumbraApiEc.InvalidArgument, args);
-
- return RemoveAllTemporaryModSettings(args, collection, key);
- }
-
- private PenumbraApiEc RemoveAllTemporaryModSettings(in LazyString args, ModCollection collection, int key)
- {
- if (collection.Identity.Index <= 0)
- return ApiHelpers.Return(PenumbraApiEc.NothingChanged, args);
-
- var numRemoved = collectionManager.Editor.ClearTemporarySettings(collection, key);
- return ApiHelpers.Return(numRemoved > 0 ? PenumbraApiEc.Success : PenumbraApiEc.NothingChanged, args);
- }
-
-
- ///
- /// Convert a dictionary of strings to a dictionary of game paths to full paths.
- /// Only returns true if all paths can successfully be converted and added.
- ///
- private static bool ConvertPaths(IReadOnlyDictionary redirections,
- [NotNullWhen(true)] out Dictionary? paths)
- {
- paths = new Dictionary(redirections.Count);
- foreach (var (gString, fString) in redirections)
- {
- if (!Utf8GamePath.FromString(gString, out var path))
- {
- paths = null;
- return false;
- }
-
- var fullPath = new FullPath(fString);
- if (!paths.TryAdd(path, fullPath))
- {
- paths = null;
- return false;
- }
- }
-
- return true;
- }
-}
diff --git a/Penumbra/Api/Api/UiApi.cs b/Penumbra/Api/Api/UiApi.cs
deleted file mode 100644
index 6fb116f3..00000000
--- a/Penumbra/Api/Api/UiApi.cs
+++ /dev/null
@@ -1,113 +0,0 @@
-using OtterGui.Services;
-using Penumbra.Api.Enums;
-using Penumbra.Communication;
-using Penumbra.GameData.Data;
-using Penumbra.Mods.Manager;
-using Penumbra.Services;
-using Penumbra.UI;
-using Penumbra.UI.Integration;
-using Penumbra.UI.Tabs;
-
-namespace Penumbra.Api.Api;
-
-public class UiApi : IPenumbraApiUi, IApiService, IDisposable
-{
- private readonly CommunicatorService _communicator;
- private readonly ConfigWindow _configWindow;
- private readonly ModManager _modManager;
- private readonly IntegrationSettingsRegistry _integrationSettings;
-
- public UiApi(CommunicatorService communicator, ConfigWindow configWindow, ModManager modManager, IntegrationSettingsRegistry integrationSettings)
- {
- _communicator = communicator;
- _configWindow = configWindow;
- _modManager = modManager;
- _integrationSettings = integrationSettings;
- _communicator.ChangedItemHover.Subscribe(OnChangedItemHover, ChangedItemHover.Priority.Default);
- _communicator.ChangedItemClick.Subscribe(OnChangedItemClick, ChangedItemClick.Priority.Default);
- }
-
- public void Dispose()
- {
- _communicator.ChangedItemHover.Unsubscribe(OnChangedItemHover);
- _communicator.ChangedItemClick.Unsubscribe(OnChangedItemClick);
- }
-
- public event Action? ChangedItemTooltip;
-
- public event Action? ChangedItemClicked;
-
- public event Action? PreSettingsTabBarDraw
- {
- add => _communicator.PreSettingsTabBarDraw.Subscribe(value!, Communication.PreSettingsTabBarDraw.Priority.Default);
- remove => _communicator.PreSettingsTabBarDraw.Unsubscribe(value!);
- }
-
- public event Action? PreSettingsPanelDraw
- {
- add => _communicator.PreSettingsPanelDraw.Subscribe(value!, Communication.PreSettingsPanelDraw.Priority.Default);
- remove => _communicator.PreSettingsPanelDraw.Unsubscribe(value!);
- }
-
- public event Action? PostEnabledDraw
- {
- add => _communicator.PostEnabledDraw.Subscribe(value!, Communication.PostEnabledDraw.Priority.Default);
- remove => _communicator.PostEnabledDraw.Unsubscribe(value!);
- }
-
- public event Action? PostSettingsPanelDraw
- {
- add => _communicator.PostSettingsPanelDraw.Subscribe(value!, Communication.PostSettingsPanelDraw.Priority.Default);
- remove => _communicator.PostSettingsPanelDraw.Unsubscribe(value!);
- }
-
- public PenumbraApiEc OpenMainWindow(TabType tab, string modDirectory, string modName)
- {
- _configWindow.IsOpen = true;
- if (!Enum.IsDefined(tab))
- return PenumbraApiEc.InvalidArgument;
-
- if (tab == TabType.Mods && (modDirectory.Length > 0 || modName.Length > 0))
- {
- if (_modManager.TryGetMod(modDirectory, modName, out var mod))
- _communicator.SelectTab.Invoke(tab, mod);
- else
- return PenumbraApiEc.ModMissing;
- }
- else if (tab != TabType.None)
- {
- _communicator.SelectTab.Invoke(tab, null);
- }
-
- return PenumbraApiEc.Success;
- }
-
- public void CloseMainWindow()
- => _configWindow.IsOpen = false;
-
- private void OnChangedItemClick(MouseButton button, IIdentifiedObjectData data)
- {
- if (ChangedItemClicked == null)
- return;
-
- var (type, id) = data.ToApiObject();
- ChangedItemClicked.Invoke(button, type, id);
- }
-
- private void OnChangedItemHover(IIdentifiedObjectData data)
- {
- if (ChangedItemTooltip == null)
- return;
-
- var (type, id) = data.ToApiObject();
- ChangedItemTooltip.Invoke(type, id);
- }
-
- public PenumbraApiEc RegisterSettingsSection(Action draw)
- => _integrationSettings.RegisterSection(draw);
-
- public PenumbraApiEc UnregisterSettingsSection(Action draw)
- => _integrationSettings.UnregisterSection(draw)
- ? PenumbraApiEc.Success
- : PenumbraApiEc.NothingChanged;
-}
diff --git a/Penumbra/Api/DalamudSubstitutionProvider.cs b/Penumbra/Api/DalamudSubstitutionProvider.cs
deleted file mode 100644
index e10dc461..00000000
--- a/Penumbra/Api/DalamudSubstitutionProvider.cs
+++ /dev/null
@@ -1,161 +0,0 @@
-using Dalamud.Interface;
-using Dalamud.Plugin.Services;
-using OtterGui.Services;
-using Penumbra.Collections;
-using Penumbra.Collections.Manager;
-using Penumbra.Communication;
-using Penumbra.Mods.Editor;
-using Penumbra.Services;
-using Penumbra.String.Classes;
-
-namespace Penumbra.Api;
-
-public class DalamudSubstitutionProvider : IDisposable, IApiService
-{
- private readonly ITextureSubstitutionProvider _substitution;
- private readonly IUiBuilder _uiBuilder;
- private readonly ActiveCollectionData _activeCollectionData;
- private readonly Configuration _config;
- private readonly CommunicatorService _communicator;
-
- public bool Enabled
- => _config.UseDalamudUiTextureRedirection;
-
- public DalamudSubstitutionProvider(ITextureSubstitutionProvider substitution, ActiveCollectionData activeCollectionData,
- Configuration config, CommunicatorService communicator, IUiBuilder ui)
- {
- _substitution = substitution;
- _uiBuilder = ui;
- _activeCollectionData = activeCollectionData;
- _config = config;
- _communicator = communicator;
- if (Enabled)
- Subscribe();
- }
-
- public void Set(bool value)
- {
- if (value)
- Enable();
- else
- Disable();
- }
-
- public void ResetSubstitutions(IEnumerable paths)
- {
- if (!_uiBuilder.UiPrepared)
- return;
-
- var transformed = paths
- .Where(p => (p.Path.StartsWith("ui/"u8) || p.Path.StartsWith("common/font/"u8)) && p.Path.EndsWith(".tex"u8))
- .Select(p => p.ToString());
- _substitution.InvalidatePaths(transformed);
- }
-
- public void Enable()
- {
- if (Enabled)
- return;
-
- _config.UseDalamudUiTextureRedirection = true;
- _config.Save();
- Subscribe();
- }
-
- public void Disable()
- {
- if (!Enabled)
- return;
-
- Unsubscribe();
- _config.UseDalamudUiTextureRedirection = false;
- _config.Save();
- }
-
- public void Dispose()
- => Unsubscribe();
-
- private void OnCollectionChange(CollectionType type, ModCollection? oldCollection, ModCollection? newCollection, string _)
- {
- if (type is not CollectionType.Interface)
- return;
-
- var enumerable = oldCollection?.ResolvedFiles.Keys ?? Array.Empty().AsEnumerable();
- enumerable = enumerable.Concat(newCollection?.ResolvedFiles.Keys ?? Array.Empty().AsEnumerable());
- ResetSubstitutions(enumerable);
- }
-
- private void OnResolvedFileChange(ModCollection collection, ResolvedFileChanged.Type type, Utf8GamePath key, FullPath _1, FullPath _2,
- IMod? _3)
- {
- if (_activeCollectionData.Interface != collection)
- return;
-
- switch (type)
- {
- case ResolvedFileChanged.Type.Added:
- case ResolvedFileChanged.Type.Removed:
- case ResolvedFileChanged.Type.Replaced:
- ResetSubstitutions([key]);
- break;
- case ResolvedFileChanged.Type.FullRecomputeStart:
- case ResolvedFileChanged.Type.FullRecomputeFinished:
- ResetSubstitutions(collection.ResolvedFiles.Keys);
- break;
- }
- }
-
- private void OnEnabledChange(bool state)
- {
- if (state)
- OnCollectionChange(CollectionType.Interface, null, _activeCollectionData.Interface, string.Empty);
- else
- OnCollectionChange(CollectionType.Interface, _activeCollectionData.Interface, null, string.Empty);
- }
-
- private void Substitute(string path, ref string? replacementPath)
- {
- // Do not replace when not enabled.
- if (!_config.EnableMods)
- return;
-
- // Let other plugins prioritize replacement paths.
- if (replacementPath != null)
- return;
-
- // Only replace interface textures.
- if (!path.StartsWith("ui/") && !path.StartsWith("common/font/"))
- return;
-
- try
- {
- if (!Utf8GamePath.FromString(path, out var utf8Path))
- return;
-
- var resolved = _activeCollectionData.Interface.ResolvePath(utf8Path);
- replacementPath = resolved?.FullName;
- }
- catch
- {
- // ignored
- }
- }
-
- private void Subscribe()
- {
- _substitution.InterceptTexDataLoad += Substitute;
- _communicator.CollectionChange.Subscribe(OnCollectionChange, CollectionChange.Priority.DalamudSubstitutionProvider);
- _communicator.ResolvedFileChanged.Subscribe(OnResolvedFileChange, ResolvedFileChanged.Priority.DalamudSubstitutionProvider);
- _communicator.EnabledChanged.Subscribe(OnEnabledChange, EnabledChanged.Priority.DalamudSubstitutionProvider);
- OnCollectionChange(CollectionType.Interface, null, _activeCollectionData.Interface, string.Empty);
- }
-
- private void Unsubscribe()
- {
- _substitution.InterceptTexDataLoad -= Substitute;
- _communicator.CollectionChange.Unsubscribe(OnCollectionChange);
- _communicator.ResolvedFileChanged.Unsubscribe(OnResolvedFileChange);
- _communicator.EnabledChanged.Unsubscribe(OnEnabledChange);
- OnCollectionChange(CollectionType.Interface, _activeCollectionData.Interface, null, string.Empty);
- }
-}
diff --git a/Penumbra/Api/HttpApi.cs b/Penumbra/Api/HttpApi.cs
index 79348a88..0d9ef997 100644
--- a/Penumbra/Api/HttpApi.cs
+++ b/Penumbra/Api/HttpApi.cs
@@ -1,41 +1,34 @@
-using Dalamud.Plugin.Services;
+using System;
+using System.Threading.Tasks;
using EmbedIO;
using EmbedIO.Routing;
using EmbedIO.WebApi;
-using OtterGui.Services;
-using Penumbra.Api.Api;
using Penumbra.Api.Enums;
-using Penumbra.Mods.Settings;
namespace Penumbra.Api;
-public class HttpApi : IDisposable, IApiService
+public class HttpApi : IDisposable
{
private partial class Controller : WebApiController
{
// @formatter:off
- [Route( HttpVerbs.Get, "/moddirectory" )] public partial string GetModDirectory();
- [Route( HttpVerbs.Get, "/mods" )] public partial object? GetMods();
- [Route( HttpVerbs.Post, "/redraw" )] public partial Task Redraw();
- [Route( HttpVerbs.Post, "/redrawAll" )] public partial Task RedrawAll();
- [Route( HttpVerbs.Post, "/reloadmod" )] public partial Task ReloadMod();
- [Route( HttpVerbs.Post, "/installmod" )] public partial Task InstallMod();
- [Route( HttpVerbs.Post, "/openwindow" )] public partial void OpenWindow();
- [Route( HttpVerbs.Post, "/focusmod" )] public partial Task FocusMod();
- [Route( HttpVerbs.Post, "/setmodsettings")] public partial Task SetModSettings();
+ [Route( HttpVerbs.Get, "/mods" )] public partial object? GetMods();
+ [Route( HttpVerbs.Post, "/redraw" )] public partial Task Redraw();
+ [Route( HttpVerbs.Post, "/redrawAll" )] public partial void RedrawAll();
+ [Route( HttpVerbs.Post, "/reloadmod" )] public partial Task ReloadMod();
+ [Route( HttpVerbs.Post, "/installmod" )] public partial Task InstallMod();
+ [Route( HttpVerbs.Post, "/openwindow" )] public partial void OpenWindow();
// @formatter:on
}
public const string Prefix = "http://localhost:42069/";
private readonly IPenumbraApi _api;
- private readonly IFramework _framework;
private WebServer? _server;
- public HttpApi(Configuration config, IPenumbraApi api, IFramework framework)
+ public HttpApi(Configuration config, IPenumbraApi api)
{
- _api = api;
- _framework = framework;
+ _api = api;
if (config.EnableHttpApi)
CreateWebServer();
}
@@ -51,7 +44,7 @@ public class HttpApi : IDisposable, IApiService
.WithUrlPrefix(Prefix)
.WithMode(HttpListenerMode.EmbedIO))
.WithCors(Prefix)
- .WithWebApi("/api", m => m.WithController(() => new Controller(_api, _framework)));
+ .WithWebApi("/api", m => m.WithController(() => new Controller(_api)));
_server.StateChanged += (_, e) => Penumbra.Log.Information($"WebServer New State - {e.NewState}");
_server.RunAsync();
@@ -66,96 +59,62 @@ public class HttpApi : IDisposable, IApiService
public void Dispose()
=> ShutdownWebServer();
- private partial class Controller(IPenumbraApi api, IFramework framework)
+ private partial class Controller
{
- public partial string GetModDirectory()
- {
- Penumbra.Log.Debug($"[HTTP] {nameof(GetModDirectory)} triggered.");
- return api.PluginState.GetModDirectory();
- }
+ private readonly IPenumbraApi _api;
+
+ public Controller(IPenumbraApi api)
+ => _api = api;
public partial object? GetMods()
{
Penumbra.Log.Debug($"[HTTP] {nameof(GetMods)} triggered.");
- return api.Mods.GetModList();
+ return _api.GetModList();
}
public async partial Task Redraw()
{
- var data = await HttpContext.GetRequestDataAsync().ConfigureAwait(false);
- Penumbra.Log.Debug($"[HTTP] [{Environment.CurrentManagedThreadId}] {nameof(Redraw)} triggered with {data}.");
- await framework.RunOnFrameworkThread(() =>
- {
- if (data.ObjectTableIndex >= 0)
- api.Redraw.RedrawObject(data.ObjectTableIndex, data.Type);
- else
- api.Redraw.RedrawAll(data.Type);
- }).ConfigureAwait(false);
+ var data = await HttpContext.GetRequestDataAsync();
+ Penumbra.Log.Debug($"[HTTP] {nameof(Redraw)} triggered with {data}.");
+ if (data.ObjectTableIndex >= 0)
+ _api.RedrawObject(data.ObjectTableIndex, data.Type);
+ else if (data.Name.Length > 0)
+ _api.RedrawObject(data.Name, data.Type);
+ else
+ _api.RedrawAll(data.Type);
}
- public async partial Task RedrawAll()
+ public partial void RedrawAll()
{
Penumbra.Log.Debug($"[HTTP] {nameof(RedrawAll)} triggered.");
- await framework.RunOnFrameworkThread(() => { api.Redraw.RedrawAll(RedrawType.Redraw); }).ConfigureAwait(false);
+ _api.RedrawAll(RedrawType.Redraw);
}
public async partial Task ReloadMod()
{
- var data = await HttpContext.GetRequestDataAsync().ConfigureAwait(false);
+ var data = await HttpContext.GetRequestDataAsync();
Penumbra.Log.Debug($"[HTTP] {nameof(ReloadMod)} triggered with {data}.");
// Add the mod if it is not already loaded and if the directory name is given.
// AddMod returns Success if the mod is already loaded.
if (data.Path.Length != 0)
- api.Mods.AddMod(data.Path);
+ _api.AddMod(data.Path);
// Reload the mod by path or name, which will also remove no-longer existing mods.
- api.Mods.ReloadMod(data.Path, data.Name);
+ _api.ReloadMod(data.Path, data.Name);
}
public async partial Task InstallMod()
{
- var data = await HttpContext.GetRequestDataAsync().ConfigureAwait(false);
+ var data = await HttpContext.GetRequestDataAsync();
Penumbra.Log.Debug($"[HTTP] {nameof(InstallMod)} triggered with {data}.");
if (data.Path.Length != 0)
- api.Mods.InstallMod(data.Path);
+ _api.InstallMod(data.Path);
}
public partial void OpenWindow()
{
Penumbra.Log.Debug($"[HTTP] {nameof(OpenWindow)} triggered.");
- api.Ui.OpenMainWindow(TabType.Mods, string.Empty, string.Empty);
- }
-
- public async partial Task FocusMod()
- {
- var data = await HttpContext.GetRequestDataAsync().ConfigureAwait(false);
- Penumbra.Log.Debug($"[HTTP] {nameof(FocusMod)} triggered.");
- if (data.Path.Length != 0)
- api.Ui.OpenMainWindow(TabType.Mods, data.Path, data.Name);
- }
-
- public async partial Task SetModSettings()
- {
- var data = await HttpContext.GetRequestDataAsync().ConfigureAwait(false);
- Penumbra.Log.Debug($"[HTTP] {nameof(SetModSettings)} triggered.");
- await framework.RunOnFrameworkThread(() =>
- {
- var collection = data.CollectionId ?? api.Collection.GetCollection(ApiCollectionType.Current)!.Value.Id;
- if (data.Inherit.HasValue)
- {
- api.ModSettings.TryInheritMod(collection, data.ModPath, data.ModName, data.Inherit.Value);
- if (data.Inherit.Value)
- return;
- }
-
- if (data.State.HasValue)
- api.ModSettings.TrySetMod(collection, data.ModPath, data.ModName, data.State.Value);
- if (data.Priority.HasValue)
- api.ModSettings.TrySetModPriority(collection, data.ModPath, data.ModName, data.Priority.Value);
- foreach (var (group, settings) in data.Settings ?? [])
- api.ModSettings.TrySetModSettings(collection, data.ModPath, data.ModName, group, settings);
- }
- ).ConfigureAwait(false);
+ _api.OpenMainWindow(TabType.Mods, string.Empty, string.Empty);
}
private record ModReloadData(string Path, string Name)
@@ -165,13 +124,6 @@ public class HttpApi : IDisposable, IApiService
{ }
}
- private record ModFocusData(string Path, string Name)
- {
- public ModFocusData()
- : this(string.Empty, string.Empty)
- { }
- }
-
private record ModInstallData(string Path)
{
public ModInstallData()
@@ -185,19 +137,5 @@ public class HttpApi : IDisposable, IApiService
: this(string.Empty, RedrawType.Redraw, -1)
{ }
}
-
- private record SetModSettingsData(
- Guid? CollectionId,
- string ModPath,
- string ModName,
- bool? Inherit,
- bool? State,
- int? Priority,
- Dictionary>? Settings)
- {
- public SetModSettingsData()
- : this(null, string.Empty, string.Empty, null, null, null, null)
- {}
- }
}
}
diff --git a/Penumbra/Api/IpcLaunchingProvider.cs b/Penumbra/Api/IpcLaunchingProvider.cs
deleted file mode 100644
index ff851003..00000000
--- a/Penumbra/Api/IpcLaunchingProvider.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-using Dalamud.Plugin;
-using OtterGui.Log;
-using OtterGui.Services;
-using Penumbra.Api.Api;
-using Serilog.Events;
-
-namespace Penumbra.Api;
-
-public sealed class IpcLaunchingProvider : IApiService
-{
- public IpcLaunchingProvider(IDalamudPluginInterface pi, Logger log)
- {
- try
- {
- using var subscriber = log.MainLogger.IsEnabled(LogEventLevel.Debug)
- ? IpcSubscribers.Launching.Subscriber(pi,
- (major, minor) => log.Debug($"[IPC] Invoked Penumbra.Launching IPC with API Version {major}.{minor}."))
- : null;
-
- using var provider = IpcSubscribers.Launching.Provider(pi);
- provider.Invoke(PenumbraApi.BreakingVersion, PenumbraApi.FeatureVersion);
- }
- catch (Exception ex)
- {
- log.Error($"[IPC] Could not invoke Penumbra.Launching IPC:\n{ex}");
- }
- }
-}
diff --git a/Penumbra/Api/IpcProviders.cs b/Penumbra/Api/IpcProviders.cs
deleted file mode 100644
index 7cbe29f6..00000000
--- a/Penumbra/Api/IpcProviders.cs
+++ /dev/null
@@ -1,161 +0,0 @@
-using Dalamud.Plugin;
-using OtterGui.Services;
-using Penumbra.Api.Api;
-using Penumbra.Api.Helpers;
-using Penumbra.Communication;
-using CharacterUtility = Penumbra.Interop.Services.CharacterUtility;
-
-namespace Penumbra.Api;
-
-public sealed class IpcProviders : IDisposable, IApiService
-{
- private readonly List _providers;
-
- private readonly EventProvider _disposedProvider;
- private readonly EventProvider _initializedProvider;
- private readonly CharacterUtility _characterUtility;
-
- public IpcProviders(IDalamudPluginInterface pi, IPenumbraApi api, CharacterUtility characterUtility)
- {
- _characterUtility = characterUtility;
- _disposedProvider = IpcSubscribers.Disposed.Provider(pi);
- _initializedProvider = IpcSubscribers.Initialized.Provider(pi);
- _providers =
- [
- IpcSubscribers.GetCollections.Provider(pi, api.Collection),
- IpcSubscribers.GetCollectionsByIdentifier.Provider(pi, api.Collection),
- IpcSubscribers.GetChangedItemsForCollection.Provider(pi, api.Collection),
- IpcSubscribers.GetCollection.Provider(pi, api.Collection),
- IpcSubscribers.GetCollectionForObject.Provider(pi, api.Collection),
- IpcSubscribers.SetCollection.Provider(pi, api.Collection),
- IpcSubscribers.SetCollectionForObject.Provider(pi, api.Collection),
- IpcSubscribers.CheckCurrentChangedItemFunc.Provider(pi, api.Collection),
-
- IpcSubscribers.ConvertTextureFile.Provider(pi, api.Editing),
- IpcSubscribers.ConvertTextureData.Provider(pi, api.Editing),
-
- IpcSubscribers.GetDrawObjectInfo.Provider(pi, api.GameState),
- IpcSubscribers.GetCutsceneParentIndex.Provider(pi, api.GameState),
- IpcSubscribers.SetCutsceneParentIndex.Provider(pi, api.GameState),
- IpcSubscribers.CreatingCharacterBase.Provider(pi, api.GameState),
- IpcSubscribers.CreatedCharacterBase.Provider(pi, api.GameState),
- IpcSubscribers.GameObjectResourcePathResolved.Provider(pi, api.GameState),
- IpcSubscribers.GetCutsceneParentIndexFunc.Provider(pi, api.GameState),
- IpcSubscribers.GetGameObjectFromDrawObjectFunc.Provider(pi, api.GameState),
-
- IpcSubscribers.GetPlayerMetaManipulations.Provider(pi, api.Meta),
- IpcSubscribers.GetMetaManipulations.Provider(pi, api.Meta),
-
- IpcSubscribers.GetModList.Provider(pi, api.Mods),
- IpcSubscribers.InstallMod.Provider(pi, api.Mods),
- IpcSubscribers.ReloadMod.Provider(pi, api.Mods),
- IpcSubscribers.AddMod.Provider(pi, api.Mods),
- IpcSubscribers.DeleteMod.Provider(pi, api.Mods),
- IpcSubscribers.ModDeleted.Provider(pi, api.Mods),
- IpcSubscribers.ModAdded.Provider(pi, api.Mods),
- IpcSubscribers.ModMoved.Provider(pi, api.Mods),
- IpcSubscribers.CreatingPcp.Provider(pi, api.Mods),
- IpcSubscribers.ParsingPcp.Provider(pi, api.Mods),
- IpcSubscribers.GetModPath.Provider(pi, api.Mods),
- IpcSubscribers.SetModPath.Provider(pi, api.Mods),
- IpcSubscribers.GetChangedItems.Provider(pi, api.Mods),
- IpcSubscribers.GetChangedItemAdapterDictionary.Provider(pi, api.Mods),
- IpcSubscribers.GetChangedItemAdapterList.Provider(pi, api.Mods),
-
- IpcSubscribers.GetAvailableModSettings.Provider(pi, api.ModSettings),
- IpcSubscribers.GetCurrentModSettings.Provider(pi, api.ModSettings),
- IpcSubscribers.GetCurrentModSettingsWithTemp.Provider(pi, api.ModSettings),
- IpcSubscribers.GetAllModSettings.Provider(pi, api.ModSettings),
- IpcSubscribers.GetSettingsInAllCollections.Provider(pi, api.ModSettings),
- IpcSubscribers.TryInheritMod.Provider(pi, api.ModSettings),
- IpcSubscribers.TrySetMod.Provider(pi, api.ModSettings),
- IpcSubscribers.TrySetModPriority.Provider(pi, api.ModSettings),
- IpcSubscribers.TrySetModSetting.Provider(pi, api.ModSettings),
- IpcSubscribers.TrySetModSettings.Provider(pi, api.ModSettings),
- IpcSubscribers.ModSettingChanged.Provider(pi, api.ModSettings),
- IpcSubscribers.CopyModSettings.Provider(pi, api.ModSettings),
-
- IpcSubscribers.ApiVersion.Provider(pi, api),
- new FuncProvider<(int Major, int Minor)>(pi, "Penumbra.ApiVersions", () => api.ApiVersion), // backward compatibility
- new FuncProvider(pi, "Penumbra.ApiVersion", () => api.ApiVersion.Breaking), // backward compatibility
- IpcSubscribers.GetModDirectory.Provider(pi, api.PluginState),
- IpcSubscribers.GetConfiguration.Provider(pi, api.PluginState),
- IpcSubscribers.ModDirectoryChanged.Provider(pi, api.PluginState),
- IpcSubscribers.GetEnabledState.Provider(pi, api.PluginState),
- IpcSubscribers.EnabledChange.Provider(pi, api.PluginState),
- IpcSubscribers.SupportedFeatures.Provider(pi, api.PluginState),
- IpcSubscribers.CheckSupportedFeatures.Provider(pi, api.PluginState),
-
- IpcSubscribers.RedrawObject.Provider(pi, api.Redraw),
- IpcSubscribers.RedrawAll.Provider(pi, api.Redraw),
- IpcSubscribers.GameObjectRedrawn.Provider(pi, api.Redraw),
- IpcSubscribers.RedrawCollectionMembers.Provider(pi, api.Redraw),
-
- IpcSubscribers.ResolveDefaultPath.Provider(pi, api.Resolve),
- IpcSubscribers.ResolveInterfacePath.Provider(pi, api.Resolve),
- IpcSubscribers.ResolveGameObjectPath.Provider(pi, api.Resolve),
- IpcSubscribers.ResolvePlayerPath.Provider(pi, api.Resolve),
- IpcSubscribers.ReverseResolveGameObjectPath.Provider(pi, api.Resolve),
- IpcSubscribers.ReverseResolvePlayerPath.Provider(pi, api.Resolve),
- IpcSubscribers.ResolvePlayerPaths.Provider(pi, api.Resolve),
- IpcSubscribers.ResolvePlayerPathsAsync.Provider(pi, api.Resolve),
- IpcSubscribers.ResolvePath.Provider(pi, api.Resolve),
- IpcSubscribers.ResolvePaths.Provider(pi, api.Resolve),
-
- IpcSubscribers.GetGameObjectResourcePaths.Provider(pi, api.ResourceTree),
- IpcSubscribers.GetPlayerResourcePaths.Provider(pi, api.ResourceTree),
- IpcSubscribers.GetGameObjectResourcesOfType.Provider(pi, api.ResourceTree),
- IpcSubscribers.GetPlayerResourcesOfType.Provider(pi, api.ResourceTree),
- IpcSubscribers.GetGameObjectResourceTrees.Provider(pi, api.ResourceTree),
- IpcSubscribers.GetPlayerResourceTrees.Provider(pi, api.ResourceTree),
-
- IpcSubscribers.CreateTemporaryCollection.Provider(pi, api.Temporary),
- IpcSubscribers.DeleteTemporaryCollection.Provider(pi, api.Temporary),
- IpcSubscribers.AssignTemporaryCollection.Provider(pi, api.Temporary),
- IpcSubscribers.AddTemporaryModAll.Provider(pi, api.Temporary),
- IpcSubscribers.AddTemporaryMod.Provider(pi, api.Temporary),
- IpcSubscribers.RemoveTemporaryModAll.Provider(pi, api.Temporary),
- IpcSubscribers.RemoveTemporaryMod.Provider(pi, api.Temporary),
- IpcSubscribers.SetTemporaryModSettings.Provider(pi, api.Temporary),
- IpcSubscribers.SetTemporaryModSettingsPlayer.Provider(pi, api.Temporary),
- IpcSubscribers.RemoveTemporaryModSettings.Provider(pi, api.Temporary),
- IpcSubscribers.RemoveTemporaryModSettingsPlayer.Provider(pi, api.Temporary),
- IpcSubscribers.RemoveAllTemporaryModSettings.Provider(pi, api.Temporary),
- IpcSubscribers.RemoveAllTemporaryModSettingsPlayer.Provider(pi, api.Temporary),
- IpcSubscribers.QueryTemporaryModSettings.Provider(pi, api.Temporary),
- IpcSubscribers.QueryTemporaryModSettingsPlayer.Provider(pi, api.Temporary),
-
- IpcSubscribers.ChangedItemTooltip.Provider(pi, api.Ui),
- IpcSubscribers.ChangedItemClicked.Provider(pi, api.Ui),
- IpcSubscribers.PreSettingsTabBarDraw.Provider(pi, api.Ui),
- IpcSubscribers.PreSettingsDraw.Provider(pi, api.Ui),
- IpcSubscribers.PostEnabledDraw.Provider(pi, api.Ui),
- IpcSubscribers.PostSettingsDraw.Provider(pi, api.Ui),
- IpcSubscribers.OpenMainWindow.Provider(pi, api.Ui),
- IpcSubscribers.CloseMainWindow.Provider(pi, api.Ui),
- IpcSubscribers.RegisterSettingsSection.Provider(pi, api.Ui),
- IpcSubscribers.UnregisterSettingsSection.Provider(pi, api.Ui),
- ];
- if (_characterUtility.Ready)
- _initializedProvider.Invoke();
- else
- _characterUtility.LoadingFinished.Subscribe(OnCharacterUtilityReady, CharacterUtilityFinished.Priority.IpcProvider);
- }
-
- private void OnCharacterUtilityReady()
- {
- _initializedProvider.Invoke();
- _characterUtility.LoadingFinished.Unsubscribe(OnCharacterUtilityReady);
- }
-
- public void Dispose()
- {
- _characterUtility.LoadingFinished.Unsubscribe(OnCharacterUtilityReady);
- foreach (var provider in _providers)
- provider.Dispose();
- _providers.Clear();
- _initializedProvider.Dispose();
- _disposedProvider.Invoke();
- _disposedProvider.Dispose();
- }
-}
diff --git a/Penumbra/Api/IpcTester.cs b/Penumbra/Api/IpcTester.cs
new file mode 100644
index 00000000..d1124847
--- /dev/null
+++ b/Penumbra/Api/IpcTester.cs
@@ -0,0 +1,1336 @@
+using Dalamud.Interface;
+using Dalamud.Plugin;
+using ImGuiNET;
+using OtterGui;
+using OtterGui.Raii;
+using Penumbra.Mods;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Numerics;
+using Dalamud.Utility;
+using Penumbra.Api.Enums;
+using Penumbra.Api.Helpers;
+using Penumbra.String;
+using Penumbra.String.Classes;
+using Penumbra.Meta.Manipulations;
+using Penumbra.Mods.Manager;
+using Penumbra.Services;
+using Penumbra.UI;
+using Penumbra.Collections.Manager;
+using Penumbra.Util;
+
+namespace Penumbra.Api;
+
+public class IpcTester : IDisposable
+{
+ private readonly PenumbraIpcProviders _ipcProviders;
+ private bool _subscribed = true;
+
+ private readonly PluginState _pluginState;
+ private readonly IpcConfiguration _ipcConfiguration;
+ private readonly Ui _ui;
+ private readonly Redrawing _redrawing;
+ private readonly GameState _gameState;
+ private readonly Resolve _resolve;
+ private readonly Collections _collections;
+ private readonly Meta _meta;
+ private readonly Mods _mods;
+ private readonly ModSettings _modSettings;
+ private readonly Temporary _temporary;
+
+ public IpcTester(Configuration config, DalamudServices dalamud, PenumbraIpcProviders ipcProviders, ModManager modManager,
+ CollectionManager collections, TempModManager tempMods, TempCollectionManager tempCollections, SaveService saveService)
+ {
+ _ipcProviders = ipcProviders;
+ _pluginState = new PluginState(dalamud.PluginInterface);
+ _ipcConfiguration = new IpcConfiguration(dalamud.PluginInterface);
+ _ui = new Ui(dalamud.PluginInterface);
+ _redrawing = new Redrawing(dalamud);
+ _gameState = new GameState(dalamud.PluginInterface);
+ _resolve = new Resolve(dalamud.PluginInterface);
+ _collections = new Collections(dalamud.PluginInterface);
+ _meta = new Meta(dalamud.PluginInterface);
+ _mods = new Mods(dalamud.PluginInterface);
+ _modSettings = new ModSettings(dalamud.PluginInterface);
+ _temporary = new Temporary(dalamud.PluginInterface, modManager, collections, tempMods, tempCollections, saveService, config);
+ UnsubscribeEvents();
+ }
+
+ public void Draw()
+ {
+ try
+ {
+ SubscribeEvents();
+ ImGui.TextUnformatted($"API Version: {_ipcProviders.Api.ApiVersion.Breaking}.{_ipcProviders.Api.ApiVersion.Feature:D4}");
+ _pluginState.Draw();
+ _ipcConfiguration.Draw();
+ _ui.Draw();
+ _redrawing.Draw();
+ _gameState.Draw();
+ _resolve.Draw();
+ _collections.Draw();
+ _meta.Draw();
+ _mods.Draw();
+ _modSettings.Draw();
+ _temporary.Draw();
+ _temporary.DrawCollections();
+ _temporary.DrawMods();
+ }
+ catch (Exception e)
+ {
+ Penumbra.Log.Error($"Error during IPC Tests:\n{e}");
+ }
+ }
+
+ private void SubscribeEvents()
+ {
+ if (!_subscribed)
+ {
+ _pluginState.Initialized.Enable();
+ _pluginState.Disposed.Enable();
+ _pluginState.EnabledChange.Enable();
+ _redrawing.Redrawn.Enable();
+ _ui.PreSettingsDraw.Enable();
+ _ui.PostSettingsDraw.Enable();
+ _modSettings.SettingChanged.Enable();
+ _gameState.CharacterBaseCreating.Enable();
+ _gameState.CharacterBaseCreated.Enable();
+ _ipcConfiguration.ModDirectoryChanged.Enable();
+ _gameState.GameObjectResourcePathResolved.Enable();
+ _mods.DeleteSubscriber.Enable();
+ _mods.AddSubscriber.Enable();
+ _mods.MoveSubscriber.Enable();
+ _subscribed = true;
+ }
+ }
+
+ public void UnsubscribeEvents()
+ {
+ if (_subscribed)
+ {
+ _pluginState.Initialized.Disable();
+ _pluginState.Disposed.Disable();
+ _pluginState.EnabledChange.Disable();
+ _redrawing.Redrawn.Disable();
+ _ui.PreSettingsDraw.Disable();
+ _ui.PostSettingsDraw.Disable();
+ _ui.Tooltip.Disable();
+ _ui.Click.Disable();
+ _modSettings.SettingChanged.Disable();
+ _gameState.CharacterBaseCreating.Disable();
+ _gameState.CharacterBaseCreated.Disable();
+ _ipcConfiguration.ModDirectoryChanged.Disable();
+ _gameState.GameObjectResourcePathResolved.Disable();
+ _mods.DeleteSubscriber.Disable();
+ _mods.AddSubscriber.Disable();
+ _mods.MoveSubscriber.Disable();
+ _subscribed = false;
+ }
+ }
+
+ public void Dispose()
+ {
+ _pluginState.Initialized.Dispose();
+ _pluginState.Disposed.Dispose();
+ _pluginState.EnabledChange.Dispose();
+ _redrawing.Redrawn.Dispose();
+ _ui.PreSettingsDraw.Dispose();
+ _ui.PostSettingsDraw.Dispose();
+ _ui.Tooltip.Dispose();
+ _ui.Click.Dispose();
+ _modSettings.SettingChanged.Dispose();
+ _gameState.CharacterBaseCreating.Dispose();
+ _gameState.CharacterBaseCreated.Dispose();
+ _ipcConfiguration.ModDirectoryChanged.Dispose();
+ _gameState.GameObjectResourcePathResolved.Dispose();
+ _mods.DeleteSubscriber.Dispose();
+ _mods.AddSubscriber.Dispose();
+ _mods.MoveSubscriber.Dispose();
+ _subscribed = false;
+ }
+
+ private static void DrawIntro(string label, string info)
+ {
+ ImGui.TableNextColumn();
+ ImGui.TextUnformatted(label);
+ ImGui.TableNextColumn();
+ ImGui.TextUnformatted(info);
+ ImGui.TableNextColumn();
+ }
+
+
+ private class PluginState
+ {
+ private readonly DalamudPluginInterface _pi;
+ public readonly EventSubscriber Initialized;
+ public readonly EventSubscriber Disposed;
+ public readonly EventSubscriber EnabledChange;
+
+ private readonly List _initializedList = new();
+ private readonly List _disposedList = new();
+
+ private DateTimeOffset _lastEnabledChange = DateTimeOffset.UnixEpoch;
+ private bool? _lastEnabledValue;
+
+ public PluginState(DalamudPluginInterface pi)
+ {
+ _pi = pi;
+ Initialized = Ipc.Initialized.Subscriber(pi, AddInitialized);
+ Disposed = Ipc.Disposed.Subscriber(pi, AddDisposed);
+ EnabledChange = Ipc.EnabledChange.Subscriber(pi, SetLastEnabled);
+ }
+
+ public void Draw()
+ {
+ using var _ = ImRaii.TreeNode("Plugin State");
+ if (!_)
+ return;
+
+ using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
+ if (!table)
+ return;
+
+ void DrawList(string label, string text, List list)
+ {
+ DrawIntro(label, text);
+ if (list.Count == 0)
+ {
+ ImGui.TextUnformatted("Never");
+ }
+ else
+ {
+ ImGui.TextUnformatted(list[^1].LocalDateTime.ToString(CultureInfo.CurrentCulture));
+ if (list.Count > 1 && ImGui.IsItemHovered())
+ ImGui.SetTooltip(string.Join("\n",
+ list.SkipLast(1).Select(t => t.LocalDateTime.ToString(CultureInfo.CurrentCulture))));
+ }
+ }
+
+ DrawList(Ipc.Initialized.Label, "Last Initialized", _initializedList);
+ DrawList(Ipc.Disposed.Label, "Last Disposed", _disposedList);
+ DrawIntro(Ipc.ApiVersions.Label, "Current Version");
+ var (breaking, features) = Ipc.ApiVersions.Subscriber(_pi).Invoke();
+ ImGui.TextUnformatted($"{breaking}.{features:D4}");
+ DrawIntro(Ipc.GetEnabledState.Label, "Current State");
+ ImGui.TextUnformatted($"{Ipc.GetEnabledState.Subscriber(_pi).Invoke()}");
+ DrawIntro(Ipc.EnabledChange.Label, "Last Change");
+ ImGui.TextUnformatted(_lastEnabledValue is { } v ? $"{_lastEnabledChange} (to {v})" : "Never");
+ }
+
+ private void AddInitialized()
+ => _initializedList.Add(DateTimeOffset.UtcNow);
+
+ private void AddDisposed()
+ => _disposedList.Add(DateTimeOffset.UtcNow);
+
+ private void SetLastEnabled(bool val)
+ => (_lastEnabledChange, _lastEnabledValue) = (DateTimeOffset.Now, val);
+ }
+
+ private class IpcConfiguration
+ {
+ private readonly DalamudPluginInterface _pi;
+ public readonly EventSubscriber ModDirectoryChanged;
+
+ private string _currentConfiguration = string.Empty;
+ private string _lastModDirectory = string.Empty;
+ private bool _lastModDirectoryValid;
+ private DateTimeOffset _lastModDirectoryTime = DateTimeOffset.MinValue;
+
+ public IpcConfiguration(DalamudPluginInterface pi)
+ {
+ _pi = pi;
+ ModDirectoryChanged = Ipc.ModDirectoryChanged.Subscriber(pi, UpdateModDirectoryChanged);
+ }
+
+ public void Draw()
+ {
+ using var _ = ImRaii.TreeNode("Configuration");
+ if (!_)
+ return;
+
+ using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
+ if (!table)
+ return;
+
+ DrawIntro(Ipc.GetModDirectory.Label, "Current Mod Directory");
+ ImGui.TextUnformatted(Ipc.GetModDirectory.Subscriber(_pi).Invoke());
+ DrawIntro(Ipc.ModDirectoryChanged.Label, "Last Mod Directory Change");
+ ImGui.TextUnformatted(_lastModDirectoryTime > DateTimeOffset.MinValue
+ ? $"{_lastModDirectory} ({(_lastModDirectoryValid ? "Valid" : "Invalid")}) at {_lastModDirectoryTime}"
+ : "None");
+ DrawIntro(Ipc.GetConfiguration.Label, "Configuration");
+ if (ImGui.Button("Get"))
+ {
+ _currentConfiguration = Ipc.GetConfiguration.Subscriber(_pi).Invoke();
+ ImGui.OpenPopup("Config Popup");
+ }
+
+ DrawConfigPopup();
+ }
+
+ private void DrawConfigPopup()
+ {
+ ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
+ using var popup = ImRaii.Popup("Config Popup");
+ if (popup)
+ {
+ using (var font = ImRaii.PushFont(UiBuilder.MonoFont))
+ {
+ ImGuiUtil.TextWrapped(_currentConfiguration);
+ }
+
+ if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
+ ImGui.CloseCurrentPopup();
+ }
+ }
+
+ private void UpdateModDirectoryChanged(string path, bool valid)
+ => (_lastModDirectory, _lastModDirectoryValid, _lastModDirectoryTime) = (path, valid, DateTimeOffset.Now);
+ }
+
+ private class Ui
+ {
+ private readonly DalamudPluginInterface _pi;
+ public readonly EventSubscriber PreSettingsDraw;
+ public readonly EventSubscriber PostSettingsDraw;
+ public readonly EventSubscriber Tooltip;
+ public readonly EventSubscriber Click;
+
+ private string _lastDrawnMod = string.Empty;
+ private DateTimeOffset _lastDrawnModTime = DateTimeOffset.MinValue;
+ private bool _subscribedToTooltip = false;
+ private bool _subscribedToClick = false;
+ private string _lastClicked = string.Empty;
+ private string _lastHovered = string.Empty;
+ private TabType _selectTab = TabType.None;
+ private string _modName = string.Empty;
+ private PenumbraApiEc _ec = PenumbraApiEc.Success;
+
+ public Ui(DalamudPluginInterface pi)
+ {
+ _pi = pi;
+ PreSettingsDraw = Ipc.PreSettingsDraw.Subscriber(pi, UpdateLastDrawnMod);
+ PostSettingsDraw = Ipc.PostSettingsDraw.Subscriber(pi, UpdateLastDrawnMod);
+ Tooltip = Ipc.ChangedItemTooltip.Subscriber(pi, AddedTooltip);
+ Click = Ipc.ChangedItemClick.Subscriber(pi, AddedClick);
+ }
+
+ public void Draw()
+ {
+ using var _ = ImRaii.TreeNode("UI");
+ if (!_)
+ return;
+
+ using (var combo = ImRaii.Combo("Tab to Open at", _selectTab.ToString()))
+ {
+ if (combo)
+ foreach (var val in Enum.GetValues())
+ {
+ if (ImGui.Selectable(val.ToString(), _selectTab == val))
+ _selectTab = val;
+ }
+ }
+
+ ImGui.InputTextWithHint("##openMod", "Mod to Open at...", ref _modName, 256);
+ using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
+ if (!table)
+ return;
+
+ DrawIntro(Ipc.PostSettingsDraw.Label, "Last Drawn Mod");
+ ImGui.TextUnformatted(_lastDrawnMod.Length > 0 ? $"{_lastDrawnMod} at {_lastDrawnModTime}" : "None");
+
+ DrawIntro(Ipc.ChangedItemTooltip.Label, "Add Tooltip");
+ if (ImGui.Checkbox("##tooltip", ref _subscribedToTooltip))
+ {
+ if (_subscribedToTooltip)
+ Tooltip.Enable();
+ else
+ Tooltip.Disable();
+ }
+
+ ImGui.SameLine();
+ ImGui.TextUnformatted(_lastHovered);
+
+ DrawIntro(Ipc.ChangedItemClick.Label, "Subscribe Click");
+ if (ImGui.Checkbox("##click", ref _subscribedToClick))
+ {
+ if (_subscribedToClick)
+ Click.Enable();
+ else
+ Click.Disable();
+ }
+
+ ImGui.SameLine();
+ ImGui.TextUnformatted(_lastClicked);
+ DrawIntro(Ipc.OpenMainWindow.Label, "Open Mod Window");
+ if (ImGui.Button("Open##window"))
+ _ec = Ipc.OpenMainWindow.Subscriber(_pi).Invoke(_selectTab, _modName, _modName);
+
+ ImGui.SameLine();
+ ImGui.TextUnformatted(_ec.ToString());
+
+ DrawIntro(Ipc.CloseMainWindow.Label, "Close Mod Window");
+ if (ImGui.Button("Close##window"))
+ Ipc.CloseMainWindow.Subscriber(_pi).Invoke();
+ }
+
+ private void UpdateLastDrawnMod(string name)
+ => (_lastDrawnMod, _lastDrawnModTime) = (name, DateTimeOffset.Now);
+
+ private void AddedTooltip(ChangedItemType type, uint id)
+ {
+ _lastHovered = $"{type} {id} at {DateTime.UtcNow.ToLocalTime().ToString(CultureInfo.CurrentCulture)}";
+ ImGui.TextUnformatted("IPC Test Successful");
+ }
+
+ private void AddedClick(MouseButton button, ChangedItemType type, uint id)
+ {
+ _lastClicked = $"{button}-click on {type} {id} at {DateTime.UtcNow.ToLocalTime().ToString(CultureInfo.CurrentCulture)}";
+ }
+ }
+
+ private class Redrawing
+ {
+ private readonly DalamudServices _dalamud;
+ public readonly EventSubscriber Redrawn;
+
+ private string _redrawName = string.Empty;
+ private int _redrawIndex = 0;
+ private string _lastRedrawnString = "None";
+
+ public Redrawing(DalamudServices dalamud)
+ {
+ _dalamud = dalamud;
+ Redrawn = Ipc.GameObjectRedrawn.Subscriber(_dalamud.PluginInterface, SetLastRedrawn);
+ }
+
+ public void Draw()
+ {
+ using var _ = ImRaii.TreeNode("Redrawing");
+ if (!_)
+ return;
+
+ using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
+ if (!table)
+ return;
+
+ DrawIntro(Ipc.RedrawObjectByName.Label, "Redraw by Name");
+ ImGui.SetNextItemWidth(100 * UiHelpers.Scale);
+ ImGui.InputTextWithHint("##redrawName", "Name...", ref _redrawName, 32);
+ ImGui.SameLine();
+ if (ImGui.Button("Redraw##Name"))
+ Ipc.RedrawObjectByName.Subscriber(_dalamud.PluginInterface).Invoke(_redrawName, RedrawType.Redraw);
+
+ DrawIntro(Ipc.RedrawObject.Label, "Redraw Player Character");
+ if (ImGui.Button("Redraw##pc") && _dalamud.ClientState.LocalPlayer != null)
+ Ipc.RedrawObject.Subscriber(_dalamud.PluginInterface).Invoke(_dalamud.ClientState.LocalPlayer, RedrawType.Redraw);
+
+ DrawIntro(Ipc.RedrawObjectByIndex.Label, "Redraw by Index");
+ var tmp = _redrawIndex;
+ ImGui.SetNextItemWidth(100 * UiHelpers.Scale);
+ if (ImGui.DragInt("##redrawIndex", ref tmp, 0.1f, 0, _dalamud.Objects.Length))
+ _redrawIndex = Math.Clamp(tmp, 0, _dalamud.Objects.Length);
+
+ ImGui.SameLine();
+ if (ImGui.Button("Redraw##Index"))
+ Ipc.RedrawObjectByIndex.Subscriber(_dalamud.PluginInterface).Invoke(_redrawIndex, RedrawType.Redraw);
+
+ DrawIntro(Ipc.RedrawAll.Label, "Redraw All");
+ if (ImGui.Button("Redraw##All"))
+ Ipc.RedrawAll.Subscriber(_dalamud.PluginInterface).Invoke(RedrawType.Redraw);
+
+ DrawIntro(Ipc.GameObjectRedrawn.Label, "Last Redrawn Object:");
+ ImGui.TextUnformatted(_lastRedrawnString);
+ }
+
+ private void SetLastRedrawn(IntPtr address, int index)
+ {
+ if (index < 0
+ || index > _dalamud.Objects.Length
+ || address == IntPtr.Zero
+ || _dalamud.Objects[index]?.Address != address)
+ _lastRedrawnString = "Invalid";
+
+ _lastRedrawnString = $"{_dalamud.Objects[index]!.Name} (0x{address:X}, {index})";
+ }
+ }
+
+ private class GameState
+ {
+ private readonly DalamudPluginInterface _pi;
+ public readonly EventSubscriber CharacterBaseCreating;
+ public readonly EventSubscriber CharacterBaseCreated;
+ public readonly EventSubscriber GameObjectResourcePathResolved;
+
+
+ private string _lastCreatedGameObjectName = string.Empty;
+ private IntPtr _lastCreatedDrawObject = IntPtr.Zero;
+ private DateTimeOffset _lastCreatedGameObjectTime = DateTimeOffset.MaxValue;
+ private string _lastResolvedGamePath = string.Empty;
+ private string _lastResolvedFullPath = string.Empty;
+ private string _lastResolvedObject = string.Empty;
+ private DateTimeOffset _lastResolvedGamePathTime = DateTimeOffset.MaxValue;
+ private string _currentDrawObjectString = string.Empty;
+ private IntPtr _currentDrawObject = IntPtr.Zero;
+ private int _currentCutsceneActor = 0;
+
+ public GameState(DalamudPluginInterface pi)
+ {
+ _pi = pi;
+ CharacterBaseCreating = Ipc.CreatingCharacterBase.Subscriber(pi, UpdateLastCreated);
+ CharacterBaseCreated = Ipc.CreatedCharacterBase.Subscriber(pi, UpdateLastCreated2);
+ GameObjectResourcePathResolved = Ipc.GameObjectResourcePathResolved.Subscriber(pi, UpdateGameObjectResourcePath);
+ }
+
+ public void Draw()
+ {
+ using var _ = ImRaii.TreeNode("Game State");
+ if (!_)
+ return;
+
+ if (ImGui.InputTextWithHint("##drawObject", "Draw Object Address..", ref _currentDrawObjectString, 16,
+ ImGuiInputTextFlags.CharsHexadecimal))
+ _currentDrawObject = IntPtr.TryParse(_currentDrawObjectString, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
+ out var tmp)
+ ? tmp
+ : IntPtr.Zero;
+
+ ImGui.InputInt("Cutscene Actor", ref _currentCutsceneActor, 0);
+ using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
+ if (!table)
+ return;
+
+ DrawIntro(Ipc.GetDrawObjectInfo.Label, "Draw Object Info");
+ if (_currentDrawObject == IntPtr.Zero)
+ {
+ ImGui.TextUnformatted("Invalid");
+ }
+ else
+ {
+ var (ptr, collection) = Ipc.GetDrawObjectInfo.Subscriber(_pi).Invoke(_currentDrawObject);
+ ImGui.TextUnformatted(ptr == IntPtr.Zero ? $"No Actor Associated, {collection}" : $"{ptr:X}, {collection}");
+ }
+
+ DrawIntro(Ipc.GetCutsceneParentIndex.Label, "Cutscene Parent");
+ ImGui.TextUnformatted(Ipc.GetCutsceneParentIndex.Subscriber(_pi).Invoke(_currentCutsceneActor).ToString());
+
+ DrawIntro(Ipc.CreatingCharacterBase.Label, "Last Drawobject created");
+ if (_lastCreatedGameObjectTime < DateTimeOffset.Now)
+ ImGui.TextUnformatted(_lastCreatedDrawObject != IntPtr.Zero
+ ? $"0x{_lastCreatedDrawObject:X} for <{_lastCreatedGameObjectName}> at {_lastCreatedGameObjectTime}"
+ : $"NULL for <{_lastCreatedGameObjectName}> at {_lastCreatedGameObjectTime}");
+
+ DrawIntro(Ipc.GameObjectResourcePathResolved.Label, "Last GamePath resolved");
+ if (_lastResolvedGamePathTime < DateTimeOffset.Now)
+ ImGui.TextUnformatted(
+ $"{_lastResolvedGamePath} -> {_lastResolvedFullPath} for <{_lastResolvedObject}> at {_lastResolvedGamePathTime}");
+ }
+
+ private void UpdateLastCreated(IntPtr gameObject, string _, IntPtr _2, IntPtr _3, IntPtr _4)
+ {
+ _lastCreatedGameObjectName = GetObjectName(gameObject);
+ _lastCreatedGameObjectTime = DateTimeOffset.Now;
+ _lastCreatedDrawObject = IntPtr.Zero;
+ }
+
+ private void UpdateLastCreated2(IntPtr gameObject, string _, IntPtr drawObject)
+ {
+ _lastCreatedGameObjectName = GetObjectName(gameObject);
+ _lastCreatedGameObjectTime = DateTimeOffset.Now;
+ _lastCreatedDrawObject = drawObject;
+ }
+
+ private void UpdateGameObjectResourcePath(IntPtr gameObject, string gamePath, string fullPath)
+ {
+ _lastResolvedObject = GetObjectName(gameObject);
+ _lastResolvedGamePath = gamePath;
+ _lastResolvedFullPath = fullPath;
+ _lastResolvedGamePathTime = DateTimeOffset.Now;
+ }
+
+ private static unsafe string GetObjectName(IntPtr gameObject)
+ {
+ var obj = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)gameObject;
+ var name = obj != null ? obj->Name : null;
+ return name != null && *name != 0 ? new ByteString(name).ToString() : "Unknown";
+ }
+ }
+
+ private class Resolve
+ {
+ private readonly DalamudPluginInterface _pi;
+
+ private string _currentResolvePath = string.Empty;
+ private string _currentResolveCharacter = string.Empty;
+ private string _currentReversePath = string.Empty;
+ private int _currentReverseIdx = 0;
+
+ public Resolve(DalamudPluginInterface pi)
+ => _pi = pi;
+
+ public void Draw()
+ {
+ using var _ = ImRaii.TreeNode("Resolving");
+ if (!_)
+ return;
+
+ ImGui.InputTextWithHint("##resolvePath", "Resolve this game path...", ref _currentResolvePath, Utf8GamePath.MaxGamePathLength);
+ ImGui.InputTextWithHint("##resolveCharacter", "Character Name (leave blank for default)...", ref _currentResolveCharacter, 32);
+ ImGui.InputTextWithHint("##resolveInversePath", "Reverse-resolve this path...", ref _currentReversePath,
+ Utf8GamePath.MaxGamePathLength);
+ ImGui.InputInt("##resolveIdx", ref _currentReverseIdx, 0, 0);
+ using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
+ if (!table)
+ return;
+
+ DrawIntro(Ipc.ResolveDefaultPath.Label, "Default Collection Resolve");
+ if (_currentResolvePath.Length != 0)
+ ImGui.TextUnformatted(Ipc.ResolveDefaultPath.Subscriber(_pi).Invoke(_currentResolvePath));
+
+ DrawIntro(Ipc.ResolveInterfacePath.Label, "Interface Collection Resolve");
+ if (_currentResolvePath.Length != 0)
+ ImGui.TextUnformatted(Ipc.ResolveInterfacePath.Subscriber(_pi).Invoke(_currentResolvePath));
+
+ DrawIntro(Ipc.ResolvePlayerPath.Label, "Player Collection Resolve");
+ if (_currentResolvePath.Length != 0)
+ ImGui.TextUnformatted(Ipc.ResolvePlayerPath.Subscriber(_pi).Invoke(_currentResolvePath));
+
+ DrawIntro(Ipc.ResolveCharacterPath.Label, "Character Collection Resolve");
+ if (_currentResolvePath.Length != 0 && _currentResolveCharacter.Length != 0)
+ ImGui.TextUnformatted(Ipc.ResolveCharacterPath.Subscriber(_pi).Invoke(_currentResolvePath, _currentResolveCharacter));
+
+ DrawIntro(Ipc.ResolveGameObjectPath.Label, "Game Object Collection Resolve");
+ if (_currentResolvePath.Length != 0)
+ ImGui.TextUnformatted(Ipc.ResolveGameObjectPath.Subscriber(_pi).Invoke(_currentResolvePath, _currentReverseIdx));
+
+ DrawIntro(Ipc.ReverseResolvePath.Label, "Reversed Game Paths");
+ if (_currentReversePath.Length > 0)
+ {
+ var list = Ipc.ReverseResolvePath.Subscriber(_pi).Invoke(_currentReversePath, _currentResolveCharacter);
+ if (list.Length > 0)
+ {
+ ImGui.TextUnformatted(list[0]);
+ if (list.Length > 1 && ImGui.IsItemHovered())
+ ImGui.SetTooltip(string.Join("\n", list.Skip(1)));
+ }
+ }
+
+ DrawIntro(Ipc.ReverseResolvePlayerPath.Label, "Reversed Game Paths (Player)");
+ if (_currentReversePath.Length > 0)
+ {
+ var list = Ipc.ReverseResolvePlayerPath.Subscriber(_pi).Invoke(_currentReversePath);
+ if (list.Length > 0)
+ {
+ ImGui.TextUnformatted(list[0]);
+ if (list.Length > 1 && ImGui.IsItemHovered())
+ ImGui.SetTooltip(string.Join("\n", list.Skip(1)));
+ }
+ }
+
+ DrawIntro(Ipc.ReverseResolveGameObjectPath.Label, "Reversed Game Paths (Game Object)");
+ if (_currentReversePath.Length > 0)
+ {
+ var list = Ipc.ReverseResolveGameObjectPath.Subscriber(_pi).Invoke(_currentReversePath, _currentReverseIdx);
+ if (list.Length > 0)
+ {
+ ImGui.TextUnformatted(list[0]);
+ if (list.Length > 1 && ImGui.IsItemHovered())
+ ImGui.SetTooltip(string.Join("\n", list.Skip(1)));
+ }
+ }
+
+ DrawIntro(Ipc.ResolvePlayerPaths.Label, "Resolved Paths (Player)");
+ if (_currentResolvePath.Length > 0 || _currentReversePath.Length > 0)
+ {
+ var forwardArray = _currentResolvePath.Length > 0
+ ? new[]
+ {
+ _currentResolvePath,
+ }
+ : Array.Empty();
+ var reverseArray = _currentReversePath.Length > 0
+ ? new[]
+ {
+ _currentReversePath,
+ }
+ : Array.Empty();
+ var ret = Ipc.ResolvePlayerPaths.Subscriber(_pi).Invoke(forwardArray, reverseArray);
+ var text = string.Empty;
+ if (ret.Item1.Length > 0)
+ {
+ if (ret.Item2.Length > 0)
+ text = $"Forward: {ret.Item1[0]} | Reverse: {string.Join("; ", ret.Item2[0])}.";
+ else
+ text = $"Forward: {ret.Item1[0]}.";
+ }
+ else if (ret.Item2.Length > 0)
+ {
+ text = $"Reverse: {string.Join("; ", ret.Item2[0])}.";
+ }
+
+ ImGui.TextUnformatted(text);
+ }
+ }
+ }
+
+ private class Collections
+ {
+ private readonly DalamudPluginInterface _pi;
+
+ private int _objectIdx = 0;
+ private string _collectionName = string.Empty;
+ private bool _allowCreation = true;
+ private bool _allowDeletion = true;
+ private ApiCollectionType _type = ApiCollectionType.Current;
+
+ private string _characterCollectionName = string.Empty;
+ private IList _collections = new List();
+ private string _changedItemCollection = string.Empty;
+ private IReadOnlyDictionary _changedItems = new Dictionary();
+ private PenumbraApiEc _returnCode = PenumbraApiEc.Success;
+ private string? _oldCollection = null;
+
+ public Collections(DalamudPluginInterface pi)
+ => _pi = pi;
+
+ public void Draw()
+ {
+ using var _ = ImRaii.TreeNode("Collections");
+ if (!_)
+ return;
+
+ ImGuiUtil.GenericEnumCombo("Collection Type", 200, _type, out _type, t => ((CollectionType)t).ToName());
+ ImGui.InputInt("Object Index##Collections", ref _objectIdx, 0, 0);
+ ImGui.InputText("Collection Name##Collections", ref _collectionName, 64);
+ ImGui.Checkbox("Allow Assignment Creation", ref _allowCreation);
+ ImGui.SameLine();
+ ImGui.Checkbox("Allow Assignment Deletion", ref _allowDeletion);
+
+ using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
+ if (!table)
+ return;
+
+ DrawIntro("Last Return Code", _returnCode.ToString());
+ if (_oldCollection != null)
+ ImGui.TextUnformatted(_oldCollection.Length == 0 ? "Created" : _oldCollection);
+
+ DrawIntro(Ipc.GetCurrentCollectionName.Label, "Current Collection");
+ ImGui.TextUnformatted(Ipc.GetCurrentCollectionName.Subscriber(_pi).Invoke());
+ DrawIntro(Ipc.GetDefaultCollectionName.Label, "Default Collection");
+ ImGui.TextUnformatted(Ipc.GetDefaultCollectionName.Subscriber(_pi).Invoke());
+ DrawIntro(Ipc.GetInterfaceCollectionName.Label, "Interface Collection");
+ ImGui.TextUnformatted(Ipc.GetInterfaceCollectionName.Subscriber(_pi).Invoke());
+ DrawIntro(Ipc.GetCharacterCollectionName.Label, "Character");
+ ImGui.SetNextItemWidth(200 * UiHelpers.Scale);
+ ImGui.InputTextWithHint("##characterCollectionName", "Character Name...", ref _characterCollectionName, 64);
+ var (c, s) = Ipc.GetCharacterCollectionName.Subscriber(_pi).Invoke(_characterCollectionName);
+ ImGui.SameLine();
+ ImGui.TextUnformatted($"{c}, {(s ? "Custom" : "Default")}");
+
+ DrawIntro(Ipc.GetCollections.Label, "Collections");
+ if (ImGui.Button("Get##Collections"))
+ {
+ _collections = Ipc.GetCollections.Subscriber(_pi).Invoke();
+ ImGui.OpenPopup("Collections");
+ }
+
+ DrawIntro(Ipc.GetCollectionForType.Label, "Get Special Collection");
+ var name = Ipc.GetCollectionForType.Subscriber(_pi).Invoke(_type);
+ ImGui.TextUnformatted(name.Length == 0 ? "Unassigned" : name);
+ DrawIntro(Ipc.SetCollectionForType.Label, "Set Special Collection");
+ if (ImGui.Button("Set##TypeCollection"))
+ (_returnCode, _oldCollection) =
+ Ipc.SetCollectionForType.Subscriber(_pi).Invoke(_type, _collectionName, _allowCreation, _allowDeletion);
+
+ DrawIntro(Ipc.GetCollectionForObject.Label, "Get Object Collection");
+ (var valid, var individual, name) = Ipc.GetCollectionForObject.Subscriber(_pi).Invoke(_objectIdx);
+ ImGui.TextUnformatted(
+ $"{(valid ? "Valid" : "Invalid")} Object, {(name.Length == 0 ? "Unassigned" : name)}{(individual ? " (Individual Assignment)" : string.Empty)}");
+ DrawIntro(Ipc.SetCollectionForObject.Label, "Set Object Collection");
+ if (ImGui.Button("Set##ObjectCollection"))
+ (_returnCode, _oldCollection) = Ipc.SetCollectionForObject.Subscriber(_pi)
+ .Invoke(_objectIdx, _collectionName, _allowCreation, _allowDeletion);
+
+ if (_returnCode == PenumbraApiEc.NothingChanged && _oldCollection.IsNullOrEmpty())
+ _oldCollection = null;
+
+ DrawIntro(Ipc.GetChangedItems.Label, "Changed Item List");
+ ImGui.SetNextItemWidth(200 * UiHelpers.Scale);
+ ImGui.InputTextWithHint("##changedCollection", "Collection Name...", ref _changedItemCollection, 64);
+ ImGui.SameLine();
+ if (ImGui.Button("Get"))
+ {
+ _changedItems = Ipc.GetChangedItems.Subscriber(_pi).Invoke(_changedItemCollection);
+ ImGui.OpenPopup("Changed Item List");
+ }
+
+ DrawChangedItemPopup();
+ DrawCollectionPopup();
+ }
+
+ private void DrawChangedItemPopup()
+ {
+ ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
+ using var p = ImRaii.Popup("Changed Item List");
+ if (!p)
+ return;
+
+ foreach (var item in _changedItems)
+ ImGui.TextUnformatted(item.Key);
+
+ if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
+ ImGui.CloseCurrentPopup();
+ }
+
+ private void DrawCollectionPopup()
+ {
+ ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
+ using var p = ImRaii.Popup("Collections");
+ if (!p)
+ return;
+
+ foreach (var collection in _collections)
+ ImGui.TextUnformatted(collection);
+
+ if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
+ ImGui.CloseCurrentPopup();
+ }
+ }
+
+ private class Meta
+ {
+ private readonly DalamudPluginInterface _pi;
+
+ private string _characterName = string.Empty;
+ private int _gameObjectIndex = 0;
+
+ public Meta(DalamudPluginInterface pi)
+ => _pi = pi;
+
+ public void Draw()
+ {
+ using var _ = ImRaii.TreeNode("Meta");
+ if (!_)
+ return;
+
+ ImGui.InputTextWithHint("##characterName", "Character Name...", ref _characterName, 64);
+ ImGui.InputInt("##metaIdx", ref _gameObjectIndex, 0, 0);
+ using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
+ if (!table)
+ return;
+
+ DrawIntro(Ipc.GetMetaManipulations.Label, "Meta Manipulations");
+ if (ImGui.Button("Copy to Clipboard"))
+ {
+ var base64 = Ipc.GetMetaManipulations.Subscriber(_pi).Invoke(_characterName);
+ ImGui.SetClipboardText(base64);
+ }
+
+ DrawIntro(Ipc.GetPlayerMetaManipulations.Label, "Player Meta Manipulations");
+ if (ImGui.Button("Copy to Clipboard##Player"))
+ {
+ var base64 = Ipc.GetPlayerMetaManipulations.Subscriber(_pi).Invoke();
+ ImGui.SetClipboardText(base64);
+ }
+
+ DrawIntro(Ipc.GetGameObjectMetaManipulations.Label, "Game Object Manipulations");
+ if (ImGui.Button("Copy to Clipboard##GameObject"))
+ {
+ var base64 = Ipc.GetGameObjectMetaManipulations.Subscriber(_pi).Invoke(_gameObjectIndex);
+ ImGui.SetClipboardText(base64);
+ }
+ }
+ }
+
+ private class Mods
+ {
+ private readonly DalamudPluginInterface _pi;
+
+ private string _modDirectory = string.Empty;
+ private string _modName = string.Empty;
+ private string _pathInput = string.Empty;
+ private string _newInstallPath = string.Empty;
+ private PenumbraApiEc _lastReloadEc;
+ private PenumbraApiEc _lastAddEc;
+ private PenumbraApiEc _lastDeleteEc;
+ private PenumbraApiEc _lastSetPathEc;
+ private PenumbraApiEc _lastInstallEc;
+ private IList<(string, string)> _mods = new List<(string, string)>();
+
+ public readonly EventSubscriber DeleteSubscriber;
+ public readonly EventSubscriber AddSubscriber;
+ public readonly EventSubscriber MoveSubscriber;
+
+ private DateTimeOffset _lastDeletedModTime = DateTimeOffset.UnixEpoch;
+ private string _lastDeletedMod = string.Empty;
+ private DateTimeOffset _lastAddedModTime = DateTimeOffset.UnixEpoch;
+ private string _lastAddedMod = string.Empty;
+ private DateTimeOffset _lastMovedModTime = DateTimeOffset.UnixEpoch;
+ private string _lastMovedModFrom = string.Empty;
+ private string _lastMovedModTo = string.Empty;
+
+ public Mods(DalamudPluginInterface pi)
+ {
+ _pi = pi;
+ DeleteSubscriber = Ipc.ModDeleted.Subscriber(pi, s =>
+ {
+ _lastDeletedModTime = DateTimeOffset.UtcNow;
+ _lastDeletedMod = s;
+ });
+ AddSubscriber = Ipc.ModAdded.Subscriber(pi, s =>
+ {
+ _lastAddedModTime = DateTimeOffset.UtcNow;
+ _lastAddedMod = s;
+ });
+ MoveSubscriber = Ipc.ModMoved.Subscriber(pi, (s1, s2) =>
+ {
+ _lastMovedModTime = DateTimeOffset.UtcNow;
+ _lastMovedModFrom = s1;
+ _lastMovedModTo = s2;
+ });
+ }
+
+ public void Draw()
+ {
+ using var _ = ImRaii.TreeNode("Mods");
+ if (!_)
+ return;
+
+ ImGui.InputTextWithHint("##install", "Install File Path...", ref _newInstallPath, 100);
+ ImGui.InputTextWithHint("##modDir", "Mod Directory Name...", ref _modDirectory, 100);
+ ImGui.InputTextWithHint("##modName", "Mod Name...", ref _modName, 100);
+ ImGui.InputTextWithHint("##path", "New Path...", ref _pathInput, 100);
+ using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
+ if (!table)
+ return;
+
+ DrawIntro(Ipc.GetMods.Label, "Mods");
+ if (ImGui.Button("Get##Mods"))
+ {
+ _mods = Ipc.GetMods.Subscriber(_pi).Invoke();
+ ImGui.OpenPopup("Mods");
+ }
+
+ DrawIntro(Ipc.ReloadMod.Label, "Reload Mod");
+ if (ImGui.Button("Reload"))
+ _lastReloadEc = Ipc.ReloadMod.Subscriber(_pi).Invoke(_modDirectory, _modName);
+
+ ImGui.SameLine();
+ ImGui.TextUnformatted(_lastReloadEc.ToString());
+
+ DrawIntro(Ipc.InstallMod.Label, "Install Mod");
+ if (ImGui.Button("Install"))
+ _lastInstallEc = Ipc.InstallMod.Subscriber(_pi).Invoke(_newInstallPath);
+
+ ImGui.SameLine();
+ ImGui.TextUnformatted(_lastInstallEc.ToString());
+
+ DrawIntro(Ipc.AddMod.Label, "Add Mod");
+ if (ImGui.Button("Add"))
+ _lastAddEc = Ipc.AddMod.Subscriber(_pi).Invoke(_modDirectory);
+
+ ImGui.SameLine();
+ ImGui.TextUnformatted(_lastAddEc.ToString());
+
+ DrawIntro(Ipc.DeleteMod.Label, "Delete Mod");
+ if (ImGui.Button("Delete"))
+ _lastDeleteEc = Ipc.DeleteMod.Subscriber(_pi).Invoke(_modDirectory, _modName);
+
+ ImGui.SameLine();
+ ImGui.TextUnformatted(_lastDeleteEc.ToString());
+
+ DrawIntro(Ipc.GetModPath.Label, "Current Path");
+ var (ec, path, def) = Ipc.GetModPath.Subscriber(_pi).Invoke(_modDirectory, _modName);
+ ImGui.TextUnformatted($"{path} ({(def ? "Custom" : "Default")}) [{ec}]");
+
+ DrawIntro(Ipc.SetModPath.Label, "Set Path");
+ if (ImGui.Button("Set"))
+ _lastSetPathEc = Ipc.SetModPath.Subscriber(_pi).Invoke(_modDirectory, _modName, _pathInput);
+
+ ImGui.SameLine();
+ ImGui.TextUnformatted(_lastSetPathEc.ToString());
+
+ DrawIntro(Ipc.ModDeleted.Label, "Last Mod Deleted");
+ if (_lastDeletedModTime > DateTimeOffset.UnixEpoch)
+ ImGui.TextUnformatted($"{_lastDeletedMod} at {_lastDeletedModTime}");
+
+ DrawIntro(Ipc.ModAdded.Label, "Last Mod Added");
+ if (_lastAddedModTime > DateTimeOffset.UnixEpoch)
+ ImGui.TextUnformatted($"{_lastAddedMod} at {_lastAddedModTime}");
+
+ DrawIntro(Ipc.ModMoved.Label, "Last Mod Moved");
+ if (_lastMovedModTime > DateTimeOffset.UnixEpoch)
+ ImGui.TextUnformatted($"{_lastMovedModFrom} -> {_lastMovedModTo} at {_lastMovedModTime}");
+
+ DrawModsPopup();
+ }
+
+ private void DrawModsPopup()
+ {
+ ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
+ using var p = ImRaii.Popup("Mods");
+ if (!p)
+ return;
+
+ foreach (var (modDir, modName) in _mods)
+ ImGui.TextUnformatted($"{modDir}: {modName}");
+
+ if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
+ ImGui.CloseCurrentPopup();
+ }
+ }
+
+ private class ModSettings
+ {
+ private readonly DalamudPluginInterface _pi;
+ public readonly EventSubscriber SettingChanged;
+
+ private PenumbraApiEc _lastSettingsError = PenumbraApiEc.Success;
+ private ModSettingChange _lastSettingChangeType;
+ private string _lastSettingChangeCollection = string.Empty;
+ private string _lastSettingChangeMod = string.Empty;
+ private bool _lastSettingChangeInherited;
+ private DateTimeOffset _lastSettingChange;
+
+ private string _settingsModDirectory = string.Empty;
+ private string _settingsModName = string.Empty;
+ private string _settingsCollection = string.Empty;
+ private bool _settingsAllowInheritance = true;
+ private bool _settingsInherit = false;
+ private bool _settingsEnabled = false;
+ private int _settingsPriority = 0;
+ private IDictionary, GroupType)>? _availableSettings;
+ private IDictionary>? _currentSettings = null;
+
+ public ModSettings(DalamudPluginInterface pi)
+ {
+ _pi = pi;
+ SettingChanged = Ipc.ModSettingChanged.Subscriber(pi, UpdateLastModSetting);
+ }
+
+ public void Draw()
+ {
+ using var _ = ImRaii.TreeNode("Mod Settings");
+ if (!_)
+ return;
+
+ ImGui.InputTextWithHint("##settingsDir", "Mod Directory Name...", ref _settingsModDirectory, 100);
+ ImGui.InputTextWithHint("##settingsName", "Mod Name...", ref _settingsModName, 100);
+ ImGui.InputTextWithHint("##settingsCollection", "Collection...", ref _settingsCollection, 100);
+ ImGui.Checkbox("Allow Inheritance", ref _settingsAllowInheritance);
+
+ using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
+ if (!table)
+ return;
+
+ DrawIntro("Last Error", _lastSettingsError.ToString());
+ DrawIntro(Ipc.ModSettingChanged.Label, "Last Mod Setting Changed");
+ ImGui.TextUnformatted(_lastSettingChangeMod.Length > 0
+ ? $"{_lastSettingChangeType} of {_lastSettingChangeMod} in {_lastSettingChangeCollection}{(_lastSettingChangeInherited ? " (Inherited)" : string.Empty)} at {_lastSettingChange}"
+ : "None");
+ DrawIntro(Ipc.GetAvailableModSettings.Label, "Get Available Settings");
+ if (ImGui.Button("Get##Available"))
+ {
+ _availableSettings = Ipc.GetAvailableModSettings.Subscriber(_pi).Invoke(_settingsModDirectory, _settingsModName);
+ _lastSettingsError = _availableSettings == null ? PenumbraApiEc.ModMissing : PenumbraApiEc.Success;
+ }
+
+
+ DrawIntro(Ipc.GetCurrentModSettings.Label, "Get Current Settings");
+ if (ImGui.Button("Get##Current"))
+ {
+ var ret = Ipc.GetCurrentModSettings.Subscriber(_pi)
+ .Invoke(_settingsCollection, _settingsModDirectory, _settingsModName, _settingsAllowInheritance);
+ _lastSettingsError = ret.Item1;
+ if (ret.Item1 == PenumbraApiEc.Success)
+ {
+ _settingsEnabled = ret.Item2?.Item1 ?? false;
+ _settingsInherit = ret.Item2?.Item4 ?? false;
+ _settingsPriority = ret.Item2?.Item2 ?? 0;
+ _currentSettings = ret.Item2?.Item3;
+ }
+ else
+ {
+ _currentSettings = null;
+ }
+ }
+
+ DrawIntro(Ipc.TryInheritMod.Label, "Inherit Mod");
+ ImGui.Checkbox("##inherit", ref _settingsInherit);
+ ImGui.SameLine();
+ if (ImGui.Button("Set##Inherit"))
+ _lastSettingsError = Ipc.TryInheritMod.Subscriber(_pi)
+ .Invoke(_settingsCollection, _settingsModDirectory, _settingsModName, _settingsInherit);
+
+ DrawIntro(Ipc.TrySetMod.Label, "Set Enabled");
+ ImGui.Checkbox("##enabled", ref _settingsEnabled);
+ ImGui.SameLine();
+ if (ImGui.Button("Set##Enabled"))
+ _lastSettingsError = Ipc.TrySetMod.Subscriber(_pi)
+ .Invoke(_settingsCollection, _settingsModDirectory, _settingsModName, _settingsEnabled);
+
+ DrawIntro(Ipc.TrySetModPriority.Label, "Set Priority");
+ ImGui.SetNextItemWidth(200 * UiHelpers.Scale);
+ ImGui.DragInt("##Priority", ref _settingsPriority);
+ ImGui.SameLine();
+ if (ImGui.Button("Set##Priority"))
+ _lastSettingsError = Ipc.TrySetModPriority.Subscriber(_pi)
+ .Invoke(_settingsCollection, _settingsModDirectory, _settingsModName, _settingsPriority);
+
+ DrawIntro(Ipc.CopyModSettings.Label, "Copy Mod Settings");
+ if (ImGui.Button("Copy Settings"))
+ _lastSettingsError = Ipc.CopyModSettings.Subscriber(_pi).Invoke(_settingsCollection, _settingsModDirectory, _settingsModName);
+
+ ImGuiUtil.HoverTooltip("Copy settings from Mod Directory Name to Mod Name (as directory) in collection.");
+
+ DrawIntro(Ipc.TrySetModSetting.Label, "Set Setting(s)");
+ if (_availableSettings == null)
+ return;
+
+ foreach (var (group, (list, type)) in _availableSettings)
+ {
+ using var id = ImRaii.PushId(group);
+ var preview = list.Count > 0 ? list[0] : string.Empty;
+ IList current;
+ if (_currentSettings != null && _currentSettings.TryGetValue(group, out current!) && current.Count > 0)
+ {
+ preview = current[0];
+ }
+ else
+ {
+ current = new List();
+ if (_currentSettings != null)
+ _currentSettings[group] = current;
+ }
+
+ ImGui.SetNextItemWidth(200 * UiHelpers.Scale);
+ using (var c = ImRaii.Combo("##group", preview))
+ {
+ if (c)
+ foreach (var s in list)
+ {
+ var contained = current.Contains(s);
+ if (ImGui.Checkbox(s, ref contained))
+ {
+ if (contained)
+ current.Add(s);
+ else
+ current.Remove(s);
+ }
+ }
+ }
+
+ ImGui.SameLine();
+ if (ImGui.Button("Set##setting"))
+ {
+ if (type == GroupType.Single)
+ _lastSettingsError = Ipc.TrySetModSetting.Subscriber(_pi).Invoke(_settingsCollection,
+ _settingsModDirectory, _settingsModName, group, current.Count > 0 ? current[0] : string.Empty);
+ else
+ _lastSettingsError = Ipc.TrySetModSettings.Subscriber(_pi).Invoke(_settingsCollection,
+ _settingsModDirectory, _settingsModName, group, current.ToArray());
+ }
+
+ ImGui.SameLine();
+ ImGui.TextUnformatted(group);
+ }
+ }
+
+ private void UpdateLastModSetting(ModSettingChange type, string collection, string mod, bool inherited)
+ {
+ _lastSettingChangeType = type;
+ _lastSettingChangeCollection = collection;
+ _lastSettingChangeMod = mod;
+ _lastSettingChangeInherited = inherited;
+ _lastSettingChange = DateTimeOffset.Now;
+ }
+ }
+
+ private class Temporary
+ {
+ private readonly DalamudPluginInterface _pi;
+ private readonly ModManager _modManager;
+ private readonly CollectionManager _collections;
+ private readonly TempModManager _tempMods;
+ private readonly TempCollectionManager _tempCollections;
+ private readonly SaveService _saveService;
+ private readonly Configuration _config;
+
+ public Temporary(DalamudPluginInterface pi, ModManager modManager, CollectionManager collections, TempModManager tempMods,
+ TempCollectionManager tempCollections, SaveService saveService, Configuration config)
+ {
+ _pi = pi;
+ _modManager = modManager;
+ _collections = collections;
+ _tempMods = tempMods;
+ _tempCollections = tempCollections;
+ _saveService = saveService;
+ _config = config;
+ }
+
+ public string LastCreatedCollectionName = string.Empty;
+
+ private string _tempCollectionName = string.Empty;
+ private string _tempCharacterName = string.Empty;
+ private string _tempModName = string.Empty;
+ private string _tempGamePath = "test/game/path.mtrl";
+ private string _tempFilePath = "test/success.mtrl";
+ private string _tempManipulation = string.Empty;
+ private PenumbraApiEc _lastTempError;
+ private int _tempActorIndex = 0;
+ private bool _forceOverwrite;
+
+ public void Draw()
+ {
+ using var _ = ImRaii.TreeNode("Temporary");
+ if (!_)
+ return;
+
+ ImGui.InputTextWithHint("##tempCollection", "Collection Name...", ref _tempCollectionName, 128);
+ ImGui.InputTextWithHint("##tempCollectionChar", "Collection Character...", ref _tempCharacterName, 32);
+ ImGui.InputInt("##tempActorIndex", ref _tempActorIndex, 0, 0);
+ ImGui.InputTextWithHint("##tempMod", "Temporary Mod Name...", ref _tempModName, 32);
+ ImGui.InputTextWithHint("##tempGame", "Game Path...", ref _tempGamePath, 256);
+ ImGui.InputTextWithHint("##tempFile", "File Path...", ref _tempFilePath, 256);
+ ImGui.InputTextWithHint("##tempManip", "Manipulation Base64 String...", ref _tempManipulation, 256);
+ ImGui.Checkbox("Force Character Collection Overwrite", ref _forceOverwrite);
+
+
+ using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
+ if (!table)
+ return;
+
+ DrawIntro("Last Error", _lastTempError.ToString());
+ DrawIntro("Last Created Collection", LastCreatedCollectionName);
+ DrawIntro(Ipc.CreateTemporaryCollection.Label, "Create Temporary Collection");
+#pragma warning disable 0612
+ if (ImGui.Button("Create##Collection"))
+ (_lastTempError, LastCreatedCollectionName) = Ipc.CreateTemporaryCollection.Subscriber(_pi)
+ .Invoke(_tempCollectionName, _tempCharacterName, _forceOverwrite);
+
+ DrawIntro(Ipc.CreateNamedTemporaryCollection.Label, "Create Named Temporary Collection");
+ if (ImGui.Button("Create##NamedCollection"))
+ _lastTempError = Ipc.CreateNamedTemporaryCollection.Subscriber(_pi).Invoke(_tempCollectionName);
+
+ DrawIntro(Ipc.RemoveTemporaryCollection.Label, "Remove Temporary Collection from Character");
+ if (ImGui.Button("Delete##Collection"))
+ _lastTempError = Ipc.RemoveTemporaryCollection.Subscriber(_pi).Invoke(_tempCharacterName);
+#pragma warning restore 0612
+ DrawIntro(Ipc.RemoveTemporaryCollectionByName.Label, "Remove Temporary Collection");
+ if (ImGui.Button("Delete##NamedCollection"))
+ _lastTempError = Ipc.RemoveTemporaryCollectionByName.Subscriber(_pi).Invoke(_tempCollectionName);
+
+ DrawIntro(Ipc.AssignTemporaryCollection.Label, "Assign Temporary Collection");
+ if (ImGui.Button("Assign##NamedCollection"))
+ _lastTempError = Ipc.AssignTemporaryCollection.Subscriber(_pi).Invoke(_tempCollectionName, _tempActorIndex, _forceOverwrite);
+
+ DrawIntro(Ipc.AddTemporaryMod.Label, "Add Temporary Mod to specific Collection");
+ if (ImGui.Button("Add##Mod"))
+ _lastTempError = Ipc.AddTemporaryMod.Subscriber(_pi).Invoke(_tempModName, _tempCollectionName,
+ new Dictionary { { _tempGamePath, _tempFilePath } },
+ _tempManipulation.Length > 0 ? _tempManipulation : string.Empty, int.MaxValue);
+
+ DrawIntro(Ipc.CreateTemporaryCollection.Label, "Copy Existing Collection");
+ if (ImGuiUtil.DrawDisabledButton("Copy##Collection", Vector2.Zero,
+ "Copies the effective list from the collection named in Temporary Mod Name...",
+ !_collections.Storage.ByName(_tempModName, out var copyCollection))
+ && copyCollection is { HasCache: true })
+ {
+ var files = copyCollection.ResolvedFiles.ToDictionary(kvp => kvp.Key.ToString(), kvp => kvp.Value.Path.ToString());
+ var manips = Functions.ToCompressedBase64(copyCollection.MetaCache?.Manipulations.ToArray() ?? Array.Empty(),
+ MetaManipulation.CurrentVersion);
+ _lastTempError = Ipc.AddTemporaryMod.Subscriber(_pi).Invoke(_tempModName, _tempCollectionName, files, manips, 999);
+ }
+
+ DrawIntro(Ipc.AddTemporaryModAll.Label, "Add Temporary Mod to all Collections");
+ if (ImGui.Button("Add##All"))
+ _lastTempError = Ipc.AddTemporaryModAll.Subscriber(_pi).Invoke(_tempModName,
+ new Dictionary { { _tempGamePath, _tempFilePath } },
+ _tempManipulation.Length > 0 ? _tempManipulation : string.Empty, int.MaxValue);
+
+ DrawIntro(Ipc.RemoveTemporaryMod.Label, "Remove Temporary Mod from specific Collection");
+ if (ImGui.Button("Remove##Mod"))
+ _lastTempError = Ipc.RemoveTemporaryMod.Subscriber(_pi).Invoke(_tempModName, _tempCollectionName, int.MaxValue);
+
+ DrawIntro(Ipc.RemoveTemporaryModAll.Label, "Remove Temporary Mod from all Collections");
+ if (ImGui.Button("Remove##ModAll"))
+ _lastTempError = Ipc.RemoveTemporaryModAll.Subscriber(_pi).Invoke(_tempModName, int.MaxValue);
+ }
+
+ public void DrawCollections()
+ {
+ using var collTree = ImRaii.TreeNode("Temporary Collections##TempCollections");
+ if (!collTree)
+ return;
+
+ using var table = ImRaii.Table("##collTree", 5);
+ if (!table)
+ return;
+
+ foreach (var collection in _tempCollections.Values)
+ {
+ ImGui.TableNextColumn();
+ var character = _tempCollections.Collections.Where(p => p.Collection == collection).Select(p => p.DisplayName)
+ .FirstOrDefault()
+ ?? "Unknown";
+ if (ImGui.Button($"Save##{collection.Name}"))
+ TemporaryMod.SaveTempCollection(_config, _saveService, _modManager, collection, character);
+
+ ImGuiUtil.DrawTableColumn(collection.Name);
+ ImGuiUtil.DrawTableColumn(collection.ResolvedFiles.Count.ToString());
+ ImGuiUtil.DrawTableColumn(collection.MetaCache?.Count.ToString() ?? "0");
+ ImGuiUtil.DrawTableColumn(string.Join(", ",
+ _tempCollections.Collections.Where(p => p.Collection == collection).Select(c => c.DisplayName)));
+ }
+ }
+
+ public void DrawMods()
+ {
+ using var modTree = ImRaii.TreeNode("Temporary Mods##TempMods");
+ if (!modTree)
+ return;
+
+ using var table = ImRaii.Table("##modTree", 5);
+
+ void PrintList(string collectionName, IReadOnlyList list)
+ {
+ foreach (var mod in list)
+ {
+ ImGui.TableNextColumn();
+ ImGui.TextUnformatted(mod.Name);
+ ImGui.TableNextColumn();
+ ImGui.TextUnformatted(mod.Priority.ToString());
+ ImGui.TableNextColumn();
+ ImGui.TextUnformatted(collectionName);
+ ImGui.TableNextColumn();
+ ImGui.TextUnformatted(mod.Default.Files.Count.ToString());
+ if (ImGui.IsItemHovered())
+ {
+ using var tt = ImRaii.Tooltip();
+ foreach (var (path, file) in mod.Default.Files)
+ ImGui.TextUnformatted($"{path} -> {file}");
+ }
+
+ ImGui.TableNextColumn();
+ ImGui.TextUnformatted(mod.TotalManipulations.ToString());
+ if (ImGui.IsItemHovered())
+ {
+ using var tt = ImRaii.Tooltip();
+ foreach (var manip in mod.Default.Manipulations)
+ ImGui.TextUnformatted(manip.ToString());
+ }
+ }
+ }
+
+ if (table)
+ {
+ PrintList("All", _tempMods.ModsForAllCollections);
+ foreach (var (collection, list) in _tempMods.Mods)
+ PrintList(collection.Name, list);
+ }
+ }
+ }
+}
diff --git a/Penumbra/Api/IpcTester/CollectionsIpcTester.cs b/Penumbra/Api/IpcTester/CollectionsIpcTester.cs
deleted file mode 100644
index f033b7c3..00000000
--- a/Penumbra/Api/IpcTester/CollectionsIpcTester.cs
+++ /dev/null
@@ -1,189 +0,0 @@
-using Dalamud.Bindings.ImGui;
-using Dalamud.Interface;
-using Dalamud.Interface.Utility;
-using Dalamud.Plugin;
-using OtterGui;
-using OtterGui.Raii;
-using OtterGui.Services;
-using Penumbra.Api.Enums;
-using Penumbra.Api.IpcSubscribers;
-using Penumbra.Collections.Manager;
-using Penumbra.GameData.Data;
-using ImGuiClip = OtterGui.ImGuiClip;
-
-namespace Penumbra.Api.IpcTester;
-
-public class CollectionsIpcTester(IDalamudPluginInterface pi) : IUiService
-{
- private int _objectIdx;
- private string _collectionIdString = string.Empty;
- private Guid? _collectionId;
- private bool _allowCreation = true;
- private bool _allowDeletion = true;
- private ApiCollectionType _type = ApiCollectionType.Yourself;
-
- private Dictionary _collections = [];
- private (string, ChangedItemType, uint)[] _changedItems = [];
- private PenumbraApiEc _returnCode = PenumbraApiEc.Success;
- private (Guid Id, string Name)? _oldCollection;
-
- public void Draw()
- {
- using var _ = ImRaii.TreeNode("Collections");
- if (!_)
- return;
-
- ImGuiUtil.GenericEnumCombo("Collection Type", 200, _type, out _type, t => ((CollectionType)t).ToName());
- ImGui.InputInt("Object Index##Collections", ref _objectIdx, 0, 0);
- ImGuiUtil.GuidInput("Collection Id##Collections", "Collection Identifier...", string.Empty, ref _collectionId, ref _collectionIdString);
- ImGui.Checkbox("Allow Assignment Creation", ref _allowCreation);
- ImGui.SameLine();
- ImGui.Checkbox("Allow Assignment Deletion", ref _allowDeletion);
-
- using var table = ImRaii.Table(string.Empty, 4, ImGuiTableFlags.SizingFixedFit);
- if (!table)
- return;
-
- IpcTester.DrawIntro("Last Return Code", _returnCode.ToString());
- if (_oldCollection != null)
- ImGui.TextUnformatted(!_oldCollection.HasValue ? "Created" : _oldCollection.ToString());
-
- IpcTester.DrawIntro(GetCollectionsByIdentifier.Label, "Collection Identifier");
- var collectionList = new GetCollectionsByIdentifier(pi).Invoke(_collectionIdString);
- if (collectionList.Count == 0)
- {
- DrawCollection(null);
- }
- else
- {
- DrawCollection(collectionList[0]);
- foreach (var pair in collectionList.Skip(1))
- {
- ImGui.TableNextRow();
- ImGui.TableNextColumn();
- ImGui.TableNextColumn();
- ImGui.TableNextColumn();
- DrawCollection(pair);
- }
- }
-
- IpcTester.DrawIntro(GetCollection.Label, "Current Collection");
- DrawCollection(new GetCollection(pi).Invoke(ApiCollectionType.Current));
-
- IpcTester.DrawIntro(GetCollection.Label, "Default Collection");
- DrawCollection(new GetCollection(pi).Invoke(ApiCollectionType.Default));
-
- IpcTester.DrawIntro(GetCollection.Label, "Interface Collection");
- DrawCollection(new GetCollection(pi).Invoke(ApiCollectionType.Interface));
-
- IpcTester.DrawIntro(GetCollection.Label, "Special Collection");
- DrawCollection(new GetCollection(pi).Invoke(_type));
-
- IpcTester.DrawIntro(GetCollections.Label, "Collections");
- DrawCollectionPopup();
- if (ImGui.Button("Get##Collections"))
- {
- _collections = new GetCollections(pi).Invoke();
- ImGui.OpenPopup("Collections");
- }
-
- IpcTester.DrawIntro(GetCollectionForObject.Label, "Get Object Collection");
- var (valid, individual, effectiveCollection) = new GetCollectionForObject(pi).Invoke(_objectIdx);
- DrawCollection(effectiveCollection);
- ImGui.SameLine();
- ImGui.TextUnformatted($"({(valid ? "Valid" : "Invalid")} Object{(individual ? ", Individual Assignment)" : ")")}");
-
- IpcTester.DrawIntro(SetCollection.Label, "Set Special Collection");
- if (ImGui.Button("Set##SpecialCollection"))
- (_returnCode, _oldCollection) =
- new SetCollection(pi).Invoke(_type, _collectionId.GetValueOrDefault(Guid.Empty), _allowCreation, _allowDeletion);
- ImGui.TableNextColumn();
- if (ImGui.Button("Remove##SpecialCollection"))
- (_returnCode, _oldCollection) = new SetCollection(pi).Invoke(_type, null, _allowCreation, _allowDeletion);
-
- IpcTester.DrawIntro(SetCollectionForObject.Label, "Set Object Collection");
- if (ImGui.Button("Set##ObjectCollection"))
- (_returnCode, _oldCollection) = new SetCollectionForObject(pi).Invoke(_objectIdx, _collectionId.GetValueOrDefault(Guid.Empty),
- _allowCreation, _allowDeletion);
- ImGui.TableNextColumn();
- if (ImGui.Button("Remove##ObjectCollection"))
- (_returnCode, _oldCollection) = new SetCollectionForObject(pi).Invoke(_objectIdx, null, _allowCreation, _allowDeletion);
-
- IpcTester.DrawIntro(GetChangedItemsForCollection.Label, "Changed Item List");
- DrawChangedItemPopup();
- if (ImGui.Button("Get##ChangedItems"))
- {
- var items = new GetChangedItemsForCollection(pi).Invoke(_collectionId.GetValueOrDefault(Guid.Empty));
- _changedItems = items.Select(kvp =>
- {
- var (type, id) = kvp.Value.ToApiObject();
- return (kvp.Key, type, id);
- }).ToArray();
- ImGui.OpenPopup("Changed Item List");
- }
- IpcTester.DrawIntro(RedrawCollectionMembers.Label, "Redraw Collection Members");
- if (ImGui.Button("Redraw##ObjectCollection"))
- new RedrawCollectionMembers(pi).Invoke(collectionList[0].Id, RedrawType.Redraw);
-
- }
-
- private void DrawChangedItemPopup()
- {
- ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
- using var p = ImRaii.Popup("Changed Item List");
- if (!p)
- return;
-
- using (var table = ImRaii.Table("##ChangedItems", 3, ImGuiTableFlags.SizingFixedFit))
- {
- if (table)
- ImGuiClip.ClippedDraw(_changedItems, t =>
- {
- ImGuiUtil.DrawTableColumn(t.Item1);
- ImGuiUtil.DrawTableColumn(t.Item2.ToString());
- ImGuiUtil.DrawTableColumn(t.Item3.ToString());
- }, ImGui.GetTextLineHeightWithSpacing());
- }
-
- if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
- ImGui.CloseCurrentPopup();
- }
-
- private void DrawCollectionPopup()
- {
- ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
- using var p = ImRaii.Popup("Collections");
- if (!p)
- return;
-
- using (var t = ImRaii.Table("collections", 2, ImGuiTableFlags.SizingFixedFit))
- {
- if (t)
- foreach (var collection in _collections)
- {
- ImGui.TableNextColumn();
- DrawCollection((collection.Key, collection.Value));
- }
- }
-
- if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
- ImGui.CloseCurrentPopup();
- }
-
- private static void DrawCollection((Guid Id, string Name)? collection)
- {
- if (collection == null)
- {
- ImGui.TextUnformatted("");
- ImGui.TableNextColumn();
- return;
- }
-
- ImGui.TextUnformatted(collection.Value.Name);
- ImGui.TableNextColumn();
- using (ImRaii.PushFont(UiBuilder.MonoFont))
- {
- ImGuiUtil.CopyOnClickSelectable(collection.Value.Id.ToString());
- }
- }
-}
diff --git a/Penumbra/Api/IpcTester/EditingIpcTester.cs b/Penumbra/Api/IpcTester/EditingIpcTester.cs
deleted file mode 100644
index d754cf90..00000000
--- a/Penumbra/Api/IpcTester/EditingIpcTester.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using Dalamud.Bindings.ImGui;
-using Dalamud.Plugin;
-using OtterGui;
-using OtterGui.Raii;
-using OtterGui.Services;
-using Penumbra.Api.Enums;
-using Penumbra.Api.IpcSubscribers;
-
-namespace Penumbra.Api.IpcTester;
-
-public class EditingIpcTester(IDalamudPluginInterface pi) : IUiService
-{
- private string _inputPath = string.Empty;
- private string _inputPath2 = string.Empty;
- private string _outputPath = string.Empty;
- private string _outputPath2 = string.Empty;
-
- private TextureType _typeSelector;
- private bool _mipMaps = true;
-
- private Task? _task1;
- private Task? _task2;
-
- public void Draw()
- {
- using var _ = ImRaii.TreeNode("Editing");
- if (!_)
- return;
-
- ImGui.InputTextWithHint("##inputPath", "Input Texture Path...", ref _inputPath, 256);
- ImGui.InputTextWithHint("##outputPath", "Output Texture Path...", ref _outputPath, 256);
- ImGui.InputTextWithHint("##inputPath2", "Input Texture Path 2...", ref _inputPath2, 256);
- ImGui.InputTextWithHint("##outputPath2", "Output Texture Path 2...", ref _outputPath2, 256);
- TypeCombo();
- ImGui.Checkbox("Add MipMaps", ref _mipMaps);
-
- using var table = ImRaii.Table("...", 3, ImGuiTableFlags.SizingFixedFit);
- if (!table)
- return;
-
- IpcTester.DrawIntro(ConvertTextureFile.Label, (string)"Convert Texture 1");
- if (ImGuiUtil.DrawDisabledButton("Save 1", Vector2.Zero, string.Empty, _task1 is { IsCompleted: false }))
- _task1 = new ConvertTextureFile(pi).Invoke(_inputPath, _outputPath, _typeSelector, _mipMaps);
- ImGui.SameLine();
- ImGui.TextUnformatted(_task1 == null ? "Not Initiated" : _task1.Status.ToString());
- if (ImGui.IsItemHovered() && _task1?.Status == TaskStatus.Faulted)
- ImGui.SetTooltip(_task1.Exception?.ToString());
-
- IpcTester.DrawIntro(ConvertTextureFile.Label, (string)"Convert Texture 2");
- if (ImGuiUtil.DrawDisabledButton("Save 2", Vector2.Zero, string.Empty, _task2 is { IsCompleted: false }))
- _task2 = new ConvertTextureFile(pi).Invoke(_inputPath2, _outputPath2, _typeSelector, _mipMaps);
- ImGui.SameLine();
- ImGui.TextUnformatted(_task2 == null ? "Not Initiated" : _task2.Status.ToString());
- if (ImGui.IsItemHovered() && _task2?.Status == TaskStatus.Faulted)
- ImGui.SetTooltip(_task2.Exception?.ToString());
- }
-
- private void TypeCombo()
- {
- using var combo = ImRaii.Combo("Convert To", _typeSelector.ToString());
- if (!combo)
- return;
-
- foreach (var value in Enum.GetValues())
- {
- if (ImGui.Selectable(value.ToString(), _typeSelector == value))
- _typeSelector = value;
- }
- }
-}
diff --git a/Penumbra/Api/IpcTester/GameStateIpcTester.cs b/Penumbra/Api/IpcTester/GameStateIpcTester.cs
deleted file mode 100644
index 38a09714..00000000
--- a/Penumbra/Api/IpcTester/GameStateIpcTester.cs
+++ /dev/null
@@ -1,139 +0,0 @@
-using Dalamud.Bindings.ImGui;
-using Dalamud.Interface;
-using Dalamud.Plugin;
-using OtterGui.Raii;
-using OtterGui.Services;
-using Penumbra.Api.Enums;
-using Penumbra.Api.Helpers;
-using Penumbra.Api.IpcSubscribers;
-using Penumbra.String;
-
-namespace Penumbra.Api.IpcTester;
-
-public class GameStateIpcTester : IUiService, IDisposable
-{
- private readonly IDalamudPluginInterface _pi;
- public readonly EventSubscriber CharacterBaseCreating;
- public readonly EventSubscriber CharacterBaseCreated;
- public readonly EventSubscriber GameObjectResourcePathResolved;
-
- private string _lastCreatedGameObjectName = string.Empty;
- private nint _lastCreatedDrawObject = nint.Zero;
- private DateTimeOffset _lastCreatedGameObjectTime = DateTimeOffset.MaxValue;
- private string _lastResolvedGamePath = string.Empty;
- private string _lastResolvedFullPath = string.Empty;
- private string _lastResolvedObject = string.Empty;
- private DateTimeOffset _lastResolvedGamePathTime = DateTimeOffset.MaxValue;
- private string _currentDrawObjectString = string.Empty;
- private nint _currentDrawObject = nint.Zero;
- private int _currentCutsceneActor;
- private int _currentCutsceneParent;
- private PenumbraApiEc _cutsceneError = PenumbraApiEc.Success;
-
- public GameStateIpcTester(IDalamudPluginInterface pi)
- {
- _pi = pi;
- CharacterBaseCreating = IpcSubscribers.CreatingCharacterBase.Subscriber(pi, UpdateLastCreated);
- CharacterBaseCreated = IpcSubscribers.CreatedCharacterBase.Subscriber(pi, UpdateLastCreated2);
- GameObjectResourcePathResolved = IpcSubscribers.GameObjectResourcePathResolved.Subscriber(pi, UpdateGameObjectResourcePath);
- CharacterBaseCreating.Disable();
- CharacterBaseCreated.Disable();
- GameObjectResourcePathResolved.Disable();
- }
-
- public void Dispose()
- {
- CharacterBaseCreating.Dispose();
- CharacterBaseCreated.Dispose();
- GameObjectResourcePathResolved.Dispose();
- }
-
- public void Draw()
- {
- using var _ = ImRaii.TreeNode("Game State");
- if (!_)
- return;
-
- if (ImGui.InputTextWithHint("##drawObject", "Draw Object Address..", ref _currentDrawObjectString, 16,
- ImGuiInputTextFlags.CharsHexadecimal))
- _currentDrawObject = nint.TryParse(_currentDrawObjectString, NumberStyles.HexNumber, CultureInfo.InvariantCulture,
- out var tmp)
- ? tmp
- : nint.Zero;
-
- ImGui.InputInt("Cutscene Actor", ref _currentCutsceneActor, 0);
- ImGui.InputInt("Cutscene Parent", ref _currentCutsceneParent, 0);
- if (_cutsceneError is not PenumbraApiEc.Success)
- {
- ImGui.SameLine();
- ImGui.TextUnformatted("Invalid Argument on last Call");
- }
-
- using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
- if (!table)
- return;
-
- IpcTester.DrawIntro(GetDrawObjectInfo.Label, "Draw Object Info");
- if (_currentDrawObject == nint.Zero)
- {
- ImGui.TextUnformatted("Invalid");
- }
- else
- {
- var (ptr, (collectionId, collectionName)) = new GetDrawObjectInfo(_pi).Invoke(_currentDrawObject);
- ImGui.TextUnformatted(ptr == nint.Zero ? $"No Actor Associated, {collectionName}" : $"{ptr:X}, {collectionName}");
- ImGui.SameLine();
- using (ImRaii.PushFont(UiBuilder.MonoFont))
- {
- ImGui.TextUnformatted(collectionId.ToString());
- }
- }
-
- IpcTester.DrawIntro(GetCutsceneParentIndex.Label, "Cutscene Parent");
- ImGui.TextUnformatted(new GetCutsceneParentIndex(_pi).Invoke(_currentCutsceneActor).ToString());
-
- IpcTester.DrawIntro(SetCutsceneParentIndex.Label, "Cutscene Parent");
- if (ImGui.Button("Set Parent"))
- _cutsceneError = new SetCutsceneParentIndex(_pi)
- .Invoke(_currentCutsceneActor, _currentCutsceneParent);
-
- IpcTester.DrawIntro(CreatingCharacterBase.Label, "Last Drawobject created");
- if (_lastCreatedGameObjectTime < DateTimeOffset.Now)
- ImGui.TextUnformatted(_lastCreatedDrawObject != nint.Zero
- ? $"0x{_lastCreatedDrawObject:X} for <{_lastCreatedGameObjectName}> at {_lastCreatedGameObjectTime}"
- : $"NULL for <{_lastCreatedGameObjectName}> at {_lastCreatedGameObjectTime}");
-
- IpcTester.DrawIntro(IpcSubscribers.GameObjectResourcePathResolved.Label, "Last GamePath resolved");
- if (_lastResolvedGamePathTime < DateTimeOffset.Now)
- ImGui.TextUnformatted(
- $"{_lastResolvedGamePath} -> {_lastResolvedFullPath} for <{_lastResolvedObject}> at {_lastResolvedGamePathTime}");
- }
-
- private void UpdateLastCreated(nint gameObject, Guid _, nint _2, nint _3, nint _4)
- {
- _lastCreatedGameObjectName = GetObjectName(gameObject);
- _lastCreatedGameObjectTime = DateTimeOffset.Now;
- _lastCreatedDrawObject = nint.Zero;
- }
-
- private void UpdateLastCreated2(nint gameObject, Guid _, nint drawObject)
- {
- _lastCreatedGameObjectName = GetObjectName(gameObject);
- _lastCreatedGameObjectTime = DateTimeOffset.Now;
- _lastCreatedDrawObject = drawObject;
- }
-
- private void UpdateGameObjectResourcePath(nint gameObject, string gamePath, string fullPath)
- {
- _lastResolvedObject = GetObjectName(gameObject);
- _lastResolvedGamePath = gamePath;
- _lastResolvedFullPath = fullPath;
- _lastResolvedGamePathTime = DateTimeOffset.Now;
- }
-
- private static unsafe string GetObjectName(nint gameObject)
- {
- var obj = (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)gameObject;
- return obj != null && obj->Name[0] != 0 ? new ByteString(obj->Name).ToString() : "Unknown";
- }
-}
diff --git a/Penumbra/Api/IpcTester/IpcTester.cs b/Penumbra/Api/IpcTester/IpcTester.cs
deleted file mode 100644
index b03d7e03..00000000
--- a/Penumbra/Api/IpcTester/IpcTester.cs
+++ /dev/null
@@ -1,133 +0,0 @@
-using Dalamud.Plugin.Services;
-using FFXIVClientStructs.FFXIV.Client.System.Framework;
-using Dalamud.Bindings.ImGui;
-using OtterGui.Services;
-using Penumbra.Api.Api;
-
-namespace Penumbra.Api.IpcTester;
-
-public class IpcTester(
- IpcProviders ipcProviders,
- IPenumbraApi api,
- PluginStateIpcTester pluginStateIpcTester,
- UiIpcTester uiIpcTester,
- RedrawingIpcTester redrawingIpcTester,
- GameStateIpcTester gameStateIpcTester,
- ResolveIpcTester resolveIpcTester,
- CollectionsIpcTester collectionsIpcTester,
- MetaIpcTester metaIpcTester,
- ModsIpcTester modsIpcTester,
- ModSettingsIpcTester modSettingsIpcTester,
- EditingIpcTester editingIpcTester,
- TemporaryIpcTester temporaryIpcTester,
- ResourceTreeIpcTester resourceTreeIpcTester,
- IFramework framework) : IUiService
-{
- private readonly IpcProviders _ipcProviders = ipcProviders;
- private DateTime _lastUpdate;
- private bool _subscribed = false;
-
- public void Draw()
- {
- try
- {
- _lastUpdate = framework.LastUpdateUTC.AddSeconds(1);
- Subscribe();
-
- ImGui.TextUnformatted($"API Version: {api.ApiVersion.Breaking}.{api.ApiVersion.Feature:D4}");
- collectionsIpcTester.Draw();
- editingIpcTester.Draw();
- gameStateIpcTester.Draw();
- metaIpcTester.Draw();
- modSettingsIpcTester.Draw();
- modsIpcTester.Draw();
- pluginStateIpcTester.Draw();
- redrawingIpcTester.Draw();
- resolveIpcTester.Draw();
- resourceTreeIpcTester.Draw();
- uiIpcTester.Draw();
- temporaryIpcTester.Draw();
- temporaryIpcTester.DrawCollections();
- temporaryIpcTester.DrawMods();
- }
- catch (Exception e)
- {
- Penumbra.Log.Error($"Error during IPC Tests:\n{e}");
- }
- }
-
- internal static void DrawIntro(string label, string info)
- {
- ImGui.TableNextRow();
- ImGui.TableNextColumn();
- ImGui.TextUnformatted(label);
- ImGui.TableNextColumn();
- ImGui.TextUnformatted(info);
- ImGui.TableNextColumn();
- }
-
- private void Subscribe()
- {
- if (_subscribed)
- return;
-
- Penumbra.Log.Debug("[IPCTester] Subscribed to IPC events for IPC tester.");
- gameStateIpcTester.GameObjectResourcePathResolved.Enable();
- gameStateIpcTester.CharacterBaseCreated.Enable();
- gameStateIpcTester.CharacterBaseCreating.Enable();
- modSettingsIpcTester.SettingChanged.Enable();
- modsIpcTester.DeleteSubscriber.Enable();
- modsIpcTester.AddSubscriber.Enable();
- modsIpcTester.MoveSubscriber.Enable();
- pluginStateIpcTester.ModDirectoryChanged.Enable();
- pluginStateIpcTester.Initialized.Enable();
- pluginStateIpcTester.Disposed.Enable();
- pluginStateIpcTester.EnabledChange.Enable();
- redrawingIpcTester.Redrawn.Enable();
- uiIpcTester.PreSettingsTabBar.Enable();
- uiIpcTester.PreSettingsPanel.Enable();
- uiIpcTester.PostEnabled.Enable();
- uiIpcTester.PostSettingsPanelDraw.Enable();
- uiIpcTester.ChangedItemTooltip.Enable();
- uiIpcTester.ChangedItemClicked.Enable();
-
- framework.Update += CheckUnsubscribe;
- _subscribed = true;
- }
-
- private void CheckUnsubscribe(IFramework framework1)
- {
- if (_lastUpdate > framework.LastUpdateUTC)
- return;
-
- Unsubscribe();
- framework.Update -= CheckUnsubscribe;
- }
-
- private void Unsubscribe()
- {
- if (!_subscribed)
- return;
-
- Penumbra.Log.Debug("[IPCTester] Unsubscribed from IPC events for IPC tester.");
- _subscribed = false;
- gameStateIpcTester.GameObjectResourcePathResolved.Disable();
- gameStateIpcTester.CharacterBaseCreated.Disable();
- gameStateIpcTester.CharacterBaseCreating.Disable();
- modSettingsIpcTester.SettingChanged.Disable();
- modsIpcTester.DeleteSubscriber.Disable();
- modsIpcTester.AddSubscriber.Disable();
- modsIpcTester.MoveSubscriber.Disable();
- pluginStateIpcTester.ModDirectoryChanged.Disable();
- pluginStateIpcTester.Initialized.Disable();
- pluginStateIpcTester.Disposed.Disable();
- pluginStateIpcTester.EnabledChange.Disable();
- redrawingIpcTester.Redrawn.Disable();
- uiIpcTester.PreSettingsTabBar.Disable();
- uiIpcTester.PreSettingsPanel.Disable();
- uiIpcTester.PostEnabled.Disable();
- uiIpcTester.PostSettingsPanelDraw.Disable();
- uiIpcTester.ChangedItemTooltip.Disable();
- uiIpcTester.ChangedItemClicked.Disable();
- }
-}
diff --git a/Penumbra/Api/IpcTester/MetaIpcTester.cs b/Penumbra/Api/IpcTester/MetaIpcTester.cs
deleted file mode 100644
index bee1981c..00000000
--- a/Penumbra/Api/IpcTester/MetaIpcTester.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-using Dalamud.Bindings.ImGui;
-using Dalamud.Plugin;
-using OtterGui.Raii;
-using OtterGui.Services;
-using OtterGui.Text;
-using Penumbra.Api.Api;
-using Penumbra.Api.IpcSubscribers;
-using Penumbra.Meta.Manipulations;
-
-namespace Penumbra.Api.IpcTester;
-
-public class MetaIpcTester(IDalamudPluginInterface pi) : IUiService
-{
- private int _gameObjectIndex;
- private string _metaBase64 = string.Empty;
- private MetaDictionary _metaDict = new();
- private byte _parsedVersion = byte.MaxValue;
-
- public void Draw()
- {
- using var _ = ImRaii.TreeNode("Meta");
- if (!_)
- return;
-
- ImGui.InputInt("##metaIdx", ref _gameObjectIndex, 0, 0);
- if (ImUtf8.InputText("##metaText"u8, ref _metaBase64, "Base64 Metadata..."u8))
- if (!MetaApi.ConvertManips(_metaBase64, out _metaDict!, out _parsedVersion))
- _metaDict ??= new MetaDictionary();
-
-
- using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
- if (!table)
- return;
-
- IpcTester.DrawIntro(GetPlayerMetaManipulations.Label, "Player Meta Manipulations");
- if (ImGui.Button("Copy to Clipboard##Player"))
- {
- var base64 = new GetPlayerMetaManipulations(pi).Invoke();
- ImGui.SetClipboardText(base64);
- }
-
- IpcTester.DrawIntro(GetMetaManipulations.Label, "Game Object Manipulations");
- if (ImGui.Button("Copy to Clipboard##GameObject"))
- {
- var base64 = new GetMetaManipulations(pi).Invoke(_gameObjectIndex);
- ImGui.SetClipboardText(base64);
- }
-
- IpcTester.DrawIntro(string.Empty, "Parsed Data");
- ImUtf8.Text($"Version: {_parsedVersion}, Count: {_metaDict.Count}");
- }
-}
diff --git a/Penumbra/Api/IpcTester/ModSettingsIpcTester.cs b/Penumbra/Api/IpcTester/ModSettingsIpcTester.cs
deleted file mode 100644
index 152efa45..00000000
--- a/Penumbra/Api/IpcTester/ModSettingsIpcTester.cs
+++ /dev/null
@@ -1,224 +0,0 @@
-using Dalamud.Bindings.ImGui;
-using Dalamud.Plugin;
-using OtterGui;
-using OtterGui.Raii;
-using OtterGui.Services;
-using OtterGui.Text;
-using Penumbra.Api.Enums;
-using Penumbra.Api.Helpers;
-using Penumbra.Api.IpcSubscribers;
-using Penumbra.UI;
-
-namespace Penumbra.Api.IpcTester;
-
-public class ModSettingsIpcTester : IUiService, IDisposable
-{
- private readonly IDalamudPluginInterface _pi;
- public readonly EventSubscriber SettingChanged;
-
- private PenumbraApiEc _lastSettingsError = PenumbraApiEc.Success;
- private ModSettingChange _lastSettingChangeType;
- private Guid _lastSettingChangeCollection = Guid.Empty;
- private string _lastSettingChangeMod = string.Empty;
- private bool _lastSettingChangeInherited;
- private DateTimeOffset _lastSettingChange;
-
- private string _settingsModDirectory = string.Empty;
- private string _settingsModName = string.Empty;
- private Guid? _settingsCollection;
- private string _settingsCollectionName = string.Empty;
- private bool _settingsIgnoreInheritance;
- private bool _settingsIgnoreTemporary;
- private int _settingsKey;
- private bool _settingsInherit;
- private bool _settingsTemporary;
- private bool _settingsEnabled;
- private int _settingsPriority;
- private IReadOnlyDictionary? _availableSettings;
- private Dictionary>? _currentSettings;
- private Dictionary>, bool, bool)>? _allSettings;
-
- public ModSettingsIpcTester(IDalamudPluginInterface pi)
- {
- _pi = pi;
- SettingChanged = ModSettingChanged.Subscriber(pi, UpdateLastModSetting);
- SettingChanged.Disable();
- }
-
- public void Dispose()
- {
- SettingChanged.Dispose();
- }
-
- public void Draw()
- {
- using var _ = ImRaii.TreeNode("Mod Settings");
- if (!_)
- return;
-
- ImGui.InputTextWithHint("##settingsDir", "Mod Directory Name...", ref _settingsModDirectory, 100);
- ImGui.InputTextWithHint("##settingsName", "Mod Name...", ref _settingsModName, 100);
- ImGuiUtil.GuidInput("##settingsCollection", "Collection...", string.Empty, ref _settingsCollection, ref _settingsCollectionName);
- ImUtf8.Checkbox("Ignore Inheritance"u8, ref _settingsIgnoreInheritance);
- ImUtf8.Checkbox("Ignore Temporary"u8, ref _settingsIgnoreTemporary);
- ImUtf8.InputScalar("Key"u8, ref _settingsKey);
- var collection = _settingsCollection.GetValueOrDefault(Guid.Empty);
-
- using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
- if (!table)
- return;
-
- IpcTester.DrawIntro("Last Error", _lastSettingsError.ToString());
-
- IpcTester.DrawIntro(ModSettingChanged.Label, "Last Mod Setting Changed");
- ImGui.TextUnformatted(_lastSettingChangeMod.Length > 0
- ? $"{_lastSettingChangeType} of {_lastSettingChangeMod} in {_lastSettingChangeCollection}{(_lastSettingChangeInherited ? " (Inherited)" : string.Empty)} at {_lastSettingChange}"
- : "None");
-
- IpcTester.DrawIntro(GetAvailableModSettings.Label, "Get Available Settings");
- if (ImGui.Button("Get##Available"))
- {
- _availableSettings = new GetAvailableModSettings(_pi).Invoke(_settingsModDirectory, _settingsModName);
- _lastSettingsError = _availableSettings == null ? PenumbraApiEc.ModMissing : PenumbraApiEc.Success;
- }
-
- IpcTester.DrawIntro(GetCurrentModSettings.Label, "Get Current Settings");
- if (ImGui.Button("Get##Current"))
- {
- var ret = new GetCurrentModSettings(_pi)
- .Invoke(collection, _settingsModDirectory, _settingsModName, _settingsIgnoreInheritance);
- _lastSettingsError = ret.Item1;
- if (ret.Item1 == PenumbraApiEc.Success)
- {
- _settingsEnabled = ret.Item2?.Item1 ?? false;
- _settingsInherit = ret.Item2?.Item4 ?? true;
- _settingsTemporary = false;
- _settingsPriority = ret.Item2?.Item2 ?? 0;
- _currentSettings = ret.Item2?.Item3;
- }
- else
- {
- _currentSettings = null;
- }
- }
-
- IpcTester.DrawIntro(GetCurrentModSettingsWithTemp.Label, "Get Current Settings With Temp");
- if (ImGui.Button("Get##CurrentTemp"))
- {
- var ret = new GetCurrentModSettingsWithTemp(_pi)
- .Invoke(collection, _settingsModDirectory, _settingsModName, _settingsIgnoreInheritance, _settingsIgnoreTemporary, _settingsKey);
- _lastSettingsError = ret.Item1;
- if (ret.Item1 == PenumbraApiEc.Success)
- {
- _settingsEnabled = ret.Item2?.Item1 ?? false;
- _settingsInherit = ret.Item2?.Item4 ?? true;
- _settingsTemporary = ret.Item2?.Item5 ?? false;
- _settingsPriority = ret.Item2?.Item2 ?? 0;
- _currentSettings = ret.Item2?.Item3;
- }
- else
- {
- _currentSettings = null;
- }
- }
-
- IpcTester.DrawIntro(GetAllModSettings.Label, "Get All Mod Settings");
- if (ImGui.Button("Get##All"))
- {
- var ret = new GetAllModSettings(_pi).Invoke(collection, _settingsIgnoreInheritance, _settingsIgnoreTemporary, _settingsKey);
- _lastSettingsError = ret.Item1;
- _allSettings = ret.Item2;
- }
-
- if (_allSettings != null)
- {
- ImGui.SameLine();
- ImUtf8.Text($"{_allSettings.Count} Mods");
- }
-
- IpcTester.DrawIntro(TryInheritMod.Label, "Inherit Mod");
- ImGui.Checkbox("##inherit", ref _settingsInherit);
- ImGui.SameLine();
- if (ImGui.Button("Set##Inherit"))
- _lastSettingsError = new TryInheritMod(_pi)
- .Invoke(collection, _settingsModDirectory, _settingsInherit, _settingsModName);
-
- IpcTester.DrawIntro(TrySetMod.Label, "Set Enabled");
- ImGui.Checkbox("##enabled", ref _settingsEnabled);
- ImGui.SameLine();
- if (ImGui.Button("Set##Enabled"))
- _lastSettingsError = new TrySetMod(_pi)
- .Invoke(collection, _settingsModDirectory, _settingsEnabled, _settingsModName);
-
- IpcTester.DrawIntro(TrySetModPriority.Label, "Set Priority");
- ImGui.SetNextItemWidth(200 * UiHelpers.Scale);
- ImGui.DragInt("##Priority", ref _settingsPriority);
- ImGui.SameLine();
- if (ImGui.Button("Set##Priority"))
- _lastSettingsError = new TrySetModPriority(_pi)
- .Invoke(collection, _settingsModDirectory, _settingsPriority, _settingsModName);
-
- IpcTester.DrawIntro(CopyModSettings.Label, "Copy Mod Settings");
- if (ImGui.Button("Copy Settings"))
- _lastSettingsError = new CopyModSettings(_pi)
- .Invoke(_settingsCollection, _settingsModDirectory, _settingsModName);
-
- ImGuiUtil.HoverTooltip("Copy settings from Mod Directory Name to Mod Name (as directory) in collection.");
-
- IpcTester.DrawIntro(TrySetModSetting.Label, "Set Setting(s)");
- if (_availableSettings == null)
- return;
-
- foreach (var (group, (list, type)) in _availableSettings)
- {
- using var id = ImRaii.PushId(group);
- var preview = list.Length > 0 ? list[0] : string.Empty;
- if (_currentSettings != null && _currentSettings.TryGetValue(group, out var current) && current.Count > 0)
- {
- preview = current[0];
- }
- else
- {
- current = [];
- if (_currentSettings != null)
- _currentSettings[group] = current;
- }
-
- ImGui.SetNextItemWidth(200 * UiHelpers.Scale);
- using (var c = ImRaii.Combo("##group", preview))
- {
- if (c)
- foreach (var s in list)
- {
- var contained = current.Contains(s);
- if (ImGui.Checkbox(s, ref contained))
- {
- if (contained)
- current.Add(s);
- else
- current.Remove(s);
- }
- }
- }
-
- ImGui.SameLine();
- if (ImGui.Button("Set##setting"))
- _lastSettingsError = type == GroupType.Single
- ? new TrySetModSetting(_pi).Invoke(collection, _settingsModDirectory, group, current.Count > 0 ? current[0] : string.Empty,
- _settingsModName)
- : new TrySetModSettings(_pi).Invoke(collection, _settingsModDirectory, group, current.ToArray(), _settingsModName);
-
- ImGui.SameLine();
- ImGui.TextUnformatted(group);
- }
- }
-
- private void UpdateLastModSetting(ModSettingChange type, Guid collection, string mod, bool inherited)
- {
- _lastSettingChangeType = type;
- _lastSettingChangeCollection = collection;
- _lastSettingChangeMod = mod;
- _lastSettingChangeInherited = inherited;
- _lastSettingChange = DateTimeOffset.Now;
- }
-}
diff --git a/Penumbra/Api/IpcTester/ModsIpcTester.cs b/Penumbra/Api/IpcTester/ModsIpcTester.cs
deleted file mode 100644
index 9ea53366..00000000
--- a/Penumbra/Api/IpcTester/ModsIpcTester.cs
+++ /dev/null
@@ -1,184 +0,0 @@
-using Dalamud.Bindings.ImGui;
-using Dalamud.Interface.Utility;
-using Dalamud.Plugin;
-using OtterGui.Raii;
-using OtterGui.Services;
-using OtterGui.Text;
-using Penumbra.Api.Enums;
-using Penumbra.Api.Helpers;
-using Penumbra.Api.IpcSubscribers;
-
-namespace Penumbra.Api.IpcTester;
-
-public class ModsIpcTester : IUiService, IDisposable
-{
- private readonly IDalamudPluginInterface _pi;
-
- private string _modDirectory = string.Empty;
- private string _modName = string.Empty;
- private string _pathInput = string.Empty;
- private string _newInstallPath = string.Empty;
- private PenumbraApiEc _lastReloadEc;
- private PenumbraApiEc _lastAddEc;
- private PenumbraApiEc _lastDeleteEc;
- private PenumbraApiEc _lastSetPathEc;
- private PenumbraApiEc _lastInstallEc;
- private Dictionary _mods = [];
- private Dictionary _changedItems = [];
-
- public readonly EventSubscriber DeleteSubscriber;
- public readonly EventSubscriber AddSubscriber;
- public readonly EventSubscriber MoveSubscriber;
-
- private DateTimeOffset _lastDeletedModTime = DateTimeOffset.UnixEpoch;
- private string _lastDeletedMod = string.Empty;
- private DateTimeOffset _lastAddedModTime = DateTimeOffset.UnixEpoch;
- private string _lastAddedMod = string.Empty;
- private DateTimeOffset _lastMovedModTime = DateTimeOffset.UnixEpoch;
- private string _lastMovedModFrom = string.Empty;
- private string _lastMovedModTo = string.Empty;
-
- public ModsIpcTester(IDalamudPluginInterface pi)
- {
- _pi = pi;
- DeleteSubscriber = ModDeleted.Subscriber(pi, s =>
- {
- _lastDeletedModTime = DateTimeOffset.UtcNow;
- _lastDeletedMod = s;
- });
- AddSubscriber = ModAdded.Subscriber(pi, s =>
- {
- _lastAddedModTime = DateTimeOffset.UtcNow;
- _lastAddedMod = s;
- });
- MoveSubscriber = ModMoved.Subscriber(pi, (s1, s2) =>
- {
- _lastMovedModTime = DateTimeOffset.UtcNow;
- _lastMovedModFrom = s1;
- _lastMovedModTo = s2;
- });
- DeleteSubscriber.Disable();
- AddSubscriber.Disable();
- MoveSubscriber.Disable();
- }
-
- public void Dispose()
- {
- DeleteSubscriber.Dispose();
- DeleteSubscriber.Disable();
- AddSubscriber.Dispose();
- AddSubscriber.Disable();
- MoveSubscriber.Dispose();
- MoveSubscriber.Disable();
- }
-
- public void Draw()
- {
- using var _ = ImRaii.TreeNode("Mods");
- if (!_)
- return;
-
- ImGui.InputTextWithHint("##install", "Install File Path...", ref _newInstallPath, 100);
- ImGui.InputTextWithHint("##modDir", "Mod Directory Name...", ref _modDirectory, 100);
- ImGui.InputTextWithHint("##modName", "Mod Name...", ref _modName, 100);
- ImGui.InputTextWithHint("##path", "New Path...", ref _pathInput, 100);
- using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
- if (!table)
- return;
-
- IpcTester.DrawIntro(GetModList.Label, "Mods");
- DrawModsPopup();
- if (ImGui.Button("Get##Mods"))
- {
- _mods = new GetModList(_pi).Invoke();
- ImGui.OpenPopup("Mods");
- }
-
- IpcTester.DrawIntro(ReloadMod.Label, "Reload Mod");
- if (ImGui.Button("Reload"))
- _lastReloadEc = new ReloadMod(_pi).Invoke(_modDirectory, _modName);
-
- ImGui.SameLine();
- ImGui.TextUnformatted(_lastReloadEc.ToString());
-
- IpcTester.DrawIntro(InstallMod.Label, "Install Mod");
- if (ImGui.Button("Install"))
- _lastInstallEc = new InstallMod(_pi).Invoke(_newInstallPath);
-
- ImGui.SameLine();
- ImGui.TextUnformatted(_lastInstallEc.ToString());
-
- IpcTester.DrawIntro(AddMod.Label, "Add Mod");
- if (ImGui.Button("Add"))
- _lastAddEc = new AddMod(_pi).Invoke(_modDirectory);
-
- ImGui.SameLine();
- ImGui.TextUnformatted(_lastAddEc.ToString());
-
- IpcTester.DrawIntro(DeleteMod.Label, "Delete Mod");
- if (ImGui.Button("Delete"))
- _lastDeleteEc = new DeleteMod(_pi).Invoke(_modDirectory, _modName);
-
- ImGui.SameLine();
- ImGui.TextUnformatted(_lastDeleteEc.ToString());
-
- IpcTester.DrawIntro(GetChangedItems.Label, "Get Changed Items");
- DrawChangedItemsPopup();
- if (ImUtf8.Button("Get##ChangedItems"u8))
- {
- _changedItems = new GetChangedItems(_pi).Invoke(_modDirectory, _modName);
- ImUtf8.OpenPopup("ChangedItems"u8);
- }
-
- IpcTester.DrawIntro(GetModPath.Label, "Current Path");
- var (ec, path, def, nameDef) = new GetModPath(_pi).Invoke(_modDirectory, _modName);
- ImGui.TextUnformatted($"{path} ({(def ? "Custom" : "Default")} Path, {(nameDef ? "Custom" : "Default")} Name) [{ec}]");
-
- IpcTester.DrawIntro(SetModPath.Label, "Set Path");
- if (ImGui.Button("Set"))
- _lastSetPathEc = new SetModPath(_pi).Invoke(_modDirectory, _pathInput, _modName);
-
- ImGui.SameLine();
- ImGui.TextUnformatted(_lastSetPathEc.ToString());
-
- IpcTester.DrawIntro(ModDeleted.Label, "Last Mod Deleted");
- if (_lastDeletedModTime > DateTimeOffset.UnixEpoch)
- ImGui.TextUnformatted($"{_lastDeletedMod} at {_lastDeletedModTime}");
-
- IpcTester.DrawIntro(ModAdded.Label, "Last Mod Added");
- if (_lastAddedModTime > DateTimeOffset.UnixEpoch)
- ImGui.TextUnformatted($"{_lastAddedMod} at {_lastAddedModTime}");
-
- IpcTester.DrawIntro(ModMoved.Label, "Last Mod Moved");
- if (_lastMovedModTime > DateTimeOffset.UnixEpoch)
- ImGui.TextUnformatted($"{_lastMovedModFrom} -> {_lastMovedModTo} at {_lastMovedModTime}");
- }
-
- private void DrawModsPopup()
- {
- ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
- using var p = ImRaii.Popup("Mods");
- if (!p)
- return;
-
- foreach (var (modDir, modName) in _mods)
- ImGui.TextUnformatted($"{modDir}: {modName}");
-
- if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
- ImGui.CloseCurrentPopup();
- }
-
- private void DrawChangedItemsPopup()
- {
- ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
- using var p = ImUtf8.Popup("ChangedItems"u8);
- if (!p)
- return;
-
- foreach (var (name, data) in _changedItems)
- ImUtf8.Text($"{name}: {data}");
-
- if (ImUtf8.Button("Close"u8, -Vector2.UnitX) || !ImGui.IsWindowFocused())
- ImGui.CloseCurrentPopup();
- }
-}
diff --git a/Penumbra/Api/IpcTester/PluginStateIpcTester.cs b/Penumbra/Api/IpcTester/PluginStateIpcTester.cs
deleted file mode 100644
index 073305d0..00000000
--- a/Penumbra/Api/IpcTester/PluginStateIpcTester.cs
+++ /dev/null
@@ -1,147 +0,0 @@
-using Dalamud.Interface;
-using Dalamud.Interface.Utility;
-using Dalamud.Plugin;
-using Dalamud.Bindings.ImGui;
-using OtterGui;
-using OtterGui.Raii;
-using OtterGui.Services;
-using OtterGui.Text;
-using Penumbra.Api.Helpers;
-using Penumbra.Api.IpcSubscribers;
-
-namespace Penumbra.Api.IpcTester;
-
-public class PluginStateIpcTester : IUiService, IDisposable
-{
- private readonly IDalamudPluginInterface _pi;
- public readonly EventSubscriber ModDirectoryChanged;
- public readonly EventSubscriber Initialized;
- public readonly EventSubscriber Disposed;
- public readonly EventSubscriber EnabledChange;
-
- private string _currentConfiguration = string.Empty;
- private string _lastModDirectory = string.Empty;
- private bool _lastModDirectoryValid;
- private DateTimeOffset _lastModDirectoryTime = DateTimeOffset.MinValue;
-
- private readonly List _initializedList = [];
- private readonly List _disposedList = [];
-
- private string _requiredFeatureString = string.Empty;
- private string[] _requiredFeatures = [];
-
- private DateTimeOffset _lastEnabledChange = DateTimeOffset.UnixEpoch;
- private bool? _lastEnabledValue;
-
- public PluginStateIpcTester(IDalamudPluginInterface pi)
- {
- _pi = pi;
- ModDirectoryChanged = IpcSubscribers.ModDirectoryChanged.Subscriber(pi, UpdateModDirectoryChanged);
- Initialized = IpcSubscribers.Initialized.Subscriber(pi, AddInitialized);
- Disposed = IpcSubscribers.Disposed.Subscriber(pi, AddDisposed);
- EnabledChange = IpcSubscribers.EnabledChange.Subscriber(pi, SetLastEnabled);
- ModDirectoryChanged.Disable();
- EnabledChange.Disable();
- }
-
- public void Dispose()
- {
- ModDirectoryChanged.Dispose();
- Initialized.Dispose();
- Disposed.Dispose();
- EnabledChange.Dispose();
- }
-
-
- public void Draw()
- {
- using var _ = ImRaii.TreeNode("Plugin State");
- if (!_)
- return;
-
- if (ImUtf8.InputText("Required Features"u8, ref _requiredFeatureString))
- _requiredFeatures = _requiredFeatureString.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
- using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
- if (!table)
- return;
-
- DrawList(IpcSubscribers.Initialized.Label, "Last Initialized", _initializedList);
- DrawList(IpcSubscribers.Disposed.Label, "Last Disposed", _disposedList);
-
- IpcTester.DrawIntro(ApiVersion.Label, "Current Version");
- var (breaking, features) = new ApiVersion(_pi).Invoke();
- ImGui.TextUnformatted($"{breaking}.{features:D4}");
-
- IpcTester.DrawIntro(GetEnabledState.Label, "Current State");
- ImGui.TextUnformatted($"{new GetEnabledState(_pi).Invoke()}");
-
- IpcTester.DrawIntro(IpcSubscribers.EnabledChange.Label, "Last Change");
- ImGui.TextUnformatted(_lastEnabledValue is { } v ? $"{_lastEnabledChange} (to {v})" : "Never");
-
- IpcTester.DrawIntro(SupportedFeatures.Label, "Supported Features");
- ImUtf8.Text(string.Join(", ", new SupportedFeatures(_pi).Invoke()));
-
- IpcTester.DrawIntro(CheckSupportedFeatures.Label, "Missing Features");
- ImUtf8.Text(string.Join(", ", new CheckSupportedFeatures(_pi).Invoke(_requiredFeatures)));
-
- DrawConfigPopup();
- IpcTester.DrawIntro(GetConfiguration.Label, "Configuration");
- if (ImGui.Button("Get"))
- {
- _currentConfiguration = new GetConfiguration(_pi).Invoke();
- ImGui.OpenPopup("Config Popup");
- }
-
- IpcTester.DrawIntro(GetModDirectory.Label, "Current Mod Directory");
- ImGui.TextUnformatted(new GetModDirectory(_pi).Invoke());
-
- IpcTester.DrawIntro(IpcSubscribers.ModDirectoryChanged.Label, "Last Mod Directory Change");
- ImGui.TextUnformatted(_lastModDirectoryTime > DateTimeOffset.MinValue
- ? $"{_lastModDirectory} ({(_lastModDirectoryValid ? "Valid" : "Invalid")}) at {_lastModDirectoryTime}"
- : "None");
-
- void DrawList(string label, string text, List list)
- {
- IpcTester.DrawIntro(label, text);
- if (list.Count == 0)
- {
- ImGui.TextUnformatted("Never");
- }
- else
- {
- ImGui.TextUnformatted(list[^1].LocalDateTime.ToString(CultureInfo.CurrentCulture));
- if (list.Count > 1 && ImGui.IsItemHovered())
- ImGui.SetTooltip(string.Join("\n",
- list.SkipLast(1).Select(t => t.LocalDateTime.ToString(CultureInfo.CurrentCulture))));
- }
- }
- }
-
- private void DrawConfigPopup()
- {
- ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(500, 500));
- using var popup = ImRaii.Popup("Config Popup");
- if (!popup)
- return;
-
- using (ImRaii.PushFont(UiBuilder.MonoFont))
- {
- ImGuiUtil.TextWrapped(_currentConfiguration);
- }
-
- if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
- ImGui.CloseCurrentPopup();
- }
-
- private void UpdateModDirectoryChanged(string path, bool valid)
- => (_lastModDirectory, _lastModDirectoryValid, _lastModDirectoryTime) = (path, valid, DateTimeOffset.Now);
-
- private void AddInitialized()
- => _initializedList.Add(DateTimeOffset.UtcNow);
-
- private void AddDisposed()
- => _disposedList.Add(DateTimeOffset.UtcNow);
-
- private void SetLastEnabled(bool val)
- => (_lastEnabledChange, _lastEnabledValue) = (DateTimeOffset.Now, val);
-}
diff --git a/Penumbra/Api/IpcTester/RedrawingIpcTester.cs b/Penumbra/Api/IpcTester/RedrawingIpcTester.cs
deleted file mode 100644
index 6b853ed2..00000000
--- a/Penumbra/Api/IpcTester/RedrawingIpcTester.cs
+++ /dev/null
@@ -1,73 +0,0 @@
-using Dalamud.Plugin;
-using Dalamud.Plugin.Services;
-using Dalamud.Bindings.ImGui;
-using OtterGui.Raii;
-using OtterGui.Services;
-using Penumbra.Api.Enums;
-using Penumbra.Api.Helpers;
-using Penumbra.Api.IpcSubscribers;
-using Penumbra.GameData.Interop;
-using Penumbra.UI;
-
-namespace Penumbra.Api.IpcTester;
-
-public class RedrawingIpcTester : IUiService, IDisposable
-{
- private readonly IDalamudPluginInterface _pi;
- private readonly ObjectManager _objects;
- public readonly EventSubscriber Redrawn;
-
- private int _redrawIndex;
- private string _lastRedrawnString = "None";
-
- public RedrawingIpcTester(IDalamudPluginInterface pi, ObjectManager objects)
- {
- _pi = pi;
- _objects = objects;
- Redrawn = GameObjectRedrawn.Subscriber(_pi, SetLastRedrawn);
- Redrawn.Disable();
- }
-
- public void Dispose()
- {
- Redrawn.Dispose();
- }
-
- public void Draw()
- {
- using var _ = ImRaii.TreeNode("Redrawing");
- if (!_)
- return;
-
- using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
- if (!table)
- return;
-
- IpcTester.DrawIntro(RedrawObject.Label, "Redraw by Index");
- var tmp = _redrawIndex;
- ImGui.SetNextItemWidth(100 * UiHelpers.Scale);
- if (ImGui.DragInt("##redrawIndex", ref tmp, 0.1f, 0, _objects.TotalCount))
- _redrawIndex = Math.Clamp(tmp, 0, _objects.TotalCount);
- ImGui.SameLine();
- if (ImGui.Button("Redraw##Index"))
- new RedrawObject(_pi).Invoke(_redrawIndex);
-
- IpcTester.DrawIntro(RedrawAll.Label, "Redraw All");
- if (ImGui.Button("Redraw##All"))
- new RedrawAll(_pi).Invoke();
-
- IpcTester.DrawIntro(GameObjectRedrawn.Label, "Last Redrawn Object:");
- ImGui.TextUnformatted(_lastRedrawnString);
- }
-
- private void SetLastRedrawn(nint address, int index)
- {
- if (index < 0
- || index > _objects.TotalCount
- || address == nint.Zero
- || _objects[index].Address != address)
- _lastRedrawnString = "Invalid";
-
- _lastRedrawnString = $"{_objects[index].Utf8Name} (0x{address:X}, {index})";
- }
-}
diff --git a/Penumbra/Api/IpcTester/ResolveIpcTester.cs b/Penumbra/Api/IpcTester/ResolveIpcTester.cs
deleted file mode 100644
index 9fc5bfc7..00000000
--- a/Penumbra/Api/IpcTester/ResolveIpcTester.cs
+++ /dev/null
@@ -1,114 +0,0 @@
-using Dalamud.Plugin;
-using Dalamud.Bindings.ImGui;
-using OtterGui.Raii;
-using OtterGui.Services;
-using Penumbra.Api.IpcSubscribers;
-using Penumbra.String.Classes;
-
-namespace Penumbra.Api.IpcTester;
-
-public class ResolveIpcTester(IDalamudPluginInterface pi) : IUiService
-{
- private string _currentResolvePath = string.Empty;
- private string _currentReversePath = string.Empty;
- private int _currentReverseIdx;
- private Task<(string[], string[][])> _task = Task.FromResult<(string[], string[][])>(([], []));
-
- public void Draw()
- {
- using var tree = ImRaii.TreeNode("Resolving");
- if (!tree)
- return;
-
- ImGui.InputTextWithHint("##resolvePath", "Resolve this game path...", ref _currentResolvePath, Utf8GamePath.MaxGamePathLength);
- ImGui.InputTextWithHint("##resolveInversePath", "Reverse-resolve this path...", ref _currentReversePath,
- Utf8GamePath.MaxGamePathLength);
- ImGui.InputInt("##resolveIdx", ref _currentReverseIdx, 0, 0);
- using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
- if (!table)
- return;
-
- IpcTester.DrawIntro(ResolveDefaultPath.Label, "Default Collection Resolve");
- if (_currentResolvePath.Length != 0)
- ImGui.TextUnformatted(new ResolveDefaultPath(pi).Invoke(_currentResolvePath));
-
- IpcTester.DrawIntro(ResolveInterfacePath.Label, "Interface Collection Resolve");
- if (_currentResolvePath.Length != 0)
- ImGui.TextUnformatted(new ResolveInterfacePath(pi).Invoke(_currentResolvePath));
-
- IpcTester.DrawIntro(ResolvePlayerPath.Label, "Player Collection Resolve");
- if (_currentResolvePath.Length != 0)
- ImGui.TextUnformatted(new ResolvePlayerPath(pi).Invoke(_currentResolvePath));
-
- IpcTester.DrawIntro(ResolveGameObjectPath.Label, "Game Object Collection Resolve");
- if (_currentResolvePath.Length != 0)
- ImGui.TextUnformatted(new ResolveGameObjectPath(pi).Invoke(_currentResolvePath, _currentReverseIdx));
-
- IpcTester.DrawIntro(ReverseResolvePlayerPath.Label, "Reversed Game Paths (Player)");
- if (_currentReversePath.Length > 0)
- {
- var list = new ReverseResolvePlayerPath(pi).Invoke(_currentReversePath);
- if (list.Length > 0)
- {
- ImGui.TextUnformatted(list[0]);
- if (list.Length > 1 && ImGui.IsItemHovered())
- ImGui.SetTooltip(string.Join("\n", list.Skip(1)));
- }
- }
-
- IpcTester.DrawIntro(ReverseResolveGameObjectPath.Label, "Reversed Game Paths (Game Object)");
- if (_currentReversePath.Length > 0)
- {
- var list = new ReverseResolveGameObjectPath(pi).Invoke(_currentReversePath, _currentReverseIdx);
- if (list.Length > 0)
- {
- ImGui.TextUnformatted(list[0]);
- if (list.Length > 1 && ImGui.IsItemHovered())
- ImGui.SetTooltip(string.Join("\n", list.Skip(1)));
- }
- }
-
- var forwardArray = _currentResolvePath.Length > 0
- ? [_currentResolvePath]
- : Array.Empty();
- var reverseArray = _currentReversePath.Length > 0
- ? [_currentReversePath]
- : Array.Empty();
-
- IpcTester.DrawIntro(ResolvePlayerPaths.Label, "Resolved Paths (Player)");
- if (forwardArray.Length > 0 || reverseArray.Length > 0)
- {
- var ret = new ResolvePlayerPaths(pi).Invoke(forwardArray, reverseArray);
- ImGui.TextUnformatted(ConvertText(ret));
- }
-
- IpcTester.DrawIntro(ResolvePlayerPathsAsync.Label, "Resolved Paths Async (Player)");
- if (ImGui.Button("Start"))
- _task = new ResolvePlayerPathsAsync(pi).Invoke(forwardArray, reverseArray);
- var hovered = ImGui.IsItemHovered();
- ImGui.SameLine();
- ImGui.AlignTextToFramePadding();
- ImGui.TextUnformatted(_task.Status.ToString());
- if ((hovered || ImGui.IsItemHovered()) && _task.IsCompletedSuccessfully)
- ImGui.SetTooltip(ConvertText(_task.Result));
- return;
-
- static string ConvertText((string[], string[][]) data)
- {
- var text = string.Empty;
- if (data.Item1.Length > 0)
- {
- if (data.Item2.Length > 0)
- text = $"Forward: {data.Item1[0]} | Reverse: {string.Join("; ", data.Item2[0])}.";
- else
- text = $"Forward: {data.Item1[0]}.";
- }
- else if (data.Item2.Length > 0)
- {
- text = $"Reverse: {string.Join("; ", data.Item2[0])}.";
- }
-
- return text;
- }
- }
-}
diff --git a/Penumbra/Api/IpcTester/ResourceTreeIpcTester.cs b/Penumbra/Api/IpcTester/ResourceTreeIpcTester.cs
deleted file mode 100644
index e6c8d52e..00000000
--- a/Penumbra/Api/IpcTester/ResourceTreeIpcTester.cs
+++ /dev/null
@@ -1,350 +0,0 @@
-using Dalamud.Bindings.ImGui;
-using Dalamud.Game.ClientState.Objects.Enums;
-using Dalamud.Interface;
-using Dalamud.Interface.Utility;
-using Dalamud.Plugin;
-using OtterGui;
-using OtterGui.Extensions;
-using OtterGui.Raii;
-using OtterGui.Services;
-using Penumbra.Api.Enums;
-using Penumbra.Api.Helpers;
-using Penumbra.Api.IpcSubscribers;
-using Penumbra.GameData.Enums;
-using Penumbra.GameData.Interop;
-using Penumbra.GameData.Structs;
-
-namespace Penumbra.Api.IpcTester;
-
-public class ResourceTreeIpcTester(IDalamudPluginInterface pi, ObjectManager objects) : IUiService
-{
- private readonly Stopwatch _stopwatch = new();
-
- private string _gameObjectIndices = "0";
- private ResourceType _type = ResourceType.Mtrl;
- private bool _withUiData;
-
- private (string, Dictionary>?)[]? _lastGameObjectResourcePaths;
- private (string, Dictionary>?)[]? _lastPlayerResourcePaths;
- private (string, IReadOnlyDictionary?)[]? _lastGameObjectResourcesOfType;
- private (string, IReadOnlyDictionary?)[]? _lastPlayerResourcesOfType;
- private (string, ResourceTreeDto?)[]? _lastGameObjectResourceTrees;
- private (string, ResourceTreeDto)[]? _lastPlayerResourceTrees;
- private TimeSpan _lastCallDuration;
-
- public void Draw()
- {
- using var _ = ImRaii.TreeNode("Resource Tree");
- if (!_)
- return;
-
- ImGui.InputText("GameObject indices", ref _gameObjectIndices, 511);
- ImGuiUtil.GenericEnumCombo("Resource type", ImGui.CalcItemWidth(), _type, out _type, Enum.GetValues());
- ImGui.Checkbox("Also get names and icons", ref _withUiData);
-
- using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
- if (!table)
- return;
-
- IpcTester.DrawIntro(GetGameObjectResourcePaths.Label, "Get GameObject resource paths");
- if (ImGui.Button("Get##GameObjectResourcePaths"))
- {
- var gameObjects = GetSelectedGameObjects();
- var subscriber = new GetGameObjectResourcePaths(pi);
- _stopwatch.Restart();
- var resourcePaths = subscriber.Invoke(gameObjects);
-
- _lastCallDuration = _stopwatch.Elapsed;
- _lastGameObjectResourcePaths = gameObjects
- .Select(i => GameObjectToString(i))
- .Zip(resourcePaths)
- .ToArray();
-
- ImGui.OpenPopup(nameof(GetGameObjectResourcePaths));
- }
-
- IpcTester.DrawIntro(GetPlayerResourcePaths.Label, "Get local player resource paths");
- if (ImGui.Button("Get##PlayerResourcePaths"))
- {
- var subscriber = new GetPlayerResourcePaths(pi);
- _stopwatch.Restart();
- var resourcePaths = subscriber.Invoke();
-
- _lastCallDuration = _stopwatch.Elapsed;
- _lastPlayerResourcePaths = resourcePaths
- .Select(pair => (GameObjectToString(pair.Key), pair.Value))
- .ToArray()!;
-
- ImGui.OpenPopup(nameof(GetPlayerResourcePaths));
- }
-
- IpcTester.DrawIntro(GetGameObjectResourcesOfType.Label, "Get GameObject resources of type");
- if (ImGui.Button("Get##GameObjectResourcesOfType"))
- {
- var gameObjects = GetSelectedGameObjects();
- var subscriber = new GetGameObjectResourcesOfType(pi);
- _stopwatch.Restart();
- var resourcesOfType = subscriber.Invoke(_type, _withUiData, gameObjects);
-
- _lastCallDuration = _stopwatch.Elapsed;
- _lastGameObjectResourcesOfType = gameObjects
- .Select(i => GameObjectToString(i))
- .Zip(resourcesOfType)
- .ToArray();
-
- ImGui.OpenPopup(nameof(GetGameObjectResourcesOfType));
- }
-
- IpcTester.DrawIntro(GetPlayerResourcesOfType.Label, "Get local player resources of type");
- if (ImGui.Button("Get##PlayerResourcesOfType"))
- {
- var subscriber = new GetPlayerResourcesOfType(pi);
- _stopwatch.Restart();
- var resourcesOfType = subscriber.Invoke(_type, _withUiData);
-
- _lastCallDuration = _stopwatch.Elapsed;
- _lastPlayerResourcesOfType = resourcesOfType
- .Select(pair => (GameObjectToString(pair.Key), (IReadOnlyDictionary?)pair.Value))
- .ToArray();
-
- ImGui.OpenPopup(nameof(GetPlayerResourcesOfType));
- }
-
- IpcTester.DrawIntro(GetGameObjectResourceTrees.Label, "Get GameObject resource trees");
- if (ImGui.Button("Get##GameObjectResourceTrees"))
- {
- var gameObjects = GetSelectedGameObjects();
- var subscriber = new GetGameObjectResourceTrees(pi);
- _stopwatch.Restart();
- var trees = subscriber.Invoke(_withUiData, gameObjects);
-
- _lastCallDuration = _stopwatch.Elapsed;
- _lastGameObjectResourceTrees = gameObjects
- .Select(i => GameObjectToString(i))
- .Zip(trees)
- .ToArray();
-
- ImGui.OpenPopup(nameof(GetGameObjectResourceTrees));
- }
-
- IpcTester.DrawIntro(GetPlayerResourceTrees.Label, "Get local player resource trees");
- if (ImGui.Button("Get##PlayerResourceTrees"))
- {
- var subscriber = new GetPlayerResourceTrees(pi);
- _stopwatch.Restart();
- var trees = subscriber.Invoke(_withUiData);
-
- _lastCallDuration = _stopwatch.Elapsed;
- _lastPlayerResourceTrees = trees
- .Select(pair => (GameObjectToString(pair.Key), pair.Value))
- .ToArray();
-
- ImGui.OpenPopup(nameof(GetPlayerResourceTrees));
- }
-
- DrawPopup(nameof(GetGameObjectResourcePaths), ref _lastGameObjectResourcePaths, DrawResourcePaths,
- _lastCallDuration);
- DrawPopup(nameof(GetPlayerResourcePaths), ref _lastPlayerResourcePaths!, DrawResourcePaths, _lastCallDuration);
-
- DrawPopup(nameof(GetGameObjectResourcesOfType), ref _lastGameObjectResourcesOfType, DrawResourcesOfType,
- _lastCallDuration);
- DrawPopup(nameof(GetPlayerResourcesOfType), ref _lastPlayerResourcesOfType, DrawResourcesOfType,
- _lastCallDuration);
-
- DrawPopup(nameof(GetGameObjectResourceTrees), ref _lastGameObjectResourceTrees, DrawResourceTrees,
- _lastCallDuration);
- DrawPopup(nameof(GetPlayerResourceTrees), ref _lastPlayerResourceTrees, DrawResourceTrees!, _lastCallDuration);
- }
-
- private static void DrawPopup(string popupId, ref T? result, Action drawResult, TimeSpan duration) where T : class
- {
- ImGui.SetNextWindowSize(ImGuiHelpers.ScaledVector2(1000, 500));
- using var popup = ImRaii.Popup(popupId);
- if (!popup)
- {
- result = null;
- return;
- }
-
- if (result == null)
- {
- ImGui.CloseCurrentPopup();
- return;
- }
-
- drawResult(result);
-
- ImGui.TextUnformatted($"Invoked in {duration.TotalMilliseconds} ms");
-
- if (ImGui.Button("Close", -Vector2.UnitX) || !ImGui.IsWindowFocused())
- {
- result = null;
- ImGui.CloseCurrentPopup();
- }
- }
-
- private static void DrawWithHeaders((string, T?)[] result, Action drawItem) where T : class
- {
- var firstSeen = new Dictionary();
- foreach (var (label, item) in result)
- {
- if (item == null)
- {
- ImRaii.TreeNode($"{label}: null", ImGuiTreeNodeFlags.Leaf).Dispose();
- continue;
- }
-
- if (firstSeen.TryGetValue(item, out var firstLabel))
- {
- ImRaii.TreeNode($"{label}: same as {firstLabel}", ImGuiTreeNodeFlags.Leaf).Dispose();
- continue;
- }
-
- firstSeen.Add(item, label);
-
- using var header = ImRaii.TreeNode(label);
- if (!header)
- continue;
-
- drawItem(item);
- }
- }
-
- private static void DrawResourcePaths((string, Dictionary>?)[] result)
- {
- DrawWithHeaders(result, paths =>
- {
- using var table = ImRaii.Table(string.Empty, 2, ImGuiTableFlags.SizingFixedFit);
- if (!table)
- return;
-
- ImGui.TableSetupColumn("Actual Path", ImGuiTableColumnFlags.WidthStretch, 0.6f);
- ImGui.TableSetupColumn("Game Paths", ImGuiTableColumnFlags.WidthStretch, 0.4f);
- ImGui.TableHeadersRow();
-
- foreach (var (actualPath, gamePaths) in paths)
- {
- ImGui.TableNextColumn();
- ImGui.TextUnformatted(actualPath);
- ImGui.TableNextColumn();
- foreach (var gamePath in gamePaths)
- ImGui.TextUnformatted(gamePath);
- }
- });
- }
-
- private void DrawResourcesOfType((string, IReadOnlyDictionary?)[] result)
- {
- DrawWithHeaders(result, resources =>
- {
- using var table = ImRaii.Table(string.Empty, _withUiData ? 3 : 2, ImGuiTableFlags.SizingFixedFit);
- if (!table)
- return;
-
- ImGui.TableSetupColumn("Resource Handle", ImGuiTableColumnFlags.WidthStretch, 0.15f);
- ImGui.TableSetupColumn("Actual Path", ImGuiTableColumnFlags.WidthStretch, _withUiData ? 0.55f : 0.85f);
- if (_withUiData)
- ImGui.TableSetupColumn("Icon & Name", ImGuiTableColumnFlags.WidthStretch, 0.3f);
- ImGui.TableHeadersRow();
-
- foreach (var (resourceHandle, (actualPath, name, icon)) in resources)
- {
- ImGui.TableNextColumn();
- TextUnformattedMono($"0x{resourceHandle:X}");
- ImGui.TableNextColumn();
- ImGui.TextUnformatted(actualPath);
- if (_withUiData)
- {
- ImGui.TableNextColumn();
- TextUnformattedMono(icon.ToString());
- ImGui.SameLine();
- ImGui.TextUnformatted(name);
- }
- }
- });
- }
-
- private void DrawResourceTrees((string, ResourceTreeDto?)[] result)
- {
- DrawWithHeaders(result, tree =>
- {
- ImGui.TextUnformatted($"Name: {tree.Name}\nRaceCode: {(GenderRace)tree.RaceCode}");
-
- using var table = ImRaii.Table(string.Empty, _withUiData ? 7 : 5, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.Resizable);
- if (!table)
- return;
-
- if (_withUiData)
- {
- ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthStretch, 0.5f);
- ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthStretch, 0.1f);
- ImGui.TableSetupColumn("Icon", ImGuiTableColumnFlags.WidthStretch, 0.15f);
- }
- else
- {
- ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthStretch, 0.5f);
- }
-
- ImGui.TableSetupColumn("Game Path", ImGuiTableColumnFlags.WidthStretch, 0.5f);
- ImGui.TableSetupColumn("Actual Path", ImGuiTableColumnFlags.WidthStretch, 0.5f);
- ImGui.TableSetupColumn("Object Address", ImGuiTableColumnFlags.WidthStretch, 0.2f);
- ImGui.TableSetupColumn("Resource Handle", ImGuiTableColumnFlags.WidthStretch, 0.2f);
- ImGui.TableHeadersRow();
-
- void DrawNode(ResourceNodeDto node)
- {
- ImGui.TableNextRow();
- ImGui.TableNextColumn();
- var hasChildren = node.Children.Any();
- using var treeNode = ImRaii.TreeNode(
- $"{(_withUiData ? node.Name ?? "Unknown" : node.Type)}##{node.ObjectAddress:X8}",
- hasChildren
- ? ImGuiTreeNodeFlags.SpanFullWidth
- : ImGuiTreeNodeFlags.SpanFullWidth | ImGuiTreeNodeFlags.Leaf | ImGuiTreeNodeFlags.NoTreePushOnOpen);
- if (_withUiData)
- {
- ImGui.TableNextColumn();
- TextUnformattedMono(node.Type.ToString());
- ImGui.TableNextColumn();
- TextUnformattedMono(node.Icon.ToString());
- }
-
- ImGui.TableNextColumn();
- ImGui.TextUnformatted(node.GamePath ?? "Unknown");
- ImGui.TableNextColumn();
- ImGui.TextUnformatted(node.ActualPath);
- ImGui.TableNextColumn();
- TextUnformattedMono($"0x{node.ObjectAddress:X8}");
- ImGui.TableNextColumn();
- TextUnformattedMono($"0x{node.ResourceHandle:X8}");
-
- if (treeNode)
- foreach (var child in node.Children)
- DrawNode(child);
- }
-
- foreach (var node in tree.Nodes)
- DrawNode(node);
- });
- }
-
- private static void TextUnformattedMono(string text)
- {
- using var _ = ImRaii.PushFont(UiBuilder.MonoFont);
- ImGui.TextUnformatted(text);
- }
-
- private ushort[] GetSelectedGameObjects()
- => _gameObjectIndices.Split(',')
- .SelectWhere(index => (ushort.TryParse(index.Trim(), out var i), i))
- .ToArray();
-
- private unsafe string GameObjectToString(ObjectIndex gameObjectIndex)
- {
- var gameObject = objects[gameObjectIndex];
-
- return gameObject.Valid
- ? $"[{gameObjectIndex}] {gameObject.Utf8Name} ({(ObjectKind)gameObject.AsObject->ObjectKind})"
- : $"[{gameObjectIndex}] null";
- }
-}
diff --git a/Penumbra/Api/IpcTester/TemporaryIpcTester.cs b/Penumbra/Api/IpcTester/TemporaryIpcTester.cs
deleted file mode 100644
index d46c5728..00000000
--- a/Penumbra/Api/IpcTester/TemporaryIpcTester.cs
+++ /dev/null
@@ -1,319 +0,0 @@
-using Dalamud.Interface;
-using Dalamud.Plugin;
-using Dalamud.Bindings.ImGui;
-using OtterGui;
-using OtterGui.Extensions;
-using OtterGui.Raii;
-using OtterGui.Services;
-using OtterGui.Text;
-using Penumbra.Api.Api;
-using Penumbra.Api.Enums;
-using Penumbra.Api.IpcSubscribers;
-using Penumbra.Collections.Manager;
-using Penumbra.Mods;
-using Penumbra.Mods.Manager;
-using Penumbra.Services;
-
-namespace Penumbra.Api.IpcTester;
-
-public class TemporaryIpcTester(
- IDalamudPluginInterface pi,
- ModManager modManager,
- CollectionManager collections,
- TempModManager tempMods,
- TempCollectionManager tempCollections,
- SaveService saveService,
- Configuration config)
- : IUiService
-{
- public Guid LastCreatedCollectionId = Guid.Empty;
-
- private readonly bool _debug = Assembly.GetAssembly(typeof(TemporaryIpcTester))?.GetName().Version?.Major >= 9;
-
- private Guid? _tempGuid;
- private string _tempCollectionName = string.Empty;
- private string _tempCollectionGuidName = string.Empty;
- private string _tempModName = string.Empty;
- private string _modDirectory = string.Empty;
- private string _tempGamePath = "test/game/path.mtrl";
- private string _tempFilePath = "test/success.mtrl";
- private string _tempManipulation = string.Empty;
- private string _identity = string.Empty;
- private PenumbraApiEc _lastTempError;
- private int _tempActorIndex;
- private bool _forceOverwrite;
-
- public void Draw()
- {
- using var _ = ImRaii.TreeNode("Temporary");
- if (!_)
- return;
-
- ImGui.InputTextWithHint("##identity", "Identity...", ref _identity, 128);
- ImGui.InputTextWithHint("##tempCollection", "Collection Name...", ref _tempCollectionName, 128);
- ImGuiUtil.GuidInput("##guid", "Collection GUID...", string.Empty, ref _tempGuid, ref _tempCollectionGuidName);
- ImGui.InputInt("##tempActorIndex", ref _tempActorIndex, 0, 0);
- ImGui.InputTextWithHint("##tempMod", "Temporary Mod Name...", ref _tempModName, 32);
- ImGui.InputTextWithHint("##mod", "Existing Mod Name...", ref _modDirectory, 256);
- ImGui.InputTextWithHint("##tempGame", "Game Path...", ref _tempGamePath, 256);
- ImGui.InputTextWithHint("##tempFile", "File Path...", ref _tempFilePath, 256);
- ImUtf8.InputText("##tempManip"u8, ref _tempManipulation, "Manipulation Base64 String..."u8);
- ImGui.Checkbox("Force Character Collection Overwrite", ref _forceOverwrite);
-
- using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
- if (!table)
- return;
-
- IpcTester.DrawIntro("Last Error", _lastTempError.ToString());
- ImGuiUtil.DrawTableColumn("Last Created Collection");
- ImGui.TableNextColumn();
- using (ImRaii.PushFont(UiBuilder.MonoFont))
- {
- ImGuiUtil.CopyOnClickSelectable(LastCreatedCollectionId.ToString());
- }
-
- IpcTester.DrawIntro(CreateTemporaryCollection.Label, "Create Temporary Collection");
- if (ImGui.Button("Create##Collection"))
- {
- _lastTempError = new CreateTemporaryCollection(pi).Invoke(_identity, _tempCollectionName, out LastCreatedCollectionId);
- if (_tempGuid == null)
- {
- _tempGuid = LastCreatedCollectionId;
- _tempCollectionGuidName = LastCreatedCollectionId.ToString();
- }
- }
-
- var guid = _tempGuid.GetValueOrDefault(Guid.Empty);
-
- IpcTester.DrawIntro(DeleteTemporaryCollection.Label, "Delete Temporary Collection");
- if (ImGui.Button("Delete##Collection"))
- _lastTempError = new DeleteTemporaryCollection(pi).Invoke(guid);
- ImGui.SameLine();
- if (ImGui.Button("Delete Last##Collection"))
- _lastTempError = new DeleteTemporaryCollection(pi).Invoke(LastCreatedCollectionId);
-
- IpcTester.DrawIntro(AssignTemporaryCollection.Label, "Assign Temporary Collection");
- if (ImGui.Button("Assign##NamedCollection"))
- _lastTempError = new AssignTemporaryCollection(pi).Invoke(guid, _tempActorIndex, _forceOverwrite);
-
- IpcTester.DrawIntro(AddTemporaryMod.Label, "Add Temporary Mod to specific Collection");
- if (ImGui.Button("Add##Mod"))
- _lastTempError = new AddTemporaryMod(pi).Invoke(_tempModName, guid,
- new Dictionary { { _tempGamePath, _tempFilePath } },
- _tempManipulation.Length > 0 ? _tempManipulation : string.Empty, int.MaxValue);
-
- IpcTester.DrawIntro(CreateTemporaryCollection.Label, "Copy Existing Collection");
- if (ImGuiUtil.DrawDisabledButton("Copy##Collection", Vector2.Zero,
- "Copies the effective list from the collection named in Temporary Mod Name...",
- !collections.Storage.ByName(_tempModName, out var copyCollection))
- && copyCollection is { HasCache: true })
- {
- var files = copyCollection.ResolvedFiles.ToDictionary(kvp => kvp.Key.ToString(), kvp => kvp.Value.Path.ToString());
- var manips = MetaApi.CompressMetaManipulations(copyCollection);
- _lastTempError = new AddTemporaryMod(pi).Invoke(_tempModName, guid, files, manips, 999);
- }
-
- IpcTester.DrawIntro(AddTemporaryModAll.Label, "Add Temporary Mod to all Collections");
- if (ImGui.Button("Add##All"))
- _lastTempError = new AddTemporaryModAll(pi).Invoke(_tempModName,
- new Dictionary { { _tempGamePath, _tempFilePath } },
- _tempManipulation.Length > 0 ? _tempManipulation : string.Empty, int.MaxValue);
-
- IpcTester.DrawIntro(RemoveTemporaryMod.Label, "Remove Temporary Mod from specific Collection");
- if (ImGui.Button("Remove##Mod"))
- _lastTempError = new RemoveTemporaryMod(pi).Invoke(_tempModName, guid, int.MaxValue);
-
- IpcTester.DrawIntro(RemoveTemporaryModAll.Label, "Remove Temporary Mod from all Collections");
- if (ImGui.Button("Remove##ModAll"))
- _lastTempError = new RemoveTemporaryModAll(pi).Invoke(_tempModName, int.MaxValue);
-
- IpcTester.DrawIntro(SetTemporaryModSettings.Label, "Set Temporary Mod Settings (to default) in specific Collection");
- if (ImUtf8.Button("Set##SetTemporary"u8))
- _lastTempError = new SetTemporaryModSettings(pi).Invoke(guid, _modDirectory, false, true, 1337,
- new Dictionary>(),
- "IPC Tester", 1337);
-
- IpcTester.DrawIntro(SetTemporaryModSettingsPlayer.Label, "Set Temporary Mod Settings (to default) in game object collection");
- if (ImUtf8.Button("Set##SetTemporaryPlayer"u8))
- _lastTempError = new SetTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, _modDirectory, false, true, 1337,
- new Dictionary>(),
- "IPC Tester", 1337);
-
- IpcTester.DrawIntro(RemoveTemporaryModSettings.Label, "Remove Temporary Mod Settings from specific Collection");
- if (ImUtf8.Button("Remove##RemoveTemporary"u8))
- _lastTempError = new RemoveTemporaryModSettings(pi).Invoke(guid, _modDirectory, 1337);
- ImGui.SameLine();
- if (ImUtf8.Button("Remove (Wrong Key)##RemoveTemporary"u8))
- _lastTempError = new RemoveTemporaryModSettings(pi).Invoke(guid, _modDirectory, 1338);
-
- IpcTester.DrawIntro(RemoveTemporaryModSettingsPlayer.Label, "Remove Temporary Mod Settings from game object Collection");
- if (ImUtf8.Button("Remove##RemoveTemporaryPlayer"u8))
- _lastTempError = new RemoveTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, _modDirectory, 1337);
- ImGui.SameLine();
- if (ImUtf8.Button("Remove (Wrong Key)##RemoveTemporaryPlayer"u8))
- _lastTempError = new RemoveTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, _modDirectory, 1338);
-
- IpcTester.DrawIntro(RemoveAllTemporaryModSettings.Label, "Remove All Temporary Mod Settings from specific Collection");
- if (ImUtf8.Button("Remove##RemoveAllTemporary"u8))
- _lastTempError = new RemoveAllTemporaryModSettings(pi).Invoke(guid, 1337);
- ImGui.SameLine();
- if (ImUtf8.Button("Remove (Wrong Key)##RemoveAllTemporary"u8))
- _lastTempError = new RemoveAllTemporaryModSettings(pi).Invoke(guid, 1338);
-
- IpcTester.DrawIntro(RemoveAllTemporaryModSettingsPlayer.Label, "Remove All Temporary Mod Settings from game object Collection");
- if (ImUtf8.Button("Remove##RemoveAllTemporaryPlayer"u8))
- _lastTempError = new RemoveAllTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, 1337);
- ImGui.SameLine();
- if (ImUtf8.Button("Remove (Wrong Key)##RemoveAllTemporaryPlayer"u8))
- _lastTempError = new RemoveAllTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, 1338);
-
- IpcTester.DrawIntro(QueryTemporaryModSettings.Label, "Query Temporary Mod Settings from specific Collection");
- ImUtf8.Button("Query##QueryTemporaryModSettings"u8);
- if (ImGui.IsItemHovered())
- {
- _lastTempError = new QueryTemporaryModSettings(pi).Invoke(guid, _modDirectory, out var settings, out var source, 1337);
- DrawTooltip(settings, source);
- }
-
- ImGui.SameLine();
- ImUtf8.Button("Query (Wrong Key)##RemoveAllTemporary"u8);
- if (ImGui.IsItemHovered())
- {
- _lastTempError = new QueryTemporaryModSettings(pi).Invoke(guid, _modDirectory, out var settings, out var source, 1338);
- DrawTooltip(settings, source);
- }
-
- IpcTester.DrawIntro(QueryTemporaryModSettingsPlayer.Label, "Query Temporary Mod Settings from game object Collection");
- ImUtf8.Button("Query##QueryTemporaryModSettingsPlayer"u8);
- if (ImGui.IsItemHovered())
- {
- _lastTempError =
- new QueryTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, _modDirectory, out var settings, out var source, 1337);
- DrawTooltip(settings, source);
- }
-
- ImGui.SameLine();
- ImUtf8.Button("Query (Wrong Key)##RemoveAllTemporaryPlayer"u8);
- if (ImGui.IsItemHovered())
- {
- _lastTempError =
- new QueryTemporaryModSettingsPlayer(pi).Invoke(_tempActorIndex, _modDirectory, out var settings, out var source, 1338);
- DrawTooltip(settings, source);
- }
-
- void DrawTooltip((bool ForceInherit, bool Enabled, int Priority, Dictionary> Settings)? settings, string source)
- {
- using var tt = ImUtf8.Tooltip();
- ImUtf8.Text($"Query returned {_lastTempError}");
- if (settings != null)
- ImUtf8.Text($"Settings created by {(source.Length == 0 ? "Unknown Source" : source)}:");
- else
- ImUtf8.Text(source.Length > 0 ? $"Locked by {source}." : "No settings exist.");
- ImGui.Separator();
- if (settings == null)
- {
-
- return;
- }
-
- using (ImUtf8.Group())
- {
- ImUtf8.Text("Force Inherit"u8);
- ImUtf8.Text("Enabled"u8);
- ImUtf8.Text("Priority"u8);
- foreach (var group in settings.Value.Settings.Keys)
- ImUtf8.Text(group);
- }
-
- ImGui.SameLine();
- using (ImUtf8.Group())
- {
- ImUtf8.Text($"{settings.Value.ForceInherit}");
- ImUtf8.Text($"{settings.Value.Enabled}");
- ImUtf8.Text($"{settings.Value.Priority}");
- foreach (var group in settings.Value.Settings.Values)
- ImUtf8.Text(string.Join("; ", group));
- }
- }
- }
-
- public void DrawCollections()
- {
- using var collTree = ImUtf8.TreeNode("Temporary Collections##TempCollections"u8);
- if (!collTree)
- return;
-
- using var table = ImUtf8.Table("##collTree"u8, 6, ImGuiTableFlags.SizingFixedFit);
- if (!table)
- return;
-
- foreach (var (collection, idx) in tempCollections.Values.WithIndex())
- {
- using var id = ImRaii.PushId(idx);
- ImGui.TableNextColumn();
- var character = tempCollections.Collections.Where(p => p.Collection == collection).Select(p => p.DisplayName)
- .FirstOrDefault()
- ?? "Unknown";
- if (_debug && ImUtf8.Button("Save##Collection"u8))
- TemporaryMod.SaveTempCollection(config, saveService, modManager, collection, character);
-
- using (ImRaii.PushFont(UiBuilder.MonoFont))
- {
- ImGui.TableNextColumn();
- ImGuiUtil.CopyOnClickSelectable(collection.Identity.Identifier);
- }
-
- ImGuiUtil.DrawTableColumn(collection.Identity.Name);
- ImGuiUtil.DrawTableColumn(collection.ResolvedFiles.Count.ToString());
- ImGuiUtil.DrawTableColumn(collection.MetaCache?.Count.ToString() ?? "0");
- ImGuiUtil.DrawTableColumn(string.Join(", ",
- tempCollections.Collections.Where(p => p.Collection == collection).Select(c => c.DisplayName)));
- }
- }
-
- public void DrawMods()
- {
- using var modTree = ImRaii.TreeNode("Temporary Mods##TempMods");
- if (!modTree)
- return;
-
- using var table = ImRaii.Table("##modTree", 5, ImGuiTableFlags.SizingFixedFit);
-
- void PrintList(string collectionName, IReadOnlyList list)
- {
- foreach (var mod in list)
- {
- ImGui.TableNextColumn();
- ImGui.TextUnformatted(mod.Name.Text);
- ImGui.TableNextColumn();
- ImGui.TextUnformatted(mod.Priority.ToString());
- ImGui.TableNextColumn();
- ImGui.TextUnformatted(collectionName);
- ImGui.TableNextColumn();
- ImGui.TextUnformatted(mod.Default.Files.Count.ToString());
- if (ImGui.IsItemHovered())
- {
- using var tt = ImRaii.Tooltip();
- foreach (var (path, file) in mod.Default.Files)
- ImGui.TextUnformatted($"{path} -> {file}");
- }
-
- ImGui.TableNextColumn();
- ImGui.TextUnformatted(mod.TotalManipulations.ToString());
- if (ImGui.IsItemHovered())
- {
- using var tt = ImRaii.Tooltip();
- foreach (var identifier in mod.Default.Manipulations.Identifiers)
- ImGui.TextUnformatted(identifier.ToString());
- }
- }
- }
-
- if (table)
- {
- PrintList("All", tempMods.ModsForAllCollections);
- foreach (var (collection, list) in tempMods.Mods)
- PrintList(collection.Identity.Name, list);
- }
- }
-}
diff --git a/Penumbra/Api/IpcTester/UiIpcTester.cs b/Penumbra/Api/IpcTester/UiIpcTester.cs
deleted file mode 100644
index 852339c9..00000000
--- a/Penumbra/Api/IpcTester/UiIpcTester.cs
+++ /dev/null
@@ -1,133 +0,0 @@
-using Dalamud.Plugin;
-using Dalamud.Bindings.ImGui;
-using OtterGui.Raii;
-using OtterGui.Services;
-using Penumbra.Api.Enums;
-using Penumbra.Api.Helpers;
-using Penumbra.Api.IpcSubscribers;
-
-namespace Penumbra.Api.IpcTester;
-
-public class UiIpcTester : IUiService, IDisposable
-{
- private readonly IDalamudPluginInterface _pi;
- public readonly EventSubscriber PreSettingsTabBar;
- public readonly EventSubscriber PreSettingsPanel;
- public readonly EventSubscriber PostEnabled;
- public readonly EventSubscriber PostSettingsPanelDraw;
- public readonly EventSubscriber ChangedItemTooltip;
- public readonly EventSubscriber ChangedItemClicked;
-
- private string _lastDrawnMod = string.Empty;
- private DateTimeOffset _lastDrawnModTime = DateTimeOffset.MinValue;
- private bool _subscribedToTooltip;
- private bool _subscribedToClick;
- private string _lastClicked = string.Empty;
- private string _lastHovered = string.Empty;
- private TabType _selectTab = TabType.None;
- private string _modName = string.Empty;
- private PenumbraApiEc _ec = PenumbraApiEc.Success;
-
- public UiIpcTester(IDalamudPluginInterface pi)
- {
- _pi = pi;
- PreSettingsTabBar = IpcSubscribers.PreSettingsTabBarDraw.Subscriber(pi, UpdateLastDrawnMod);
- PreSettingsPanel = IpcSubscribers.PreSettingsDraw.Subscriber(pi, UpdateLastDrawnMod);
- PostEnabled = IpcSubscribers.PostEnabledDraw.Subscriber(pi, UpdateLastDrawnMod);
- PostSettingsPanelDraw = IpcSubscribers.PostSettingsDraw.Subscriber(pi, UpdateLastDrawnMod);
- ChangedItemTooltip = IpcSubscribers.ChangedItemTooltip.Subscriber(pi, AddedTooltip);
- ChangedItemClicked = IpcSubscribers.ChangedItemClicked.Subscriber(pi, AddedClick);
- PreSettingsTabBar.Disable();
- PreSettingsPanel.Disable();
- PostEnabled.Disable();
- PostSettingsPanelDraw.Disable();
- ChangedItemTooltip.Disable();
- ChangedItemClicked.Disable();
- }
-
- public void Dispose()
- {
- PreSettingsTabBar.Dispose();
- PreSettingsPanel.Dispose();
- PostEnabled.Dispose();
- PostSettingsPanelDraw.Dispose();
- ChangedItemTooltip.Dispose();
- ChangedItemClicked.Dispose();
- }
-
- public void Draw()
- {
- using var _ = ImRaii.TreeNode("UI");
- if (!_)
- return;
-
- using (var combo = ImRaii.Combo("Tab to Open at", _selectTab.ToString()))
- {
- if (combo)
- foreach (var val in Enum.GetValues())
- {
- if (ImGui.Selectable(val.ToString(), _selectTab == val))
- _selectTab = val;
- }
- }
-
- ImGui.InputTextWithHint("##openMod", "Mod to Open at...", ref _modName, 256);
- using var table = ImRaii.Table(string.Empty, 3, ImGuiTableFlags.SizingFixedFit);
- if (!table)
- return;
-
- IpcTester.DrawIntro(IpcSubscribers.PostSettingsDraw.Label, "Last Drawn Mod");
- ImGui.TextUnformatted(_lastDrawnMod.Length > 0 ? $"{_lastDrawnMod} at {_lastDrawnModTime}" : "None");
-
- IpcTester.DrawIntro(IpcSubscribers.ChangedItemTooltip.Label, "Add Tooltip");
- if (ImGui.Checkbox("##tooltip", ref _subscribedToTooltip))
- {
- if (_subscribedToTooltip)
- ChangedItemTooltip.Enable();
- else
- ChangedItemTooltip.Disable();
- }
-
- ImGui.SameLine();
- ImGui.TextUnformatted(_lastHovered);
-
- IpcTester.DrawIntro(IpcSubscribers.ChangedItemClicked.Label, "Subscribe Click");
- if (ImGui.Checkbox("##click", ref _subscribedToClick))
- {
- if (_subscribedToClick)
- ChangedItemClicked.Enable();
- else
- ChangedItemClicked.Disable();
- }
-
- ImGui.SameLine();
- ImGui.TextUnformatted(_lastClicked);
- IpcTester.DrawIntro(OpenMainWindow.Label, "Open Mod Window");
- if (ImGui.Button("Open##window"))
- _ec = new OpenMainWindow(_pi).Invoke(_selectTab, _modName, _modName);
-
- ImGui.SameLine();
- ImGui.TextUnformatted(_ec.ToString());
-
- IpcTester.DrawIntro(CloseMainWindow.Label, "Close Mod Window");
- if (ImGui.Button("Close##window"))
- new CloseMainWindow(_pi).Invoke();
- }
-
- private void UpdateLastDrawnMod(string name)
- => (_lastDrawnMod, _lastDrawnModTime) = (name, DateTimeOffset.Now);
-
- private void UpdateLastDrawnMod(string name, float _1, float _2)
- => (_lastDrawnMod, _lastDrawnModTime) = (name, DateTimeOffset.Now);
-
- private void AddedTooltip(ChangedItemType type, uint id)
- {
- _lastHovered = $"{type} {id} at {DateTime.UtcNow.ToLocalTime().ToString(CultureInfo.CurrentCulture)}";
- ImGui.TextUnformatted("IPC Test Successful");
- }
-
- private void AddedClick(MouseButton button, ChangedItemType type, uint id)
- {
- _lastClicked = $"{button}-click on {type} {id} at {DateTime.UtcNow.ToLocalTime().ToString(CultureInfo.CurrentCulture)}";
- }
-}
diff --git a/Penumbra/Api/ModChangedItemAdapter.cs b/Penumbra/Api/ModChangedItemAdapter.cs
deleted file mode 100644
index 8d2d473c..00000000
--- a/Penumbra/Api/ModChangedItemAdapter.cs
+++ /dev/null
@@ -1,103 +0,0 @@
-using Penumbra.GameData.Data;
-using Penumbra.Mods.Manager;
-
-namespace Penumbra.Api;
-
-public sealed class ModChangedItemAdapter(WeakReference storage)
- : IReadOnlyDictionary>,
- IReadOnlyList<(string ModDirectory, IReadOnlyDictionary ChangedItems)>
-{
- IEnumerator<(string ModDirectory, IReadOnlyDictionary ChangedItems)>
- IEnumerable<(string ModDirectory, IReadOnlyDictionary ChangedItems)>.GetEnumerator()
- => Storage.Select(m => (m.Identifier, (IReadOnlyDictionary)new ChangedItemDictionaryAdapter(m.ChangedItems)))
- .GetEnumerator();
-
- public IEnumerator>> GetEnumerator()
- => Storage.Select(m => new KeyValuePair>(m.Identifier,
- new ChangedItemDictionaryAdapter(m.ChangedItems)))
- .GetEnumerator();
-
- IEnumerator IEnumerable.GetEnumerator()
- => GetEnumerator();
-
- public int Count
- => Storage.Count;
-
- public bool ContainsKey(string key)
- => Storage.TryGetMod(key, string.Empty, out _);
-
- public bool TryGetValue(string key, [NotNullWhen(true)] out IReadOnlyDictionary? value)
- {
- if (Storage.TryGetMod(key, string.Empty, out var mod))
- {
- value = new ChangedItemDictionaryAdapter(mod.ChangedItems);
- return true;
- }
-
- value = null;
- return false;
- }
-
- public IReadOnlyDictionary this[string key]
- => TryGetValue(key, out var v) ? v : throw new KeyNotFoundException();
-
- (string ModDirectory, IReadOnlyDictionary ChangedItems)
- IReadOnlyList<(string ModDirectory, IReadOnlyDictionary ChangedItems)>.this[int index]
- {
- get
- {
- var m = Storage[index];
- return (m.Identifier, new ChangedItemDictionaryAdapter(m.ChangedItems));
- }
- }
-
- public IEnumerable Keys
- => Storage.Select(m => m.Identifier);
-
- public IEnumerable> Values
- => Storage.Select(m => new ChangedItemDictionaryAdapter(m.ChangedItems));
-
- private ModStorage Storage
- {
- [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
- get => storage.TryGetTarget(out var t)
- ? t
- : throw new ObjectDisposedException("The underlying mod storage of this IPC container was disposed.");
- }
-
- private sealed class ChangedItemDictionaryAdapter(SortedList