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