using System.Text.Json; namespace GenshinAssistant.InputHelper; internal static class IkInventoryLists { public static IkInventoryListStatus Load(string dataDir) { var dir = ResolveDirectory(dataDir); var required = new[] { "artifacts.json", "weapons.json", "characters.json", "materials.json", "version.txt" }; var missing = required.Where(file => !File.Exists(Path.Combine(dir, file))).ToArray(); var status = new IkInventoryListStatus { Directory = dir, Missing = missing }; if (missing.Length > 0) return status; status.Version = File.ReadAllText(Path.Combine(dir, "version.txt")).Trim(); status.ArtifactSets = CountJsonObjectProperties(Path.Combine(dir, "artifacts.json")); status.ArtifactPieces = CountArtifactPieces(Path.Combine(dir, "artifacts.json")); status.Weapons = CountJsonObjectProperties(Path.Combine(dir, "weapons.json")); status.Characters = CountJsonObjectProperties(Path.Combine(dir, "characters.json")); status.Materials = CountJsonObjectProperties(Path.Combine(dir, "materials.json")); return status; } private static string ResolveDirectory(string dataDir) { if (!string.IsNullOrWhiteSpace(dataDir)) return dataDir; var candidates = new List(); var envDir = Environment.GetEnvironmentVariable("IK_INVENTORYLISTS_DIR"); if (!string.IsNullOrWhiteSpace(envDir)) candidates.Add(envDir); AddDirectoryCandidates(candidates, AppContext.BaseDirectory); AddDirectoryCandidates(candidates, Environment.CurrentDirectory); var baseParent = Directory.GetParent(AppContext.BaseDirectory); for (var depth = 0; depth < 6 && baseParent != null; depth++) { AddDirectoryCandidates(candidates, baseParent.FullName); baseParent = baseParent.Parent; } return candidates.FirstOrDefault(IsCompleteInventoryListDirectory) ?? Path.Combine(AppContext.BaseDirectory, "inventorylists"); } private static void AddDirectoryCandidates(List candidates, string root) { if (string.IsNullOrWhiteSpace(root)) return; candidates.Add(Path.Combine(root, "inventorylists")); candidates.Add(Path.Combine(root, "ik-inventorylists")); candidates.Add(Path.Combine(root, "data", "ik-inventorylists")); } private static bool IsCompleteInventoryListDirectory(string dir) { return File.Exists(Path.Combine(dir, "artifacts.json")) && File.Exists(Path.Combine(dir, "weapons.json")) && File.Exists(Path.Combine(dir, "characters.json")) && File.Exists(Path.Combine(dir, "materials.json")) && File.Exists(Path.Combine(dir, "version.txt")); } public static object CatalogPayload(string dataDir) { var status = Load(dataDir); if (!status.Valid) { return new { data = status.ToPayload(), artifacts = Array.Empty(), weapons = Array.Empty(), characters = Array.Empty(), materials = Array.Empty(), }; } return new { data = status.ToPayload(), artifacts = LoadArtifactCatalog(Path.Combine(status.Directory, "artifacts.json")), weapons = LoadStringMapCatalog(Path.Combine(status.Directory, "weapons.json")), characters = LoadCharacterCatalog(Path.Combine(status.Directory, "characters.json")), materials = LoadStringMapCatalog(Path.Combine(status.Directory, "materials.json")), }; } private static int CountJsonObjectProperties(string path) { using var doc = JsonDocument.Parse(File.ReadAllText(path)); return doc.RootElement.ValueKind == JsonValueKind.Object ? doc.RootElement.EnumerateObject().Count() : 0; } private static int CountArtifactPieces(string path) { using var doc = JsonDocument.Parse(File.ReadAllText(path)); if (doc.RootElement.ValueKind != JsonValueKind.Object) return 0; var count = 0; foreach (var set in doc.RootElement.EnumerateObject()) { if (!set.Value.TryGetProperty("artifacts", out var artifacts)) continue; if (artifacts.ValueKind == JsonValueKind.Object) count += artifacts.EnumerateObject().Count(); } return count; } private static List LoadStringMapCatalog(string path) { using var doc = JsonDocument.Parse(File.ReadAllText(path)); var entries = new List(); if (doc.RootElement.ValueKind != JsonValueKind.Object) return entries; foreach (var entry in doc.RootElement.EnumerateObject()) { entries.Add(new { normalizedName = entry.Name, good = entry.Value.ValueKind == JsonValueKind.String ? entry.Value.GetString() ?? "" : "", }); } return entries; } private static List LoadCharacterCatalog(string path) { using var doc = JsonDocument.Parse(File.ReadAllText(path)); var entries = new List(); if (doc.RootElement.ValueKind != JsonValueKind.Object) return entries; foreach (var entry in doc.RootElement.EnumerateObject()) { var value = entry.Value; entries.Add(new { normalizedName = entry.Name, good = JsonString(value, "GOOD"), element = JsonFirstString(value, "Element"), weaponType = JsonFirstInt(value, "WeaponType"), constellationName = JsonFirstString(value, "ConstellationName"), }); } return entries; } private static List LoadArtifactCatalog(string path) { using var doc = JsonDocument.Parse(File.ReadAllText(path)); var entries = new List(); if (doc.RootElement.ValueKind != JsonValueKind.Object) return entries; foreach (var set in doc.RootElement.EnumerateObject()) { var pieces = new List(); if (set.Value.TryGetProperty("artifacts", out var artifacts) && artifacts.ValueKind == JsonValueKind.Object) { foreach (var piece in artifacts.EnumerateObject()) { pieces.Add(new { slot = piece.Name, artifactName = JsonString(piece.Value, "artifactName"), good = JsonString(piece.Value, "GOOD"), normalizedName = JsonString(piece.Value, "normalizedName"), }); } } entries.Add(new { normalizedName = set.Name, setName = JsonString(set.Value, "setName"), good = JsonString(set.Value, "GOOD"), pieces, }); } return entries; } private static string JsonString(JsonElement value, string propertyName) => value.TryGetProperty(propertyName, out var property) && property.ValueKind == JsonValueKind.String ? property.GetString() ?? "" : ""; private static string JsonFirstString(JsonElement value, string propertyName) { if (!value.TryGetProperty(propertyName, out var property)) return ""; if (property.ValueKind == JsonValueKind.String) return property.GetString() ?? ""; if (property.ValueKind == JsonValueKind.Array && property.GetArrayLength() > 0) { var first = property[0]; return first.ValueKind == JsonValueKind.String ? first.GetString() ?? "" : ""; } return ""; } private static int JsonFirstInt(JsonElement value, string propertyName) { if (!value.TryGetProperty(propertyName, out var property)) return -1; if (property.ValueKind == JsonValueKind.Number && property.TryGetInt32(out var number)) return number; if (property.ValueKind == JsonValueKind.Array && property.GetArrayLength() > 0) { var first = property[0]; return first.ValueKind == JsonValueKind.Number && first.TryGetInt32(out number) ? number : -1; } return -1; } } internal sealed class IkInventoryListStatus { public string Directory { get; init; } = ""; public string Version { get; set; } = ""; public int ArtifactSets { get; set; } public int ArtifactPieces { get; set; } public int Weapons { get; set; } public int Characters { get; set; } public int Materials { get; set; } public string[] Missing { get; init; } = Array.Empty(); public bool Valid => Missing.Length == 0; public object SupportedCategoriesPayload() => new { artifacts = ArtifactCategoryPayload("artifacts.json", ArtifactSets, ArtifactPieces), weapons = SimpleCategoryPayload("weapons.json", Weapons), characters = SimpleCategoryPayload("characters.json", Characters), materials = SimpleCategoryPayload("materials.json", Materials), }; private object ArtifactCategoryPayload(string file, int setCount, int pieceCount) { var catalogAvailable = Valid; var nativeCaptureSupported = Valid; return new { file, setCount, pieceCount, supported = nativeCaptureSupported, catalogAvailable, nativeCaptureSupported, scanStatus = nativeCaptureSupported ? "native_capture" : "missing_data", }; } private object SimpleCategoryPayload(string file, int count) { var catalogAvailable = Valid; const bool nativeCaptureSupported = false; return new { file, count, supported = nativeCaptureSupported, catalogAvailable, nativeCaptureSupported, scanStatus = catalogAvailable ? "catalog_only" : "missing_data", }; } public object ToPayload() => new { directory = Directory, version = Version, artifactSets = ArtifactSets, artifactPieces = ArtifactPieces, weapons = Weapons, characters = Characters, materials = Materials, totalEntries = ArtifactPieces + Weapons + Characters + Materials, categories = SupportedCategoriesPayload(), missing = Missing, valid = Valid, source = "InventoryKamera inventorylists", }; }