diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 0000000..5573da1
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,12 @@
+{
+ "$schema": "https://json.schemastore.org/dotnet-tools.json",
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "csharpier": {
+ "version": "1.2.6",
+ "commands": ["csharpier"],
+ "rollForward": false
+ }
+ }
+}
diff --git a/Anvil/Anvil.csproj b/Anvil/Anvil.csproj
index 7b8fb3a..52208a0 100644
--- a/Anvil/Anvil.csproj
+++ b/Anvil/Anvil.csproj
@@ -24,18 +24,9 @@
Include="Microsoft.Extensions.DependencyInjection"
Version="[10.0.7, 11.0.0)"
/>
-
-
-
+
+
+
diff --git a/Anvil/RecipeData/AnvilAction.cs b/Anvil/RecipeData/AnvilAction.cs
index 99a8c74..5b0d45c 100644
--- a/Anvil/RecipeData/AnvilAction.cs
+++ b/Anvil/RecipeData/AnvilAction.cs
@@ -103,6 +103,7 @@ public enum AnvilActionFlags : ushort
None = 0,
RequiresGoodOrExcellent = 1 << 0,
RequiresFirstStep = 1 << 1,
+
// 1 << 2 reserved (see comment above)
SpecialistOnly = 1 << 3,
NoBuffTick = 1 << 4,
diff --git a/Anvil/RecipeData/Mechanics/ActionMechanicsTable.cs b/Anvil/RecipeData/Mechanics/ActionMechanicsTable.cs
index b75d862..93efb7d 100644
--- a/Anvil/RecipeData/Mechanics/ActionMechanicsTable.cs
+++ b/Anvil/RecipeData/Mechanics/ActionMechanicsTable.cs
@@ -265,7 +265,8 @@ internal static class ActionMechanicsTable
IqStackBonus = 0,
ChargesPerCraft = 1,
GrantsBuff = null,
- Flags = AnvilActionFlags.RequiresFirstStep | AnvilActionFlags.RequiresLowLevelRecipe,
+ Flags =
+ AnvilActionFlags.RequiresFirstStep | AnvilActionFlags.RequiresLowLevelRecipe,
},
[AnvilActionKind.TrainedFinesse] = new ActionMechanicsEntry
{
diff --git a/Anvil/RecipeData/Mechanics/ConditionMechanicsTable.cs b/Anvil/RecipeData/Mechanics/ConditionMechanicsTable.cs
index c466b10..ace0e77 100644
--- a/Anvil/RecipeData/Mechanics/ConditionMechanicsTable.cs
+++ b/Anvil/RecipeData/Mechanics/ConditionMechanicsTable.cs
@@ -20,7 +20,10 @@ namespace Anvil.RecipeData.Mechanics;
internal static class ConditionMechanicsTable
{
- public static IReadOnlyDictionary Entries { get; } =
+ public static IReadOnlyDictionary<
+ AnvilConditionKind,
+ ConditionMechanicsEntry
+ > Entries { get; } =
new Dictionary
{
[AnvilConditionKind.Normal] = new ConditionMechanicsEntry
diff --git a/Anvil/RecipeData/RecipeDataCatalog.cs b/Anvil/RecipeData/RecipeDataCatalog.cs
new file mode 100644
index 0000000..6994da5
--- /dev/null
+++ b/Anvil/RecipeData/RecipeDataCatalog.cs
@@ -0,0 +1,160 @@
+// Public read-only catalog of all Anvil RecipeData. Registered as a DI
+// singleton; consumers (simulator, solver, macro engine, IPC, UI) inject
+// the instance and pull whichever dictionary they need.
+//
+// IsLoaded gates everything: the catalog starts empty (no NREs, just empty
+// collections) and the LuminaRecipeAdapter swaps in a fully populated state
+// snapshot once the sheet walk finishes. UI windows render a spinner until
+// IsLoaded flips to true (200-800 ms after plugin start). The swap goes
+// through a volatile reference so cross-thread readers see the new state
+// atomically without an explicit lock.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+
+namespace Anvil.RecipeData;
+
+public sealed class RecipeDataCatalog
+{
+ private volatile CatalogState _state = CatalogState.Empty;
+
+ // Recipe lookups
+ public IReadOnlyDictionary RecipesById => _state.RecipesById;
+ public IReadOnlyDictionary> RecipesByOutputItemId =>
+ _state.RecipesByOutputItemId;
+ public IReadOnlyDictionary> RecipesByClassJobId =>
+ _state.RecipesByClassJobId;
+
+ // Item lookups
+ public IReadOnlyDictionary ItemsById => _state.ItemsById;
+
+ // Action lookups - two complementary paths.
+ // ActionsByKind is the simulator's path ("Basic Synthesis" is one entity).
+ // ActionsByLuminaRowId is the hook's path (UseAction / IsActionHighlighted
+ // see the per-job RowId from the game). Cosmic actions appear once in
+ // ActionsByLuminaRowId (their single sentinel RowId from the Action sheet);
+ // non-Cosmic actions appear eight times (one per crafter job), each
+ // pointing at the same AnvilAction record.
+ public IReadOnlyDictionary ActionsByKind => _state.ActionsByKind;
+ public IReadOnlyDictionary ActionsByLuminaRowId =>
+ _state.ActionsByLuminaRowId;
+
+ // Buff / Condition catalogs
+ public IReadOnlyDictionary BuffsByKind => _state.BuffsByKind;
+ public IReadOnlyDictionary ConditionsByKind =>
+ _state.ConditionsByKind;
+
+ // Food / Medicine catalogs (split into two lists because the UI treats
+ // them as two separate equip slots).
+ public IReadOnlyList Foods => _state.Foods;
+ public IReadOnlyList Medicines => _state.Medicines;
+
+ // Meta
+ public bool IsLoaded => _state.IsLoaded;
+ public DateTime? LoadedAt => _state.LoadedAt;
+ public int RecipeCount => _state.RecipesById.Count;
+ public int ActionCount => _state.ActionsByKind.Count;
+
+ // Returns every action a given crafter job has unlocked at the given
+ // level. Cosmic-only actions (sentinel-keyed under ClassJobId 0) are
+ // excluded - this helper has no recipe argument and therefore no way
+ // to gate the Cosmic surface correctly. Consumers that need Cosmic
+ // actions must combine AnvilRecipe.IsCosmic with
+ // AnvilActionFlags.CosmicOnly themselves.
+ public IReadOnlyList ActionsForClassJob(uint classJobId, byte characterLevel)
+ {
+ if (classJobId == 0)
+ return Array.Empty();
+
+ var result = new List();
+ foreach (var action in _state.ActionsByKind.Values)
+ {
+ if (!action.RowIdByClassJob.ContainsKey(classJobId))
+ continue;
+ if (action.LevelRequired > characterLevel)
+ continue;
+ result.Add(action);
+ }
+ return result;
+ }
+
+ // Resolves the in-game RowId an UseAction / IsActionHighlighted hook
+ // needs for a given action + job pairing. Cosmic actions resolve via
+ // the sentinel RowIdByClassJob[0] (one RowId covers every crafter job);
+ // every other action resolves via RowIdByClassJob[classJobId]. Returns
+ // false when the action is not available to the job (e.g. a Cosmic
+ // action queried on a non-Cosmic recipe, or a per-job action queried
+ // for a job that is missing the per-job row).
+ public bool TryResolveActionRowId(AnvilAction action, uint classJobId, out uint rowId)
+ {
+ if (action.Flags.HasFlag(AnvilActionFlags.CosmicOnly))
+ {
+ if (action.RowIdByClassJob.TryGetValue(0u, out rowId))
+ return true;
+ rowId = 0;
+ return false;
+ }
+
+ if (action.RowIdByClassJob.TryGetValue(classJobId, out rowId))
+ return true;
+ rowId = 0;
+ return false;
+ }
+
+ // Adapter-only entry point. Internal so the LuminaRecipeAdapter (same
+ // assembly) and the Anvil.Tests build-suite project (via
+ // InternalsVisibleTo) can populate the catalog. Single atomic write to
+ // a volatile reference - readers either see the old empty state or the
+ // fully built new state, never a partial one.
+ internal void ApplyLoad(CatalogState newState)
+ {
+ _state = newState;
+ }
+}
+
+// Immutable snapshot of every catalog bucket. The adapter builds one of
+// these and hands it to ApplyLoad. Records' value-semantics plus
+// ImmutableDictionary defaults keep the public surface allocation-free
+// before the first load.
+internal sealed record CatalogState
+{
+ public required bool IsLoaded { get; init; }
+ public required DateTime? LoadedAt { get; init; }
+ public required IReadOnlyDictionary RecipesById { get; init; }
+ public required IReadOnlyDictionary<
+ uint,
+ IReadOnlyList
+ > RecipesByOutputItemId { get; init; }
+ public required IReadOnlyDictionary<
+ uint,
+ IReadOnlyList
+ > RecipesByClassJobId { get; init; }
+ public required IReadOnlyDictionary ItemsById { get; init; }
+ public required IReadOnlyDictionary ActionsByKind { get; init; }
+ public required IReadOnlyDictionary ActionsByLuminaRowId { get; init; }
+ public required IReadOnlyDictionary BuffsByKind { get; init; }
+ public required IReadOnlyDictionary<
+ AnvilConditionKind,
+ AnvilCondition
+ > ConditionsByKind { get; init; }
+ public required IReadOnlyList Foods { get; init; }
+ public required IReadOnlyList Medicines { get; init; }
+
+ public static CatalogState Empty { get; } =
+ new()
+ {
+ IsLoaded = false,
+ LoadedAt = null,
+ RecipesById = ImmutableDictionary.Empty,
+ RecipesByOutputItemId = ImmutableDictionary>.Empty,
+ RecipesByClassJobId = ImmutableDictionary>.Empty,
+ ItemsById = ImmutableDictionary.Empty,
+ ActionsByKind = ImmutableDictionary.Empty,
+ ActionsByLuminaRowId = ImmutableDictionary.Empty,
+ BuffsByKind = ImmutableDictionary.Empty,
+ ConditionsByKind = ImmutableDictionary.Empty,
+ Foods = Array.Empty(),
+ Medicines = Array.Empty(),
+ };
+}
diff --git a/Anvil/RecipeData/RecipeDataLoadResult.cs b/Anvil/RecipeData/RecipeDataLoadResult.cs
new file mode 100644
index 0000000..1aa38ec
--- /dev/null
+++ b/Anvil/RecipeData/RecipeDataLoadResult.cs
@@ -0,0 +1,20 @@
+// Result of one LuminaRecipeAdapter load pass. Surfaced to the SelfTest step
+// for /xlperf reporting and (later) to any UI status surface that wants to
+// show "X recipes / Y actions" after init. Warnings carries non-fatal
+// problems the adapter saw while walking the sheets (unknown action names,
+// inconsistent per-job rows, etc.) - the load still succeeds and the
+// catalog stays usable.
+
+using System;
+using System.Collections.Generic;
+
+namespace Anvil.RecipeData;
+
+public sealed record RecipeDataLoadResult(
+ int RecipesLoaded,
+ int ItemsLoaded,
+ int ActionsLoaded,
+ int FoodsLoaded,
+ TimeSpan Duration,
+ IReadOnlyList Warnings
+);
diff --git a/Anvil/packages.lock.json b/Anvil/packages.lock.json
new file mode 100644
index 0000000..8ced478
--- /dev/null
+++ b/Anvil/packages.lock.json
@@ -0,0 +1,307 @@
+{
+ "version": 1,
+ "dependencies": {
+ "net10.0-windows7.0": {
+ "DalamudPackager": {
+ "type": "Direct",
+ "requested": "[15.0.0, )",
+ "resolved": "15.0.0",
+ "contentHash": "411vwC8/X8Z/sQ2TI6v3SvOn66xFPeOjFn3Zn+h0d3Ox2t1kFm66AhDvmx/qcMwVrR+Hidxj0dadpQ2dgyXMBQ=="
+ },
+ "DotNet.ReproducibleBuilds": {
+ "type": "Direct",
+ "requested": "[1.2.39, )",
+ "resolved": "1.2.39",
+ "contentHash": "fcFN01tDTIQqDuTwr1jUQK/geofiwjG5DycJQOnC72i1SsLAk1ELe+apBOuZ11UMQG8YKFZG1FgvjZPbqHyatg=="
+ },
+ "Microsoft.Extensions.DependencyInjection": {
+ "type": "Direct",
+ "requested": "[10.0.7, 11.0.0)",
+ "resolved": "10.0.7",
+ "contentHash": "91F/o3emPV/+xY/ip3s2LqDNF14kjttlVtq0BXgg6p4MnCzeSZxnUJm+t6WRrtD3JdGo88/oX+z7OwK4y8PZuw==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Hosting": {
+ "type": "Direct",
+ "requested": "[10.0.7, 11.0.0)",
+ "resolved": "10.0.7",
+ "contentHash": "M/vBpfWcschvS2EUeq7cHfscsxabiGTptXwV7GeSueovGiSoNjyo1j5PMcWuOAAQrRW3nRqxZk8NeumrmpzUBg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.7",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.7",
+ "Microsoft.Extensions.Configuration.CommandLine": "10.0.7",
+ "Microsoft.Extensions.Configuration.EnvironmentVariables": "10.0.7",
+ "Microsoft.Extensions.Configuration.FileExtensions": "10.0.7",
+ "Microsoft.Extensions.Configuration.Json": "10.0.7",
+ "Microsoft.Extensions.Configuration.UserSecrets": "10.0.7",
+ "Microsoft.Extensions.DependencyInjection": "10.0.7",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Diagnostics": "10.0.7",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.7",
+ "Microsoft.Extensions.FileProviders.Physical": "10.0.7",
+ "Microsoft.Extensions.Hosting.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Logging": "10.0.7",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Logging.Configuration": "10.0.7",
+ "Microsoft.Extensions.Logging.Console": "10.0.7",
+ "Microsoft.Extensions.Logging.Debug": "10.0.7",
+ "Microsoft.Extensions.Logging.EventLog": "10.0.7",
+ "Microsoft.Extensions.Logging.EventSource": "10.0.7",
+ "Microsoft.Extensions.Options": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Logging": {
+ "type": "Direct",
+ "requested": "[10.0.7, 11.0.0)",
+ "resolved": "10.0.7",
+ "contentHash": "hOeRIQ63GkgiYCB/MIFp+LQs8aXpJXpB55t6Aj37ab7t2/6WeFcPXxYM9hdy/o5tffzwf8mhqzLJP6mjGYCxjw==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection": "10.0.7",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Options": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Options": {
+ "type": "Direct",
+ "requested": "[10.0.7, 11.0.0)",
+ "resolved": "10.0.7",
+ "contentHash": "00SHUGTh2jSMvIr6x9Xwd2nE+B5/qFCO/9hDwUDhJsjYRDlADmaBZ7tqehXzBDsfjHSXJzuRHJzPYPPjphBQ7Q==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Primitives": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Configuration": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "wZbGh7J8R1vXN525O6d8dlcDTxhRTnd5MyW4LdfP5S0tSnTwTCseYSrq6g0Mxh7W9xn8P/2xPuf0D/m6k2dy2w==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Primitives": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "t56nEgvECcyLPojZIUFWJknQQDAbgfTf9J+QMYJE1YYvVgz69vN6B/AKL8Grvj3Lcnp8kTpNqwmwFhb3YLJmtQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Binder": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "8bS1qIaRivny+WX+49pmeJ6iAylbtX8C0DLEcCQWZjdxQvLqaMssXiGD9P/6pYElrHbK5/nAHmjbQ8STqdMYeg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.7",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Configuration.CommandLine": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "3lNjglxfFxOzI9zG+3HSg/YSGqo//8Fqw6u6iuIamZb4JCorbA3JLaeWOpfKTAPi2UJwaispOXWx14dUqcGz4A==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.7",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Configuration.EnvironmentVariables": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "TWto3imA+mJMLZI+5sbgLiFFoOFNFkizQYNaC5jTuiHKn3diwm1RN7mWDOEZN9kG2bixw7IvgpvtUG5/teSRzA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.7",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Configuration.FileExtensions": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "qbZLvLsoTdArSloEnSxs21P781YUmwVmHc5NJPQD/ezAreQ7884z+6QfAZVKi86WAZtzx83jK2uC4itxOM44gQ==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.7",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.7",
+ "Microsoft.Extensions.FileProviders.Physical": "10.0.7",
+ "Microsoft.Extensions.Primitives": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Configuration.Json": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "64dimvyyKk0dbUbrLg/YCv4ugJ4sVz2aXLwfvZwR1EC4tJqW9ru/oVRcXwoJRa2lQGXtYtlpk4maWOeIb48tQw==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.7",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Configuration.FileExtensions": "10.0.7",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Configuration.UserSecrets": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "YqVIICoIdl0016wkeO2WQS+uEbEXbUhMLKdC5rZNl1X3nu59F+nwaAHdHjq/4OK+Cx31DYmNUSFh+MUot8qSDw==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Configuration.Json": "10.0.7",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.7",
+ "Microsoft.Extensions.FileProviders.Physical": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.DependencyInjection.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "Z6mfFEaFcwCfSboxJwOLfu7/31npCY9q70WUamHW/vRQhDvBKOT4Vf9YkZj5J6hLvJpb0oDEYfHunQZj0xxvKw=="
+ },
+ "Microsoft.Extensions.Diagnostics": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "l+smp1qPlU0OUXD0OGfdp7OUFrbdq7ZaP5T7m2WpfZ4RFKD7iG73BAT7tjSMxNmbSXkhAn1jYHOAqzYG1r9sNg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.7",
+ "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Diagnostics.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "uJ9JP677y+uy+C0vtaSfi7XXgFAdz8DhU3M9lwwIXDfQKcyQ0yxM9DVYa0NXDtdVTYA2eBUtVFZ8LY0GCdeE/w==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Options": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.FileProviders.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "teioDgVpi8L186wUfrXQV1YuBt6lCSPmFZiMZo53+FZxHFjOV+f4GXo4LXgJ273Mku9//AdXWVjk9J7eJP6inw==",
+ "dependencies": {
+ "Microsoft.Extensions.Primitives": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.FileProviders.Physical": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "zhgWg/i0ECj5v0jLFBSZHplvc5ygCI91DR4nne+BP4XAKF5ycz0pEKnFiTw8C1jCABJEZsnBZh6pXAvn71kFmw==",
+ "dependencies": {
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.7",
+ "Microsoft.Extensions.FileSystemGlobbing": "10.0.7",
+ "Microsoft.Extensions.Primitives": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.FileSystemGlobbing": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "NTUspqB+vH9g4wAD6KPOBx01xqYuKXR/cHXm449zpbq1GqfjdAxBmg7eJXrNsPw7SKwIdT2cJ05GxYVvc+lvsA=="
+ },
+ "Microsoft.Extensions.Hosting.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "5s8d6qC6EA8UOI4wR/+zlsq7SXttJMRb9d7zvVZ7+bE3CQEfVtC9ITUDCommm87R1zzj6WJBbCnztuIJXnP3DA==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Diagnostics.Abstractions": "10.0.7",
+ "Microsoft.Extensions.FileProviders.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Logging.Abstractions": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "tIEcQ2gvERrH2KiCjdsVcHGhXt9lIsuDStfOIeZWr7/fP8IXhGiYfx0/80PNI7WPO2IYuFtlZLSlnTS8+/Mchw==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Logging.Configuration": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "7BBnoGF37USiu7j434put9mDp7EjdlNDIZsR4vHfC1FbLZeLqiWjgJbeEtF0p59Ryqt8AtraHawf0ZKbe5jibg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration": "10.0.7",
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.7",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Logging": "10.0.7",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Options": "10.0.7",
+ "Microsoft.Extensions.Options.ConfigurationExtensions": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Logging.Console": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "DA++Es6v6W0HfrOrw+K8WyN6jNnZHp640PDdEvl8yfeVmgflKdn6vSSFvufNUSOuY+M2ZaSUgfY+jUKtNpXcCw==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Logging": "10.0.7",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Logging.Configuration": "10.0.7",
+ "Microsoft.Extensions.Options": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Logging.Debug": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "Y6DSt/JZApunYWKqTtqbdsR6iqAvHx3D0tavbNJ1rnC24MUpF+3XO/VKgFi+9PFqMyvQ2GHBBGb8H3cLSw7rDg==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Logging": "10.0.7",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Logging.EventLog": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "1C8eTuxF6BLncNSJ1HCfmaBcjpUSqQDPlBVdYTlet9oldHTPpNh9iatxSJLs8TOqdp/FOpH+nSLdBve7fu9mTQ==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Logging": "10.0.7",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Options": "10.0.7",
+ "System.Diagnostics.EventLog": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Logging.EventSource": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "YWfndnDX1jVMGCN8d5T+rO+BO8sDw6BkYlUk0BYui+WP7+HhlWx8QLdA4yUDjrkGVb3AQxIWWEPVKw5Nnfj5GQ==",
+ "dependencies": {
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Logging": "10.0.7",
+ "Microsoft.Extensions.Logging.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Options": "10.0.7",
+ "Microsoft.Extensions.Primitives": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Options.ConfigurationExtensions": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "IT7f+EMXZtkjatEcF+o6aOw/7OE4etRrMiDGEWH/iiTu2R3uhC4NEQJCfHiibtX45U3sIQ5Fh6tbb1qaOz3YAg==",
+ "dependencies": {
+ "Microsoft.Extensions.Configuration.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Configuration.Binder": "10.0.7",
+ "Microsoft.Extensions.DependencyInjection.Abstractions": "10.0.7",
+ "Microsoft.Extensions.Options": "10.0.7",
+ "Microsoft.Extensions.Primitives": "10.0.7"
+ }
+ },
+ "Microsoft.Extensions.Primitives": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "D5M0Jr551iTgwkZMN9rm0pSkgNLj5quUWQUmQPMZh7k/bnvZTnXRGfE2KuvXf1EEjt/ofD9yw9IumpgdP9QCnw=="
+ },
+ "System.Diagnostics.EventLog": {
+ "type": "Transitive",
+ "resolved": "10.0.7",
+ "contentHash": "WbmDLeTPYhEzXhvYVioTVn/D1XX6bovyny9n5p8Zxtf03+eY385RB818teZm6n+fA63iZNvng0/Np4tLuhkMhQ=="
+ }
+ }
+ }
+}
\ No newline at end of file