From e32a2ac525ea3911e62d30d6079363b8e95dcbd1 Mon Sep 17 00:00:00 2001 From: McSwindler Date: Thu, 2 Nov 2023 16:07:49 -0500 Subject: [PATCH] update to API 9 --- SneakOnBy/DeepDungeonDex.cs | 2 - SneakOnBy/ECommons/CardinalDirection.cs | 9 ++ SneakOnBy/ECommons/DalamudReflector.cs | 168 ++++++++++++++++++++++++ SneakOnBy/ECommons/GenericHelpers.cs | 28 ++++ SneakOnBy/ECommons/IScheduler.cs | 7 + SneakOnBy/ECommons/MathHelper.cs | 132 +++++++++++++++++++ SneakOnBy/ECommons/ReflectionHelper.cs | 64 +++++++++ SneakOnBy/ECommons/TickScheduler.cs | 40 ++++++ SneakOnBy/Plugin.cs | 22 +++- SneakOnBy/Services.cs | 22 ++++ SneakOnBy/SneakOnBy.csproj | 3 +- SneakOnBy/Windows/Canvas.cs | 15 +-- SneakOnBy/Windows/ConvexShape.cs | 3 +- SneakOnBy/packages.lock.json | 12 +- 14 files changed, 497 insertions(+), 30 deletions(-) create mode 100644 SneakOnBy/ECommons/CardinalDirection.cs create mode 100644 SneakOnBy/ECommons/DalamudReflector.cs create mode 100644 SneakOnBy/ECommons/GenericHelpers.cs create mode 100644 SneakOnBy/ECommons/IScheduler.cs create mode 100644 SneakOnBy/ECommons/MathHelper.cs create mode 100644 SneakOnBy/ECommons/ReflectionHelper.cs create mode 100644 SneakOnBy/ECommons/TickScheduler.cs create mode 100644 SneakOnBy/Services.cs diff --git a/SneakOnBy/DeepDungeonDex.cs b/SneakOnBy/DeepDungeonDex.cs index 67caee1..d79d732 100644 --- a/SneakOnBy/DeepDungeonDex.cs +++ b/SneakOnBy/DeepDungeonDex.cs @@ -1,7 +1,5 @@ using Dalamud.Logging; using Dalamud.Plugin; -using ECommons.DalamudServices; -using ECommons.ExcelServices; using ECommons.Reflection; using FFXIVClientStructs.FFXIV.Component.GUI; using System; diff --git a/SneakOnBy/ECommons/CardinalDirection.cs b/SneakOnBy/ECommons/CardinalDirection.cs new file mode 100644 index 0000000..2d52d38 --- /dev/null +++ b/SneakOnBy/ECommons/CardinalDirection.cs @@ -0,0 +1,9 @@ +namespace ECommons.MathHelpers; + +public enum CardinalDirection +{ + North, + South, + West, + East, +} diff --git a/SneakOnBy/ECommons/DalamudReflector.cs b/SneakOnBy/ECommons/DalamudReflector.cs new file mode 100644 index 0000000..e8cc22c --- /dev/null +++ b/SneakOnBy/ECommons/DalamudReflector.cs @@ -0,0 +1,168 @@ +using Dalamud; +using Dalamud.Game.ClientState.Keys; +using Dalamud.Plugin; +using ECommons.Schedulers; +using SneakOnBy; +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace ECommons.Reflection; + +public static class DalamudReflector +{ + delegate ref int GetRefValue(int vkCode); + static GetRefValue getRefValue; + static Dictionary pluginCache; + static List onPluginsChangedActions; + + internal static void Init() + { + onPluginsChangedActions = new(); + pluginCache = new(); + GenericHelpers.Safe(delegate + { + getRefValue = (GetRefValue)Delegate.CreateDelegate(typeof(GetRefValue), Services.KeyState, + Services.KeyState.GetType().GetMethod("GetRefValue", + BindingFlags.NonPublic | BindingFlags.Instance, + null, new Type[] { typeof(int) }, null)); + }, (e) => { + Services.PluginLog.Error(e); + }); + Services.PluginInterface.ActivePluginsChanged += OnInstalledPluginsChanged; + } + + internal static void Dispose() + { + if (pluginCache != null) + { + pluginCache = null; + onPluginsChangedActions = null; + } + Services.PluginInterface.ActivePluginsChanged -= OnInstalledPluginsChanged; + } + + public static void RegisterOnInstalledPluginsChangedEvents(params Action[] actions) + { + foreach (var x in actions) + { + onPluginsChangedActions.Add(x); + } + } + + public static void SetKeyState(VirtualKey key, int state) + { + getRefValue((int)key) = state; + } + + public static object GetPluginManager() + { + return Services.PluginInterface.GetType().Assembly. + GetType("Dalamud.Service`1", true).MakeGenericType(Services.PluginInterface.GetType().Assembly.GetType("Dalamud.Plugin.Internal.PluginManager", true)). + GetMethod("Get").Invoke(null, BindingFlags.Default, null, Array.Empty(), null); + } + + public static object GetService(string serviceFullName) + { + return Services.PluginInterface.GetType().Assembly. + GetType("Dalamud.Service`1", true).MakeGenericType(Services.PluginInterface.GetType().Assembly.GetType(serviceFullName, true)). + GetMethod("Get").Invoke(null, BindingFlags.Default, null, Array.Empty(), null); + } + + public static bool TryGetLocalPlugin(out object localPlugin, out Type type) + { + try + { + var pluginManager = GetPluginManager(); + var installedPlugins = (System.Collections.IList)pluginManager.GetType().GetProperty("InstalledPlugins").GetValue(pluginManager); + + foreach (var t in installedPlugins) + { + if (t != null) + { + type = t.GetType().Name == "LocalDevPlugin" ? t.GetType().BaseType : t.GetType(); + if (object.ReferenceEquals(type.GetField("instance", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(t), Plugin.Instance)) + { + localPlugin = t; + return true; + } + } + } + localPlugin = type = null; + return false; + } + catch (Exception e) + { + Services.PluginLog.Error(e.StackTrace); + localPlugin = type = null; + return false; + } + } + + public static bool TryGetDalamudPlugin(string internalName, out IDalamudPlugin instance, bool suppressErrors = false, bool ignoreCache = false) + { + if (pluginCache == null) + { + throw new Exception("PluginCache is null. Have you initialised the DalamudReflector module on ECommons initialisation?"); + } + + if (!ignoreCache && pluginCache.TryGetValue(internalName, out instance) && instance != null) + { + return true; + } + try + { + var pluginManager = GetPluginManager(); + var installedPlugins = (System.Collections.IList)pluginManager.GetType().GetProperty("InstalledPlugins").GetValue(pluginManager); + + foreach (var t in installedPlugins) + { + if ((string)t.GetType().GetProperty("Name").GetValue(t) == internalName) + { + var type = t.GetType().Name == "LocalDevPlugin" ? t.GetType().BaseType : t.GetType(); + var plugin = (IDalamudPlugin)type.GetField("instance", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(t); + if (plugin == null) + { + Services.PluginLog.Warning($"Found requested plugin {internalName} but it was null"); + } + else + { + instance = plugin; + pluginCache[internalName] = plugin; + return true; + } + } + } + instance = null; + return false; + } + catch (Exception e) + { + if (!suppressErrors) + { + Services.PluginLog.Error($"Can't find {internalName} plugin: " + e.Message); + Services.PluginLog.Error(e.StackTrace); + } + instance = null; + return false; + } + } + + public static string GetPluginName() + { + return Services.PluginInterface?.InternalName ?? "Not initialized"; + } + + internal static void OnInstalledPluginsChanged(PluginListInvalidationKind kind, bool affectedThisPlugin) + { + Services.PluginLog.Verbose("Installed plugins changed event fired"); + _ = new TickScheduler(delegate + { + pluginCache.Clear(); + foreach (var x in onPluginsChangedActions) + { + x(); + } + }); + } +} diff --git a/SneakOnBy/ECommons/GenericHelpers.cs b/SneakOnBy/ECommons/GenericHelpers.cs new file mode 100644 index 0000000..f182da5 --- /dev/null +++ b/SneakOnBy/ECommons/GenericHelpers.cs @@ -0,0 +1,28 @@ +using System; + +namespace ECommons; + +public static unsafe class GenericHelpers +{ + public static void Safe(System.Action a, Action fail, bool suppressErrors = false) + { + try + { + a(); + } + catch (Exception e) + { + try + { + fail(e.Message); + } + catch(Exception ex) + { + Services.PluginLog.Error("Error while trying to process error handler:"); + Services.PluginLog.Error($"{ex.Message}\n{ex.StackTrace ?? ""}"); + suppressErrors = false; + } + if (!suppressErrors) Services.PluginLog.Error($"{e.Message}\n{e.StackTrace ?? ""}"); + } + } +} diff --git a/SneakOnBy/ECommons/IScheduler.cs b/SneakOnBy/ECommons/IScheduler.cs new file mode 100644 index 0000000..c0c8493 --- /dev/null +++ b/SneakOnBy/ECommons/IScheduler.cs @@ -0,0 +1,7 @@ +using System; + +namespace ECommons.Schedulers; + +public interface IScheduler : IDisposable +{ +} diff --git a/SneakOnBy/ECommons/MathHelper.cs b/SneakOnBy/ECommons/MathHelper.cs new file mode 100644 index 0000000..7d66c48 --- /dev/null +++ b/SneakOnBy/ECommons/MathHelper.cs @@ -0,0 +1,132 @@ +using System; +using System.Numerics; + +namespace ECommons.MathHelpers; + +public static class MathHelper +{ + public static Vector3 RotateWorldPoint(Vector3 origin, float angle, Vector3 p) + { + if (angle == 0f) return p; + var s = (float)Math.Sin(angle); + var c = (float)Math.Cos(angle); + + // translate point back to origin: + p.X -= origin.X; + p.Z -= origin.Z; + + // rotate point + float xnew = p.X * c - p.Z * s; + float ynew = p.X * s + p.Z * c; + + // translate point back: + p.X = xnew + origin.X; + p.Z = ynew + origin.Z; + return p; + } + + public static float Float(this int i) + { + return (float)i; + } + + public static Vector2 ToVector2(this Vector3 vector3) + { + return new Vector2(vector3.X, vector3.Z); + } + + public static Vector3 ToVector3(this Vector2 vector2) + { + return vector2.ToVector3(Services.ClientState.LocalPlayer?.Position.Y ?? 0); + } + + public static Vector3 ToVector3(this Vector2 vector2, float Y) + { + return new Vector3(vector2.X, Y, vector2.Y); + } + + public static float GetRelativeAngle(Vector3 origin, Vector3 target) + { + return GetRelativeAngle(origin.ToVector2(), target.ToVector2()); + } + + public static float GetRelativeAngle(Vector2 origin, Vector2 target) + { + var vector2 = target - origin; + var vector1 = new Vector2(0, 1); + return ((MathF.Atan2(vector2.Y, vector2.X) - MathF.Atan2(vector1.Y, vector1.X)) * (180 / MathF.PI) + 360 + 180) % 360; + } + + public static float RadToDeg(this float f) + { + return (f * (180 / MathF.PI) + 360) % 360; + } + + public static CardinalDirection GetCardinalDirection(Vector3 origin, Vector3 target) + { + return GetCardinalDirection(GetRelativeAngle(origin, target)); + } + + public static CardinalDirection GetCardinalDirection(Vector2 origin, Vector2 target) + { + return GetCardinalDirection(GetRelativeAngle(origin, target)); + } + + public static CardinalDirection GetCardinalDirection(float angle) + { + if (angle.InRange(45, 135)) return CardinalDirection.East; + if (angle.InRange(135, 225)) return CardinalDirection.South; + if (angle.InRange(225, 315)) return CardinalDirection.West; + return CardinalDirection.North; + } + + public static bool InRange(this double f, double inclusiveStart, double exclusiveEnd) + { + return f >= inclusiveStart && f < exclusiveEnd; + } + + public static bool InRange(this float f, float inclusiveStart, float exclusiveEnd) + { + return f >= inclusiveStart && f < exclusiveEnd; + } + + public static bool InRange(this long f, long inclusiveStart, long exclusiveEnd) + { + return f >= inclusiveStart && f < exclusiveEnd; + } + + public static bool InRange(this int f, int inclusiveStart, int exclusiveEnd) + { + return f >= inclusiveStart && f < exclusiveEnd; + } + + public static bool InRange(this short f, short inclusiveStart, short exclusiveEnd) + { + return f >= inclusiveStart && f < exclusiveEnd; + } + + public static bool InRange(this byte f, byte inclusiveStart, byte exclusiveEnd) + { + return f >= inclusiveStart && f < exclusiveEnd; + } + + public static bool InRange(this ulong f, ulong inclusiveStart, ulong exclusiveEnd) + { + return f >= inclusiveStart && f < exclusiveEnd; + } + + public static bool InRange(this uint f, uint inclusiveStart, uint exclusiveEnd) + { + return f >= inclusiveStart && f < exclusiveEnd; + } + + public static bool InRange(this ushort f, ushort inclusiveStart, ushort exclusiveEnd) + { + return f >= inclusiveStart && f < exclusiveEnd; + } + + public static bool InRange(this sbyte f, sbyte inclusiveStart, sbyte exclusiveEnd) + { + return f >= inclusiveStart && f < exclusiveEnd; + } +} diff --git a/SneakOnBy/ECommons/ReflectionHelper.cs b/SneakOnBy/ECommons/ReflectionHelper.cs new file mode 100644 index 0000000..8bcfe8f --- /dev/null +++ b/SneakOnBy/ECommons/ReflectionHelper.cs @@ -0,0 +1,64 @@ +using System.Linq; +using System.Reflection; + +namespace ECommons.Reflection; + +public static class ReflectionHelper +{ + public const BindingFlags AllFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; + public const BindingFlags StaticFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; + + public static object GetFoP(this object obj, string name) + { + return obj.GetType().GetField(name, AllFlags)?.GetValue(obj) + ?? obj.GetType().GetProperty(name, AllFlags)?.GetValue(obj); + } + + public static T GetFoP(this object obj, string name) + { + return (T)GetFoP(obj, name); + } + + public static void SetFoP(this object obj, string name, object value) + { + var field = obj.GetType().GetField(name, AllFlags); + if(field != null) + { + field.SetValue(obj, value); + } + else + { + obj.GetType().GetProperty(name, AllFlags).SetValue(obj, value); + } + } + + public static object GetStaticFoP(this object obj, string type, string name) + { + return obj.GetType().Assembly.GetType(type).GetField(name, StaticFlags)?.GetValue(null) + ?? obj.GetType().Assembly.GetType(type).GetProperty(name, StaticFlags)?.GetValue(null); + } + + public static T GetStaticFoP(this object obj, string type, string name) + { + return (T)GetStaticFoP(obj, type, name); + } + + public static void SetStaticFoP(this object obj, string type, string name, object value) + { + var field = obj.GetType().Assembly.GetType(type).GetField(name, StaticFlags); + if (field != null) + { + field.SetValue(null, value); + } + else + { + obj.GetType().Assembly.GetType(type).GetProperty(name, StaticFlags).SetValue(null, value); + } + } + + public static object Call(this object obj, string name, params object[] values) + { + var info = obj.GetType().GetMethod(name, AllFlags, values.Select(x => x.GetType()).ToArray()); + return info.Invoke(obj, values); + } +} diff --git a/SneakOnBy/ECommons/TickScheduler.cs b/SneakOnBy/ECommons/TickScheduler.cs new file mode 100644 index 0000000..4387908 --- /dev/null +++ b/SneakOnBy/ECommons/TickScheduler.cs @@ -0,0 +1,40 @@ +using System; + +namespace ECommons.Schedulers; + +public class TickScheduler : IScheduler +{ + long executeAt; + Action function; + bool disposed = false; + + public TickScheduler(Action function, long delayMS = 0) + { + executeAt = Environment.TickCount64 + delayMS; + this.function = function; + Services.Framework.Update += Execute; + } + + public void Dispose() + { + if (!disposed) + { + Services.Framework.Update -= Execute; + } + disposed = true; + } + + void Execute(object _) + { + if (Environment.TickCount64 < executeAt) return; + try + { + function(); + } + catch (Exception e) + { + Services.PluginLog.Error(e.Message + "\n" + e.StackTrace ?? ""); + } + Dispose(); + } +} diff --git a/SneakOnBy/Plugin.cs b/SneakOnBy/Plugin.cs index ae609b3..9c3164e 100644 --- a/SneakOnBy/Plugin.cs +++ b/SneakOnBy/Plugin.cs @@ -4,6 +4,8 @@ using Dalamud.Interface.Windowing; using SneakOnBy.Windows; using ECommons; +using Dalamud.Plugin.Services; +using ECommons.Reflection; namespace SneakOnBy { @@ -12,8 +14,10 @@ public sealed class Plugin : IDalamudPlugin public string Name => "Sneak On By"; private const string CommandName = "/sneaky"; + public static IDalamudPlugin Instance; + private DalamudPluginInterface PluginInterface { get; init; } - private CommandManager CommandManager { get; init; } + private ICommandManager CommandManager { get; init; } public Configuration Configuration { get; init; } public WindowSystem WindowSystem = new("SneakOnBy"); @@ -22,12 +26,15 @@ public sealed class Plugin : IDalamudPlugin public Plugin( [RequiredVersion("1.0")] DalamudPluginInterface pluginInterface, - [RequiredVersion("1.0")] CommandManager commandManager) + [RequiredVersion("1.0")] ICommandManager commandManager) { + + Instance = this; this.PluginInterface = pluginInterface; this.CommandManager = commandManager; - ECommonsMain.Init(pluginInterface, this, Module.DalamudReflector, Module.ObjectFunctions); + Services.Initialize(pluginInterface); + //ECommonsMain.Init(pluginInterface, this, Module.DalamudReflector, Module.ObjectFunctions); this.Configuration = this.PluginInterface.GetPluginConfig() as Configuration ?? new Configuration(); this.Configuration.Initialize(this.PluginInterface); @@ -47,20 +54,25 @@ public sealed class Plugin : IDalamudPlugin this.PluginInterface.UiBuilder.Draw += DrawUI; this.PluginInterface.UiBuilder.OpenConfigUi += DrawConfigUI; - + + DalamudReflector.Init(); + + } public void Dispose() { this.WindowSystem.RemoveAllWindows(); - ECommonsMain.Dispose(); + //ECommonsMain.Dispose(); ConfigWindow.Dispose(); Canvas.Dispose(); this.CommandManager.RemoveHandler(CommandName); + + DalamudReflector.Dispose(); } private void OnCommand(string command, string args) diff --git a/SneakOnBy/Services.cs b/SneakOnBy/Services.cs new file mode 100644 index 0000000..eb5fff6 --- /dev/null +++ b/SneakOnBy/Services.cs @@ -0,0 +1,22 @@ +using Dalamud.IoC; +using Dalamud.Plugin.Services; +using Dalamud.Plugin; + +public class Services +{ + public static void Initialize(DalamudPluginInterface pluginInterface) + => pluginInterface.Create(); + + // @formatter:off + [PluginService][RequiredVersion("1.0")] public static IClientState ClientState { get; private set; } = null!; + [PluginService][RequiredVersion("1.0")] public static IGameInteropProvider GameInteropProvider { get; private set; } = null!; + [PluginService][RequiredVersion("1.0")] public static IPluginLog PluginLog { get; private set; } = null!; + [PluginService][RequiredVersion("1.0")] public static DalamudPluginInterface PluginInterface { get; private set; } = null!; + [PluginService][RequiredVersion("1.0")] public static IFramework Framework { get; private set; } = null!; + [PluginService][RequiredVersion("1.0")] public static ICondition Condition { get; private set; } = null!; + [PluginService][RequiredVersion("1.0")] public static IObjectTable Objects { get; private set; } = null!; + [PluginService][RequiredVersion("1.0")] public static IGameGui GameGui { get; private set; } = null!; + [PluginService][RequiredVersion("1.0")] public static IKeyState KeyState { get; private set; } = null!; + // @formatter:on + +} diff --git a/SneakOnBy/SneakOnBy.csproj b/SneakOnBy/SneakOnBy.csproj index b3d7437..3749a4e 100644 --- a/SneakOnBy/SneakOnBy.csproj +++ b/SneakOnBy/SneakOnBy.csproj @@ -30,8 +30,7 @@ - - + diff --git a/SneakOnBy/Windows/Canvas.cs b/SneakOnBy/Windows/Canvas.cs index 10d11ad..f112cdb 100644 --- a/SneakOnBy/Windows/Canvas.cs +++ b/SneakOnBy/Windows/Canvas.cs @@ -3,15 +3,10 @@ using Dalamud.Interface.Windowing; using ImGuiNET; using Dalamud.Game.ClientState.Objects.Types; -using ECommons.GameFunctions; -using ECommons.DalamudServices; -using Dalamud.Interface; using ECommons.MathHelpers; using Dalamud.Game.ClientState.Objects.Enums; -using Dalamud.Interface.Colors; -using Dalamud.Utility.Numerics; using Dalamud.Game.ClientState.Conditions; -using Lumina.Excel.GeneratedSheets; +using Dalamud.Interface.Utility; namespace SneakOnBy.Windows; @@ -45,16 +40,16 @@ public override void PreDraw() public override bool DrawConditions() { - return Svc.ClientState.LocalPlayer != null && Svc.Condition[ConditionFlag.InDeepDungeon]; + return Services.ClientState.LocalPlayer != null && Services.Condition[ConditionFlag.InDeepDungeon]; } public void Dispose() { } public override void Draw() { - foreach (GameObject obj in Svc.Objects) + foreach (GameObject obj in Services.Objects) { - if (obj is BattleNpc bnpc && bnpc.IsHostile() && !bnpc.StatusFlags.HasFlag(StatusFlags.InCombat) && bnpc.IsCharacterVisible()) { + if (obj is BattleNpc bnpc && bnpc.StatusFlags.HasFlag(StatusFlags.Hostile) && !bnpc.StatusFlags.HasFlag(StatusFlags.InCombat)) { Aggro aggro = DeepDungeonDex.GetMobAggroType(bnpc.NameId); switch (aggro) { @@ -117,7 +112,7 @@ internal static (int min, int max) Get18PieForAngle(float a) internal static float GetAngle(GameObject bnpc) { - return (MathHelper.GetRelativeAngle(Svc.ClientState.LocalPlayer.Position, bnpc.Position) + bnpc.Rotation.RadToDeg()) % 360; + return (MathHelper.GetRelativeAngle(Services.ClientState.LocalPlayer.Position, bnpc.Position) + bnpc.Rotation.RadToDeg()) % 360; } internal static void ActorConeXZ(GameObject actor, float radius, float startRads, float endRads, Vector4 color, bool lines = true) diff --git a/SneakOnBy/Windows/ConvexShape.cs b/SneakOnBy/Windows/ConvexShape.cs index dabcd68..91ba060 100644 --- a/SneakOnBy/Windows/ConvexShape.cs +++ b/SneakOnBy/Windows/ConvexShape.cs @@ -1,4 +1,3 @@ -using ECommons.DalamudServices; using System.Numerics; using ImGuiNET; using System; @@ -26,7 +25,7 @@ internal void Point(Vector3 worldPos) // TODO: implement proper clipping. everything goes crazy when // drawing lines outside the clip window and behind the camera // point - var visible = Svc.GameGui.WorldToScreen(worldPos, out Vector2 pos); + var visible = Services.GameGui.WorldToScreen(worldPos, out Vector2 pos); DrawList.PathLineTo(pos); if (visible) { cullObject = false; } } diff --git a/SneakOnBy/packages.lock.json b/SneakOnBy/packages.lock.json index fd63291..6cf1c73 100644 --- a/SneakOnBy/packages.lock.json +++ b/SneakOnBy/packages.lock.json @@ -4,15 +4,9 @@ "net7.0-windows7.0": { "DalamudPackager": { "type": "Direct", - "requested": "[2.1.11, )", - "resolved": "2.1.11", - "contentHash": "9qlAWoRRTiL/geAvuwR/g6Bcbrd/bJJgVnB/RurBiyKs6srsP0bvpoo8IK+Eg8EA6jWeM6/YJWs66w4FIAzqPw==" - }, - "ECommons": { - "type": "Direct", - "requested": "[2.0.0.4, )", - "resolved": "2.0.0.4", - "contentHash": "joGkq0Y+K9da2TD/8/felotoTuqaUPjyKFjr2aeod77e28YMEmFrwVH+r91cU9X8hs50LEEp4QTzUnIrMJgcNw==" + "requested": "[2.1.12, )", + "resolved": "2.1.12", + "contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg==" } } }