├── .gitignore
├── Snapper
├── Snapper.json
├── PMP
│ ├── PMPManipulationEntry.cs
│ ├── PMPDefaultMod.cs
│ ├── PMPMetadata.cs
│ └── PMPExportManager.cs
├── Interop
│ ├── Material.cs
│ ├── ResourceHandle.cs
│ ├── MtrlResource.cs
│ ├── RenderModel.cs
│ └── Weapon.cs
├── Models
│ ├── SnapshotInfo.cs
│ └── FileReplacement.cs
├── Export
│ ├── MareCharaFileData.cs
│ ├── MareCharaFileHeader.cs
│ └── MareCharaFileManager.cs
├── Configuration.cs
├── CharacterFactory.cs
├── Windows
│ ├── ConfigWindow.cs
│ ├── MainWindow.cs
│ ├── ImGuiRaii.cs
│ ├── PlayerPanelPart.cs
│ └── PlayerSelectorPart.cs
├── Snapper.csproj
├── Utils
│ ├── Logger.cs
│ └── DalamudUtil.cs
├── Plugin.cs
├── Managers
│ ├── IpcManager.cs
│ └── SnapshotManager.cs
└── packages.lock.json
├── mcdf_pmp_ripper
├── mcdf_pmp_ripper.csproj
└── Program.cs
├── .gitmodules
├── README.md
├── Snapper.sln
└── .editorconfig
/.gitignore:
--------------------------------------------------------------------------------
1 | .vs/
2 | obj/
3 | bin/
4 | *.user
--------------------------------------------------------------------------------
/Snapper/Snapper.json:
--------------------------------------------------------------------------------
1 | {
2 | "Author": "Eqbot, astrodoobs, Vivi",
3 | "Name": "Snapper",
4 | "Punchline": "Snapshot mods from your friends",
5 | "Description": "/psnap to use",
6 | "InternalName": "snapper",
7 | "ApplicableVersion": "any",
8 | "Tags": [
9 | "snapper"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/Snapper/PMP/PMPManipulationEntry.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Snapper.PMP
8 | {
9 | internal class PMPManipulationEntry
10 | {
11 | public string Type { get; set; }
12 | public object Manipulation { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/mcdf_pmp_ripper/mcdf_pmp_ripper.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net9.0-windows
6 | enable
7 | enable
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "Penumbra.GameData"]
2 | path = Penumbra.GameData
3 | url = https://github.com/Ottermandias/Penumbra.GameData
4 | [submodule "Penumbra.Api"]
5 | path = Penumbra.Api
6 | url = https://github.com/Ottermandias/Penumbra.Api
7 | [submodule "Penumbra.String"]
8 | path = Penumbra.String
9 | url = https://github.com/Ottermandias/Penumbra.String
10 | [submodule "OtterGui"]
11 | path = OtterGui
12 | url = https://github.com/Ottermandias/OtterGui
13 |
--------------------------------------------------------------------------------
/Snapper/Interop/Material.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 | using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
3 |
4 | namespace Penumbra.Interop.Structs;
5 |
6 | [StructLayout( LayoutKind.Explicit )]
7 | public unsafe struct Material
8 | {
9 | [FieldOffset( 0x10 )]
10 | public ResourceHandle* ResourceHandle;
11 | }
12 |
13 | [StructLayout( LayoutKind.Explicit )]
14 | public unsafe struct MaterialData
15 | {
16 | [FieldOffset( 0x0 )]
17 | public byte* Data;
18 | }
--------------------------------------------------------------------------------
/Snapper/Models/SnapshotInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Snapper.Models
8 | {
9 | internal class SnapshotInfo
10 | {
11 | public string GlamourerString { get; set; } = string.Empty;
12 | public string CustomizeData { get; set; } = string.Empty;
13 | public string ManipulationString { get; set; } = string.Empty;
14 | public Dictionary> FileReplacements { get; set; } = new();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/Snapper/PMP/PMPDefaultMod.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Snapper.PMP
8 | {
9 | internal class PMPDefaultMod
10 | {
11 | public string Name { get; } = "Default";
12 | public string Description { get; } = "";
13 | public Dictionary Files { get; set; } = new();
14 | public Dictionary FileSwaps { get; set; } = new();
15 | public List Manipulations { get; set; } = new();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Snapper/PMP/PMPMetadata.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Snapper.PMP
8 | {
9 | internal class PMPMetadata
10 | {
11 | public int FileVersion { get; set; } = 3;
12 | public string Name { get; set; } = "";
13 | public string Author { get; set; } = "";
14 | public string Description { get; set; } = "Yoink!";
15 | public string Version { get; set; } = "1.0.0";
16 | public string Website { get; set; } = "";
17 | public string[] ModTags { get; set; } = { };
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/mcdf_pmp_ripper/Program.cs:
--------------------------------------------------------------------------------
1 | // See https://aka.ms/new-console-template for more information
2 | using Snapper.PMP;
3 | using MareSynchronos.Export;
4 | using Snapper;
5 |
6 | Configuration configuration = new Configuration();
7 | configuration.WorkingDirectory = ".";
8 | MareCharaFileManager fileManager = new MareCharaFileManager(configuration);
9 |
10 | Console.WriteLine("Enter path to MCDF");
11 | string mcdf_path = Console.ReadLine();
12 |
13 | fileManager.LoadMareCharaFile(mcdf_path);
14 | string snapshot_path = fileManager.ExtractMareCharaFile();
15 |
16 | PMPExportManager pmpManager = new PMPExportManager(configuration);
17 | pmpManager.SnapshotToPMP(snapshot_path);
18 |
--------------------------------------------------------------------------------
/Snapper/Interop/ResourceHandle.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | using FFXIVClientStructs.FFXIV.Client.System.Resource;
4 |
5 | namespace Penumbra.Interop.Structs;
6 |
7 | [StructLayout( LayoutKind.Explicit )]
8 | public unsafe struct ResourceHandle
9 | {
10 | public const int SsoSize = 15;
11 |
12 | public byte* FileName()
13 | {
14 | if( FileNameLength > SsoSize )
15 | {
16 | return FileNameData;
17 | }
18 |
19 | fixed( byte** name = &FileNameData )
20 | {
21 | return ( byte* )name;
22 | }
23 | }
24 |
25 | [FieldOffset( 0x08 )]
26 | public ResourceCategory Category;
27 |
28 | [FieldOffset( 0x48 )]
29 | public byte* FileNameData;
30 |
31 | [FieldOffset( 0x58 )]
32 | public int FileNameLength;
33 | }
--------------------------------------------------------------------------------
/Snapper/Interop/MtrlResource.cs:
--------------------------------------------------------------------------------
1 | using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
2 | using System.Runtime.InteropServices;
3 |
4 | namespace Penumbra.Interop.Structs;
5 |
6 | [StructLayout( LayoutKind.Explicit )]
7 | public unsafe struct MtrlResource
8 | {
9 | [FieldOffset( 0x00 )]
10 | public ResourceHandle Handle;
11 |
12 | [FieldOffset( 0xD0 )]
13 | public ushort* TexSpace; // Contains the offsets for the tex files inside the string list.
14 |
15 | [FieldOffset( 0xE0 )]
16 | public byte* StringList;
17 |
18 | [FieldOffset( 0xF8 )]
19 | public ushort ShpkOffset;
20 |
21 | [FieldOffset( 0xFA )]
22 | public byte NumTex;
23 |
24 | public byte* ShpkString
25 | => StringList + ShpkOffset;
26 |
27 | public byte* TexString( int idx )
28 | => StringList + *( TexSpace + 4 + idx * 8 );
29 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Snapper
2 | A plugin for saving/loading the current appearance of a character and all applied mods
3 |
4 | Get it from: [https://raw.githubusercontent.com/eqbot/plugins/main/PluginMaster.json](https://raw.githubusercontent.com/eqbot/DalamudPluginRepo/master/pluginmaster.json)
5 |
6 | this is probably going to be abandoned after 7.3, so enjoy, i hear 'Snappy' is an alternative but I don't know where the repository for it is
7 |
8 | # Usage
9 | First, configure a working directory in the settings window.
10 |
11 | For best results saving snapshots, take them outside of GPose.
12 |
13 | To load snapshots, enter GPose and select an actor, then hit the Load button to select a folder containing a snapshot.
14 |
15 | For best results loading, use actors spawned by [Brio](https://github.com/AsgardXIV/Brio) to load onto. I validate loading behavior against those actors and likely will be slow to fix any issues stemming from loading onto other actors.
16 |
--------------------------------------------------------------------------------
/Snapper/Interop/RenderModel.cs:
--------------------------------------------------------------------------------
1 | using System.Runtime.InteropServices;
2 | using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
3 | using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
4 |
5 | namespace Penumbra.Interop.Structs;
6 |
7 | [StructLayout( LayoutKind.Explicit )]
8 | public unsafe struct RenderModel
9 | {
10 | [FieldOffset( 0x18 )]
11 | public RenderModel* PreviousModel;
12 |
13 | [FieldOffset( 0x20 )]
14 | public RenderModel* NextModel;
15 |
16 | [FieldOffset( 0x30 )]
17 | public ResourceHandle* ResourceHandle;
18 |
19 | [FieldOffset( 0x40 )]
20 | public Skeleton* Skeleton;
21 |
22 | [FieldOffset( 0x58 )]
23 | public void** BoneList;
24 |
25 | [FieldOffset( 0x60 )]
26 | public int BoneListCount;
27 |
28 | [FieldOffset( 0x70 )]
29 | private void* UnkDXBuffer1;
30 |
31 | [FieldOffset( 0x78 )]
32 | private void* UnkDXBuffer2;
33 |
34 | [FieldOffset( 0x80 )]
35 | private void* UnkDXBuffer3;
36 |
37 | [FieldOffset( 0x98 )]
38 | public void** Materials;
39 |
40 | [FieldOffset( 0xA0 )]
41 | public int MaterialCount;
42 | }
43 |
--------------------------------------------------------------------------------
/Snapper/Export/MareCharaFileData.cs:
--------------------------------------------------------------------------------
1 | using Dalamud.Game.ClientState.Objects.Enums;
2 | using Newtonsoft.Json;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Text;
6 |
7 | namespace MareSynchronos.Export;
8 |
9 | public record MareCharaFileData
10 | {
11 | public string Description { get; set; } = string.Empty;
12 | public string GlamourerData { get; set; } = string.Empty;
13 | public string CustomizePlusData { get; set; } = string.Empty;
14 | public string ManipulationData { get; set; } = string.Empty;
15 | public List Files { get; set; } = new();
16 | public List FileSwaps { get; set; } = new();
17 |
18 | public MareCharaFileData() { }
19 |
20 | public byte[] ToByteArray()
21 | {
22 | return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(this));
23 | }
24 |
25 | public static MareCharaFileData FromByteArray(byte[] data)
26 | {
27 | return JsonConvert.DeserializeObject(Encoding.UTF8.GetString(data))!;
28 | }
29 |
30 | public record FileSwap(IEnumerable GamePaths, string FileSwapPath);
31 |
32 | public record FileData(IEnumerable GamePaths, long Length);
33 | }
34 |
--------------------------------------------------------------------------------
/Snapper/Interop/Weapon.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | using FFXIVClientStructs.FFXIV.Client.Game.Character;
4 | using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
5 | using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
6 | using Penumbra.Interop.Structs;
7 |
8 | namespace Snapper.Interop;
9 |
10 | [StructLayout(LayoutKind.Explicit)]
11 | public unsafe struct Weapon
12 | {
13 | [FieldOffset(0x18)] public IntPtr Parent;
14 | [FieldOffset(0x20)] public IntPtr NextSibling;
15 | [FieldOffset(0x28)] public IntPtr PreviousSibling;
16 | [FieldOffset(0xA8)] public WeaponDrawObject* WeaponRenderModel;
17 | }
18 |
19 | [StructLayout(LayoutKind.Explicit)]
20 | public unsafe struct WeaponDrawObject
21 | {
22 | [FieldOffset(0x00)] public RenderModel* RenderModel;
23 | }
24 |
25 | [StructLayout(LayoutKind.Explicit)]
26 | public unsafe struct HumanExt
27 | {
28 | [FieldOffset(0x0)] public Human Human;
29 | [FieldOffset(0x9E8)] public Penumbra.Interop.Structs.ResourceHandle* Decal;
30 | [FieldOffset(0x9F0)] public Penumbra.Interop.Structs.ResourceHandle* LegacyBodyDecal;
31 | }
32 |
33 | [StructLayout(LayoutKind.Explicit)]
34 | public unsafe struct CharaExt
35 | {
36 | [FieldOffset(0x0)] public Character Character;
37 | [FieldOffset(0x650)] public Character* Mount;
38 | }
39 |
--------------------------------------------------------------------------------
/Snapper/Export/MareCharaFileHeader.cs:
--------------------------------------------------------------------------------
1 | using Lumina.Extensions;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace MareSynchronos.Export;
7 |
8 | public record MareCharaFileHeader(byte Version, MareCharaFileData CharaFileData)
9 | {
10 | public static readonly byte CurrentVersion = 1;
11 |
12 | public byte Version { get; set; } = Version;
13 | public MareCharaFileData CharaFileData { get; set; } = CharaFileData;
14 | public string FilePath { get; private set; }
15 |
16 | public void WriteToStream(BinaryWriter writer)
17 | {
18 | writer.Write('M');
19 | writer.Write('C');
20 | writer.Write('D');
21 | writer.Write('F');
22 | writer.Write(Version);
23 | var charaFileDataArray = CharaFileData.ToByteArray();
24 | writer.Write(charaFileDataArray.Length);
25 | writer.Write(charaFileDataArray);
26 | }
27 |
28 | public static MareCharaFileHeader? FromBinaryReader(string path, BinaryReader reader)
29 | {
30 | var chars = new string(reader.ReadChars(4));
31 | if (!string.Equals(chars, "MCDF", System.StringComparison.Ordinal)) throw new System.Exception("Not a Mare Chara File");
32 |
33 | MareCharaFileHeader? decoded = null;
34 |
35 | var version = reader.ReadByte();
36 | if (version == 1)
37 | {
38 | var dataLength = reader.ReadInt32();
39 |
40 | decoded = new(version, MareCharaFileData.FromByteArray(reader.ReadBytes(dataLength)));
41 | decoded.FilePath = path;
42 | }
43 |
44 | return decoded;
45 | }
46 |
47 | public void AdvanceReaderToData(BinaryReader reader)
48 | {
49 | reader.ReadChars(4);
50 | var version = reader.ReadByte();
51 | if (version == 1)
52 | {
53 | var length = reader.ReadInt32();
54 | _ = reader.ReadBytes(length);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/Snapper/Configuration.cs:
--------------------------------------------------------------------------------
1 | using Dalamud.Configuration;
2 | using Dalamud.Plugin;
3 | using System;
4 |
5 | namespace Snapper
6 | {
7 | [Serializable]
8 | public class Configuration : IPluginConfiguration
9 | {
10 | public int Version { get; set; } = 0;
11 |
12 | public bool SomePropertyToBeSavedAndWithADefault { get; set; } = true;
13 |
14 | public string WorkingDirectory { get; set; } = string.Empty;
15 |
16 | public string FallBackGlamourerString { get; set; } = "Bh+LCAAAAAAAAArEV9ly2jAU/Rc/Mxkv8pa3hoRAJ6QZoMmzAhfQRNiuJSdDM/n3XsmYeMNl2nr6Joujc+7mY/FujBiHR0gFiyPj0hoYNz8yluwgksbluzGlLBrTaKXWEwm7Ca4cYrr2wBimIBCzplzAwPiSJHxvXMo0Kx7mEs9WdqonDr+bh5WNy4+B8W29rusROySh54WW9Rei+U675hhoi6BveaQnwat4tS8L2mHg95UbFlNUuufapK/m3cGmquWYZl9aIwBZ0fLd3vK6oaloGxA36EvxHpYvrYp+X4pPKROyPUuvL83ZiEUbSFtF3d6G9H+Ijqke1vk2fquczR8Q8MhEnMckFvFmw2HVjnsCmiijLriacX5og4ky3ctbToUAvdR7KluzAR9mQsY79hO05ccr4AfcjC713iPlGS6c40ktiydvIVrltTxAauS51y32SZnGqmPGwDZbWUYo36hihpxGJYRb/31UDbWpQVkq5J53gzAOrmIRZZQd1HHzFxYNY553rIApD6/CbvagUbN6ek7QFl6d0fNPx9dQJy0FYZSPgMosBaurRRUkDuyZSOdsJDkb6Z6N9M5G+l1I/GTR5X5BpYzjLlyOqFfdbOn4cxq/Vcbn1FTcwfo3Q4HI+ZYm7a9gMYo7yvkEzbsrrPtYdM79V/rWdXwaZ3LbWUWWCMnyT9bptwZRjbH1m2KZWHKYom9194Pxem0ar8AV+tr8YGsdowIPaNTlVpwGzeAVr8rKnM8AN5JtluQJZASVTKuGnvssAh9oSncgUVxhjxLfX6cZlyzhrOLC1oXZsOrSGbxki/z2VOTQgs/7sIgjXb4HSJf4n4BuTrCrYb5ju2fKJ5GESDC5rx9rE9HO+AfnlAFfs/U6y+d6pjpiXniBjdfbEO8rtymA+gpfuIHphDb+Xbk6JOqaodpyG5TKgZuUvuPbLglLjJ7pB8SxvE9KojlJK+PRsUucrmmFnhmUOImOm5TCdCzfI77ptxYafaFE52hkOWmHWOp48EnntVf+XxAdXPY47EXhGmTHnYLruFFPkSXNRhCsOfaiTGjnncDCF4xYwC882VK9PtGVa1hSXo82tE1H3eaP3MVGwVw8H+ktPVhqDOqv6pTia4qfH/Wmfnz8AgAA//8=";
17 |
18 | // the below exist just to make saving less cumbersome
19 | [NonSerialized]
20 | private IDalamudPluginInterface? PluginInterface;
21 |
22 | public void Initialize(IDalamudPluginInterface pluginInterface)
23 | {
24 | this.PluginInterface = pluginInterface;
25 | }
26 |
27 | public void Save()
28 | {
29 | this.PluginInterface!.SavePluginConfig(this);
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Snapper/CharacterFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using Dalamud.Game.ClientState.Objects.Enums;
4 | using Dalamud.Game.ClientState.Objects.SubKinds;
5 | using Dalamud.Game.ClientState.Objects.Types;
6 |
7 | namespace Snapper
8 | {
9 | public static class CharacterFactory
10 | {
11 | private static ConstructorInfo? _characterConstructor;
12 |
13 | private static void Initialize()
14 | {
15 | _characterConstructor ??= typeof( ICharacter ).GetConstructor( BindingFlags.NonPublic | BindingFlags.Instance, null, new[]
16 | {
17 | typeof( IntPtr ),
18 | }, null )!;
19 | }
20 |
21 | private static ICharacter Character( IntPtr address )
22 | {
23 | Initialize();
24 | return (ICharacter)_characterConstructor?.Invoke( new object[]
25 | {
26 | address,
27 | } )!;
28 | }
29 |
30 | public static ICharacter? Convert( IGameObject? actor )
31 | {
32 | if( actor == null )
33 | {
34 | return null;
35 | }
36 |
37 | return actor switch
38 | {
39 | IPlayerCharacter p => p,
40 | IBattleChara b => b,
41 | _ => actor.ObjectKind switch
42 | {
43 | ObjectKind.BattleNpc => Character( actor.Address ),
44 | ObjectKind.Companion => Character( actor.Address ),
45 | ObjectKind.Retainer => Character( actor.Address ),
46 | ObjectKind.EventNpc => Character( actor.Address ),
47 | _ => null,
48 | },
49 | };
50 | }
51 | }
52 |
53 | public static class GameObjectExtensions
54 | {
55 | private const int ModelTypeOffset = 0x01B4;
56 |
57 | public static unsafe int ModelType( this IGameObject actor )
58 | => *( int* )( actor.Address + ModelTypeOffset );
59 |
60 | public static unsafe void SetModelType( this IGameObject actor, int value )
61 | => *( int* )( actor.Address + ModelTypeOffset ) = value;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Snapper/Windows/ConfigWindow.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Numerics;
4 | using Dalamud.Interface;
5 | using Dalamud.Interface.ImGuiFileDialog;
6 | using Dalamud.Interface.Windowing;
7 | using Dalamud.Bindings.ImGui;
8 |
9 | namespace Snapper.Windows;
10 |
11 | public class ConfigWindow : Window, IDisposable
12 | {
13 | private Configuration Configuration;
14 | private FileDialogManager FileDialogManager;
15 |
16 | //ImGuiWindowFlags.NoResize
17 | public ConfigWindow(Plugin plugin) : base(
18 | "Snapper Settings",
19 | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoScrollbar |
20 | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoResize)
21 | {
22 | this.Size = new Vector2(500, 115);
23 | this.SizeCondition = ImGuiCond.Always;
24 |
25 | this.Configuration = plugin.Configuration;
26 | this.FileDialogManager = plugin.FileDialogManager;
27 | }
28 |
29 | public void Dispose() { }
30 |
31 | public override void Draw()
32 | {
33 | var workingDirectory = Configuration.WorkingDirectory;
34 | ImGui.InputText("Working Folder", ref workingDirectory, 255, ImGuiInputTextFlags.ReadOnly);
35 | ImGui.SameLine();
36 | ImGui.PushFont(UiBuilder.IconFont);
37 | string folderIcon = FontAwesomeIcon.Folder.ToIconString();
38 | if (ImGui.Button(folderIcon))
39 | {
40 | FileDialogManager.OpenFolderDialog("Snapper working directory", (status, path) =>
41 | {
42 | if (!status)
43 | {
44 | return;
45 | }
46 |
47 | if (Directory.Exists(path))
48 | {
49 | this.Configuration.WorkingDirectory = path;
50 | this.Configuration.Save();
51 | }
52 | });
53 | }
54 | ImGui.PopFont();
55 |
56 | ImGui.Text("Glamourer design fallback string (Temp until GlamourerAPI workaround)");
57 | string fallbackString = Configuration.FallBackGlamourerString;
58 | ImGui.InputText("##input-format", ref fallbackString, 2500);
59 | if (fallbackString != Configuration.FallBackGlamourerString)
60 | {
61 | Configuration.FallBackGlamourerString = fallbackString;
62 | Configuration.Save();
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Snapper/Models/FileReplacement.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 | using System.Text.RegularExpressions;
6 | using Microsoft.Extensions.Logging;
7 |
8 | namespace Snapper.Models;
9 |
10 | public class FileReplacement
11 | {
12 | private readonly Plugin plugin;
13 |
14 | public FileReplacement(Plugin plugin)
15 | {
16 | this.plugin = plugin;
17 | }
18 |
19 | public bool Computed => IsFileSwap || !HasFileReplacement || !string.IsNullOrEmpty(Hash);
20 |
21 | public List GamePaths { get; set; } = new();
22 |
23 | public bool HasFileReplacement => GamePaths.Count >= 1 && GamePaths.Any(p => !string.Equals(p, ResolvedPath, System.StringComparison.Ordinal));
24 |
25 | public bool IsFileSwap => !Regex.IsMatch(ResolvedPath, @"^[a-zA-Z]:(/|\\)", RegexOptions.ECMAScript) && !string.Equals(GamePaths.First(), ResolvedPath, System.StringComparison.Ordinal);
26 |
27 | public string Hash { get; set; } = string.Empty;
28 |
29 | public string ResolvedPath { get; set; } = string.Empty;
30 |
31 | private void SetResolvedPath(string path)
32 | {
33 | ResolvedPath = path.ToLowerInvariant().Replace('\\', '/');
34 | if (!HasFileReplacement || IsFileSwap) return;
35 | }
36 |
37 | public bool Verify()
38 | {
39 | ResolvePath(GamePaths.First());
40 |
41 | var success = IsFileSwap;
42 |
43 | return success;
44 | }
45 |
46 | public override string ToString()
47 | {
48 | StringBuilder builder = new();
49 | builder.AppendLine($"Modded: {HasFileReplacement} - {string.Join(",", GamePaths)} => {ResolvedPath}");
50 | return builder.ToString();
51 | }
52 |
53 | internal void ReverseResolvePath(string path)
54 | {
55 | GamePaths = plugin.IpcManager.PenumbraReverseResolvePlayer(path).ToList();
56 | SetResolvedPath(path);
57 | }
58 |
59 | internal void ResolvePath(string path)
60 | {
61 | GamePaths = new List { path };
62 | SetResolvedPath(plugin.IpcManager.PenumbraResolvePath(path));
63 | }
64 |
65 | internal void ResolvePathObject(string path, int objIdx)
66 | {
67 | GamePaths = new List { path };
68 | SetResolvedPath(plugin.IpcManager.PenumbraResolvePathObject(path, objIdx));
69 | }
70 |
71 | internal void ReverseResolvePathObject(string path, int objIdx)
72 | {
73 | GamePaths = plugin.IpcManager.PenumbraReverseResolveObject(path, objIdx).ToList();
74 | SetResolvedPath(path);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Snapper/Windows/MainWindow.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Numerics;
6 | using Dalamud.Game.ClientState.Objects.Enums;
7 | using Dalamud.Game.ClientState.Objects.Types;
8 | using Dalamud.Interface.Windowing;
9 | using Dalamud.Logging;
10 | using Dalamud.Bindings.ImGui;
11 | using ImGuiScene;
12 | using LZ4;
13 | using Snapper.Utils;
14 |
15 | namespace Snapper.Windows;
16 |
17 | public partial class MainWindow : Window, IDisposable
18 | {
19 | private const float SelectorWidth = 200;
20 |
21 | private Plugin Plugin;
22 |
23 | public MainWindow(Plugin plugin) : base(
24 | "Snapper - Fork", ImGuiWindowFlags.NoScrollbar | ImGuiWindowFlags.NoScrollWithMouse)
25 | {
26 | this.SizeConstraints = new WindowSizeConstraints
27 | {
28 | MinimumSize = new Vector2(375, 330),
29 | MaximumSize = new Vector2(float.MaxValue, float.MaxValue)
30 | };
31 |
32 | this.Plugin = plugin;
33 | }
34 |
35 | public void Dispose()
36 | {
37 | }
38 |
39 | public override void Draw()
40 | {
41 | if (ImGui.Button("Show Settings"))
42 | {
43 | this.Plugin.DrawConfigUI();
44 | }
45 |
46 | ImGui.SameLine();
47 | if(ImGui.Button("Revert snapshots"))
48 | {
49 | this.Plugin.SnapshotManager.RevertAllSnapshots();
50 | }
51 |
52 | ImGui.SameLine();
53 | if(ImGui.Button("Import MCDF file"))
54 | {
55 | Plugin.FileDialogManager.OpenFileDialog("Snapshot selection", ".mcdf", (status, path) =>
56 | {
57 | if (!status)
58 | {
59 | return;
60 | }
61 |
62 | if (File.Exists(path[0]))
63 | {
64 | this.Plugin.MCDFManager.LoadMareCharaFile(path[0]);
65 | this.Plugin.MCDFManager.ExtractMareCharaFile();
66 | }
67 | }, 1, Plugin.Configuration.WorkingDirectory);
68 | }
69 |
70 | ImGui.SameLine();
71 | if(ImGui.Button("Export snapshot as PMP"))
72 | {
73 | Plugin.FileDialogManager.OpenFolderDialog("Snapshot selection", (status, path) =>
74 | {
75 | if (!status)
76 | {
77 | return;
78 | }
79 |
80 | if (Directory.Exists(path))
81 | {
82 | Plugin.PMPExportManager.SnapshotToPMP(path);
83 | }
84 | }, Plugin.Configuration.WorkingDirectory);
85 | }
86 |
87 | ImGui.Spacing();
88 |
89 | this.DrawPlayerSelector();
90 | if (!currentLabel.Any())
91 | return;
92 |
93 | ImGui.SameLine();
94 | this.DrawActorPanel();
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/Snapper/Snapper.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 0.0.0.6
7 | Snapper
8 |
9 | https://github.com/goatcorp/SamplePlugin
10 |
11 |
12 |
13 | net9.0-windows7.0
14 | x64
15 | enable
16 | latest
17 | true
18 | false
19 | false
20 | true
21 | true
22 |
23 |
24 |
25 | $(appdata)\XIVLauncher\addon\Hooks\dev\
26 |
27 |
28 |
29 | $(DALAMUD_HOME)/
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | $(DalamudLibPath)FFXIVClientStructs.dll
48 | false
49 |
50 |
51 | $(DalamudLibPath)Newtonsoft.Json.dll
52 | false
53 |
54 |
55 | $(DalamudLibPath)Dalamud.dll
56 | false
57 |
58 |
59 | $(DalamudLibPath)Dalamud.Bindings.ImGui.dll
60 | false
61 |
62 |
63 | $(DalamudLibPath)InteropGenerator.Runtime.dll
64 | false
65 |
66 |
67 | $(DalamudLibPath)ImGuiScene.dll
68 | false
69 |
70 |
71 | $(DalamudLibPath)Lumina.dll
72 | false
73 |
74 |
75 | $(DalamudLibPath)Lumina.Excel.dll
76 | false
77 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/Snapper/Utils/Logger.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using Dalamud.Logging;
4 | using Dalamud.Plugin.Services;
5 | using Dalamud.Utility;
6 | using FFXIVClientStructs.FFXIV.Client.UI.Agent;
7 | using Microsoft.Extensions.Logging;
8 |
9 | namespace Snapper.Utils;
10 |
11 | internal class Logger : ILogger
12 | {
13 | private readonly string name;
14 |
15 | public static void Info(string info)
16 | {
17 | var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
18 | Plugin.Log.Information($"[{caller}] {info}", "");
19 | }
20 |
21 | public static void Debug(string debug, string stringToHighlight = "")
22 | {
23 | var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
24 | if (debug.Contains(stringToHighlight, StringComparison.Ordinal) && !stringToHighlight.IsNullOrEmpty())
25 | {
26 | Plugin.Log.Warning($"[{caller}] {debug}");
27 | }
28 | else
29 | {
30 | Plugin.Log.Debug($"[{caller}] {debug}");
31 | }
32 | }
33 |
34 | public static void Error(string msg, Exception ex)
35 | {
36 | var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
37 | Plugin.Log.Error($"[{caller}] {msg} {Environment.NewLine} Exception: {ex.Message} {Environment.NewLine} {ex.StackTrace}");
38 | }
39 |
40 | public static void Warn(string msg, Exception ex)
41 | {
42 | var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
43 | Plugin.Log.Warning($"[{caller}] {msg} {Environment.NewLine} Exception: {ex.Message} {Environment.NewLine} {ex.StackTrace}");
44 | }
45 |
46 | public static void Error(string msg)
47 | {
48 | var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
49 | Plugin.Log.Error($"[{caller}] {msg}");
50 | }
51 |
52 | public static void Warn(string warn)
53 | {
54 | var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
55 | Plugin.Log.Warning($"[{caller}] {warn}");
56 | }
57 |
58 | public static void Verbose(string verbose)
59 | {
60 | var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
61 | #if DEBUG
62 | Plugin.Log.Debug($"[{caller}] {verbose}");
63 | #else
64 | Plugin.Log.Verbose($"[{caller}] {verbose}");
65 | #endif
66 | }
67 | public Logger(string name)
68 | {
69 | this.name = name;
70 | }
71 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
72 | {
73 | if (!IsEnabled(logLevel)) return;
74 |
75 | switch (logLevel)
76 | {
77 | case LogLevel.Debug:
78 | Plugin.Log.Debug($"[{name}] [{eventId}] {formatter(state, exception)}");
79 | break;
80 | case LogLevel.Error:
81 | case LogLevel.Critical:
82 | Plugin.Log.Error($"[{name}] [{eventId}] {formatter(state, exception)}");
83 | break;
84 | case LogLevel.Information:
85 | Plugin.Log.Information($"[{name}] [{eventId}] {formatter(state, exception)}");
86 | break;
87 | case LogLevel.Warning:
88 | Plugin.Log.Warning($"[{name}] [{eventId}] {formatter(state, exception)}");
89 | break;
90 | case LogLevel.Trace:
91 | default:
92 | #if DEBUG
93 | Plugin.Log.Verbose($"[{name}] [{eventId}] {formatter(state, exception)}");
94 | #else
95 | Plugin.Log.Verbose($"[{name}] {eventId} {state} {formatter(state, exception)}");
96 | #endif
97 | break;
98 | }
99 | }
100 |
101 | public bool IsEnabled(LogLevel logLevel)
102 | {
103 | return true;
104 | }
105 |
106 | public IDisposable BeginScope(TState state) => default!;
107 | }
108 |
--------------------------------------------------------------------------------
/Snapper/Plugin.cs:
--------------------------------------------------------------------------------
1 | using Dalamud.Game.Command;
2 | using Dalamud.IoC;
3 | using Dalamud.Plugin;
4 | using Dalamud.Plugin.Services;
5 | using System.IO;
6 | using System.Reflection;
7 | using Dalamud.Interface.Windowing;
8 | using Snapper.Windows;
9 | using Dalamud.Interface.ImGuiFileDialog;
10 | using Dalamud.Game.ClientState.Objects;
11 | using Snapper.Utils;
12 | using Dalamud.Game.ClientState;
13 | using Dalamud.Game;
14 | using Dalamud.Game.Gui;
15 | using Dalamud.Game.ClientState.Conditions;
16 | using Snapper.Managers;
17 | using MareSynchronos.Export;
18 | using Snapper.PMP;
19 |
20 | namespace Snapper
21 | {
22 | public sealed class Plugin : IDalamudPlugin
23 | {
24 | public string Name => "Snapper";
25 | private const string CommandName = "/psnap";
26 |
27 | public Configuration Configuration { get; init; }
28 | public IObjectTable Objects { get; init; }
29 | public WindowSystem WindowSystem = new("Snapper");
30 | public FileDialogManager FileDialogManager = new FileDialogManager();
31 | public DalamudUtil DalamudUtil { get; init; }
32 | public IpcManager IpcManager { get; init; }
33 | public SnapshotManager SnapshotManager { get; init; }
34 | public MareCharaFileManager MCDFManager { get; init; }
35 | public PMPExportManager PMPExportManager { get; init; }
36 |
37 | private ConfigWindow ConfigWindow { get; init; }
38 | private MainWindow MainWindow { get; init; }
39 |
40 |
41 | [PluginService] public static IPluginLog Log { get; private set; } = null!;
42 | [PluginService] public static ICommandManager CommandManager { get; private set; } = null!;
43 | [PluginService] public static IDalamudPluginInterface PluginInterface { get; private set; } = null!;
44 | //[PluginService] public static Configuration Configuration { get; private set; } = null!;
45 |
46 | //public IPluginLog PluginLog { get; private set; } = null!;
47 | //public static object Log { get; internal set; }
48 |
49 | public Plugin(
50 | IFramework framework,
51 | IObjectTable objectTable,
52 | IClientState clientState,
53 | ICondition condition,
54 | IChatGui chatGui)
55 | {
56 | this.Objects = objectTable;
57 |
58 | this.DalamudUtil = new DalamudUtil(clientState, objectTable, framework, condition, chatGui);
59 | this.IpcManager = new IpcManager(PluginInterface, this.DalamudUtil);
60 |
61 | Configuration = PluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
62 | Configuration.Initialize(PluginInterface);
63 |
64 | this.SnapshotManager = new SnapshotManager(this);
65 | this.MCDFManager = new MareCharaFileManager(this);
66 | this.PMPExportManager = new PMPExportManager(this);
67 |
68 |
69 | ConfigWindow = new ConfigWindow(this);
70 | MainWindow = new MainWindow(this);
71 | WindowSystem.AddWindow(ConfigWindow);
72 | WindowSystem.AddWindow(MainWindow);
73 |
74 | CommandManager.AddHandler(CommandName, new CommandInfo(OnCommand)
75 | {
76 | HelpMessage = "Opens main Snapper interface"
77 | });
78 |
79 | PluginInterface.UiBuilder.Draw += DrawUI;
80 | PluginInterface.UiBuilder.OpenConfigUi += DrawConfigUI;
81 | PluginInterface.UiBuilder.DisableGposeUiHide = true;
82 | PluginInterface.UiBuilder.OpenMainUi += ToggleMainUI;
83 | }
84 |
85 | public void Dispose()
86 | {
87 | this.WindowSystem.RemoveAllWindows();
88 | CommandManager.RemoveHandler(CommandName);
89 | this.SnapshotManager.RevertAllSnapshots();
90 | }
91 |
92 | private void OnCommand(string command, string args)
93 | {
94 | // in response to the slash command, just display our main ui
95 | ToggleMainUI();
96 | }
97 |
98 | private void DrawUI()
99 | {
100 | this.WindowSystem.Draw();
101 | this.FileDialogManager.Draw();
102 | }
103 |
104 | public void DrawConfigUI()
105 | {
106 | ConfigWindow.IsOpen = true;
107 | }
108 |
109 | public void ToggleMainUI() => MainWindow.Toggle();
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/Snapper/Windows/ImGuiRaii.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Numerics;
4 | using Dalamud.Bindings.ImGui;
5 |
6 | namespace Snapper.Windows
7 | {
8 | public sealed class ImGuiRaii : IDisposable
9 | {
10 | private int _colorStack;
11 | private int _fontStack;
12 | private int _styleStack;
13 | private float _indentation;
14 |
15 | private Stack? _onDispose;
16 |
17 | public static ImGuiRaii NewGroup()
18 | => new ImGuiRaii().Group();
19 |
20 | public ImGuiRaii Group()
21 | => Begin(ImGui.BeginGroup, ImGui.EndGroup);
22 |
23 | public static ImGuiRaii NewTooltip()
24 | => new ImGuiRaii().Tooltip();
25 |
26 | public ImGuiRaii Tooltip()
27 | => Begin(ImGui.BeginTooltip, ImGui.EndTooltip);
28 |
29 | public ImGuiRaii PushColor(ImGuiCol which, uint color)
30 | {
31 | ImGui.PushStyleColor(which, color);
32 | ++_colorStack;
33 | return this;
34 | }
35 |
36 | public ImGuiRaii PushColor(ImGuiCol which, Vector4 color)
37 | {
38 | ImGui.PushStyleColor(which, color);
39 | ++_colorStack;
40 | return this;
41 | }
42 |
43 | public ImGuiRaii PopColors(int n = 1)
44 | {
45 | var actualN = Math.Min(n, _colorStack);
46 | if (actualN > 0)
47 | {
48 | ImGui.PopStyleColor(actualN);
49 | _colorStack -= actualN;
50 | }
51 |
52 | return this;
53 | }
54 |
55 | public ImGuiRaii PushStyle(ImGuiStyleVar style, Vector2 value)
56 | {
57 | ImGui.PushStyleVar(style, value);
58 | ++_styleStack;
59 | return this;
60 | }
61 |
62 | public ImGuiRaii PushStyle(ImGuiStyleVar style, float value)
63 | {
64 | ImGui.PushStyleVar(style, value);
65 | ++_styleStack;
66 | return this;
67 | }
68 |
69 | public ImGuiRaii PopStyles(int n = 1)
70 | {
71 | var actualN = Math.Min(n, _styleStack);
72 | if (actualN > 0)
73 | {
74 | ImGui.PopStyleVar(actualN);
75 | _styleStack -= actualN;
76 | }
77 |
78 | return this;
79 | }
80 |
81 | public ImGuiRaii PushFont(ImFontPtr font)
82 | {
83 | ImGui.PushFont(font);
84 | ++_fontStack;
85 | return this;
86 | }
87 |
88 | public ImGuiRaii PopFonts(int n = 1)
89 | {
90 | var actualN = Math.Min(n, _fontStack);
91 |
92 | while (actualN-- > 0)
93 | {
94 | ImGui.PopFont();
95 | --_fontStack;
96 | }
97 |
98 | return this;
99 | }
100 |
101 | public ImGuiRaii Indent(float width)
102 | {
103 | if (width != 0)
104 | {
105 | ImGui.Indent(width);
106 | _indentation += width;
107 | }
108 |
109 | return this;
110 | }
111 |
112 | public ImGuiRaii Unindent(float width)
113 | => Indent(-width);
114 |
115 | public bool Begin(Func begin, Action end)
116 | {
117 | if (begin())
118 | {
119 | _onDispose ??= new Stack();
120 | _onDispose.Push(end);
121 | return true;
122 | }
123 |
124 | return false;
125 | }
126 |
127 | public ImGuiRaii Begin(Action begin, Action end)
128 | {
129 | begin();
130 | _onDispose ??= new Stack();
131 | _onDispose.Push(end);
132 | return this;
133 | }
134 |
135 | public void End(int n = 1)
136 | {
137 | var actualN = Math.Min(n, _onDispose?.Count ?? 0);
138 | while (actualN-- > 0)
139 | _onDispose!.Pop()();
140 | }
141 |
142 | public void Dispose()
143 | {
144 | Unindent(_indentation);
145 | PopColors(_colorStack);
146 | PopStyles(_styleStack);
147 | PopFonts(_fontStack);
148 | if (_onDispose != null)
149 | {
150 | End(_onDispose.Count);
151 | _onDispose = null;
152 | }
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/Snapper/PMP/PMPExportManager.cs:
--------------------------------------------------------------------------------
1 | using Snapper.Models;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using Snapper.Utils;
9 | using System.Text.Json;
10 | using System.ComponentModel.Design;
11 | using System.IO.Compression;
12 |
13 | namespace Snapper.PMP
14 | {
15 | public class PMPExportManager
16 | {
17 | private Configuration configuration;
18 | public PMPExportManager(Plugin plugin)
19 | {
20 | this.configuration = plugin.Configuration;
21 | }
22 | public PMPExportManager(Configuration configuration)
23 | {
24 | this.configuration = configuration;
25 | }
26 |
27 | public void SnapshotToPMP(string snapshotPath)
28 | {
29 | Logger.Debug($"Operating on {snapshotPath}");
30 | //read snapshot
31 | string infoJson = File.ReadAllText(Path.Combine(snapshotPath, "snapshot.json"));
32 | if (infoJson == null)
33 | {
34 | Logger.Warn("No snapshot json found, aborting");
35 | return;
36 | }
37 | SnapshotInfo? snapshotInfo = JsonSerializer.Deserialize(infoJson);
38 | if (snapshotInfo == null)
39 | {
40 | Logger.Warn("Failed to deserialize snapshot json, aborting");
41 | return;
42 | }
43 |
44 | //begin building PMP
45 | string snapshotName = new DirectoryInfo(snapshotPath).Name;
46 | string pmpFileName = $"{snapshotName}_{Guid.NewGuid()}";
47 |
48 |
49 | string workingDirectory = Path.Combine(configuration.WorkingDirectory, $"temp_{pmpFileName}");
50 | if (!Directory.Exists(workingDirectory))
51 | {
52 | Directory.CreateDirectory(workingDirectory);
53 | }
54 |
55 | //meta.json
56 | PMPMetadata metadata = new PMPMetadata();
57 | metadata.Name = snapshotName;
58 | metadata.Author = $"not {snapshotName} anymore :3";
59 | using(FileStream stream = new FileStream(Path.Combine(workingDirectory, "meta.json"), FileMode.Create))
60 | {
61 | JsonSerializer.Serialize(stream, metadata);
62 | }
63 |
64 | //default_mod.json
65 | PMPDefaultMod defaultMod = new PMPDefaultMod();
66 | foreach (var file in snapshotInfo.FileReplacements)
67 | {
68 | foreach(var replacement in file.Value)
69 | {
70 | defaultMod.Files.Add(replacement, file.Key);
71 | }
72 | }
73 |
74 | List? manipulations;
75 | FromCompressedBase64>(snapshotInfo.ManipulationString, out manipulations);
76 | if(manipulations != null)
77 | {
78 | defaultMod.Manipulations = manipulations;
79 | }
80 | using (FileStream stream = new FileStream(Path.Combine(workingDirectory, "default_mod.json"), FileMode.Create))
81 | {
82 | JsonSerializer.Serialize(stream, defaultMod, new JsonSerializerOptions { WriteIndented = true});
83 | }
84 |
85 | //mods
86 | foreach(var file in snapshotInfo.FileReplacements)
87 | {
88 |
89 | string modPath = Path.Combine(snapshotPath, file.Key);
90 | string destPath = Path.Combine(workingDirectory, file.Key);
91 | Logger.Debug($"Copying {modPath}");
92 | Directory.CreateDirectory(Path.GetDirectoryName(destPath) ?? "");
93 | File.Copy(modPath, destPath);
94 | }
95 |
96 | //zip and make pmp file
97 | ZipFile.CreateFromDirectory(workingDirectory, Path.Combine(configuration.WorkingDirectory, $"{pmpFileName}.pmp"));
98 |
99 | //cleanup
100 | Directory.Delete(workingDirectory, true);
101 | }
102 |
103 |
104 | // Decompress a base64 encoded string to the given type and a prepended version byte if possible.
105 | // On failure, data will be default and version will be byte.MaxValue.
106 | internal static byte FromCompressedBase64(string base64, out T? data)
107 | {
108 | var version = byte.MaxValue;
109 | try
110 | {
111 | var bytes = Convert.FromBase64String(base64);
112 | using var compressedStream = new MemoryStream(bytes);
113 | using var zipStream = new GZipStream(compressedStream, CompressionMode.Decompress);
114 | using var resultStream = new MemoryStream();
115 | zipStream.CopyTo(resultStream);
116 | bytes = resultStream.ToArray();
117 | version = bytes[0];
118 | var json = Encoding.UTF8.GetString(bytes, 1, bytes.Length - 1);
119 | data = JsonSerializer.Deserialize(json);
120 | }
121 | catch
122 | {
123 | data = default;
124 | }
125 |
126 | return version;
127 | }
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/Snapper/Export/MareCharaFileManager.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 |
3 | using Dalamud.Game.ClientState.Objects.Types;
4 | using LZ4;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.IO;
8 | using System.Threading.Tasks;
9 | using Snapper.Utils;
10 | using Snapper;
11 | using Snapper.Managers;
12 | using Snapper.Models;
13 | using System.Text.Json;
14 |
15 | namespace MareSynchronos.Export;
16 | public class MareCharaFileManager
17 | {
18 | private readonly Configuration _configuration;
19 | public MareCharaFileHeader? LoadedCharaFile { get; private set; }
20 |
21 | public MareCharaFileManager(Plugin plugin)
22 | {
23 | _configuration = plugin.Configuration;
24 | }
25 |
26 | public MareCharaFileManager(Configuration configuration)
27 | {
28 | _configuration = configuration;
29 | }
30 |
31 | public void ClearMareCharaFile()
32 | {
33 | LoadedCharaFile = null;
34 | }
35 |
36 | public void LoadMareCharaFile(string filePath)
37 | {
38 | try
39 | {
40 | using var unwrapped = File.OpenRead(filePath);
41 | using var lz4Stream = new LZ4Stream(unwrapped, LZ4StreamMode.Decompress, LZ4StreamFlags.HighCompression);
42 | using var reader = new BinaryReader(lz4Stream);
43 | LoadedCharaFile = MareCharaFileHeader.FromBinaryReader(filePath, reader);
44 | Logger.Debug("Read Mare Chara File");
45 | Logger.Debug("Version: " + LoadedCharaFile.Version);
46 |
47 | }
48 | catch { throw; }
49 | }
50 |
51 | private Dictionary ExtractFilesFromCharaFile(MareCharaFileHeader charaFileHeader, BinaryReader reader)
52 | {
53 | Dictionary gamePathToFilePath = new(StringComparer.Ordinal);
54 | int i = 0;
55 | foreach (var fileData in charaFileHeader.CharaFileData.Files)
56 | {
57 | var fileName = Path.Combine(_configuration.WorkingDirectory, "mcdf_" + charaFileHeader.CharaFileData.Description, "mare_" + (i++) + ".tmp");
58 | var length = fileData.Length;
59 | var bufferSize = 4 * 1024 * 1024;
60 | var buffer = new byte[bufferSize];
61 | using var fs = File.OpenWrite(fileName);
62 | using var wr = new BinaryWriter(fs);
63 | while (length > 0)
64 | {
65 | if (length < bufferSize) bufferSize = (int)length;
66 | buffer = reader.ReadBytes(bufferSize);
67 | wr.Write(length > bufferSize ? buffer : buffer.Take((int)length).ToArray());
68 | length -= bufferSize;
69 | }
70 | foreach (var path in fileData.GamePaths)
71 | {
72 | gamePathToFilePath[path] = Path.GetFileName(fileName);
73 | Logger.Verbose(path + " => " + fileName);
74 | }
75 | }
76 |
77 | return gamePathToFilePath;
78 | }
79 |
80 | public string? ExtractMareCharaFile()
81 | {
82 | Dictionary extractedFiles = new();
83 | try
84 | {
85 | if (LoadedCharaFile == null || !File.Exists(LoadedCharaFile.FilePath)) return null;
86 | var path = Path.Combine(_configuration.WorkingDirectory, "mcdf_" + LoadedCharaFile.CharaFileData.Description);
87 | //create directory if needed
88 | if (Directory.Exists(path))
89 | {
90 | Logger.Warn("Snapshot already existed, deleting");
91 | Directory.Delete(path, true);
92 | }
93 | Directory.CreateDirectory(path);
94 |
95 | //extract files
96 | using var unwrapped = File.OpenRead(LoadedCharaFile.FilePath);
97 | using var lz4Stream = new LZ4Stream(unwrapped, LZ4StreamMode.Decompress, LZ4StreamFlags.HighCompression);
98 | using var reader = new BinaryReader(lz4Stream);
99 | LoadedCharaFile.AdvanceReaderToData(reader);
100 | extractedFiles = ExtractFilesFromCharaFile(LoadedCharaFile, reader);
101 |
102 | //generate snapper JSON manifest
103 | SnapshotInfo snapInfo = new SnapshotInfo();
104 | snapInfo.GlamourerString = LoadedCharaFile.CharaFileData.GlamourerData;
105 | snapInfo.ManipulationString = LoadedCharaFile.CharaFileData.ManipulationData;
106 | snapInfo.CustomizeData = LoadedCharaFile.CharaFileData.CustomizePlusData;
107 | snapInfo.FileReplacements = new();
108 | foreach(var record in extractedFiles)
109 | {
110 | if (snapInfo.FileReplacements.ContainsKey(record.Value))
111 | {
112 | snapInfo.FileReplacements[record.Value].Add(record.Key);
113 | }
114 | else
115 | {
116 | snapInfo.FileReplacements.Add(record.Value, new List() { record.Key });
117 | }
118 | }
119 |
120 | string infoJson = JsonSerializer.Serialize(snapInfo);
121 | File.WriteAllText(Path.Combine(_configuration.WorkingDirectory, "mcdf_" + LoadedCharaFile.CharaFileData.Description, "snapshot.json"), infoJson);
122 | return path;
123 | }
124 | catch { throw; }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/Snapper.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.3.32825.248
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snapper", "Snapper\Snapper.csproj", "{13C812E9-0D42-4B95-8646-40EEBF30636F}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mcdf_pmp_ripper", "mcdf_pmp_ripper\mcdf_pmp_ripper.csproj", "{3D1F6AAF-8825-4CD8-AB9C-E9E891330C5B}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Penumbra.GameData", "Penumbra.GameData\Penumbra.GameData.csproj", "{5AA03B81-A0A2-4EE1-8510-A656577F89FB}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Penumbra.Api", "Penumbra.Api\Penumbra.Api.csproj", "{EF6463D8-94E4-432F-9FAA-94160AAD7B27}"
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OtterGui", "OtterGui\OtterGui.csproj", "{56ECE7C7-73F4-4E1C-92FB-9015F37BCAFE}"
15 | EndProject
16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Penumbra.String", "Penumbra.String\Penumbra.String.csproj", "{E506688B-ED69-4FFB-9751-E716A005506C}"
17 | EndProject
18 | Global
19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
20 | Debug|Any CPU = Debug|Any CPU
21 | Debug|x64 = Debug|x64
22 | Release|Any CPU = Release|Any CPU
23 | Release|x64 = Release|x64
24 | EndGlobalSection
25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
26 | {13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|Any CPU.ActiveCfg = Debug|x64
27 | {13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|Any CPU.Build.0 = Debug|x64
28 | {13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.ActiveCfg = Debug|x64
29 | {13C812E9-0D42-4B95-8646-40EEBF30636F}.Debug|x64.Build.0 = Debug|x64
30 | {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|Any CPU.ActiveCfg = Release|x64
31 | {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|Any CPU.Build.0 = Release|x64
32 | {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.ActiveCfg = Release|x64
33 | {13C812E9-0D42-4B95-8646-40EEBF30636F}.Release|x64.Build.0 = Release|x64
34 | {3D1F6AAF-8825-4CD8-AB9C-E9E891330C5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
35 | {3D1F6AAF-8825-4CD8-AB9C-E9E891330C5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
36 | {3D1F6AAF-8825-4CD8-AB9C-E9E891330C5B}.Debug|x64.ActiveCfg = Debug|Any CPU
37 | {3D1F6AAF-8825-4CD8-AB9C-E9E891330C5B}.Debug|x64.Build.0 = Debug|Any CPU
38 | {3D1F6AAF-8825-4CD8-AB9C-E9E891330C5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {3D1F6AAF-8825-4CD8-AB9C-E9E891330C5B}.Release|Any CPU.Build.0 = Release|Any CPU
40 | {3D1F6AAF-8825-4CD8-AB9C-E9E891330C5B}.Release|x64.ActiveCfg = Release|Any CPU
41 | {3D1F6AAF-8825-4CD8-AB9C-E9E891330C5B}.Release|x64.Build.0 = Release|Any CPU
42 | {5AA03B81-A0A2-4EE1-8510-A656577F89FB}.Debug|Any CPU.ActiveCfg = Debug|x64
43 | {5AA03B81-A0A2-4EE1-8510-A656577F89FB}.Debug|Any CPU.Build.0 = Debug|x64
44 | {5AA03B81-A0A2-4EE1-8510-A656577F89FB}.Debug|x64.ActiveCfg = Debug|x64
45 | {5AA03B81-A0A2-4EE1-8510-A656577F89FB}.Debug|x64.Build.0 = Debug|x64
46 | {5AA03B81-A0A2-4EE1-8510-A656577F89FB}.Release|Any CPU.ActiveCfg = Release|x64
47 | {5AA03B81-A0A2-4EE1-8510-A656577F89FB}.Release|Any CPU.Build.0 = Release|x64
48 | {5AA03B81-A0A2-4EE1-8510-A656577F89FB}.Release|x64.ActiveCfg = Release|x64
49 | {5AA03B81-A0A2-4EE1-8510-A656577F89FB}.Release|x64.Build.0 = Release|x64
50 | {EF6463D8-94E4-432F-9FAA-94160AAD7B27}.Debug|Any CPU.ActiveCfg = Debug|x64
51 | {EF6463D8-94E4-432F-9FAA-94160AAD7B27}.Debug|Any CPU.Build.0 = Debug|x64
52 | {EF6463D8-94E4-432F-9FAA-94160AAD7B27}.Debug|x64.ActiveCfg = Debug|x64
53 | {EF6463D8-94E4-432F-9FAA-94160AAD7B27}.Debug|x64.Build.0 = Debug|x64
54 | {EF6463D8-94E4-432F-9FAA-94160AAD7B27}.Release|Any CPU.ActiveCfg = Release|x64
55 | {EF6463D8-94E4-432F-9FAA-94160AAD7B27}.Release|Any CPU.Build.0 = Release|x64
56 | {EF6463D8-94E4-432F-9FAA-94160AAD7B27}.Release|x64.ActiveCfg = Release|x64
57 | {EF6463D8-94E4-432F-9FAA-94160AAD7B27}.Release|x64.Build.0 = Release|x64
58 | {56ECE7C7-73F4-4E1C-92FB-9015F37BCAFE}.Debug|Any CPU.ActiveCfg = Debug|x64
59 | {56ECE7C7-73F4-4E1C-92FB-9015F37BCAFE}.Debug|Any CPU.Build.0 = Debug|x64
60 | {56ECE7C7-73F4-4E1C-92FB-9015F37BCAFE}.Debug|x64.ActiveCfg = Debug|x64
61 | {56ECE7C7-73F4-4E1C-92FB-9015F37BCAFE}.Debug|x64.Build.0 = Debug|x64
62 | {56ECE7C7-73F4-4E1C-92FB-9015F37BCAFE}.Release|Any CPU.ActiveCfg = Release|x64
63 | {56ECE7C7-73F4-4E1C-92FB-9015F37BCAFE}.Release|Any CPU.Build.0 = Release|x64
64 | {56ECE7C7-73F4-4E1C-92FB-9015F37BCAFE}.Release|x64.ActiveCfg = Release|x64
65 | {56ECE7C7-73F4-4E1C-92FB-9015F37BCAFE}.Release|x64.Build.0 = Release|x64
66 | {E506688B-ED69-4FFB-9751-E716A005506C}.Debug|Any CPU.ActiveCfg = Debug|x64
67 | {E506688B-ED69-4FFB-9751-E716A005506C}.Debug|Any CPU.Build.0 = Debug|x64
68 | {E506688B-ED69-4FFB-9751-E716A005506C}.Debug|x64.ActiveCfg = Debug|x64
69 | {E506688B-ED69-4FFB-9751-E716A005506C}.Debug|x64.Build.0 = Debug|x64
70 | {E506688B-ED69-4FFB-9751-E716A005506C}.Release|Any CPU.ActiveCfg = Release|x64
71 | {E506688B-ED69-4FFB-9751-E716A005506C}.Release|Any CPU.Build.0 = Release|x64
72 | {E506688B-ED69-4FFB-9751-E716A005506C}.Release|x64.ActiveCfg = Release|x64
73 | {E506688B-ED69-4FFB-9751-E716A005506C}.Release|x64.Build.0 = Release|x64
74 | EndGlobalSection
75 | GlobalSection(SolutionProperties) = preSolution
76 | HideSolutionNode = FALSE
77 | EndGlobalSection
78 | GlobalSection(ExtensibilityGlobals) = postSolution
79 | SolutionGuid = {B17E85B1-5F60-4440-9F9A-3DDE877E8CDF}
80 | EndGlobalSection
81 | EndGlobal
82 |
--------------------------------------------------------------------------------
/Snapper/Windows/PlayerPanelPart.cs:
--------------------------------------------------------------------------------
1 | using Dalamud.Interface;
2 | using Dalamud.Interface.Utility;
3 | using Dalamud.Interface.Colors;
4 | using Dalamud.Plugin.Services;
5 | using Dalamud.Interface.ImGuiFileDialog;
6 | using Dalamud.Bindings.ImGui;
7 | using Snapper.Utils;
8 | using System;
9 | using System.Collections.Generic;
10 | using System.IO;
11 | using System.Linq;
12 | using System.Numerics;
13 | using System.Text;
14 | using System.Threading.Tasks;
15 | using static Lumina.Data.Parsing.Layer.LayerCommon;
16 |
17 | namespace Snapper.Windows
18 | {
19 | public partial class MainWindow
20 | {
21 | private const uint RedHeaderColor = 0xFF1818C0;
22 | private const uint GreenHeaderColor = 0xFF18C018;
23 | public bool IsInGpose { get; private set; } = false;
24 |
25 | private void DrawPlayerHeader()
26 | {
27 | var color = player == null ? RedHeaderColor : GreenHeaderColor;
28 | var buttonColor = ImGui.GetColorU32(ImGuiCol.FrameBg);
29 | ImGui.Button($"{currentLabel}##playerHeader", -Vector2.UnitX * 0.0001f);
30 | }
31 |
32 | private void DrawPlayerPanel()
33 | {
34 | ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.ParsedOrange);
35 | ImGui.Text("WARNING:");
36 | ImGui.PopStyleColor();
37 | ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow);
38 | ImGui.Text("Glamourer API currently does not allow you to get their Glamourer design automatically like before when synced with mare.");
39 | ImGui.Text("As a temporary workaround, copy their Glamourer design to clipboard and edit the file it creates.");
40 | ImGui.PopStyleColor();
41 | ImGui.Text("Save snapshot of player ");
42 | ImGui.SameLine();
43 | ImGui.PushFont(UiBuilder.IconFont);
44 | try
45 | {
46 | string saveIcon = FontAwesomeIcon.Save.ToIconString();
47 | if (ImGui.Button(saveIcon))
48 | {
49 | //save snapshot
50 | if (player != null)
51 | Plugin.SnapshotManager.SaveSnapshot(player);
52 | }
53 | }
54 | finally
55 | {
56 | ImGui.PopFont();
57 | }
58 | if (!IsInGpose)
59 | {
60 | ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudYellow);
61 | ImGui.Text("Saving snapshots while GPose is active may result in broken/incorrect snapshots. For best results, leave GPose first.");
62 | ImGui.PopStyleColor();
63 | ImGui.Spacing();
64 | }
65 |
66 | ImGui.Text("Append to existing snapshot");
67 | ImGui.SameLine();
68 | ImGui.PushFont(UiBuilder.IconFont);
69 | try
70 | {
71 | string addIcon = FontAwesomeIcon.Plus.ToIconString();
72 | if(ImGui.Button(addIcon))
73 | {
74 | if (player != null)
75 | Plugin.SnapshotManager.AppendSnapshot(player);
76 | }
77 | }
78 | finally
79 | {
80 | ImGui.PopFont();
81 | }
82 |
83 | if (this.modifiable)
84 | {
85 | ImGui.Text("Load snapshot onto ");
86 | ImGui.SameLine();
87 | ImGui.PushFont(UiBuilder.IconFont);
88 | try
89 | {
90 | string loadIcon = FontAwesomeIcon.Clipboard.ToIconString();
91 | if (ImGui.Button(loadIcon))
92 | {
93 | Plugin.FileDialogManager.OpenFolderDialog("Snapshot selection", (status, path) =>
94 | {
95 | if (!status)
96 | {
97 | return;
98 | }
99 |
100 | if (Directory.Exists(path))
101 | {
102 | if (player != null && objIdxSelected.HasValue)
103 | Plugin.SnapshotManager.LoadSnapshot(player, objIdxSelected.Value, path);
104 | }
105 | }, Plugin.Configuration.WorkingDirectory);
106 | }
107 | }
108 | finally
109 | {
110 | ImGui.PopFont();
111 | }
112 | }
113 | else
114 | {
115 | ImGui.Text("Loading snapshots can only be done on GPose actors");
116 | }
117 | }
118 |
119 | private void DrawMonsterPanel()
120 | {
121 |
122 | }
123 |
124 | private void DrawActorPanel()
125 | {
126 | using var raii = ImGuiRaii.NewGroup();
127 | DrawPlayerHeader();
128 | if (!ImGui.BeginChild("##playerData", -Vector2.One, true))
129 | {
130 | ImGui.EndChild();
131 | return;
132 | }
133 |
134 | if (player != null || player.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player)
135 | DrawPlayerPanel();
136 | else
137 | DrawMonsterPanel();
138 |
139 | ImGui.EndChild();
140 | }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/Snapper/Windows/PlayerSelectorPart.cs:
--------------------------------------------------------------------------------
1 | using Dalamud.Game.ClientState.Objects.Enums;
2 | using Dalamud.Game.ClientState.Objects.Types;
3 | using Dalamud.Plugin.Services;
4 | using Dalamud.Bindings.ImGui;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Numerics;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 | using Penumbra.GameData.Structs;
12 |
13 | namespace Snapper.Windows
14 | {
15 | public partial class MainWindow
16 | {
17 | public static int GPoseObjectId = ObjectIndex.GPosePlayer.Index;
18 | public static int CharacterScreenIndex = ObjectIndex.CharacterScreen.Index;
19 | public static int ExamineScreenIndex = ObjectIndex.ExamineScreen.Index;
20 | public static int FittingRoomIndex = ObjectIndex.FittingRoom.Index;
21 | public static int DyePreviewIndex = ObjectIndex.DyePreview.Index;
22 |
23 | private string playerFilter = string.Empty;
24 | private string playerFilterLower = string.Empty;
25 | private string currentLabel = string.Empty;
26 | private ICharacter? player;
27 | private bool modifiable = false;
28 | private int? objIdxSelected;
29 | private ICharacter? playerSelected;
30 | private readonly Dictionary playerNames = new(100);
31 | private readonly Dictionary gPoseActors = new(CharacterScreenIndex - GPoseObjectId);
32 |
33 | private IPluginLog PluginLog { get; init; }
34 | private void DrawPlayerFilter()
35 | {
36 | using var raii = new ImGuiRaii()
37 | .PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
38 | .PushStyle(ImGuiStyleVar.FrameRounding, 0);
39 | ImGui.SetNextItemWidth(SelectorWidth * ImGui.GetIO().FontGlobalScale);
40 | if (ImGui.InputTextWithHint("##playerFilter", "Filter Players...", ref playerFilter, 32))
41 | playerFilterLower = playerFilter.ToLowerInvariant();
42 | }
43 |
44 | private void DrawGPoseSelectable(ICharacter player, int objIdx)
45 | {
46 | var playerName = player.Name.ToString();
47 | if (!playerName.Any())
48 | return;
49 |
50 | gPoseActors[playerName] = null;
51 |
52 | DrawSelectable(player, $"{playerName} (GPose)", true, objIdx);
53 | }
54 |
55 | private void DrawSelectable(ICharacter player, string label, bool modifiable, int objIdx)
56 | {
57 | if (!playerFilterLower.Any() || label.ToLowerInvariant().Contains(playerFilterLower))
58 | if (ImGui.Selectable(label, currentLabel == label))
59 | {
60 | currentLabel = label;
61 | this.player = player;
62 | this.objIdxSelected = objIdx;
63 | this.modifiable = modifiable;
64 | return;
65 | }
66 |
67 | if (currentLabel != label)
68 | return;
69 |
70 | try
71 | {
72 | this.player = player;
73 | this.objIdxSelected = objIdx;
74 | this.modifiable = modifiable;
75 | }
76 | catch (Exception e)
77 | {
78 | PluginLog.Error($"Could not load character {player.Name}s information:\n{e}");
79 | }
80 | }
81 |
82 | private void DrawPlayerSelectable(ICharacter player, int idx)
83 | {
84 | string playerName;
85 | bool modifiable = false;
86 |
87 | if(idx == CharacterScreenIndex)
88 | {
89 | playerName = "Character Screen Actor";
90 | }
91 | else if(idx == ExamineScreenIndex)
92 | {
93 | playerName = "Examine Screen Actor";
94 | }
95 | else if(idx == FittingRoomIndex)
96 | {
97 | playerName = "Fitting Room Actor";
98 | }
99 | else if(idx == DyePreviewIndex)
100 | {
101 | playerName = "Dye Preview Actor";
102 | }
103 | else
104 | {
105 | playerName = player.Name.ToString();
106 | }
107 |
108 |
109 | if (!playerName.Any())
110 | return;
111 |
112 | if (playerNames.TryGetValue(playerName, out var num))
113 | playerNames[playerName] = ++num;
114 | else
115 | playerNames[playerName] = num = 1;
116 |
117 | if (gPoseActors.ContainsKey(playerName))
118 | {
119 | gPoseActors[playerName] = player;
120 | return;
121 | }
122 |
123 | var label = GetLabel(player, playerName, num);
124 | DrawSelectable(player, label, modifiable, idx);
125 | }
126 |
127 | private static string GetLabel(ICharacter player, string playerName, int num)
128 | {
129 | if (player.ObjectKind == ObjectKind.Player)
130 | return num == 1 ? playerName : $"{playerName} #{num}";
131 |
132 | if (player.ObjectKind == ObjectKind.EventNpc)
133 | return num == 1 ? $"{playerName} (NPC)" : $"{playerName} #{num} (NPC)";
134 |
135 | return num == 1 ? $"{playerName} (Monster)" : $"{playerName} #{num} (Monster)";
136 | }
137 |
138 | private void DrawPlayerSelector()
139 | {
140 | ImGui.BeginGroup();
141 | DrawPlayerFilter();
142 | if (!ImGui.BeginChild("##playerSelector",
143 | new Vector2(SelectorWidth * ImGui.GetIO().FontGlobalScale, -ImGui.GetFrameHeight() - 1), true))
144 | {
145 | ImGui.EndChild();
146 | ImGui.EndGroup();
147 | return;
148 | }
149 |
150 | playerNames.Clear();
151 | gPoseActors.Clear();
152 |
153 | for (var i = GPoseObjectId; i < GPoseObjectId + 48; ++i)
154 | {
155 | var player = CharacterFactory.Convert(Plugin.Objects[i]);
156 |
157 | if (player == null)
158 | continue;
159 |
160 | DrawGPoseSelectable(player, i);
161 | }
162 |
163 | for (var i = 0; i < GPoseObjectId; ++i)
164 | {
165 | var player = CharacterFactory.Convert(Plugin.Objects[i]);
166 | if (player != null)
167 | DrawPlayerSelectable(player, i);
168 | }
169 |
170 | for (var i = CharacterScreenIndex; i < Plugin.Objects.Length; ++i)
171 | {
172 | var player = CharacterFactory.Convert(Plugin.Objects[i]);
173 | if (player != null)
174 | DrawPlayerSelectable(player, i);
175 | }
176 | ImGui.EndChild();
177 |
178 |
179 | //DrawSelectionButtons();
180 | ImGui.EndGroup();
181 | }
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 | # top-most EditorConfig file
3 |
4 | [*]
5 | charset = utf-8
6 |
7 | end_of_line = lf
8 | insert_final_newline = true
9 |
10 | # 4 space indentation
11 | indent_style = space
12 | indent_size = 4
13 |
14 | # disable redundant style warnings
15 |
16 | # Microsoft .NET properties
17 | csharp_indent_braces = false
18 | csharp_new_line_before_catch = true
19 | csharp_new_line_before_else = true
20 | csharp_new_line_before_finally = true
21 | csharp_new_line_before_members_in_object_initializers = false
22 | csharp_new_line_before_open_brace = all
23 | csharp_preferred_modifier_order = public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion
24 | csharp_style_var_elsewhere = true:suggestion
25 | csharp_style_var_for_built_in_types = true:suggestion
26 | csharp_style_var_when_type_is_apparent = true:suggestion
27 | dotnet_code_quality_unused_parameters = non_public
28 | dotnet_naming_rule.event_rule.severity = warning
29 | dotnet_naming_rule.event_rule.style = on_upper_camel_case_style
30 | dotnet_naming_rule.event_rule.symbols = event_symbols
31 | dotnet_naming_rule.private_constants_rule.severity = warning
32 | dotnet_naming_rule.private_constants_rule.style = upper_camel_case_style
33 | dotnet_naming_rule.private_constants_rule.symbols = private_constants_symbols
34 | dotnet_naming_rule.private_instance_fields_rule.severity = warning
35 | dotnet_naming_rule.private_instance_fields_rule.style = lower_camel_case_style
36 | dotnet_naming_rule.private_instance_fields_rule.symbols = private_instance_fields_symbols
37 | dotnet_naming_rule.private_static_fields_rule.severity = warning
38 | dotnet_naming_rule.private_static_fields_rule.style = upper_camel_case_style
39 | dotnet_naming_rule.private_static_fields_rule.symbols = private_static_fields_symbols
40 | dotnet_naming_rule.private_static_readonly_rule.severity = warning
41 | dotnet_naming_rule.private_static_readonly_rule.style = upper_camel_case_style
42 | dotnet_naming_rule.private_static_readonly_rule.symbols = private_static_readonly_symbols
43 | dotnet_naming_style.lower_camel_case_style.capitalization = camel_case
44 | dotnet_naming_style.on_upper_camel_case_style.capitalization = pascal_case
45 | dotnet_naming_style.on_upper_camel_case_style.required_prefix = On
46 | dotnet_naming_style.upper_camel_case_style.capitalization = pascal_case
47 | dotnet_naming_symbols.event_symbols.applicable_accessibilities = *
48 | dotnet_naming_symbols.event_symbols.applicable_kinds = event
49 | dotnet_naming_symbols.private_constants_symbols.applicable_accessibilities = private
50 | dotnet_naming_symbols.private_constants_symbols.applicable_kinds = field
51 | dotnet_naming_symbols.private_constants_symbols.required_modifiers = const
52 | dotnet_naming_symbols.private_instance_fields_symbols.applicable_accessibilities = private
53 | dotnet_naming_symbols.private_instance_fields_symbols.applicable_kinds = field
54 | dotnet_naming_symbols.private_static_fields_symbols.applicable_accessibilities = private
55 | dotnet_naming_symbols.private_static_fields_symbols.applicable_kinds = field
56 | dotnet_naming_symbols.private_static_fields_symbols.required_modifiers = static
57 | dotnet_naming_symbols.private_static_readonly_symbols.applicable_accessibilities = private
58 | dotnet_naming_symbols.private_static_readonly_symbols.applicable_kinds = field
59 | dotnet_naming_symbols.private_static_readonly_symbols.required_modifiers = static,readonly
60 | dotnet_style_parentheses_in_arithmetic_binary_operators =always_for_clarity:suggestion
61 | dotnet_style_parentheses_in_other_binary_operators =always_for_clarity:suggestion
62 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:suggestion
63 | dotnet_style_predefined_type_for_member_access = true:suggestion
64 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
65 | dotnet_style_parentheses_in_other_operators=always_for_clarity:silent
66 | dotnet_style_object_initializer = false
67 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
68 | csharp_space_between_method_call_parameter_list_parentheses = false
69 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
70 | csharp_space_between_empty_square_brackets = false
71 | csharp_space_before_semicolon_in_for_statement = false
72 | csharp_space_before_open_square_brackets = false
73 | csharp_space_before_comma = false
74 | csharp_space_after_keywords_in_control_flow_statements = true
75 | csharp_space_after_comma = true
76 | csharp_space_after_cast = false
77 | csharp_space_around_binary_operators = before_and_after
78 | csharp_space_between_method_declaration_name_and_open_parenthesis = false
79 | csharp_space_between_method_declaration_parameter_list_parentheses = false
80 | csharp_space_between_parentheses = none
81 | csharp_space_between_square_brackets = false
82 |
83 | # ReSharper properties
84 | resharper_align_linq_query = true
85 | resharper_align_multiline_argument = true
86 | resharper_align_multiline_calls_chain = true
87 | resharper_align_multiline_expression = true
88 | resharper_align_multiline_extends_list = true
89 | resharper_align_multiline_for_stmt = true
90 | resharper_align_multline_type_parameter_constrains = true
91 | resharper_align_multline_type_parameter_list = true
92 | resharper_apply_on_completion = true
93 | resharper_auto_property_can_be_made_get_only_global_highlighting = none
94 | resharper_auto_property_can_be_made_get_only_local_highlighting = none
95 | resharper_autodetect_indent_settings = true
96 | resharper_braces_for_ifelse = required_for_multiline
97 | resharper_can_use_global_alias = false
98 | resharper_csharp_align_multiline_parameter = true
99 | resharper_csharp_align_multiple_declaration = true
100 | resharper_csharp_empty_block_style = together_same_line
101 | resharper_csharp_int_align_comments = true
102 | resharper_csharp_new_line_before_while = true
103 | resharper_csharp_wrap_after_declaration_lpar = true
104 | resharper_enforce_line_ending_style = true
105 | resharper_member_can_be_private_global_highlighting = none
106 | resharper_member_can_be_private_local_highlighting = none
107 | resharper_new_line_before_finally = false
108 | resharper_place_accessorholder_attribute_on_same_line = false
109 | resharper_place_field_attribute_on_same_line = false
110 | resharper_show_autodetect_configure_formatting_tip = false
111 | resharper_use_indent_from_vs = false
112 |
113 | # ReSharper inspection severities
114 | resharper_arrange_missing_parentheses_highlighting = hint
115 | resharper_arrange_redundant_parentheses_highlighting = hint
116 | resharper_arrange_this_qualifier_highlighting = none
117 | resharper_arrange_type_member_modifiers_highlighting = hint
118 | resharper_arrange_type_modifiers_highlighting = hint
119 | resharper_built_in_type_reference_style_for_member_access_highlighting = hint
120 | resharper_built_in_type_reference_style_highlighting = none
121 | resharper_foreach_can_be_converted_to_query_using_another_get_enumerator_highlighting = none
122 | resharper_foreach_can_be_partly_converted_to_query_using_another_get_enumerator_highlighting = none
123 | resharper_invert_if_highlighting = none
124 | resharper_loop_can_be_converted_to_query_highlighting = none
125 | resharper_method_has_async_overload_highlighting = none
126 | resharper_private_field_can_be_converted_to_local_variable_highlighting = none
127 | resharper_redundant_base_qualifier_highlighting = none
128 | resharper_suggest_var_or_type_built_in_types_highlighting = hint
129 | resharper_suggest_var_or_type_elsewhere_highlighting = hint
130 | resharper_suggest_var_or_type_simple_types_highlighting = hint
131 | resharper_unused_auto_property_accessor_global_highlighting = none
132 | csharp_style_deconstructed_variable_declaration=true:silent
133 |
134 | [*.{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}]
135 | indent_style = space
136 | indent_size = 4
137 | tab_width = 4
138 | dotnet_style_parentheses_in_other_operators=always_for_clarity:silent
139 |
--------------------------------------------------------------------------------
/Snapper/Utils/DalamudUtil.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using Dalamud.Game.ClientState;
7 | using Dalamud.Game.ClientState.Conditions;
8 | using Dalamud.Game.ClientState.Objects;
9 | using Dalamud.Game.ClientState.Objects.SubKinds;
10 | using Dalamud.Game.Gui;
11 | using Dalamud.Game.Text.SeStringHandling;
12 | using Dalamud.Plugin.Services;
13 | using FFXIVClientStructs.FFXIV.Client.Game.Character;
14 | using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
15 |
16 | namespace Snapper.Utils;
17 |
18 | public delegate void PlayerChange(Dalamud.Game.ClientState.Objects.Types.ICharacter actor);
19 |
20 | public delegate void LogIn();
21 | public delegate void LogOut();
22 | public delegate void ClassJobChanged();
23 |
24 | public delegate void FrameworkUpdate();
25 | public delegate void VoidDelegate();
26 |
27 | public class DalamudUtil : IDisposable
28 | {
29 | private readonly IClientState _clientState;
30 | private readonly IObjectTable _objectTable;
31 | private readonly IFramework _framework;
32 | private readonly ICondition _condition;
33 | private readonly IChatGui _chatGui;
34 |
35 | public event LogIn? LogIn;
36 | public event LogOut? LogOut;
37 | public event FrameworkUpdate? FrameworkUpdate;
38 | public event FrameworkUpdate? DelayedFrameworkUpdate;
39 | public event VoidDelegate? ZoneSwitchStart;
40 | public event VoidDelegate? ZoneSwitchEnd;
41 | private DateTime _delayedFrameworkUpdateCheck = DateTime.Now;
42 | private bool _sentBetweenAreas = false;
43 |
44 | public unsafe bool IsGameObjectPresent(IntPtr key)
45 | {
46 | foreach (var obj in _objectTable)
47 | {
48 | if (obj.Address == key)
49 | {
50 | return true;
51 | }
52 | }
53 |
54 | return false;
55 | }
56 |
57 | public DalamudUtil(IClientState clientState, IObjectTable objectTable, IFramework framework, ICondition condition, IChatGui chatGui)
58 | {
59 | _clientState = clientState;
60 | _objectTable = objectTable;
61 | _framework = framework;
62 | _condition = condition;
63 | _chatGui = chatGui;
64 | _clientState.Login += OnLogin;
65 | //_clientState.Logout += OnLogout;
66 | _framework.Update += FrameworkOnUpdate;
67 | if (IsLoggedIn)
68 | {
69 | OnLogin();
70 | }
71 | }
72 |
73 | public void PrintInfoChat(string message)
74 | {
75 | SeStringBuilder se = new SeStringBuilder().AddText("[Mare Synchronos] Info: ").AddItalics(message);
76 | _chatGui.Print(se.BuiltString);
77 | }
78 |
79 | public void PrintWarnChat(string message)
80 | {
81 | SeStringBuilder se = new SeStringBuilder().AddText("[Mare Synchronos] ").AddUiForeground("Warning: " + message, 31).AddUiForegroundOff();
82 | _chatGui.Print(se.BuiltString);
83 | }
84 |
85 | public void PrintErrorChat(string message)
86 | {
87 | SeStringBuilder se = new SeStringBuilder().AddText("[Mare Synchronos] ").AddUiForeground("Error: ", 534).AddItalicsOn().AddUiForeground(message, 534).AddUiForegroundOff().AddItalicsOff();
88 | _chatGui.Print(se.BuiltString);
89 | }
90 |
91 | private void FrameworkOnUpdate(IFramework framework)
92 | {
93 | if (_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51] || IsInGpose)
94 | {
95 | if (!_sentBetweenAreas)
96 | {
97 | Logger.Debug("Zone switch/Gpose start");
98 | _sentBetweenAreas = true;
99 | ZoneSwitchStart?.Invoke();
100 | }
101 |
102 | return;
103 | }
104 | else if (_sentBetweenAreas)
105 | {
106 | Logger.Debug("Zone switch/Gpose end");
107 | _sentBetweenAreas = false;
108 | ZoneSwitchEnd?.Invoke();
109 | }
110 |
111 | foreach (FrameworkUpdate? frameworkInvocation in (FrameworkUpdate?.GetInvocationList() ?? Array.Empty()).Cast())
112 | {
113 | try
114 | {
115 | frameworkInvocation?.Invoke();
116 | }
117 | catch (Exception ex)
118 | {
119 | Logger.Warn(ex.Message);
120 | Logger.Warn(ex.StackTrace ?? string.Empty);
121 | }
122 | }
123 |
124 | if (DateTime.Now < _delayedFrameworkUpdateCheck.AddSeconds(1)) return;
125 |
126 | foreach (FrameworkUpdate? frameworkInvocation in (DelayedFrameworkUpdate?.GetInvocationList() ?? Array.Empty()).Cast())
127 | {
128 | try
129 | {
130 | frameworkInvocation?.Invoke();
131 | }
132 | catch (Exception ex)
133 | {
134 | Logger.Warn(ex.Message);
135 | Logger.Warn(ex.StackTrace ?? string.Empty);
136 | }
137 | }
138 | _delayedFrameworkUpdateCheck = DateTime.Now;
139 | }
140 |
141 | private void OnLogout()
142 | {
143 | LogOut?.Invoke();
144 | }
145 |
146 | private void OnLogin()
147 | {
148 | LogIn?.Invoke();
149 | }
150 |
151 | public Dalamud.Game.ClientState.Objects.Types.IGameObject? CreateGameObject(IntPtr reference)
152 | {
153 | return _objectTable.CreateObjectReference(reference);
154 | }
155 |
156 | public bool IsLoggedIn => _clientState.IsLoggedIn;
157 |
158 | public bool IsPlayerPresent => _clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid();
159 |
160 | public bool IsObjectPresent(Dalamud.Game.ClientState.Objects.Types.IGameObject? obj)
161 | {
162 | return obj != null && obj.IsValid();
163 | }
164 |
165 | public unsafe IntPtr GetMinion()
166 | {
167 | return (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)PlayerPointer)->CompanionObject;
168 | }
169 |
170 | public unsafe IntPtr GetPet(IntPtr? playerPointer = null)
171 | {
172 | var mgr = CharacterManager.Instance();
173 | if (playerPointer == null) playerPointer = PlayerPointer;
174 | return (IntPtr)mgr->LookupPetByOwnerObject((FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)playerPointer);
175 | }
176 |
177 | public unsafe IntPtr GetCompanion(IntPtr? playerPointer = null)
178 | {
179 | var mgr = CharacterManager.Instance();
180 | if (playerPointer == null) playerPointer = PlayerPointer;
181 | return (IntPtr)mgr->LookupBuddyByOwnerObject((FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)playerPointer);
182 | }
183 |
184 | public string PlayerName => _clientState.LocalPlayer?.Name.ToString() ?? "--";
185 |
186 | public IntPtr PlayerPointer => _clientState.LocalPlayer?.Address ?? IntPtr.Zero;
187 |
188 | public IPlayerCharacter PlayerCharacter => _clientState.LocalPlayer!;
189 |
190 | public bool IsInGpose => _objectTable[201] != null;
191 |
192 | public List GetPlayerCharacters()
193 | {
194 | return _objectTable.Where(obj =>
195 | obj.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player &&
196 | !string.Equals(obj.Name.ToString(), PlayerName, StringComparison.Ordinal)).Select(p => (IPlayerCharacter)p).ToList();
197 | }
198 |
199 | public Dalamud.Game.ClientState.Objects.Types.ICharacter? GetCharacterFromObjectTableByIndex(int index)
200 | {
201 | var objTableObj = _objectTable[index];
202 | if (objTableObj!.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) return null;
203 | return (Dalamud.Game.ClientState.Objects.Types.ICharacter)objTableObj;
204 | }
205 |
206 | public IPlayerCharacter? GetPlayerCharacterFromObjectTableByName(string characterName)
207 | {
208 | foreach (var item in _objectTable)
209 | {
210 | if (item.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue;
211 | if (string.Equals(item.Name.ToString(), characterName, StringComparison.Ordinal)) return (IPlayerCharacter)item;
212 | }
213 |
214 | return null;
215 | }
216 |
217 | public int? GetIndexFromObjectTableByName(string characterName)
218 | {
219 | for (int i = 0; i < _objectTable.Length; i++)
220 | {
221 | if (_objectTable[i] == null) continue;
222 | if (_objectTable[i]!.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue;
223 | if (string.Equals(_objectTable[i]!.Name.ToString(), characterName, StringComparison.Ordinal)) return i;
224 | }
225 |
226 | return null;
227 | }
228 |
229 | public async Task RunOnFrameworkThread(Func func)
230 | {
231 | return await _framework.RunOnFrameworkThread(func).ConfigureAwait(false);
232 | }
233 |
234 | public unsafe void WaitWhileCharacterIsDrawing(string name, IntPtr characterAddress, int timeOut = 5000, CancellationToken? ct = null)
235 | {
236 | if (!_clientState.IsLoggedIn || characterAddress == IntPtr.Zero) return;
237 |
238 | var obj = (GameObject*)characterAddress;
239 | const int tick = 250;
240 | int curWaitTime = 0;
241 | // ReSharper disable once LoopVariableIsNeverChangedInsideLoop
242 | while ((obj->RenderFlags & 0b100000000000) == 0b100000000000 && (!ct?.IsCancellationRequested ?? true) && curWaitTime < timeOut) // 0b100000000000 is "still rendering" or something
243 | {
244 | Logger.Verbose($"Waiting for {name} to finish drawing");
245 | curWaitTime += tick;
246 | Thread.Sleep(tick);
247 | }
248 |
249 | if (ct?.IsCancellationRequested ?? false) return;
250 | // wait quarter a second just in case
251 | Thread.Sleep(tick);
252 | }
253 |
254 | public void Dispose()
255 | {
256 | _clientState.Login -= OnLogin;
257 | //_clientState.Logout -= OnLogout;
258 | _framework.Update -= FrameworkOnUpdate;
259 | }
260 | }
261 |
--------------------------------------------------------------------------------
/Snapper/Managers/IpcManager.cs:
--------------------------------------------------------------------------------
1 | using Dalamud.Plugin;
2 | using Dalamud.Plugin.Ipc;
3 | using System;
4 | using System.Collections.Generic;
5 | using Dalamud.Game.ClientState.Objects.Types;
6 | using Action = System.Action;
7 | using System.Collections.Concurrent;
8 | using System.Text;
9 | using Penumbra.Api.Enums;
10 | using Penumbra.Api.Helpers;
11 | using Snapper.Utils;
12 | using System.Security;
13 | using Glamourer.Api.Helpers;
14 | using Penumbra.Api.IpcSubscribers;
15 | using System.Linq;
16 | //using Penumbra.Api.IpcSubscribers.Legacy;
17 | //using Glamourer.Api.IpcSubscribers.Legacy;
18 | using Microsoft.Extensions.Logging;
19 | using Glamourer.Api.IpcSubscribers;
20 | using Glamourer.Api.Enums;
21 | using FFXIVClientStructs.FFXIV.Client.Game.Object;
22 | using Dalamud.Utility;
23 |
24 | namespace Snapper.Managers;
25 |
26 | public delegate void PenumbraRedrawEvent(IntPtr address, int objTblIdx);
27 | public delegate void HeelsOffsetChange(float change);
28 | public delegate void PenumbraResourceLoadEvent(IntPtr drawObject, string gamePath, string filePath);
29 | public delegate void CustomizePlusScaleChange(string? scale);
30 |
31 |
32 |
33 |
34 | public class IpcManager : IDisposable
35 | {
36 | private readonly IDalamudPluginInterface _pi;
37 | private const string TempCollectionPrefix = "Snap_";
38 |
39 | private readonly Glamourer.Api.IpcSubscribers.ApiVersion _glamourerApiVersion;
40 | private readonly ApplyState _glamourerApplyAll;
41 | private readonly GetStateBase64 _glamourerGetAllCustomization;
42 | private readonly GetStateBase64Name _glamourerGetAllCustomizationName;
43 | private readonly RevertState _glamourerRevertCustomization;
44 |
45 | private readonly Penumbra.Api.Helpers.EventSubscriber _penumbraInit;
46 | private readonly Penumbra.Api.Helpers.EventSubscriber _penumbraDispose;
47 | private readonly Penumbra.Api.Helpers.EventSubscriber _penumbraObjectIsRedrawn;
48 | private readonly Penumbra.Api.Helpers.EventSubscriber _penumbraGameObjectResourcePathResolved;
49 | private readonly Penumbra.Api.Helpers.EventSubscriber _penumbraModSettingChanged;
50 |
51 | private readonly GetModDirectory _penumbraResolveModDir;
52 | private readonly ResolvePlayerPath _penumbraResolvePlayer;
53 | private readonly ResolveGameObjectPath _penumbraResolvePlayerObject;
54 | private readonly ReverseResolveGameObjectPath _penumbraReverseResolvePlayerObject;
55 | private readonly GetEnabledState _penumbraEnabled;
56 | private readonly RedrawObject _penumbraRedraw;
57 | private readonly Penumbra.Api.IpcSubscribers.Legacy.RedrawObject _penumbraRedrawObject;
58 | private readonly GetMetaManipulations _penumbraGetGameObjectMetaManipulations;
59 | private readonly AddTemporaryMod _penumbraAddTemporaryMod;
60 | private readonly CreateTemporaryCollection _penumbraCreateTemporaryCollection;
61 | //private readonly RemoveTemporaryCollection _penumbraRemoveTemporaryCollection;
62 | private readonly Penumbra.Api.IpcSubscribers.Legacy.RemoveTemporaryMod _penumbraRemoveTemporaryMod;
63 | private readonly AssignTemporaryCollection _penumbraAssignTemporaryCollection;
64 | private readonly ReverseResolvePlayerPath _reverseResolvePlayer;
65 |
66 | private readonly ICallGateSubscriber _customizePlusApiVersion;
67 | private readonly ICallGateSubscriber _customizePlusBranch;
68 | private readonly ICallGateSubscriber _customizePlusGetBodyScale;
69 | private readonly ICallGateSubscriber _customizePlusGetBodyScaleFromCharacter;
70 | private readonly ICallGateSubscriber _customizePlusSetBodyScaleToCharacter;
71 | private readonly ICallGateSubscriber _customizePlusRevert;
72 | private readonly ICallGateSubscriber _customizePlusOnScaleUpdate;
73 |
74 | private readonly DalamudUtil _dalamudUtil;
75 | private readonly ConcurrentQueue actionQueue = new();
76 |
77 | private Configuration _configuration;
78 | private string backupBase64 = "Bh+LCAAAAAAAAArEV9ly2jAU/Rc/Mxkv8pa3hoRAJ6QZoMmzAhfQRNiuJSdDM/n3XsmYeMNl2nr6Joujc+7mY/FujBiHR0gFiyPj0hoYNz8yluwgksbluzGlLBrTaKXWEwm7Ca4cYrr2wBimIBCzplzAwPiSJHxvXMo0Kx7mEs9WdqonDr+bh5WNy4+B8W29rusROySh54WW9Rei+U675hhoi6BveaQnwat4tS8L2mHg95UbFlNUuufapK/m3cGmquWYZl9aIwBZ0fLd3vK6oaloGxA36EvxHpYvrYp+X4pPKROyPUuvL83ZiEUbSFtF3d6G9H+Ijqke1vk2fquczR8Q8MhEnMckFvFmw2HVjnsCmiijLriacX5og4ky3ctbToUAvdR7KluzAR9mQsY79hO05ccr4AfcjC713iPlGS6c40ktiydvIVrltTxAauS51y32SZnGqmPGwDZbWUYo36hihpxGJYRb/31UDbWpQVkq5J53gzAOrmIRZZQd1HHzFxYNY553rIApD6/CbvagUbN6ek7QFl6d0fNPx9dQJy0FYZSPgMosBaurRRUkDuyZSOdsJDkb6Z6N9M5G+l1I/GTR5X5BpYzjLlyOqFfdbOn4cxq/Vcbn1FTcwfo3Q4HI+ZYm7a9gMYo7yvkEzbsrrPtYdM79V/rWdXwaZ3LbWUWWCMnyT9bptwZRjbH1m2KZWHKYom9194Pxem0ar8AV+tr8YGsdowIPaNTlVpwGzeAVr8rKnM8AN5JtluQJZASVTKuGnvssAh9oSncgUVxhjxLfX6cZlyzhrOLC1oXZsOrSGbxki/z2VOTQgs/7sIgjXb4HSJf4n4BuTrCrYb5ju2fKJ5GESDC5rx9rE9HO+AfnlAFfs/U6y+d6pjpiXniBjdfbEO8rtymA+gpfuIHphDb+Xbk6JOqaodpyG5TKgZuUvuPbLglLjJ7pB8SxvE9KojlJK+PRsUucrmmFnhmUOImOm5TCdCzfI77ptxYafaFE52hkOWmHWOp48EnntVf+XxAdXPY47EXhGmTHnYLruFFPkSXNRhCsOfaiTGjnncDCF4xYwC882VK9PtGVa1hSXo82tE1H3eaP3MVGwVw8H+ktPVhqDOqv6pTia4qfH/Wmfnz8AgAA//8=";
79 |
80 | public IpcManager(IDalamudPluginInterface pi, DalamudUtil dalamudUtil)
81 | {
82 | Logger.Verbose("Creating " + nameof(IpcManager));
83 | _configuration = (Configuration?)pi.GetPluginConfig();
84 | _pi = pi;
85 |
86 | _penumbraInit = Penumbra.Api.IpcSubscribers.Initialized.Subscriber(pi, () => PenumbraInit());
87 | _penumbraDispose = Penumbra.Api.IpcSubscribers.Disposed.Subscriber(pi, () => PenumbraDispose());
88 | _penumbraResolvePlayer = new ResolvePlayerPath(pi);
89 | _penumbraResolvePlayerObject = new ResolveGameObjectPath(pi);
90 | _penumbraReverseResolvePlayerObject = new ReverseResolveGameObjectPath(pi);
91 | _penumbraResolveModDir = new GetModDirectory(pi);
92 | _penumbraRedraw = new RedrawObject(pi);
93 | _penumbraRedrawObject = new Penumbra.Api.IpcSubscribers.Legacy.RedrawObject(pi);
94 | _reverseResolvePlayer = new ReverseResolvePlayerPath(pi);
95 | _penumbraObjectIsRedrawn = Penumbra.Api.IpcSubscribers.GameObjectRedrawn.Subscriber(pi, (ptr, idx) => RedrawEvent((IntPtr)ptr, idx));
96 | _penumbraGetGameObjectMetaManipulations = new GetMetaManipulations(pi);
97 | _penumbraAddTemporaryMod = new AddTemporaryMod(pi);
98 | _penumbraCreateTemporaryCollection = new CreateTemporaryCollection(pi);
99 | //_penumbraRemoveTemporaryCollection = new RemoveTemporaryCollectionByName(pi);
100 | _penumbraRemoveTemporaryMod = new Penumbra.Api.IpcSubscribers.Legacy.RemoveTemporaryMod(pi);
101 | _penumbraAssignTemporaryCollection = new AssignTemporaryCollection(pi);
102 | _penumbraEnabled = new GetEnabledState(pi);
103 |
104 | _penumbraGameObjectResourcePathResolved = Penumbra.Api.IpcSubscribers.GameObjectResourcePathResolved.Subscriber(pi, (ptr, arg1, arg2) => ResourceLoaded((IntPtr)ptr, arg1, arg2));
105 | _penumbraModSettingChanged = Penumbra.Api.IpcSubscribers.ModSettingChanged.Subscriber(pi, (modsetting, a, b, c) => PenumbraModSettingChangedHandler());
106 |
107 | _glamourerApiVersion = new Glamourer.Api.IpcSubscribers.ApiVersion(pi);
108 | _glamourerGetAllCustomization = new GetStateBase64(pi);
109 | _glamourerGetAllCustomizationName = new GetStateBase64Name(pi);
110 |
111 | _glamourerApplyAll = new ApplyState(pi);
112 | _glamourerRevertCustomization = new RevertState(pi);
113 |
114 |
115 | _customizePlusApiVersion = pi.GetIpcSubscriber("CustomizePlus.GetApiVersion");
116 | _customizePlusBranch = pi.GetIpcSubscriber("CustomizePlus.GetBranch");
117 | _customizePlusGetBodyScale = pi.GetIpcSubscriber("CustomizePlus.GetTemporaryScale");
118 | _customizePlusGetBodyScaleFromCharacter = pi.GetIpcSubscriber("CustomizePlus.GetBodyScaleFromCharacter");
119 | _customizePlusRevert = pi.GetIpcSubscriber("CustomizePlus.RevertCharacter");
120 | _customizePlusSetBodyScaleToCharacter = pi.GetIpcSubscriber("CustomizePlus.SetBodyScaleToCharacter");
121 | _customizePlusOnScaleUpdate = pi.GetIpcSubscriber("CustomizePlus.OnScaleUpdate");
122 |
123 | _customizePlusOnScaleUpdate.Subscribe(OnCustomizePlusScaleChange);
124 |
125 | if (Initialized)
126 | {
127 | PenumbraInitialized?.Invoke();
128 | }
129 |
130 | _dalamudUtil = dalamudUtil;
131 | _dalamudUtil.FrameworkUpdate += HandleActionQueue;
132 | _dalamudUtil.ZoneSwitchEnd += ClearActionQueue;
133 |
134 | }
135 |
136 | private void PenumbraModSettingChangedHandler()
137 | {
138 | PenumbraModSettingChanged?.Invoke();
139 | }
140 |
141 | private void ClearActionQueue()
142 | {
143 | actionQueue.Clear();
144 | }
145 |
146 | private void ResourceLoaded(IntPtr ptr, string arg1, string arg2)
147 | {
148 | if (ptr != IntPtr.Zero && string.Compare(arg1, arg2, true, System.Globalization.CultureInfo.InvariantCulture) != 0)
149 | {
150 | PenumbraResourceLoadEvent?.Invoke(ptr, arg1, arg2);
151 | }
152 | }
153 |
154 | private void HandleActionQueue()
155 | {
156 | if (actionQueue.TryDequeue(out var action))
157 | {
158 | if (action == null) return;
159 | Logger.Debug("Execution action in queue: " + action.Method);
160 | action();
161 | }
162 | }
163 |
164 | public event VoidDelegate? PenumbraModSettingChanged;
165 | public event VoidDelegate? PenumbraInitialized;
166 | public event VoidDelegate? PenumbraDisposed;
167 | public event PenumbraRedrawEvent? PenumbraRedrawEvent;
168 | public event HeelsOffsetChange? HeelsOffsetChangeEvent;
169 | public event PenumbraResourceLoadEvent? PenumbraResourceLoadEvent;
170 | public event CustomizePlusScaleChange? CustomizePlusScaleChange;
171 |
172 | public bool Initialized => CheckPenumbraApi();
173 | public bool CheckGlamourerApi()
174 | {
175 | try
176 | {
177 | return _glamourerApiVersion.Invoke() is { Major: 1, Minor: >= 1 };
178 | }
179 | catch
180 | {
181 | Logger.Warn("Glamourer API was not available");
182 | return false;
183 | }
184 | }
185 |
186 | public bool CheckPenumbraApi()
187 | {
188 | bool penumbraAvailable = false;
189 | try
190 | {
191 | var penumbraVersion = (_pi.InstalledPlugins
192 | .FirstOrDefault(p => string.Equals(p.InternalName, "Penumbra", StringComparison.OrdinalIgnoreCase))
193 | ?.Version ?? new Version(0, 0, 0, 0));
194 | penumbraAvailable = penumbraVersion >= new Version(1, 1, 0, 0);
195 | penumbraAvailable &= _penumbraEnabled.Invoke();
196 | return penumbraAvailable;
197 | }
198 | catch
199 | {
200 | Logger.Warn("Penumbra API was not available");
201 | return false;
202 | }
203 | }
204 |
205 | public bool CheckCustomizePlusApi()
206 | {
207 | try
208 | {
209 | return string.Equals(_customizePlusApiVersion.InvokeFunc(), "1.0", StringComparison.Ordinal) && string.Equals(_customizePlusBranch.InvokeFunc(), "eqbot", StringComparison.Ordinal);
210 | }
211 | catch
212 | {
213 | return false;
214 | }
215 | }
216 | public bool CheckCustomizePlusBranch()
217 | {
218 | try
219 | {
220 | return string.Equals(_customizePlusApiVersion.InvokeFunc(), "1.0", StringComparison.Ordinal);
221 | }
222 | catch
223 | {
224 | return false;
225 | }
226 | }
227 |
228 | public void Dispose()
229 | {
230 | Logger.Verbose("Disposing " + nameof(IpcManager));
231 |
232 | int totalSleepTime = 0;
233 | while (actionQueue.Count > 0 && totalSleepTime < 2000)
234 | {
235 | Logger.Verbose("Waiting for actionqueue to clear...");
236 | HandleActionQueue();
237 | System.Threading.Thread.Sleep(16);
238 | totalSleepTime += 16;
239 | }
240 |
241 | if (totalSleepTime >= 2000)
242 | {
243 | Logger.Verbose("Action queue clear or not, disposing");
244 | }
245 |
246 | _dalamudUtil.FrameworkUpdate -= HandleActionQueue;
247 | _dalamudUtil.ZoneSwitchEnd -= ClearActionQueue;
248 | actionQueue.Clear();
249 |
250 | _penumbraGameObjectResourcePathResolved.Dispose();
251 | _penumbraDispose.Dispose();
252 | _penumbraInit.Dispose();
253 | _penumbraObjectIsRedrawn.Dispose();
254 | _penumbraModSettingChanged.Dispose();
255 | }
256 |
257 | public string GetCustomizePlusScale()
258 | {
259 | if (!CheckCustomizePlusApi()) return string.Empty;
260 | var scale = _customizePlusGetBodyScale.InvokeFunc(_dalamudUtil.PlayerName);
261 | if (string.IsNullOrEmpty(scale)) return string.Empty;
262 | return Convert.ToBase64String(Encoding.UTF8.GetBytes(scale));
263 | }
264 |
265 | public string GetCustomizePlusScaleFromCharacter(ICharacter character)
266 | {
267 | if (!CheckCustomizePlusApi()) return string.Empty;
268 | var scale = _customizePlusGetBodyScale.InvokeFunc(character.Name.ToString());
269 | if (string.IsNullOrEmpty(scale))
270 | {
271 | Logger.Debug("C+ returned null");
272 | return string.Empty;
273 | }
274 | return scale;
275 | }
276 |
277 | public void CustomizePlusSetBodyScale(IntPtr character, string scale)
278 | {
279 | if (!CheckCustomizePlusApi() || string.IsNullOrEmpty(scale)) return;
280 | actionQueue.Enqueue(() =>
281 | {
282 | var gameObj = _dalamudUtil.CreateGameObject(character);
283 | if (gameObj is ICharacter c)
284 | {
285 | Logger.Verbose("CustomizePlus applying for " + c.Address.ToString("X"));
286 | _customizePlusSetBodyScaleToCharacter!.InvokeAction(scale, c);
287 | }
288 | });
289 | }
290 |
291 | public void CustomizePlusRevert(IntPtr character)
292 | {
293 | if (!CheckCustomizePlusApi()) return;
294 | actionQueue.Enqueue(() =>
295 | {
296 | var gameObj = _dalamudUtil.CreateGameObject(character);
297 | if (gameObj is ICharacter c)
298 | {
299 | Logger.Verbose("CustomizePlus reverting for " + c.Address.ToString("X"));
300 | _customizePlusRevert!.InvokeAction(c);
301 | }
302 | });
303 | }
304 |
305 | public void GlamourerApplyAll(string? customization, ICharacter obj)
306 | {
307 | if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return;
308 | Logger.Verbose("Glamourer applying for " + obj.Address.ToString("X"));
309 | _glamourerApplyAll!.Invoke(customization, obj.ObjectIndex);
310 | }
311 |
312 | public string GlamourerGetCharacterCustomization(IntPtr character)
313 | {
314 | object temp = "";
315 | object tempGameObj = "";
316 | Logger.Debug("Getting character customization");
317 | if (!CheckGlamourerApi()) return string.Empty;
318 | try
319 | {
320 | var gameObj = _dalamudUtil.CreateGameObject(character);
321 | if (gameObj is ICharacter c)
322 | {
323 | Logger.Debug($"Attempting to get customizations for {c.Name} with ObjectIndex {c.ObjectIndex}");
324 | (GlamourerApiEc apiec, string glamourerString) = _glamourerGetAllCustomization!.Invoke(c.ObjectIndex);
325 | temp = glamourerString;
326 | tempGameObj = c.Name;
327 | Logger.Debug($"Got glamourer customizations {glamourerString} for {c.Name}");
328 | if (glamourerString.IsNullOrEmpty())
329 | {
330 | glamourerString = _configuration.FallBackGlamourerString;
331 | }
332 |
333 | byte[] bytes;
334 |
335 | try
336 | {
337 | bytes = Convert.FromBase64String(glamourerString);
338 | }
339 | catch
340 | {
341 | //if your backup string is not valid, you are getting my shitty lala.
342 | bytes = Convert.FromBase64String(backupBase64);
343 | }
344 |
345 | return Convert.ToBase64String(bytes);
346 | }
347 | Logger.Warn("Game object is not an ICharacter or could not retrieve customization data.");
348 | return string.Empty;
349 | }
350 | catch (Exception ex)
351 | {
352 | Logger.Error($"Error occurred while getting customizations for {tempGameObj}. GlamourerString = '{temp}', Exception: {ex.Message}");
353 | throw;
354 | return string.Empty;
355 | }
356 | }
357 |
358 | public void GlamourerRevertCharacterCustomization(IGameObject character)
359 | {
360 | if (!CheckGlamourerApi()) return;
361 | if(character is ICharacter c)
362 | {
363 | actionQueue.Enqueue(() => _glamourerRevertCustomization!.Invoke(c.ObjectIndex));
364 | }
365 | else
366 | {
367 | Logger.Error("Tried to revert a non-Character game object and failed");
368 | }
369 | }
370 |
371 | public string PenumbraGetGameObjectMetaManipulations(int objIdx)
372 | {
373 | if (!CheckPenumbraApi()) return string.Empty;
374 | return _penumbraGetGameObjectMetaManipulations.Invoke(objIdx);
375 | }
376 |
377 | public void PenumbraRedraw(IntPtr obj)
378 | {
379 | if (!CheckPenumbraApi()) return;
380 | actionQueue.Enqueue(() =>
381 | {
382 | var gameObj = _dalamudUtil.CreateGameObject(obj);
383 | if (gameObj != null)
384 | {
385 | Logger.Verbose("Redrawing " + gameObj.ToString());
386 | _penumbraRedrawObject!.Invoke(gameObj, RedrawType.Redraw);
387 | }
388 | });
389 | }
390 |
391 | public void PenumbraRedraw(int objIdx)
392 | {
393 | if (!CheckPenumbraApi()) return;
394 | _penumbraRedraw!.Invoke(objIdx, RedrawType.Redraw);
395 | }
396 |
397 | public void PenumbraRemoveTemporaryCollection(string characterName)
398 | {
399 | if (!CheckPenumbraApi()) return;
400 | actionQueue.Enqueue(() =>
401 | {
402 | var collName = TempCollectionPrefix + characterName;
403 | Logger.Verbose("Removing temp collection for " + collName);
404 | var ret = _penumbraRemoveTemporaryMod.Invoke("Snap", collName, 0);
405 | Logger.Verbose("RemoveTemporaryMod: " + ret);
406 | //var ret2 = _penumbraRemoveTemporaryCollection.Invoke(collName);
407 | //Logger.Verbose("RemoveTemporaryCollection: " + ret2);
408 | });
409 | }
410 |
411 | public string PenumbraResolvePath(string path)
412 | {
413 | if (!CheckPenumbraApi()) return path;
414 | var resolvedPath = _penumbraResolvePlayer!.Invoke(path);
415 | return resolvedPath ?? path;
416 | }
417 |
418 | public string[] PenumbraReverseResolvePlayer(string path)
419 | {
420 | if (!CheckPenumbraApi()) return new[] { path };
421 | var resolvedPaths = _reverseResolvePlayer.Invoke(path);
422 | if (resolvedPaths.Length == 0)
423 | {
424 | resolvedPaths = new[] { path };
425 | }
426 | return resolvedPaths;
427 | }
428 |
429 | public string PenumbraResolvePathObject(string path, int objIdx)
430 | {
431 | if (!CheckPenumbraApi()) return path;
432 | var resolvedPath = _penumbraResolvePlayerObject!.Invoke(path, objIdx);
433 | return resolvedPath ?? path;
434 | }
435 |
436 | public string[] PenumbraReverseResolveObject(string path, int objIdx)
437 | {
438 | if (!CheckPenumbraApi()) return new[] { path };
439 | var resolvedPaths = _penumbraReverseResolvePlayerObject.Invoke(path, objIdx);
440 | if (resolvedPaths.Length == 0)
441 | {
442 | resolvedPaths = new[] { path };
443 | }
444 | return resolvedPaths;
445 | }
446 |
447 | public void PenumbraSetTemporaryMods(ICharacter character, int? idx, Dictionary modPaths, string manipulationData)
448 | {
449 | if (!CheckPenumbraApi()) return;
450 | if (idx == null)
451 | {
452 | return;
453 | }
454 | var collName = TempCollectionPrefix + character.Name.TextValue;
455 | var ret = _penumbraCreateTemporaryCollection.Invoke(collName);
456 | Logger.Verbose("Creating Temp Collection " + collName + ", Success: " + ret);
457 | var retAssign = _penumbraAssignTemporaryCollection.Invoke(ret, idx.Value, true);
458 | Logger.Verbose("Assigning Temp Collection " + collName + " to index " + idx.Value);
459 | Logger.Verbose("Penumbra response" + retAssign);
460 | foreach (var mod in modPaths)
461 | {
462 | Logger.Verbose(mod.Key + " => " + mod.Value);
463 | }
464 |
465 | var ret2 = _penumbraAddTemporaryMod.Invoke("Snap", ret, modPaths, manipulationData, 0);
466 | Logger.Verbose("Setting temp mods for " + collName + ", Success: " + ret2);
467 | }
468 |
469 | private void RedrawEvent(IntPtr objectAddress, int objectTableIndex)
470 | {
471 | PenumbraRedrawEvent?.Invoke(objectAddress, objectTableIndex);
472 | }
473 |
474 | private void PenumbraInit()
475 | {
476 | PenumbraInitialized?.Invoke();
477 | //_penumbraRedraw!.Invoke("self", RedrawType.Redraw);
478 | }
479 |
480 | private void OnCustomizePlusScaleChange(string? scale)
481 | {
482 | if (scale != null) scale = Convert.ToBase64String(Encoding.UTF8.GetBytes(scale));
483 | CustomizePlusScaleChange?.Invoke(scale);
484 | }
485 |
486 | private void PenumbraDispose()
487 | {
488 | PenumbraDisposed?.Invoke();
489 | actionQueue.Clear();
490 | }
491 | }
492 |
--------------------------------------------------------------------------------
/Snapper/Managers/SnapshotManager.cs:
--------------------------------------------------------------------------------
1 | using Dalamud.Game.ClientState.Objects.Types;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using Penumbra.Api;
8 | using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
9 | using FFXIVClientStructs.FFXIV.Client.System.Resource;
10 | using Penumbra.Interop.Structs;
11 | using Lumina.Models.Materials;
12 | using Snapper.Models;
13 | using Snapper.Utils;
14 | using System.Threading;
15 | using Dalamud.Game.ClientState.Objects.Enums;
16 | using Penumbra.String;
17 | using FFXIVClientStructs.FFXIV.Client.Game.Object;
18 | using System.IO;
19 | using System.Text.Json;
20 | using Snapper.Interop;
21 | using Dalamud.Utility;
22 |
23 | namespace Snapper.Managers
24 | {
25 | public class SnapshotManager
26 | {
27 | private Plugin Plugin;
28 | private List tempCollections = new();
29 |
30 | public SnapshotManager(Plugin plugin)
31 | {
32 |
33 | this.Plugin = plugin;
34 | }
35 |
36 | public void RevertAllSnapshots()
37 | {
38 | foreach(var character in tempCollections)
39 | {
40 | Plugin.IpcManager.PenumbraRemoveTemporaryCollection(character.Name.TextValue);
41 | Plugin.IpcManager.GlamourerRevertCharacterCustomization(character);
42 | Plugin.IpcManager.CustomizePlusRevert(character.Address);
43 | }
44 | tempCollections.Clear();
45 | }
46 |
47 | public bool AppendSnapshot(ICharacter character)
48 | {
49 | var charaName = character.Name.TextValue;
50 | var path = Path.Combine(Plugin.Configuration.WorkingDirectory, charaName);
51 | string infoJson = File.ReadAllText(Path.Combine(path, "snapshot.json"));
52 | if (infoJson == null)
53 | {
54 | Logger.Warn("No snapshot json found, aborting");
55 | return false;
56 | }
57 | SnapshotInfo? snapshotInfo = JsonSerializer.Deserialize(infoJson);
58 | if (snapshotInfo == null)
59 | {
60 | Logger.Warn("Failed to deserialize snapshot json, aborting");
61 | return false;
62 | }
63 |
64 | if (!Directory.Exists(path))
65 | {
66 | //no existing snapshot for character, just use save mode
67 | this.SaveSnapshot(character);
68 | }
69 |
70 | //Merge file replacements
71 | List replacements = GetFileReplacementsForCharacter(character);
72 |
73 | Logger.Debug($"Got {replacements.Count} replacements");
74 |
75 | foreach (var replacement in replacements)
76 | {
77 | FileInfo replacementFile = new FileInfo(replacement.ResolvedPath);
78 | FileInfo fileToCreate = new FileInfo(Path.Combine(path, replacement.GamePaths[0]));
79 | if (!fileToCreate.Exists)
80 | {
81 | //totally new file
82 | fileToCreate.Directory.Create();
83 | replacementFile.CopyTo(fileToCreate.FullName);
84 | foreach (var gamePath in replacement.GamePaths)
85 | {
86 | var collisions = snapshotInfo.FileReplacements.Where(src => src.Value.Any(path => path == gamePath));
87 | //gamepath already exists in snapshot, overwrite with new file
88 | foreach (var collision in collisions)
89 | {
90 | collision.Value.Remove(gamePath);
91 | if (collision.Value.Count == 0)
92 | {
93 | //delete file if it no longer has any references
94 | snapshotInfo.FileReplacements.Remove(collision.Key);
95 | File.Delete(Path.Combine(path, collision.Key));
96 | }
97 | }
98 | }
99 | snapshotInfo.FileReplacements.Add(replacement.GamePaths[0], replacement.GamePaths);
100 | }
101 | }
102 |
103 | //Merge meta manips
104 | //Meta manipulations seem to be sent containing every mod a character has enabled, regardless of whether it's actively being used.
105 | //This may end up shooting me in the foot, but a newer snapshot should contain the info of an older one.
106 | snapshotInfo.ManipulationString = Plugin.IpcManager.PenumbraGetGameObjectMetaManipulations(character.ObjectIndex);
107 |
108 | string infoJsonWrite = JsonSerializer.Serialize(snapshotInfo);
109 | File.WriteAllText(Path.Combine(path, "snapshot.json"), infoJsonWrite);
110 |
111 | return true;
112 | }
113 |
114 | public bool SaveSnapshot(ICharacter character)
115 | {
116 | var charaName = character.Name.TextValue;
117 | var path = Path.Combine(Plugin.Configuration.WorkingDirectory,charaName);
118 | SnapshotInfo snapshotInfo = new();
119 |
120 | if (Directory.Exists(path))
121 | {
122 | Logger.Warn("Snapshot already existed, deleting");
123 | Directory.Delete(path, true);
124 | }
125 | Directory.CreateDirectory(path);
126 |
127 | //Get glamourer string
128 | snapshotInfo.GlamourerString = Plugin.IpcManager.GlamourerGetCharacterCustomization(character.Address);
129 | Logger.Debug($"Got glamourer string {snapshotInfo.GlamourerString}");
130 |
131 | //Save all file replacements
132 |
133 | List replacements = GetFileReplacementsForCharacter(character);
134 |
135 | Logger.Debug($"Got {replacements.Count} replacements");
136 |
137 | foreach(var replacement in replacements)
138 | {
139 | FileInfo replacementFile = new FileInfo(replacement.ResolvedPath);
140 | FileInfo fileToCreate = new FileInfo(Path.Combine(path, replacement.GamePaths[0]));
141 | fileToCreate.Directory.Create();
142 | replacementFile.CopyTo(fileToCreate.FullName);
143 | snapshotInfo.FileReplacements.Add(replacement.GamePaths[0], replacement.GamePaths);
144 | }
145 |
146 | snapshotInfo.ManipulationString = Plugin.IpcManager.PenumbraGetGameObjectMetaManipulations(character.ObjectIndex);
147 |
148 | //Get customize+ data, if applicable
149 | if (Plugin.IpcManager.CheckCustomizePlusApi())
150 | {
151 | Logger.Debug("C+ api loaded");
152 | var data = Plugin.IpcManager.GetCustomizePlusScaleFromCharacter(character);
153 | //Logger.Info(Plugin.DalamudUtil.PlayerName);
154 | //Logger.Info(character.Name.TextValue);
155 | //Logger.Info($"Cust+: {data}");
156 | if (!data.IsNullOrEmpty())
157 | {
158 | File.WriteAllText(Path.Combine(path, "customizePlus.json"), data);
159 | }
160 | }
161 |
162 | string infoJson = JsonSerializer.Serialize(snapshotInfo);
163 | File.WriteAllText(Path.Combine(path, "snapshot.json"), infoJson);
164 |
165 | return true;
166 | }
167 |
168 | public bool LoadSnapshot(ICharacter characterApplyTo, int objIdx, string path)
169 | {
170 | Logger.Info($"Applying snapshot to {characterApplyTo.Address}");
171 | string infoJson = File.ReadAllText(Path.Combine(path,"snapshot.json"));
172 | if (infoJson == null)
173 | {
174 | Logger.Warn("No snapshot json found, aborting");
175 | return false;
176 | }
177 | SnapshotInfo? snapshotInfo = JsonSerializer.Deserialize(infoJson);
178 | if(snapshotInfo == null)
179 | {
180 | Logger.Warn("Failed to deserialize snapshot json, aborting");
181 | return false;
182 | }
183 |
184 | //Apply mods
185 | Dictionary moddedPaths = new();
186 | foreach(var replacement in snapshotInfo.FileReplacements)
187 | {
188 | foreach(var gamePath in replacement.Value)
189 | {
190 | moddedPaths.Add(gamePath, Path.Combine(path, replacement.Key));
191 | }
192 | }
193 | Logger.Debug($"Applied {moddedPaths.Count} replacements");
194 |
195 | Plugin.IpcManager.PenumbraRemoveTemporaryCollection(characterApplyTo.Name.TextValue);
196 | Plugin.IpcManager.PenumbraSetTemporaryMods(characterApplyTo, objIdx, moddedPaths, snapshotInfo.ManipulationString);
197 | if (!tempCollections.Contains(characterApplyTo))
198 | {
199 | tempCollections.Add(characterApplyTo);
200 | }
201 |
202 | //Apply Customize+ if it exists and C+ is installed
203 | if (Plugin.IpcManager.CheckCustomizePlusApi())
204 | {
205 | if(File.Exists(Path.Combine(path, "customizePlus.json")))
206 | {
207 | string custPlusData = File.ReadAllText(Path.Combine(path, "customizePlus.json"));
208 | Plugin.IpcManager.CustomizePlusSetBodyScale(characterApplyTo.Address, custPlusData);
209 | }
210 | }
211 |
212 | //Apply glamourer string
213 | Plugin.IpcManager.GlamourerApplyAll(snapshotInfo.GlamourerString, characterApplyTo);
214 |
215 | //Redraw
216 | Plugin.IpcManager.PenumbraRedraw(objIdx);
217 |
218 | return true;
219 | }
220 |
221 | private int? GetObjIDXFromCharacter(ICharacter character)
222 | {
223 | for (var i = 0; i <= Plugin.Objects.Length; i++)
224 | {
225 | global::Dalamud.Game.ClientState.Objects.Types.IGameObject current = Plugin.Objects[i];
226 | if (!(current == null) && current.GameObjectId == character.GameObjectId)
227 | {
228 | return i;
229 | }
230 | }
231 | return null;
232 | }
233 |
234 | public unsafe List GetFileReplacementsForCharacter(ICharacter character)
235 | {
236 | List replacements = new List();
237 | var charaPointer = character.Address;
238 | var objectKind = character.ObjectKind;
239 | var charaName = character.Name.TextValue;
240 | int? objIdx = GetObjIDXFromCharacter(character);
241 |
242 | Logger.Debug($"Character name {charaName}");
243 | if(objIdx == null)
244 | {
245 | Logger.Error("Unable to find character in object table, aborting search for file replacements");
246 | return replacements;
247 | }
248 | Logger.Debug($"Object IDX {objIdx}");
249 |
250 | var chara = Plugin.DalamudUtil.CreateGameObject(charaPointer)!;
251 | while (!Plugin.DalamudUtil.IsObjectPresent(chara))
252 | {
253 | Logger.Verbose("Character is null but it shouldn't be, waiting");
254 | Thread.Sleep(50);
255 | }
256 |
257 | Plugin.DalamudUtil.WaitWhileCharacterIsDrawing(objectKind.ToString(), charaPointer, 15000);
258 |
259 | var baseCharacter = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)(void*)charaPointer;
260 | var human = (Human*)baseCharacter->GameObject.GetDrawObject();
261 | for (var mdlIdx = 0; mdlIdx < human->CharacterBase.SlotCount; ++mdlIdx)
262 | {
263 | var mdl = (RenderModel*)human->CharacterBase.Models[mdlIdx];
264 | if (mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara)
265 | {
266 | continue;
267 | }
268 |
269 | AddReplacementsFromRenderModel(mdl, replacements, objIdx.Value, 0);
270 | }
271 |
272 | AddPlayerSpecificReplacements(replacements, charaPointer, human, objIdx.Value);
273 |
274 | return replacements;
275 | }
276 |
277 | private unsafe void AddReplacementsFromRenderModel(RenderModel* mdl, List replacements, int objIdx, int inheritanceLevel = 0)
278 | {
279 | if (mdl == null || mdl->ResourceHandle == null || mdl->ResourceHandle->Category != ResourceCategory.Chara)
280 | {
281 | return;
282 | }
283 |
284 | string mdlPath;
285 | try
286 | {
287 | mdlPath = new ByteString(mdl->ResourceHandle->FileName()).ToString();
288 | }
289 | catch
290 | {
291 | Logger.Warn("Could not get model data");
292 | return;
293 | }
294 | Logger.Verbose("Checking File Replacement for Model " + mdlPath);
295 |
296 | FileReplacement mdlFileReplacement = CreateFileReplacement(mdlPath, objIdx);
297 | //DebugPrint(mdlFileReplacement, objectKind, "Model", inheritanceLevel);
298 |
299 | AddFileReplacement(replacements, mdlFileReplacement);
300 |
301 | for (var mtrlIdx = 0; mtrlIdx < mdl->MaterialCount; mtrlIdx++)
302 | {
303 | var mtrl = (Penumbra.Interop.Structs.Material*)mdl->Materials[mtrlIdx];
304 | if (mtrl == null) continue;
305 |
306 | AddReplacementsFromMaterial(mtrl, replacements, objIdx, inheritanceLevel + 1);
307 | }
308 | }
309 |
310 | private unsafe void AddReplacementsFromMaterial(Penumbra.Interop.Structs.Material* mtrl, List replacements, int objIdx, int inheritanceLevel = 0)
311 | {
312 | string fileName;
313 | try
314 | {
315 | fileName = new ByteString(mtrl->ResourceHandle->FileName()).ToString();
316 |
317 | }
318 | catch
319 | {
320 | Logger.Warn("Could not get material data");
321 | return;
322 | }
323 |
324 | Logger.Verbose("Checking File Replacement for Material " + fileName);
325 | var mtrlArray = fileName.Split("|");
326 | string mtrlPath;
327 | if (mtrlArray.Count() >= 3)
328 | {
329 | mtrlPath = fileName.Split("|")[2];
330 | }
331 | else
332 | {
333 | Logger.Warn($"Material {fileName} did not split into at least 3 partts");
334 | return;
335 | }
336 |
337 | if (replacements.Any(c => c.ResolvedPath.Contains(mtrlPath, StringComparison.Ordinal)))
338 | {
339 | return;
340 | }
341 |
342 | var mtrlFileReplacement = CreateFileReplacement(mtrlPath, objIdx);
343 | //DebugPrint(mtrlFileReplacement, objectKind, "Material", inheritanceLevel);
344 |
345 | AddFileReplacement(replacements, mtrlFileReplacement);
346 |
347 | var mtrlResourceHandle = (MtrlResource*)mtrl->ResourceHandle;
348 | for (var resIdx = 0; resIdx < mtrlResourceHandle->NumTex; resIdx++)
349 | {
350 | string? texPath = null;
351 | try
352 | {
353 | texPath = new ByteString(mtrlResourceHandle->TexString(resIdx)).ToString();
354 | }
355 | catch
356 | {
357 | Logger.Warn("Could not get Texture data for Material " + fileName);
358 | }
359 |
360 | if (string.IsNullOrEmpty(texPath)) continue;
361 |
362 | Logger.Verbose("Checking File Replacement for Texture " + texPath);
363 |
364 | AddReplacementsFromTexture(texPath, replacements, objIdx, inheritanceLevel + 1);
365 | }
366 |
367 | try
368 | {
369 | var shpkPath = "shader/sm5/shpk/" + new ByteString(mtrlResourceHandle->ShpkString).ToString();
370 | Logger.Verbose("Checking File Replacement for Shader " + shpkPath);
371 | AddReplacementsFromShader(shpkPath, replacements, objIdx, inheritanceLevel + 1);
372 | }
373 | catch
374 | {
375 | Logger.Verbose("Could not find shpk for Material " + fileName);
376 | }
377 | }
378 |
379 | private void AddReplacementsFromTexture(string texPath, List replacements, int objIdx, int inheritanceLevel = 0, bool doNotReverseResolve = true)
380 | {
381 | Logger.Debug($"Adding replacement for texture {texPath}");
382 | if (string.IsNullOrEmpty(texPath)) return;
383 |
384 | if(replacements.Any(c => c.GamePaths.Contains(texPath, StringComparer.Ordinal)))
385 | {
386 | Logger.Debug($"Replacements already contain {texPath}, skipping");
387 | return;
388 | }
389 |
390 | var texFileReplacement = CreateFileReplacement(texPath, objIdx, doNotReverseResolve);
391 | //DebugPrint(texFileReplacement, objectKind, "Texture", inheritanceLevel);
392 |
393 | AddFileReplacement(replacements, texFileReplacement);
394 |
395 | if (texPath.Contains("/--", StringComparison.Ordinal)) return;
396 |
397 | var texDx11Replacement =
398 | CreateFileReplacement(texPath.Insert(texPath.LastIndexOf('/') + 1, "--"), objIdx, doNotReverseResolve);
399 |
400 | //DebugPrint(texDx11Replacement, objectKind, "Texture (DX11)", inheritanceLevel);
401 |
402 | AddFileReplacement(replacements, texDx11Replacement);
403 | }
404 |
405 | private void AddReplacementsFromShader(string shpkPath, List replacements, int objIdx, int inheritanceLevel = 0)
406 | {
407 | if (string.IsNullOrEmpty(shpkPath)) return;
408 |
409 | if (replacements.Any(c => c.GamePaths.Contains(shpkPath, StringComparer.Ordinal)))
410 | {
411 | return;
412 | }
413 |
414 | var shpkFileReplacement = CreateFileReplacement(shpkPath, objIdx);
415 | //DebugPrint(shpkFileReplacement, objectKind, "Shader", inheritanceLevel);
416 | AddFileReplacement(replacements, shpkFileReplacement);
417 | }
418 |
419 | private unsafe void AddPlayerSpecificReplacements(List replacements, IntPtr charaPointer, Human* human, int objIdx)
420 | {
421 | var weaponObject = (Interop.Weapon*)((FFXIVClientStructs.FFXIV.Client.Graphics.Scene.Object*)human)->ChildObject;
422 |
423 | if ((IntPtr)weaponObject != IntPtr.Zero)
424 | {
425 | var mainHandWeapon = weaponObject->WeaponRenderModel->RenderModel;
426 |
427 | AddReplacementsFromRenderModel(mainHandWeapon, replacements, objIdx, 0);
428 |
429 | /*
430 | foreach (var item in replacements)
431 | {
432 | _transientResourceManager.RemoveTransientResource(charaPointer, item);
433 | }
434 | */
435 | /*
436 | foreach (var item in _transientResourceManager.GetTransientResources((IntPtr)weaponObject))
437 | {
438 | Logger.Verbose("Found transient weapon resource: " + item);
439 | AddReplacement(item, objectKind, previousData, 1, true);
440 | }
441 | */
442 |
443 |
444 | if (weaponObject->NextSibling != (IntPtr)weaponObject)
445 | {
446 | var offHandWeapon = ((Interop.Weapon*)weaponObject->NextSibling)->WeaponRenderModel->RenderModel;
447 |
448 | AddReplacementsFromRenderModel(offHandWeapon, replacements, objIdx, 1);
449 | /*
450 | foreach (var item in replacements)
451 | {
452 | _transientResourceManager.RemoveTransientResource((IntPtr)offHandWeapon, item);
453 | }
454 |
455 | foreach (var item in _transientResourceManager.GetTransientResources((IntPtr)offHandWeapon))
456 | {
457 | Logger.Verbose("Found transient offhand weapon resource: " + item);
458 | AddReplacement(item, objectKind, previousData, 1, true);
459 | }
460 | */
461 | }
462 | }
463 |
464 | AddReplacementSkeleton(((Interop.HumanExt*)human)->Human.RaceSexId, objIdx, replacements);
465 | try
466 | {
467 | AddReplacementsFromTexture(new ByteString(((Interop.HumanExt*)human)->Decal->FileName()).ToString(), replacements, objIdx, 0, false);
468 | }
469 | catch
470 | {
471 | Logger.Warn("Could not get Decal data");
472 | }
473 | try
474 | {
475 | AddReplacementsFromTexture(new ByteString(((Interop.HumanExt*)human)->LegacyBodyDecal->FileName()).ToString(), replacements, objIdx, 0, false);
476 | }
477 | catch
478 | {
479 | Logger.Warn("Could not get Legacy Body Decal Data");
480 | }
481 | /*
482 | foreach (var item in previousData.FileReplacements[objectKind])
483 | {
484 | _transientResourceManager.RemoveTransientResource(charaPointer, item);
485 | }
486 | */
487 | }
488 |
489 | private void AddReplacementSkeleton(ushort raceSexId, int objIdx, List replacements)
490 | {
491 | string raceSexIdString = raceSexId.ToString("0000");
492 |
493 | string skeletonPath = $"chara/human/c{raceSexIdString}/skeleton/base/b0001/skl_c{raceSexIdString}b0001.sklb";
494 |
495 | var replacement = CreateFileReplacement(skeletonPath, objIdx, true);
496 | AddFileReplacement(replacements, replacement);
497 |
498 | //DebugPrint(replacement, objectKind, "SKLB", 0);
499 | }
500 |
501 | private void AddFileReplacement(List replacements, FileReplacement newReplacement)
502 | {
503 | if (!newReplacement.HasFileReplacement)
504 | {
505 | Logger.Debug($"Replacement for {newReplacement.ResolvedPath} does not have a file replacement, skipping");
506 | foreach(var path in newReplacement.GamePaths)
507 | {
508 | Logger.Debug(path);
509 | }
510 | return;
511 | }
512 |
513 | var existingReplacement = replacements.SingleOrDefault(f => string.Equals(f.ResolvedPath, newReplacement.ResolvedPath, System.StringComparison.OrdinalIgnoreCase));
514 | if (existingReplacement != null)
515 | {
516 | Logger.Debug($"Added replacement for existing path {existingReplacement.ResolvedPath}");
517 | existingReplacement.GamePaths.AddRange(newReplacement.GamePaths.Where(e => !existingReplacement.GamePaths.Contains(e, System.StringComparer.OrdinalIgnoreCase)));
518 | }
519 | else
520 | {
521 | Logger.Debug($"Added new replacement {newReplacement.ResolvedPath}");
522 | replacements.Add(newReplacement);
523 | }
524 | }
525 |
526 | private FileReplacement CreateFileReplacement(string path, int objIdx, bool doNotReverseResolve = false)
527 | {
528 | var fileReplacement = new FileReplacement(Plugin);
529 |
530 | if (!doNotReverseResolve)
531 | {
532 | fileReplacement.ReverseResolvePathObject(path, objIdx);
533 | }
534 | else
535 | {
536 | fileReplacement.ResolvePathObject(path, objIdx);
537 | }
538 |
539 | Logger.Debug($"Created file replacement for resolved path {fileReplacement.ResolvedPath}, hash {fileReplacement.Hash}, gamepath {fileReplacement.GamePaths[0]}");
540 | return fileReplacement;
541 | }
542 | }
543 | }
544 |
--------------------------------------------------------------------------------
/Snapper/packages.lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "dependencies": {
4 | "net9.0-windows7.0": {
5 | "DalamudPackager": {
6 | "type": "Direct",
7 | "requested": "[13.0.0, )",
8 | "resolved": "13.0.0",
9 | "contentHash": "Mb3cUDSK/vDPQ8gQIeuCw03EMYrej1B4J44a1AvIJ9C759p9XeqdU9Hg4WgOmlnlPe0G7ILTD32PKSUpkQNa8w=="
10 | },
11 | "Glamourer.Api": {
12 | "type": "Direct",
13 | "requested": "[2.6.0, )",
14 | "resolved": "2.6.0",
15 | "contentHash": "zysCZgNBRm3k3qvibyw/31MmEckX0Uh0ZsT+Sax3ZHnYIRELr9Qhbz3cjJz7u0RHGIrNJiRpktu/LxgHEqDItw=="
16 | },
17 | "lz4net": {
18 | "type": "Direct",
19 | "requested": "[1.0.15.93, )",
20 | "resolved": "1.0.15.93",
21 | "contentHash": "s92+YYtH4tiw0/0a7cyb+EXAOICtxZgPA1cL19L8paJZe39QViBaNl24o9ETBJxjgbm11OIMjuMUzX86id6SEg==",
22 | "dependencies": {
23 | "NETStandard.Library": "1.6.1",
24 | "lz4net.unsafe.netcore": "[1.0.15.93]"
25 | }
26 | },
27 | "Microsoft.Extensions.Logging": {
28 | "type": "Direct",
29 | "requested": "[9.0.8, )",
30 | "resolved": "9.0.8",
31 | "contentHash": "Z/7ze+0iheT7FJeZPqJKARYvyC2bmwu3whbm/48BJjdlGVvgDguoCqJIkI/67NkroTYobd5geai1WheNQvWrgA==",
32 | "dependencies": {
33 | "Microsoft.Extensions.DependencyInjection": "9.0.8",
34 | "Microsoft.Extensions.Logging.Abstractions": "9.0.8",
35 | "Microsoft.Extensions.Options": "9.0.8"
36 | }
37 | },
38 | "FlatSharp.Compiler": {
39 | "type": "Transitive",
40 | "resolved": "7.9.0",
41 | "contentHash": "MU6808xvdbWJ3Ev+5PKalqQuzvVbn1DzzQH8txRDHGFUNDvHjd+ejqpvnYc9BSJ8Qp8VjkkpJD8OzRYilbPp3A=="
42 | },
43 | "FlatSharp.Runtime": {
44 | "type": "Transitive",
45 | "resolved": "7.9.0",
46 | "contentHash": "Bm8+WqzEsWNpxqrD5x4x+zQ8dyINlToCreM5FI2oNSfUVc9U9ZB+qztX/jd8rlJb3r0vBSlPwVLpw0xBtPa3Vw==",
47 | "dependencies": {
48 | "System.Memory": "4.5.5"
49 | }
50 | },
51 | "JetBrains.Annotations": {
52 | "type": "Transitive",
53 | "resolved": "2024.3.0",
54 | "contentHash": "ox5pkeLQXjvJdyAB4b2sBYAlqZGLh3PjSnP1bQNVx72ONuTJ9+34/+Rq91Fc0dG29XG9RgZur9+NcP4riihTug=="
55 | },
56 | "lz4net.unsafe.netcore": {
57 | "type": "Transitive",
58 | "resolved": "1.0.15.93",
59 | "contentHash": "tuJA5s13/5AF+fUcvERBBXtzK/gARwpJiHrqsCh2UH57e8qNp+WETNrKw63byK0Zle4OVTwuWgN0cY/uSc388Q==",
60 | "dependencies": {
61 | "NETStandard.Library": "1.6.1"
62 | }
63 | },
64 | "Microsoft.Extensions.DependencyInjection": {
65 | "type": "Transitive",
66 | "resolved": "9.0.8",
67 | "contentHash": "JJjI2Fa+QtZcUyuNjbKn04OjIUX5IgFGFu/Xc+qvzh1rXdZHLcnqqVXhR4093bGirTwacRlHiVg1XYI9xum6QQ==",
68 | "dependencies": {
69 | "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8"
70 | }
71 | },
72 | "Microsoft.Extensions.DependencyInjection.Abstractions": {
73 | "type": "Transitive",
74 | "resolved": "9.0.8",
75 | "contentHash": "xY3lTjj4+ZYmiKIkyWitddrp1uL5uYiweQjqo4BKBw01ZC4HhcfgLghDpPZcUlppgWAFqFy9SgkiYWOMx365pw=="
76 | },
77 | "Microsoft.Extensions.Logging.Abstractions": {
78 | "type": "Transitive",
79 | "resolved": "9.0.8",
80 | "contentHash": "pYnAffJL7ARD/HCnnPvnFKSIHnTSmWz84WIlT9tPeQ4lHNiu0Az7N/8itihWvcF8sT+VVD5lq8V+ckMzu4SbOw==",
81 | "dependencies": {
82 | "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8"
83 | }
84 | },
85 | "Microsoft.Extensions.Options": {
86 | "type": "Transitive",
87 | "resolved": "9.0.8",
88 | "contentHash": "OmTaQ0v4gxGQkehpwWIqPoEiwsPuG/u4HUsbOFoWGx4DKET2AXzopnFe/fE608FIhzc/kcg2p8JdyMRCCUzitQ==",
89 | "dependencies": {
90 | "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.8",
91 | "Microsoft.Extensions.Primitives": "9.0.8"
92 | }
93 | },
94 | "Microsoft.Extensions.Primitives": {
95 | "type": "Transitive",
96 | "resolved": "9.0.8",
97 | "contentHash": "tizSIOEsIgSNSSh+hKeUVPK7xmTIjR8s+mJWOu1KXV3htvNQiPMFRMO17OdI1y/4ZApdBVk49u/08QGC9yvLug=="
98 | },
99 | "Microsoft.NETCore.Platforms": {
100 | "type": "Transitive",
101 | "resolved": "1.1.0",
102 | "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A=="
103 | },
104 | "Microsoft.NETCore.Targets": {
105 | "type": "Transitive",
106 | "resolved": "1.1.0",
107 | "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg=="
108 | },
109 | "Microsoft.Win32.Primitives": {
110 | "type": "Transitive",
111 | "resolved": "4.3.0",
112 | "contentHash": "9ZQKCWxH7Ijp9BfahvL2Zyf1cJIk8XYLF6Yjzr2yi0b2cOut/HQ31qf1ThHAgCc3WiZMdnWcfJCgN82/0UunxA==",
113 | "dependencies": {
114 | "Microsoft.NETCore.Platforms": "1.1.0",
115 | "Microsoft.NETCore.Targets": "1.1.0",
116 | "System.Runtime": "4.3.0"
117 | }
118 | },
119 | "NETStandard.Library": {
120 | "type": "Transitive",
121 | "resolved": "1.6.1",
122 | "contentHash": "WcSp3+vP+yHNgS8EV5J7pZ9IRpeDuARBPN28by8zqff1wJQXm26PVU8L3/fYLBJVU7BtDyqNVWq2KlCVvSSR4A==",
123 | "dependencies": {
124 | "Microsoft.NETCore.Platforms": "1.1.0",
125 | "Microsoft.Win32.Primitives": "4.3.0",
126 | "System.AppContext": "4.3.0",
127 | "System.Collections": "4.3.0",
128 | "System.Collections.Concurrent": "4.3.0",
129 | "System.Console": "4.3.0",
130 | "System.Diagnostics.Debug": "4.3.0",
131 | "System.Diagnostics.Tools": "4.3.0",
132 | "System.Diagnostics.Tracing": "4.3.0",
133 | "System.Globalization": "4.3.0",
134 | "System.Globalization.Calendars": "4.3.0",
135 | "System.IO": "4.3.0",
136 | "System.IO.Compression": "4.3.0",
137 | "System.IO.Compression.ZipFile": "4.3.0",
138 | "System.IO.FileSystem": "4.3.0",
139 | "System.IO.FileSystem.Primitives": "4.3.0",
140 | "System.Linq": "4.3.0",
141 | "System.Linq.Expressions": "4.3.0",
142 | "System.Net.Http": "4.3.0",
143 | "System.Net.Primitives": "4.3.0",
144 | "System.Net.Sockets": "4.3.0",
145 | "System.ObjectModel": "4.3.0",
146 | "System.Reflection": "4.3.0",
147 | "System.Reflection.Extensions": "4.3.0",
148 | "System.Reflection.Primitives": "4.3.0",
149 | "System.Resources.ResourceManager": "4.3.0",
150 | "System.Runtime": "4.3.0",
151 | "System.Runtime.Extensions": "4.3.0",
152 | "System.Runtime.Handles": "4.3.0",
153 | "System.Runtime.InteropServices": "4.3.0",
154 | "System.Runtime.InteropServices.RuntimeInformation": "4.3.0",
155 | "System.Runtime.Numerics": "4.3.0",
156 | "System.Security.Cryptography.Algorithms": "4.3.0",
157 | "System.Security.Cryptography.Encoding": "4.3.0",
158 | "System.Security.Cryptography.Primitives": "4.3.0",
159 | "System.Security.Cryptography.X509Certificates": "4.3.0",
160 | "System.Text.Encoding": "4.3.0",
161 | "System.Text.Encoding.Extensions": "4.3.0",
162 | "System.Text.RegularExpressions": "4.3.0",
163 | "System.Threading": "4.3.0",
164 | "System.Threading.Tasks": "4.3.0",
165 | "System.Threading.Timer": "4.3.0",
166 | "System.Xml.ReaderWriter": "4.3.0",
167 | "System.Xml.XDocument": "4.3.0"
168 | }
169 | },
170 | "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
171 | "type": "Transitive",
172 | "resolved": "4.3.0",
173 | "contentHash": "HdSSp5MnJSsg08KMfZThpuLPJpPwE5hBXvHwoKWosyHHfe8Mh5WKT0ylEOf6yNzX6Ngjxe4Whkafh5q7Ymac4Q=="
174 | },
175 | "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
176 | "type": "Transitive",
177 | "resolved": "4.3.0",
178 | "contentHash": "+yH1a49wJMy8Zt4yx5RhJrxO/DBDByAiCzNwiETI+1S4mPdCu0OY4djdciC7Vssk0l22wQaDLrXxXkp+3+7bVA=="
179 | },
180 | "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
181 | "type": "Transitive",
182 | "resolved": "4.3.0",
183 | "contentHash": "c3YNH1GQJbfIPJeCnr4avseugSqPrxwIqzthYyZDN6EuOyNOzq+y2KSUfRcXauya1sF4foESTgwM5e1A8arAKw=="
184 | },
185 | "runtime.native.System": {
186 | "type": "Transitive",
187 | "resolved": "4.3.0",
188 | "contentHash": "c/qWt2LieNZIj1jGnVNsE2Kl23Ya2aSTBuXMD6V7k9KWr6l16Tqdwq+hJScEpWER9753NWC8h96PaVNY5Ld7Jw==",
189 | "dependencies": {
190 | "Microsoft.NETCore.Platforms": "1.1.0",
191 | "Microsoft.NETCore.Targets": "1.1.0"
192 | }
193 | },
194 | "runtime.native.System.IO.Compression": {
195 | "type": "Transitive",
196 | "resolved": "4.3.0",
197 | "contentHash": "INBPonS5QPEgn7naufQFXJEp3zX6L4bwHgJ/ZH78aBTpeNfQMtf7C6VrAFhlq2xxWBveIOWyFzQjJ8XzHMhdOQ==",
198 | "dependencies": {
199 | "Microsoft.NETCore.Platforms": "1.1.0",
200 | "Microsoft.NETCore.Targets": "1.1.0"
201 | }
202 | },
203 | "runtime.native.System.Net.Http": {
204 | "type": "Transitive",
205 | "resolved": "4.3.0",
206 | "contentHash": "ZVuZJqnnegJhd2k/PtAbbIcZ3aZeITq3sj06oKfMBSfphW3HDmk/t4ObvbOk/JA/swGR0LNqMksAh/f7gpTROg==",
207 | "dependencies": {
208 | "Microsoft.NETCore.Platforms": "1.1.0",
209 | "Microsoft.NETCore.Targets": "1.1.0"
210 | }
211 | },
212 | "runtime.native.System.Security.Cryptography.Apple": {
213 | "type": "Transitive",
214 | "resolved": "4.3.0",
215 | "contentHash": "DloMk88juo0OuOWr56QG7MNchmafTLYWvABy36izkrLI5VledI0rq28KGs1i9wbpeT9NPQrx/wTf8U2vazqQ3Q==",
216 | "dependencies": {
217 | "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": "4.3.0"
218 | }
219 | },
220 | "runtime.native.System.Security.Cryptography.OpenSsl": {
221 | "type": "Transitive",
222 | "resolved": "4.3.0",
223 | "contentHash": "NS1U+700m4KFRHR5o4vo9DSlTmlCKu/u7dtE5sUHVIPB+xpXxYQvgBgA6wEIeCz6Yfn0Z52/72WYsToCEPJnrw==",
224 | "dependencies": {
225 | "runtime.debian.8-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
226 | "runtime.fedora.23-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
227 | "runtime.fedora.24-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
228 | "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
229 | "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
230 | "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
231 | "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
232 | "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
233 | "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0",
234 | "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
235 | }
236 | },
237 | "runtime.opensuse.13.2-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
238 | "type": "Transitive",
239 | "resolved": "4.3.0",
240 | "contentHash": "b3pthNgxxFcD+Pc0WSEoC0+md3MyhRS6aCEeenvNE3Fdw1HyJ18ZhRFVJJzIeR/O/jpxPboB805Ho0T3Ul7w8A=="
241 | },
242 | "runtime.opensuse.42.1-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
243 | "type": "Transitive",
244 | "resolved": "4.3.0",
245 | "contentHash": "KeLz4HClKf+nFS7p/6Fi/CqyLXh81FpiGzcmuS8DGi9lUqSnZ6Es23/gv2O+1XVGfrbNmviF7CckBpavkBoIFQ=="
246 | },
247 | "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.Apple": {
248 | "type": "Transitive",
249 | "resolved": "4.3.0",
250 | "contentHash": "kVXCuMTrTlxq4XOOMAysuNwsXWpYeboGddNGpIgNSZmv1b6r/s/DPk0fYMB7Q5Qo4bY68o48jt4T4y5BVecbCQ=="
251 | },
252 | "runtime.osx.10.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
253 | "type": "Transitive",
254 | "resolved": "4.3.0",
255 | "contentHash": "X7IdhILzr4ROXd8mI1BUCQMSHSQwelUlBjF1JyTKCjXaOGn2fB4EKBxQbCK2VjO3WaWIdlXZL3W6TiIVnrhX4g=="
256 | },
257 | "runtime.rhel.7-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
258 | "type": "Transitive",
259 | "resolved": "4.3.0",
260 | "contentHash": "nyFNiCk/r+VOiIqreLix8yN+q3Wga9+SE8BCgkf+2BwEKiNx6DyvFjCgkfV743/grxv8jHJ8gUK4XEQw7yzRYg=="
261 | },
262 | "runtime.ubuntu.14.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
263 | "type": "Transitive",
264 | "resolved": "4.3.0",
265 | "contentHash": "ytoewC6wGorL7KoCAvRfsgoJPJbNq+64k2SqW6JcOAebWsFUvCCYgfzQMrnpvPiEl4OrblUlhF2ji+Q1+SVLrQ=="
266 | },
267 | "runtime.ubuntu.16.04-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
268 | "type": "Transitive",
269 | "resolved": "4.3.0",
270 | "contentHash": "I8bKw2I8k58Wx7fMKQJn2R8lamboCAiHfHeV/pS65ScKWMMI0+wJkLYlEKvgW1D/XvSl/221clBoR2q9QNNM7A=="
271 | },
272 | "runtime.ubuntu.16.10-x64.runtime.native.System.Security.Cryptography.OpenSsl": {
273 | "type": "Transitive",
274 | "resolved": "4.3.0",
275 | "contentHash": "VB5cn/7OzUfzdnC8tqAIMQciVLiq2epm2NrAm1E9OjNRyG4lVhfR61SMcLizejzQP8R8Uf/0l5qOIbUEi+RdEg=="
276 | },
277 | "System.AppContext": {
278 | "type": "Transitive",
279 | "resolved": "4.3.0",
280 | "contentHash": "fKC+rmaLfeIzUhagxY17Q9siv/sPrjjKcfNg1Ic8IlQkZLipo8ljcaZQu4VtI4Jqbzjc2VTjzGLF6WmsRXAEgA==",
281 | "dependencies": {
282 | "System.Runtime": "4.3.0"
283 | }
284 | },
285 | "System.Buffers": {
286 | "type": "Transitive",
287 | "resolved": "4.3.0",
288 | "contentHash": "ratu44uTIHgeBeI0dE8DWvmXVBSo4u7ozRZZHOMmK/JPpYyo0dAfgSiHlpiObMQ5lEtEyIXA40sKRYg5J6A8uQ==",
289 | "dependencies": {
290 | "System.Diagnostics.Debug": "4.3.0",
291 | "System.Diagnostics.Tracing": "4.3.0",
292 | "System.Resources.ResourceManager": "4.3.0",
293 | "System.Runtime": "4.3.0",
294 | "System.Threading": "4.3.0"
295 | }
296 | },
297 | "System.Collections": {
298 | "type": "Transitive",
299 | "resolved": "4.3.0",
300 | "contentHash": "3Dcj85/TBdVpL5Zr+gEEBUuFe2icOnLalmEh9hfck1PTYbbyWuZgh4fmm2ysCLTrqLQw6t3TgTyJ+VLp+Qb+Lw==",
301 | "dependencies": {
302 | "Microsoft.NETCore.Platforms": "1.1.0",
303 | "Microsoft.NETCore.Targets": "1.1.0",
304 | "System.Runtime": "4.3.0"
305 | }
306 | },
307 | "System.Collections.Concurrent": {
308 | "type": "Transitive",
309 | "resolved": "4.3.0",
310 | "contentHash": "ztl69Xp0Y/UXCL+3v3tEU+lIy+bvjKNUmopn1wep/a291pVPK7dxBd6T7WnlQqRog+d1a/hSsgRsmFnIBKTPLQ==",
311 | "dependencies": {
312 | "System.Collections": "4.3.0",
313 | "System.Diagnostics.Debug": "4.3.0",
314 | "System.Diagnostics.Tracing": "4.3.0",
315 | "System.Globalization": "4.3.0",
316 | "System.Reflection": "4.3.0",
317 | "System.Resources.ResourceManager": "4.3.0",
318 | "System.Runtime": "4.3.0",
319 | "System.Runtime.Extensions": "4.3.0",
320 | "System.Threading": "4.3.0",
321 | "System.Threading.Tasks": "4.3.0"
322 | }
323 | },
324 | "System.Console": {
325 | "type": "Transitive",
326 | "resolved": "4.3.0",
327 | "contentHash": "DHDrIxiqk1h03m6khKWV2X8p/uvN79rgSqpilL6uzpmSfxfU5ng8VcPtW4qsDsQDHiTv6IPV9TmD5M/vElPNLg==",
328 | "dependencies": {
329 | "Microsoft.NETCore.Platforms": "1.1.0",
330 | "Microsoft.NETCore.Targets": "1.1.0",
331 | "System.IO": "4.3.0",
332 | "System.Runtime": "4.3.0",
333 | "System.Text.Encoding": "4.3.0"
334 | }
335 | },
336 | "System.Diagnostics.Debug": {
337 | "type": "Transitive",
338 | "resolved": "4.3.0",
339 | "contentHash": "ZUhUOdqmaG5Jk3Xdb8xi5kIyQYAA4PnTNlHx1mu9ZY3qv4ELIdKbnL/akbGaKi2RnNUWaZsAs31rvzFdewTj2g==",
340 | "dependencies": {
341 | "Microsoft.NETCore.Platforms": "1.1.0",
342 | "Microsoft.NETCore.Targets": "1.1.0",
343 | "System.Runtime": "4.3.0"
344 | }
345 | },
346 | "System.Diagnostics.DiagnosticSource": {
347 | "type": "Transitive",
348 | "resolved": "4.3.0",
349 | "contentHash": "tD6kosZnTAGdrEa0tZSuFyunMbt/5KYDnHdndJYGqZoNy00XVXyACd5d6KnE1YgYv3ne2CjtAfNXo/fwEhnKUA==",
350 | "dependencies": {
351 | "System.Collections": "4.3.0",
352 | "System.Diagnostics.Tracing": "4.3.0",
353 | "System.Reflection": "4.3.0",
354 | "System.Runtime": "4.3.0",
355 | "System.Threading": "4.3.0"
356 | }
357 | },
358 | "System.Diagnostics.Tools": {
359 | "type": "Transitive",
360 | "resolved": "4.3.0",
361 | "contentHash": "UUvkJfSYJMM6x527dJg2VyWPSRqIVB0Z7dbjHst1zmwTXz5CcXSYJFWRpuigfbO1Lf7yfZiIaEUesfnl/g5EyA==",
362 | "dependencies": {
363 | "Microsoft.NETCore.Platforms": "1.1.0",
364 | "Microsoft.NETCore.Targets": "1.1.0",
365 | "System.Runtime": "4.3.0"
366 | }
367 | },
368 | "System.Diagnostics.Tracing": {
369 | "type": "Transitive",
370 | "resolved": "4.3.0",
371 | "contentHash": "rswfv0f/Cqkh78rA5S8eN8Neocz234+emGCtTF3lxPY96F+mmmUen6tbn0glN6PMvlKQb9bPAY5e9u7fgPTkKw==",
372 | "dependencies": {
373 | "Microsoft.NETCore.Platforms": "1.1.0",
374 | "Microsoft.NETCore.Targets": "1.1.0",
375 | "System.Runtime": "4.3.0"
376 | }
377 | },
378 | "System.Globalization": {
379 | "type": "Transitive",
380 | "resolved": "4.3.0",
381 | "contentHash": "kYdVd2f2PAdFGblzFswE4hkNANJBKRmsfa2X5LG2AcWE1c7/4t0pYae1L8vfZ5xvE2nK/R9JprtToA61OSHWIg==",
382 | "dependencies": {
383 | "Microsoft.NETCore.Platforms": "1.1.0",
384 | "Microsoft.NETCore.Targets": "1.1.0",
385 | "System.Runtime": "4.3.0"
386 | }
387 | },
388 | "System.Globalization.Calendars": {
389 | "type": "Transitive",
390 | "resolved": "4.3.0",
391 | "contentHash": "GUlBtdOWT4LTV3I+9/PJW+56AnnChTaOqqTLFtdmype/L500M2LIyXgmtd9X2P2VOkmJd5c67H5SaC2QcL1bFA==",
392 | "dependencies": {
393 | "Microsoft.NETCore.Platforms": "1.1.0",
394 | "Microsoft.NETCore.Targets": "1.1.0",
395 | "System.Globalization": "4.3.0",
396 | "System.Runtime": "4.3.0"
397 | }
398 | },
399 | "System.Globalization.Extensions": {
400 | "type": "Transitive",
401 | "resolved": "4.3.0",
402 | "contentHash": "FhKmdR6MPG+pxow6wGtNAWdZh7noIOpdD5TwQ3CprzgIE1bBBoim0vbR1+AWsWjQmU7zXHgQo4TWSP6lCeiWcQ==",
403 | "dependencies": {
404 | "Microsoft.NETCore.Platforms": "1.1.0",
405 | "System.Globalization": "4.3.0",
406 | "System.Resources.ResourceManager": "4.3.0",
407 | "System.Runtime": "4.3.0",
408 | "System.Runtime.Extensions": "4.3.0",
409 | "System.Runtime.InteropServices": "4.3.0"
410 | }
411 | },
412 | "System.IO": {
413 | "type": "Transitive",
414 | "resolved": "4.3.0",
415 | "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==",
416 | "dependencies": {
417 | "Microsoft.NETCore.Platforms": "1.1.0",
418 | "Microsoft.NETCore.Targets": "1.1.0",
419 | "System.Runtime": "4.3.0",
420 | "System.Text.Encoding": "4.3.0",
421 | "System.Threading.Tasks": "4.3.0"
422 | }
423 | },
424 | "System.IO.Compression": {
425 | "type": "Transitive",
426 | "resolved": "4.3.0",
427 | "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==",
428 | "dependencies": {
429 | "Microsoft.NETCore.Platforms": "1.1.0",
430 | "System.Buffers": "4.3.0",
431 | "System.Collections": "4.3.0",
432 | "System.Diagnostics.Debug": "4.3.0",
433 | "System.IO": "4.3.0",
434 | "System.Resources.ResourceManager": "4.3.0",
435 | "System.Runtime": "4.3.0",
436 | "System.Runtime.Extensions": "4.3.0",
437 | "System.Runtime.Handles": "4.3.0",
438 | "System.Runtime.InteropServices": "4.3.0",
439 | "System.Text.Encoding": "4.3.0",
440 | "System.Threading": "4.3.0",
441 | "System.Threading.Tasks": "4.3.0",
442 | "runtime.native.System": "4.3.0",
443 | "runtime.native.System.IO.Compression": "4.3.0"
444 | }
445 | },
446 | "System.IO.Compression.ZipFile": {
447 | "type": "Transitive",
448 | "resolved": "4.3.0",
449 | "contentHash": "G4HwjEsgIwy3JFBduZ9quBkAu+eUwjIdJleuNSgmUojbH6O3mlvEIme+GHx/cLlTAPcrnnL7GqvB9pTlWRfhOg==",
450 | "dependencies": {
451 | "System.Buffers": "4.3.0",
452 | "System.IO": "4.3.0",
453 | "System.IO.Compression": "4.3.0",
454 | "System.IO.FileSystem": "4.3.0",
455 | "System.IO.FileSystem.Primitives": "4.3.0",
456 | "System.Resources.ResourceManager": "4.3.0",
457 | "System.Runtime": "4.3.0",
458 | "System.Runtime.Extensions": "4.3.0",
459 | "System.Text.Encoding": "4.3.0"
460 | }
461 | },
462 | "System.IO.FileSystem": {
463 | "type": "Transitive",
464 | "resolved": "4.3.0",
465 | "contentHash": "3wEMARTnuio+ulnvi+hkRNROYwa1kylvYahhcLk4HSoVdl+xxTFVeVlYOfLwrDPImGls0mDqbMhrza8qnWPTdA==",
466 | "dependencies": {
467 | "Microsoft.NETCore.Platforms": "1.1.0",
468 | "Microsoft.NETCore.Targets": "1.1.0",
469 | "System.IO": "4.3.0",
470 | "System.IO.FileSystem.Primitives": "4.3.0",
471 | "System.Runtime": "4.3.0",
472 | "System.Runtime.Handles": "4.3.0",
473 | "System.Text.Encoding": "4.3.0",
474 | "System.Threading.Tasks": "4.3.0"
475 | }
476 | },
477 | "System.IO.FileSystem.Primitives": {
478 | "type": "Transitive",
479 | "resolved": "4.3.0",
480 | "contentHash": "6QOb2XFLch7bEc4lIcJH49nJN2HV+OC3fHDgsLVsBVBk3Y4hFAnOBGzJ2lUu7CyDDFo9IBWkSsnbkT6IBwwiMw==",
481 | "dependencies": {
482 | "System.Runtime": "4.3.0"
483 | }
484 | },
485 | "System.Linq": {
486 | "type": "Transitive",
487 | "resolved": "4.3.0",
488 | "contentHash": "5DbqIUpsDp0dFftytzuMmc0oeMdQwjcP/EWxsksIz/w1TcFRkZ3yKKz0PqiYFMmEwPSWw+qNVqD7PJ889JzHbw==",
489 | "dependencies": {
490 | "System.Collections": "4.3.0",
491 | "System.Diagnostics.Debug": "4.3.0",
492 | "System.Resources.ResourceManager": "4.3.0",
493 | "System.Runtime": "4.3.0",
494 | "System.Runtime.Extensions": "4.3.0"
495 | }
496 | },
497 | "System.Linq.Expressions": {
498 | "type": "Transitive",
499 | "resolved": "4.3.0",
500 | "contentHash": "PGKkrd2khG4CnlyJwxwwaWWiSiWFNBGlgXvJpeO0xCXrZ89ODrQ6tjEWS/kOqZ8GwEOUATtKtzp1eRgmYNfclg==",
501 | "dependencies": {
502 | "System.Collections": "4.3.0",
503 | "System.Diagnostics.Debug": "4.3.0",
504 | "System.Globalization": "4.3.0",
505 | "System.IO": "4.3.0",
506 | "System.Linq": "4.3.0",
507 | "System.ObjectModel": "4.3.0",
508 | "System.Reflection": "4.3.0",
509 | "System.Reflection.Emit": "4.3.0",
510 | "System.Reflection.Emit.ILGeneration": "4.3.0",
511 | "System.Reflection.Emit.Lightweight": "4.3.0",
512 | "System.Reflection.Extensions": "4.3.0",
513 | "System.Reflection.Primitives": "4.3.0",
514 | "System.Reflection.TypeExtensions": "4.3.0",
515 | "System.Resources.ResourceManager": "4.3.0",
516 | "System.Runtime": "4.3.0",
517 | "System.Runtime.Extensions": "4.3.0",
518 | "System.Threading": "4.3.0"
519 | }
520 | },
521 | "System.Memory": {
522 | "type": "Transitive",
523 | "resolved": "4.5.5",
524 | "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw=="
525 | },
526 | "System.Net.Http": {
527 | "type": "Transitive",
528 | "resolved": "4.3.0",
529 | "contentHash": "sYg+FtILtRQuYWSIAuNOELwVuVsxVyJGWQyOnlAzhV4xvhyFnON1bAzYYC+jjRW8JREM45R0R5Dgi8MTC5sEwA==",
530 | "dependencies": {
531 | "Microsoft.NETCore.Platforms": "1.1.0",
532 | "System.Collections": "4.3.0",
533 | "System.Diagnostics.Debug": "4.3.0",
534 | "System.Diagnostics.DiagnosticSource": "4.3.0",
535 | "System.Diagnostics.Tracing": "4.3.0",
536 | "System.Globalization": "4.3.0",
537 | "System.Globalization.Extensions": "4.3.0",
538 | "System.IO": "4.3.0",
539 | "System.IO.FileSystem": "4.3.0",
540 | "System.Net.Primitives": "4.3.0",
541 | "System.Resources.ResourceManager": "4.3.0",
542 | "System.Runtime": "4.3.0",
543 | "System.Runtime.Extensions": "4.3.0",
544 | "System.Runtime.Handles": "4.3.0",
545 | "System.Runtime.InteropServices": "4.3.0",
546 | "System.Security.Cryptography.Algorithms": "4.3.0",
547 | "System.Security.Cryptography.Encoding": "4.3.0",
548 | "System.Security.Cryptography.OpenSsl": "4.3.0",
549 | "System.Security.Cryptography.Primitives": "4.3.0",
550 | "System.Security.Cryptography.X509Certificates": "4.3.0",
551 | "System.Text.Encoding": "4.3.0",
552 | "System.Threading": "4.3.0",
553 | "System.Threading.Tasks": "4.3.0",
554 | "runtime.native.System": "4.3.0",
555 | "runtime.native.System.Net.Http": "4.3.0",
556 | "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
557 | }
558 | },
559 | "System.Net.Primitives": {
560 | "type": "Transitive",
561 | "resolved": "4.3.0",
562 | "contentHash": "qOu+hDwFwoZPbzPvwut2qATe3ygjeQBDQj91xlsaqGFQUI5i4ZnZb8yyQuLGpDGivEPIt8EJkd1BVzVoP31FXA==",
563 | "dependencies": {
564 | "Microsoft.NETCore.Platforms": "1.1.0",
565 | "Microsoft.NETCore.Targets": "1.1.0",
566 | "System.Runtime": "4.3.0",
567 | "System.Runtime.Handles": "4.3.0"
568 | }
569 | },
570 | "System.Net.Sockets": {
571 | "type": "Transitive",
572 | "resolved": "4.3.0",
573 | "contentHash": "m6icV6TqQOAdgt5N/9I5KNpjom/5NFtkmGseEH+AK/hny8XrytLH3+b5M8zL/Ycg3fhIocFpUMyl/wpFnVRvdw==",
574 | "dependencies": {
575 | "Microsoft.NETCore.Platforms": "1.1.0",
576 | "Microsoft.NETCore.Targets": "1.1.0",
577 | "System.IO": "4.3.0",
578 | "System.Net.Primitives": "4.3.0",
579 | "System.Runtime": "4.3.0",
580 | "System.Threading.Tasks": "4.3.0"
581 | }
582 | },
583 | "System.ObjectModel": {
584 | "type": "Transitive",
585 | "resolved": "4.3.0",
586 | "contentHash": "bdX+80eKv9bN6K4N+d77OankKHGn6CH711a6fcOpMQu2Fckp/Ft4L/kW9WznHpyR0NRAvJutzOMHNNlBGvxQzQ==",
587 | "dependencies": {
588 | "System.Collections": "4.3.0",
589 | "System.Diagnostics.Debug": "4.3.0",
590 | "System.Resources.ResourceManager": "4.3.0",
591 | "System.Runtime": "4.3.0",
592 | "System.Threading": "4.3.0"
593 | }
594 | },
595 | "System.Reflection": {
596 | "type": "Transitive",
597 | "resolved": "4.3.0",
598 | "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==",
599 | "dependencies": {
600 | "Microsoft.NETCore.Platforms": "1.1.0",
601 | "Microsoft.NETCore.Targets": "1.1.0",
602 | "System.IO": "4.3.0",
603 | "System.Reflection.Primitives": "4.3.0",
604 | "System.Runtime": "4.3.0"
605 | }
606 | },
607 | "System.Reflection.Emit": {
608 | "type": "Transitive",
609 | "resolved": "4.3.0",
610 | "contentHash": "228FG0jLcIwTVJyz8CLFKueVqQK36ANazUManGaJHkO0icjiIypKW7YLWLIWahyIkdh5M7mV2dJepllLyA1SKg==",
611 | "dependencies": {
612 | "System.IO": "4.3.0",
613 | "System.Reflection": "4.3.0",
614 | "System.Reflection.Emit.ILGeneration": "4.3.0",
615 | "System.Reflection.Primitives": "4.3.0",
616 | "System.Runtime": "4.3.0"
617 | }
618 | },
619 | "System.Reflection.Emit.ILGeneration": {
620 | "type": "Transitive",
621 | "resolved": "4.3.0",
622 | "contentHash": "59tBslAk9733NXLrUJrwNZEzbMAcu8k344OYo+wfSVygcgZ9lgBdGIzH/nrg3LYhXceynyvTc8t5/GD4Ri0/ng==",
623 | "dependencies": {
624 | "System.Reflection": "4.3.0",
625 | "System.Reflection.Primitives": "4.3.0",
626 | "System.Runtime": "4.3.0"
627 | }
628 | },
629 | "System.Reflection.Emit.Lightweight": {
630 | "type": "Transitive",
631 | "resolved": "4.3.0",
632 | "contentHash": "oadVHGSMsTmZsAF864QYN1t1QzZjIcuKU3l2S9cZOwDdDueNTrqq1yRj7koFfIGEnKpt6NjpL3rOzRhs4ryOgA==",
633 | "dependencies": {
634 | "System.Reflection": "4.3.0",
635 | "System.Reflection.Emit.ILGeneration": "4.3.0",
636 | "System.Reflection.Primitives": "4.3.0",
637 | "System.Runtime": "4.3.0"
638 | }
639 | },
640 | "System.Reflection.Extensions": {
641 | "type": "Transitive",
642 | "resolved": "4.3.0",
643 | "contentHash": "rJkrJD3kBI5B712aRu4DpSIiHRtr6QlfZSQsb0hYHrDCZORXCFjQfoipo2LaMUHoT9i1B7j7MnfaEKWDFmFQNQ==",
644 | "dependencies": {
645 | "Microsoft.NETCore.Platforms": "1.1.0",
646 | "Microsoft.NETCore.Targets": "1.1.0",
647 | "System.Reflection": "4.3.0",
648 | "System.Runtime": "4.3.0"
649 | }
650 | },
651 | "System.Reflection.Primitives": {
652 | "type": "Transitive",
653 | "resolved": "4.3.0",
654 | "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==",
655 | "dependencies": {
656 | "Microsoft.NETCore.Platforms": "1.1.0",
657 | "Microsoft.NETCore.Targets": "1.1.0",
658 | "System.Runtime": "4.3.0"
659 | }
660 | },
661 | "System.Reflection.TypeExtensions": {
662 | "type": "Transitive",
663 | "resolved": "4.3.0",
664 | "contentHash": "7u6ulLcZbyxB5Gq0nMkQttcdBTx57ibzw+4IOXEfR+sXYQoHvjW5LTLyNr8O22UIMrqYbchJQJnos4eooYzYJA==",
665 | "dependencies": {
666 | "System.Reflection": "4.3.0",
667 | "System.Runtime": "4.3.0"
668 | }
669 | },
670 | "System.Resources.ResourceManager": {
671 | "type": "Transitive",
672 | "resolved": "4.3.0",
673 | "contentHash": "/zrcPkkWdZmI4F92gL/TPumP98AVDu/Wxr3CSJGQQ+XN6wbRZcyfSKVoPo17ilb3iOr0cCRqJInGwNMolqhS8A==",
674 | "dependencies": {
675 | "Microsoft.NETCore.Platforms": "1.1.0",
676 | "Microsoft.NETCore.Targets": "1.1.0",
677 | "System.Globalization": "4.3.0",
678 | "System.Reflection": "4.3.0",
679 | "System.Runtime": "4.3.0"
680 | }
681 | },
682 | "System.Runtime": {
683 | "type": "Transitive",
684 | "resolved": "4.3.0",
685 | "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==",
686 | "dependencies": {
687 | "Microsoft.NETCore.Platforms": "1.1.0",
688 | "Microsoft.NETCore.Targets": "1.1.0"
689 | }
690 | },
691 | "System.Runtime.Extensions": {
692 | "type": "Transitive",
693 | "resolved": "4.3.0",
694 | "contentHash": "guW0uK0fn5fcJJ1tJVXYd7/1h5F+pea1r7FLSOz/f8vPEqbR2ZAknuRDvTQ8PzAilDveOxNjSfr0CHfIQfFk8g==",
695 | "dependencies": {
696 | "Microsoft.NETCore.Platforms": "1.1.0",
697 | "Microsoft.NETCore.Targets": "1.1.0",
698 | "System.Runtime": "4.3.0"
699 | }
700 | },
701 | "System.Runtime.Handles": {
702 | "type": "Transitive",
703 | "resolved": "4.3.0",
704 | "contentHash": "OKiSUN7DmTWeYb3l51A7EYaeNMnvxwE249YtZz7yooT4gOZhmTjIn48KgSsw2k2lYdLgTKNJw/ZIfSElwDRVgg==",
705 | "dependencies": {
706 | "Microsoft.NETCore.Platforms": "1.1.0",
707 | "Microsoft.NETCore.Targets": "1.1.0",
708 | "System.Runtime": "4.3.0"
709 | }
710 | },
711 | "System.Runtime.InteropServices": {
712 | "type": "Transitive",
713 | "resolved": "4.3.0",
714 | "contentHash": "uv1ynXqiMK8mp1GM3jDqPCFN66eJ5w5XNomaK2XD+TuCroNTLFGeZ+WCmBMcBDyTFKou3P6cR6J/QsaqDp7fGQ==",
715 | "dependencies": {
716 | "Microsoft.NETCore.Platforms": "1.1.0",
717 | "Microsoft.NETCore.Targets": "1.1.0",
718 | "System.Reflection": "4.3.0",
719 | "System.Reflection.Primitives": "4.3.0",
720 | "System.Runtime": "4.3.0",
721 | "System.Runtime.Handles": "4.3.0"
722 | }
723 | },
724 | "System.Runtime.InteropServices.RuntimeInformation": {
725 | "type": "Transitive",
726 | "resolved": "4.3.0",
727 | "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==",
728 | "dependencies": {
729 | "System.Reflection": "4.3.0",
730 | "System.Reflection.Extensions": "4.3.0",
731 | "System.Resources.ResourceManager": "4.3.0",
732 | "System.Runtime": "4.3.0",
733 | "System.Runtime.InteropServices": "4.3.0",
734 | "System.Threading": "4.3.0",
735 | "runtime.native.System": "4.3.0"
736 | }
737 | },
738 | "System.Runtime.Numerics": {
739 | "type": "Transitive",
740 | "resolved": "4.3.0",
741 | "contentHash": "yMH+MfdzHjy17l2KESnPiF2dwq7T+xLnSJar7slyimAkUh/gTrS9/UQOtv7xarskJ2/XDSNvfLGOBQPjL7PaHQ==",
742 | "dependencies": {
743 | "System.Globalization": "4.3.0",
744 | "System.Resources.ResourceManager": "4.3.0",
745 | "System.Runtime": "4.3.0",
746 | "System.Runtime.Extensions": "4.3.0"
747 | }
748 | },
749 | "System.Security.Cryptography.Algorithms": {
750 | "type": "Transitive",
751 | "resolved": "4.3.0",
752 | "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==",
753 | "dependencies": {
754 | "Microsoft.NETCore.Platforms": "1.1.0",
755 | "System.Collections": "4.3.0",
756 | "System.IO": "4.3.0",
757 | "System.Resources.ResourceManager": "4.3.0",
758 | "System.Runtime": "4.3.0",
759 | "System.Runtime.Extensions": "4.3.0",
760 | "System.Runtime.Handles": "4.3.0",
761 | "System.Runtime.InteropServices": "4.3.0",
762 | "System.Runtime.Numerics": "4.3.0",
763 | "System.Security.Cryptography.Encoding": "4.3.0",
764 | "System.Security.Cryptography.Primitives": "4.3.0",
765 | "System.Text.Encoding": "4.3.0",
766 | "runtime.native.System.Security.Cryptography.Apple": "4.3.0",
767 | "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
768 | }
769 | },
770 | "System.Security.Cryptography.Cng": {
771 | "type": "Transitive",
772 | "resolved": "4.3.0",
773 | "contentHash": "03idZOqFlsKRL4W+LuCpJ6dBYDUWReug6lZjBa3uJWnk5sPCUXckocevTaUA8iT/MFSrY/2HXkOt753xQ/cf8g==",
774 | "dependencies": {
775 | "Microsoft.NETCore.Platforms": "1.1.0",
776 | "System.IO": "4.3.0",
777 | "System.Resources.ResourceManager": "4.3.0",
778 | "System.Runtime": "4.3.0",
779 | "System.Runtime.Extensions": "4.3.0",
780 | "System.Runtime.Handles": "4.3.0",
781 | "System.Runtime.InteropServices": "4.3.0",
782 | "System.Security.Cryptography.Algorithms": "4.3.0",
783 | "System.Security.Cryptography.Encoding": "4.3.0",
784 | "System.Security.Cryptography.Primitives": "4.3.0",
785 | "System.Text.Encoding": "4.3.0"
786 | }
787 | },
788 | "System.Security.Cryptography.Csp": {
789 | "type": "Transitive",
790 | "resolved": "4.3.0",
791 | "contentHash": "X4s/FCkEUnRGnwR3aSfVIkldBmtURMhmexALNTwpjklzxWU7yjMk7GHLKOZTNkgnWnE0q7+BCf9N2LVRWxewaA==",
792 | "dependencies": {
793 | "Microsoft.NETCore.Platforms": "1.1.0",
794 | "System.IO": "4.3.0",
795 | "System.Reflection": "4.3.0",
796 | "System.Resources.ResourceManager": "4.3.0",
797 | "System.Runtime": "4.3.0",
798 | "System.Runtime.Extensions": "4.3.0",
799 | "System.Runtime.Handles": "4.3.0",
800 | "System.Runtime.InteropServices": "4.3.0",
801 | "System.Security.Cryptography.Algorithms": "4.3.0",
802 | "System.Security.Cryptography.Encoding": "4.3.0",
803 | "System.Security.Cryptography.Primitives": "4.3.0",
804 | "System.Text.Encoding": "4.3.0",
805 | "System.Threading": "4.3.0"
806 | }
807 | },
808 | "System.Security.Cryptography.Encoding": {
809 | "type": "Transitive",
810 | "resolved": "4.3.0",
811 | "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==",
812 | "dependencies": {
813 | "Microsoft.NETCore.Platforms": "1.1.0",
814 | "System.Collections": "4.3.0",
815 | "System.Collections.Concurrent": "4.3.0",
816 | "System.Linq": "4.3.0",
817 | "System.Resources.ResourceManager": "4.3.0",
818 | "System.Runtime": "4.3.0",
819 | "System.Runtime.Extensions": "4.3.0",
820 | "System.Runtime.Handles": "4.3.0",
821 | "System.Runtime.InteropServices": "4.3.0",
822 | "System.Security.Cryptography.Primitives": "4.3.0",
823 | "System.Text.Encoding": "4.3.0",
824 | "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
825 | }
826 | },
827 | "System.Security.Cryptography.OpenSsl": {
828 | "type": "Transitive",
829 | "resolved": "4.3.0",
830 | "contentHash": "h4CEgOgv5PKVF/HwaHzJRiVboL2THYCou97zpmhjghx5frc7fIvlkY1jL+lnIQyChrJDMNEXS6r7byGif8Cy4w==",
831 | "dependencies": {
832 | "System.Collections": "4.3.0",
833 | "System.IO": "4.3.0",
834 | "System.Resources.ResourceManager": "4.3.0",
835 | "System.Runtime": "4.3.0",
836 | "System.Runtime.Extensions": "4.3.0",
837 | "System.Runtime.Handles": "4.3.0",
838 | "System.Runtime.InteropServices": "4.3.0",
839 | "System.Runtime.Numerics": "4.3.0",
840 | "System.Security.Cryptography.Algorithms": "4.3.0",
841 | "System.Security.Cryptography.Encoding": "4.3.0",
842 | "System.Security.Cryptography.Primitives": "4.3.0",
843 | "System.Text.Encoding": "4.3.0",
844 | "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
845 | }
846 | },
847 | "System.Security.Cryptography.Primitives": {
848 | "type": "Transitive",
849 | "resolved": "4.3.0",
850 | "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==",
851 | "dependencies": {
852 | "System.Diagnostics.Debug": "4.3.0",
853 | "System.Globalization": "4.3.0",
854 | "System.IO": "4.3.0",
855 | "System.Resources.ResourceManager": "4.3.0",
856 | "System.Runtime": "4.3.0",
857 | "System.Threading": "4.3.0",
858 | "System.Threading.Tasks": "4.3.0"
859 | }
860 | },
861 | "System.Security.Cryptography.X509Certificates": {
862 | "type": "Transitive",
863 | "resolved": "4.3.0",
864 | "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==",
865 | "dependencies": {
866 | "Microsoft.NETCore.Platforms": "1.1.0",
867 | "System.Collections": "4.3.0",
868 | "System.Diagnostics.Debug": "4.3.0",
869 | "System.Globalization": "4.3.0",
870 | "System.Globalization.Calendars": "4.3.0",
871 | "System.IO": "4.3.0",
872 | "System.IO.FileSystem": "4.3.0",
873 | "System.IO.FileSystem.Primitives": "4.3.0",
874 | "System.Resources.ResourceManager": "4.3.0",
875 | "System.Runtime": "4.3.0",
876 | "System.Runtime.Extensions": "4.3.0",
877 | "System.Runtime.Handles": "4.3.0",
878 | "System.Runtime.InteropServices": "4.3.0",
879 | "System.Runtime.Numerics": "4.3.0",
880 | "System.Security.Cryptography.Algorithms": "4.3.0",
881 | "System.Security.Cryptography.Cng": "4.3.0",
882 | "System.Security.Cryptography.Csp": "4.3.0",
883 | "System.Security.Cryptography.Encoding": "4.3.0",
884 | "System.Security.Cryptography.OpenSsl": "4.3.0",
885 | "System.Security.Cryptography.Primitives": "4.3.0",
886 | "System.Text.Encoding": "4.3.0",
887 | "System.Threading": "4.3.0",
888 | "runtime.native.System": "4.3.0",
889 | "runtime.native.System.Net.Http": "4.3.0",
890 | "runtime.native.System.Security.Cryptography.OpenSsl": "4.3.0"
891 | }
892 | },
893 | "System.Text.Encoding": {
894 | "type": "Transitive",
895 | "resolved": "4.3.0",
896 | "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==",
897 | "dependencies": {
898 | "Microsoft.NETCore.Platforms": "1.1.0",
899 | "Microsoft.NETCore.Targets": "1.1.0",
900 | "System.Runtime": "4.3.0"
901 | }
902 | },
903 | "System.Text.Encoding.Extensions": {
904 | "type": "Transitive",
905 | "resolved": "4.3.0",
906 | "contentHash": "YVMK0Bt/A43RmwizJoZ22ei2nmrhobgeiYwFzC4YAN+nue8RF6djXDMog0UCn+brerQoYVyaS+ghy9P/MUVcmw==",
907 | "dependencies": {
908 | "Microsoft.NETCore.Platforms": "1.1.0",
909 | "Microsoft.NETCore.Targets": "1.1.0",
910 | "System.Runtime": "4.3.0",
911 | "System.Text.Encoding": "4.3.0"
912 | }
913 | },
914 | "System.Text.RegularExpressions": {
915 | "type": "Transitive",
916 | "resolved": "4.3.0",
917 | "contentHash": "RpT2DA+L660cBt1FssIE9CAGpLFdFPuheB7pLpKpn6ZXNby7jDERe8Ua/Ne2xGiwLVG2JOqziiaVCGDon5sKFA==",
918 | "dependencies": {
919 | "System.Runtime": "4.3.0"
920 | }
921 | },
922 | "System.Threading": {
923 | "type": "Transitive",
924 | "resolved": "4.3.0",
925 | "contentHash": "VkUS0kOBcUf3Wwm0TSbrevDDZ6BlM+b/HRiapRFWjM5O0NS0LviG0glKmFK+hhPDd1XFeSdU1GmlLhb2CoVpIw==",
926 | "dependencies": {
927 | "System.Runtime": "4.3.0",
928 | "System.Threading.Tasks": "4.3.0"
929 | }
930 | },
931 | "System.Threading.Tasks": {
932 | "type": "Transitive",
933 | "resolved": "4.3.0",
934 | "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==",
935 | "dependencies": {
936 | "Microsoft.NETCore.Platforms": "1.1.0",
937 | "Microsoft.NETCore.Targets": "1.1.0",
938 | "System.Runtime": "4.3.0"
939 | }
940 | },
941 | "System.Threading.Tasks.Extensions": {
942 | "type": "Transitive",
943 | "resolved": "4.3.0",
944 | "contentHash": "npvJkVKl5rKXrtl1Kkm6OhOUaYGEiF9wFbppFRWSMoApKzt2PiPHT2Bb8a5sAWxprvdOAtvaARS9QYMznEUtug==",
945 | "dependencies": {
946 | "System.Collections": "4.3.0",
947 | "System.Runtime": "4.3.0",
948 | "System.Threading.Tasks": "4.3.0"
949 | }
950 | },
951 | "System.Threading.Timer": {
952 | "type": "Transitive",
953 | "resolved": "4.3.0",
954 | "contentHash": "Z6YfyYTCg7lOZjJzBjONJTFKGN9/NIYKSxhU5GRd+DTwHSZyvWp1xuI5aR+dLg+ayyC5Xv57KiY4oJ0tMO89fQ==",
955 | "dependencies": {
956 | "Microsoft.NETCore.Platforms": "1.1.0",
957 | "Microsoft.NETCore.Targets": "1.1.0",
958 | "System.Runtime": "4.3.0"
959 | }
960 | },
961 | "System.Xml.ReaderWriter": {
962 | "type": "Transitive",
963 | "resolved": "4.3.0",
964 | "contentHash": "GrprA+Z0RUXaR4N7/eW71j1rgMnEnEVlgii49GZyAjTH7uliMnrOU3HNFBr6fEDBCJCIdlVNq9hHbaDR621XBA==",
965 | "dependencies": {
966 | "System.Collections": "4.3.0",
967 | "System.Diagnostics.Debug": "4.3.0",
968 | "System.Globalization": "4.3.0",
969 | "System.IO": "4.3.0",
970 | "System.IO.FileSystem": "4.3.0",
971 | "System.IO.FileSystem.Primitives": "4.3.0",
972 | "System.Resources.ResourceManager": "4.3.0",
973 | "System.Runtime": "4.3.0",
974 | "System.Runtime.Extensions": "4.3.0",
975 | "System.Runtime.InteropServices": "4.3.0",
976 | "System.Text.Encoding": "4.3.0",
977 | "System.Text.Encoding.Extensions": "4.3.0",
978 | "System.Text.RegularExpressions": "4.3.0",
979 | "System.Threading.Tasks": "4.3.0",
980 | "System.Threading.Tasks.Extensions": "4.3.0"
981 | }
982 | },
983 | "System.Xml.XDocument": {
984 | "type": "Transitive",
985 | "resolved": "4.3.0",
986 | "contentHash": "5zJ0XDxAIg8iy+t4aMnQAu0MqVbqyvfoUVl1yDV61xdo3Vth45oA2FoY4pPkxYAH5f8ixpmTqXeEIya95x0aCQ==",
987 | "dependencies": {
988 | "System.Collections": "4.3.0",
989 | "System.Diagnostics.Debug": "4.3.0",
990 | "System.Diagnostics.Tools": "4.3.0",
991 | "System.Globalization": "4.3.0",
992 | "System.IO": "4.3.0",
993 | "System.Reflection": "4.3.0",
994 | "System.Resources.ResourceManager": "4.3.0",
995 | "System.Runtime": "4.3.0",
996 | "System.Runtime.Extensions": "4.3.0",
997 | "System.Text.Encoding": "4.3.0",
998 | "System.Threading": "4.3.0",
999 | "System.Xml.ReaderWriter": "4.3.0"
1000 | }
1001 | },
1002 | "ottergui": {
1003 | "type": "Project",
1004 | "dependencies": {
1005 | "JetBrains.Annotations": "[2024.3.0, )",
1006 | "Microsoft.Extensions.DependencyInjection": "[9.0.2, )"
1007 | }
1008 | },
1009 | "penumbra.api": {
1010 | "type": "Project"
1011 | },
1012 | "penumbra.gamedata": {
1013 | "type": "Project",
1014 | "dependencies": {
1015 | "FlatSharp.Compiler": "[7.9.0, )",
1016 | "FlatSharp.Runtime": "[7.9.0, )",
1017 | "OtterGui": "[1.0.0, )",
1018 | "Penumbra.Api": "[5.10.0, )",
1019 | "Penumbra.String": "[1.0.6, )"
1020 | }
1021 | },
1022 | "penumbra.string": {
1023 | "type": "Project"
1024 | }
1025 | }
1026 | }
1027 | }
--------------------------------------------------------------------------------