using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Numerics; using System.Text; using System.Text.Json; using Dalamud.Bindings.ImGui; using Dalamud.Interface.Utility.Raii; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game; using Lumina.Data.Files; using Lumina.Data.Parsing.Layer; using Lumina.Excel; using Lumina.Excel.Sheets; using Microsoft.Extensions.Logging; using Questionable.Data; using SmartNav; using SmartNav.Data; using SmartNav.Graph; using SmartNav.Model.Navigation; namespace Questionable.Windows.SmartNavComponents; internal sealed class DataProbesPanel { private static readonly string[] PlanFiles = new string[4] { "planmap.lgb", "planevent.lgb", "planner.lgb", "planlive.lgb" }; private static readonly JsonSerializerOptions IndentedJson = new JsonSerializerOptions { WriteIndented = true }; private readonly NavRouter _navRouter; private readonly WarpDataService _warpDataService; private readonly TerritoryData _territoryData; private readonly IDataManager _dataManager; private readonly IClientState _clientState; private readonly SmartNavDataOptions _dataOptions; private readonly ILogger _logger; private string? _multiQuestReport; private bool _popRangeOnlyDestinationless = true; private string? _popRangeReport; private int _lvbTerritoryId; private string? _lvbReport; private string? _arrivalExportReport; public DataProbesPanel(NavRouter navRouter, WarpDataService warpDataService, TerritoryData territoryData, IDataManager dataManager, IClientState clientState, SmartNavDataOptions dataOptions, ILogger logger) { _navRouter = navRouter; _warpDataService = warpDataService; _territoryData = territoryData; _dataManager = dataManager; _clientState = clientState; _dataOptions = dataOptions; _logger = logger; } public void Draw() { DrawMultiQuestWarpAudit(); ImGui.Separator(); DrawPopRangeProbe(); ImGui.Separator(); DrawDerivedArrivalExport(); ImGui.Separator(); DrawLayerSetProbe(); } private static void DrawReport(string id, ref string? report) { if (report == null) { return; } ImGui.SameLine(); ImU8String label = new ImU8String(6, 1); label.AppendLiteral("Copy##"); label.AppendFormatted(id); if (ImGui.Button(label)) { ImGui.SetClipboardText(report); } ImGui.SameLine(); ImU8String label2 = new ImU8String(7, 1); label2.AppendLiteral("Clear##"); label2.AppendFormatted(id); if (ImGui.Button(label2)) { report = null; return; } ImU8String strId = new ImU8String(7, 1); strId.AppendLiteral("##"); strId.AppendFormatted(id); strId.AppendLiteral("child"); using ImRaii.ChildDisposable childDisposable = ImRaii.Child(strId, new Vector2(-1f, 300f), border: true); if ((bool)childDisposable) { ImGui.TextUnformatted(report); } } private void DrawMultiQuestWarpAudit() { if (ImGui.CollapsingHeader("Multi-Quest Warp Audit (CompleteParam)###multiquestaudit", ImGuiTreeNodeFlags.DefaultOpen)) { ImGui.TextWrapped("Warps whose WarpCondition names 2+ quests. Verifies the CompleteParam any-of fix: conds 3/9/10 (airships, ferry) should read any-of, cond 34 all-of. 'FIX CHANGES THIS' marks warps the pre-fix all-of admission got wrong for this character."); if (ImGui.Button("Run Audit##multiquest")) { _multiQuestReport = RunGuarded(BuildMultiQuestReport); } DrawReport("multiquest", ref _multiQuestReport); } } private string BuildMultiQuestReport() { StringBuilder stringBuilder = new StringBuilder(); ExcelSheet excelSheet = _dataManager.GetExcelSheet(); ExcelSheet excelSheet2 = _dataManager.GetExcelSheet(); Dictionary dictionary = (from n in _navRouter.StaticGraph.Nodes.Values.OfType() group n by n.WarpRowId).ToDictionary((IGrouping g) => g.Key, (IGrouping g) => g.Count()); int num = 0; StringBuilder stringBuilder2; IFormatProvider invariantCulture; StringBuilder.AppendInterpolatedStringHandler handler; foreach (WarpDataService.WarpInfo item in from w in _warpDataService.Warps group w by w.RowId into g select g.First()) { if (item.RequiredQuestIds.Length < 2) { continue; } num++; Warp? rowOrDefault = excelSheet.GetRowOrDefault(item.RowId); uint value = rowOrDefault?.WarpCondition.RowId ?? 0; byte? b = rowOrDefault?.WarpCondition.ValueNullable?.CompleteParam; stringBuilder2 = stringBuilder; StringBuilder stringBuilder3 = stringBuilder2; invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider = invariantCulture; handler = new StringBuilder.AppendInterpolatedStringHandler(12, 3, stringBuilder2, invariantCulture); handler.AppendLiteral("warp "); handler.AppendFormatted(item.RowId); handler.AppendLiteral(" '"); handler.AppendFormatted(item.Name ?? item.Question ?? "?"); handler.AppendLiteral("' "); handler.AppendLiteral("-> "); handler.AppendFormatted(_territoryData.GetNameAndId(item.DestTerritoryId)); stringBuilder3.AppendLine(provider, ref handler); stringBuilder2 = stringBuilder; StringBuilder stringBuilder4 = stringBuilder2; invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider2 = invariantCulture; handler = new StringBuilder.AppendInterpolatedStringHandler(47, 3, stringBuilder2, invariantCulture); handler.AppendLiteral(" condition "); handler.AppendFormatted(value); handler.AppendLiteral(", CompleteParam="); handler.AppendFormatted(b?.ToString(CultureInfo.InvariantCulture) ?? "?"); handler.AppendLiteral(", "); handler.AppendLiteral("RequireAllQuests="); handler.AppendFormatted(item.RequireAllQuests); stringBuilder4.AppendLine(provider2, ref handler); stringBuilder2 = stringBuilder; StringBuilder stringBuilder5 = stringBuilder2; invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider3 = invariantCulture; handler = new StringBuilder.AppendInterpolatedStringHandler(37, 4, stringBuilder2, invariantCulture); handler.AppendLiteral(" source: territory "); handler.AppendFormatted(item.SourceTerritoryId); handler.AppendLiteral(" "); handler.AppendFormatted(item.SourcePosition); handler.AppendLiteral(", "); handler.AppendLiteral("npc "); handler.AppendFormatted(item.NpcDataId?.ToString(CultureInfo.InvariantCulture) ?? "-"); handler.AppendLiteral(", derived="); handler.AppendFormatted(item.DerivedSource); stringBuilder5.AppendLine(provider3, ref handler); stringBuilder2 = stringBuilder; StringBuilder stringBuilder6 = stringBuilder2; invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider4 = invariantCulture; handler = new StringBuilder.AppendInterpolatedStringHandler(35, 1, stringBuilder2, invariantCulture); handler.AppendLiteral(" static graph nodes for this row: "); handler.AppendFormatted(dictionary.GetValueOrDefault(item.RowId)); stringBuilder6.AppendLine(provider4, ref handler); int num2 = 0; uint[] requiredQuestIds = item.RequiredQuestIds; foreach (uint num4 in requiredQuestIds) { bool flag = QuestManager.IsQuestComplete((ushort)(num4 & 0xFFFF)); if (flag) { num2++; } string value2 = excelSheet2.GetRowOrDefault(num4)?.Name.ExtractText() ?? "?"; stringBuilder2 = stringBuilder; StringBuilder stringBuilder7 = stringBuilder2; invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider5 = invariantCulture; handler = new StringBuilder.AppendInterpolatedStringHandler(13, 3, stringBuilder2, invariantCulture); handler.AppendLiteral(" quest "); handler.AppendFormatted(num4); handler.AppendLiteral(" '"); handler.AppendFormatted(value2); handler.AppendLiteral("': "); handler.AppendFormatted(flag ? "COMPLETE" : "not complete"); stringBuilder7.AppendLine(provider5, ref handler); } bool flag2 = (item.RequireAllQuests ? (num2 == item.RequiredQuestIds.Length) : (num2 > 0)); bool flag3 = num2 == item.RequiredQuestIds.Length; stringBuilder2 = stringBuilder; StringBuilder stringBuilder8 = stringBuilder2; invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider6 = invariantCulture; handler = new StringBuilder.AppendInterpolatedStringHandler(34, 3, stringBuilder2, invariantCulture); handler.AppendLiteral(" admission: now="); handler.AppendFormatted(flag2); handler.AppendLiteral(", pre-fix all-of="); handler.AppendFormatted(flag3); handler.AppendFormatted((flag2 != flag3) ? " <-- FIX CHANGES THIS" : ""); stringBuilder8.AppendLine(provider6, ref handler); } stringBuilder2 = stringBuilder; StringBuilder stringBuilder9 = stringBuilder2; invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider7 = invariantCulture; handler = new StringBuilder.AppendInterpolatedStringHandler(26, 1, stringBuilder2, invariantCulture); handler.AppendFormatted(num); handler.AppendLiteral(" multi-quest warps audited"); stringBuilder9.AppendLine(provider7, ref handler); return stringBuilder.ToString(); } private void DrawPopRangeProbe() { if (ImGui.CollapsingHeader("Arrival PopRange Plan-File Probe###poprangeprobe", ImGuiTreeNodeFlags.DefaultOpen)) { ImGui.TextWrapped("For each warp, reads Warp.PopRange both ways: as a Level sheet row (SmartNav's source resolution) and as an LGB PopRange instance id in the destination territory's plan files (NoireLib's arrival claim: planevent holds most arrivals, planlive holds switched-content arrivals found nowhere else). Decides whether warp-destination seeding shrinks to 'extend the scan'. Unchecked, warps with a recorded arrival get a position delta (LGB PopRange translation vs recorded DestPosition) - the control for using LGB positions as arrivals."); ImGui.Checkbox("Only destination-less warps (no recorded arrival)", ref _popRangeOnlyDestinationless); if (ImGui.Button("Run Probe##poprange")) { _popRangeReport = RunGuarded(BuildPopRangeReport); } DrawReport("poprange", ref _popRangeReport); } } private string BuildPopRangeReport() { StringBuilder stringBuilder = new StringBuilder(); ExcelSheet excelSheet = _dataManager.GetExcelSheet(); ExcelSheet excelSheet2 = _dataManager.GetExcelSheet(); Dictionary>> cache = new Dictionary>>(); Dictionary dictionary = PlanFiles.ToDictionary((string f) => f, (string _) => 0); List<(uint, float)> list = new List<(uint, float)>(); int num = 0; int num2 = 0; int num3 = 0; StringBuilder stringBuilder2; IFormatProvider invariantCulture; StringBuilder.AppendInterpolatedStringHandler handler; foreach (WarpDataService.WarpInfo item4 in from w in _warpDataService.Warps group w by w.RowId into g select g.First()) { if (_popRangeOnlyDestinationless && item4.DestPosition.HasValue) { continue; } Warp? rowOrDefault = excelSheet.GetRowOrDefault(item4.RowId); if (!rowOrDefault.HasValue) { continue; } num++; uint rowId = rowOrDefault.Value.PopRange.RowId; uint rowId2 = rowOrDefault.Value.TerritoryType.RowId; bool flag = rowId != 0 && excelSheet2.HasRow(rowId); if (flag) { num3++; } List list2 = new List(); Vector3? vector = null; foreach (KeyValuePair> item5 in ScanPopRanges(cache, rowId2)) { item5.Deconstruct(out var key, out var value); string text = key; if (value.TryGetValue(rowId, out var value2)) { list2.Add(text); key = text; dictionary[key]++; Vector3 valueOrDefault = vector.GetValueOrDefault(); if (!vector.HasValue) { valueOrDefault = value2; vector = valueOrDefault; } } } if (list2.Count == 0) { num2++; } string value3 = ""; if (vector.HasValue && !item4.DerivedArrival) { Vector3? destPosition = item4.DestPosition; if (destPosition.HasValue) { Vector3 valueOrDefault2 = destPosition.GetValueOrDefault(); float item = Vector3.Distance(vector.Value, valueOrDefault2); list.Add((item4.RowId, item)); value3 = ", delta=" + item.ToString("F1", CultureInfo.InvariantCulture) + "y"; goto IL_02c9; } } if (item4.DerivedArrival) { value3 = ", derived-arrival"; } goto IL_02c9; IL_02c9: stringBuilder2 = stringBuilder; StringBuilder stringBuilder3 = stringBuilder2; invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider = invariantCulture; handler = new StringBuilder.AppendInterpolatedStringHandler(39, 8, stringBuilder2, invariantCulture); handler.AppendLiteral("warp "); handler.AppendFormatted(item4.RowId); handler.AppendLiteral(" '"); handler.AppendFormatted(item4.Name ?? item4.Question ?? "?"); handler.AppendLiteral("' popId="); handler.AppendFormatted(rowId); handler.AppendLiteral(" "); handler.AppendLiteral("dest="); handler.AppendFormatted(_territoryData.GetNameAndId(rowId2)); handler.AppendLiteral(": "); handler.AppendLiteral("level-row="); handler.AppendFormatted(flag); handler.AppendLiteral(", lgb="); handler.AppendFormatted((list2.Count > 0) ? string.Join("+", list2) : "NOT FOUND"); handler.AppendFormatted(vector.HasValue ? $" pos={vector.Value}" : ""); handler.AppendFormatted(value3); stringBuilder3.AppendLine(provider, ref handler); } stringBuilder.AppendLine(); stringBuilder2 = stringBuilder; StringBuilder stringBuilder4 = stringBuilder2; invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider2 = invariantCulture; handler = new StringBuilder.AppendInterpolatedStringHandler(65, 3, stringBuilder2, invariantCulture); handler.AppendFormatted(num); handler.AppendLiteral(" warps probed, "); handler.AppendFormatted(num3); handler.AppendLiteral(" with a Level sheet row, "); handler.AppendFormatted(num2); handler.AppendLiteral(" with no LGB PopRange hit"); stringBuilder4.AppendLine(provider2, ref handler); string[] planFiles = PlanFiles; foreach (string text2 in planFiles) { stringBuilder2 = stringBuilder; StringBuilder stringBuilder5 = stringBuilder2; invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider3 = invariantCulture; handler = new StringBuilder.AppendInterpolatedStringHandler(17, 2, stringBuilder2, invariantCulture); handler.AppendLiteral(" "); handler.AppendFormatted(text2); handler.AppendLiteral(": "); handler.AppendFormatted(dictionary[text2]); handler.AppendLiteral(" arrival hits"); stringBuilder5.AppendLine(provider3, ref handler); } if (list.Count > 0) { List<(uint, float)> list3 = list.OrderBy<(uint, float), float>(((uint RowId, float Delta) d) => d.Delta).ToList(); float item2 = list3[list3.Count / 2].Item2; float item3 = list3[Math.Min(list3.Count - 1, (int)((double)list3.Count * 0.9))].Item2; List<(uint, float)> list4 = list3.Where<(uint, float)>(((uint RowId, float Delta) d) => d.Delta > 20f).ToList(); stringBuilder2 = stringBuilder; StringBuilder stringBuilder6 = stringBuilder2; invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider4 = invariantCulture; handler = new StringBuilder.AppendInterpolatedStringHandler(112, 5, stringBuilder2, invariantCulture); handler.AppendLiteral("recorded-arrival delta (LGB PopRange pos vs recorded DestPosition): "); handler.AppendFormatted(list3.Count); handler.AppendLiteral(" compared, "); handler.AppendLiteral("median "); handler.AppendFormatted(item2, "F1"); handler.AppendLiteral("y, p90 "); handler.AppendFormatted(item3, "F1"); handler.AppendLiteral("y, max "); handler.AppendFormatted(list3[list3.Count - 1].Item2, "F1"); handler.AppendLiteral("y, "); handler.AppendFormatted(list4.Count); handler.AppendLiteral(" over 20y"); stringBuilder6.AppendLine(provider4, ref handler); if (list4.Count > 0) { stringBuilder2 = stringBuilder; StringBuilder stringBuilder7 = stringBuilder2; invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider5 = invariantCulture; handler = new StringBuilder.AppendInterpolatedStringHandler(17, 1, stringBuilder2, invariantCulture); handler.AppendLiteral(" >20y outliers: "); handler.AppendFormatted(string.Join(", ", list4.Select<(uint, float), string>(((uint RowId, float Delta) d) => $"{d.RowId} ({d.Delta.ToString("F1", CultureInfo.InvariantCulture)}y)"))); stringBuilder7.AppendLine(provider5, ref handler); } } return stringBuilder.ToString(); } private Dictionary> ScanPopRanges(Dictionary>> cache, uint territoryId) { if (cache.TryGetValue(territoryId, out Dictionary> value)) { return value; } Dictionary> dictionary = (cache[territoryId] = new Dictionary>()); string text = _dataManager.GetExcelSheet().GetRowOrDefault(territoryId)?.Bg.ExtractText(); if (string.IsNullOrEmpty(text)) { return dictionary; } int num = text.IndexOf("/level/", StringComparison.Ordinal); if (num < 0) { return dictionary; } string[] planFiles = PlanFiles; foreach (string text2 in planFiles) { Dictionary dictionary3 = (dictionary[text2] = new Dictionary()); string text3 = "bg/" + text.Substring(0, num + 1) + "level/" + text2; try { LgbFile file = _dataManager.GetFile(text3); if (file == null) { continue; } LayerCommon.Layer[] layers = file.Layers; for (int j = 0; j < layers.Length; j++) { LayerCommon.InstanceObject[] instanceObjects = layers[j].InstanceObjects; for (int k = 0; k < instanceObjects.Length; k++) { LayerCommon.InstanceObject instanceObject = instanceObjects[k]; if (instanceObject.AssetType == LayerEntryType.PopRange) { dictionary3[instanceObject.InstanceId] = new Vector3(instanceObject.Transform.Translation.X, instanceObject.Transform.Translation.Y, instanceObject.Transform.Translation.Z); } } } } catch (Exception exception) { _logger.LogTrace(exception, "Failed to scan {Path}", text3); } } return dictionary; } private void DrawDerivedArrivalExport() { if (!ImGui.CollapsingHeader("Derived Arrival Export (authoring)###arrivalexport")) { return; } ImGui.TextWrapped("Scans every Warp row's destination territory for its arrival PopRange and writes derived-arrivals.json into the SmartNav.Data checkout (DevSourceDirectory) for bundling - runtime never scans LGB itself. Rerun after game patches. The game FREEZES for the scan (~1-2 min on a Debug build) - expected, wait it out."); if (_dataOptions.DevSourceDirectory == null) { ImGui.TextDisabled("DevSourceDirectory not set - available in DEBUG builds only."); return; } if (ImGui.Button("Scan + Export##arrivalexportrun")) { _arrivalExportReport = RunGuarded(BuildDerivedArrivalExport); } DrawReport("arrivalexport", ref _arrivalExportReport); } private string BuildDerivedArrivalExport() { StringBuilder stringBuilder = new StringBuilder(); ExcelSheet excelSheet = _dataManager.GetExcelSheet(); Dictionary>> cache = new Dictionary>>(); Dictionary dictionary = PlanFiles.ToDictionary((string f) => f, (string _) => 0); Dictionary dictionary2 = new Dictionary(); List list = new List(); int num = 0; foreach (Warp item in excelSheet.Where((Warp x) => x.RowId != 0)) { uint rowId = item.PopRange.RowId; ushort num2 = (ushort)item.TerritoryType.RowId; if (rowId == 0 || num2 == 0) { continue; } num++; Vector3? vector = null; foreach (KeyValuePair> item2 in ScanPopRanges(cache, num2)) { item2.Deconstruct(out var key, out var value); string text = key; if (value.TryGetValue(rowId, out var value2)) { key = text; dictionary[key]++; Vector3 valueOrDefault = vector.GetValueOrDefault(); if (!vector.HasValue) { valueOrDefault = value2; vector = valueOrDefault; } } } if (vector.HasValue) { dictionary2[item.RowId] = new DerivedArrivalEntry { PopRangeId = rowId, TerritoryId = num2, X = vector.Value.X, Y = vector.Value.Y, Z = vector.Value.Z }; continue; } string value3 = item.Name.ExtractText(); if (string.IsNullOrEmpty(value3)) { value3 = item.Question.ExtractText(); } list.Add($" {item.RowId} '{value3}' popId={rowId} dest={_territoryData.GetNameAndId(num2)}"); } DerivedArrivalFile derivedArrivalFile = new DerivedArrivalFile { GameVersion = _dataOptions.GameVersion, Arrivals = dictionary2 }; string text2 = Path.Combine(_dataOptions.DevSourceDirectory.FullName, "derived-arrivals.json"); File.WriteAllText(text2, JsonSerializer.Serialize(derivedArrivalFile, IndentedJson)); StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder3 = stringBuilder2; IFormatProvider invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider = invariantCulture; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(57, 2, stringBuilder2, invariantCulture); handler.AppendFormatted(num); handler.AppendLiteral(" warp rows with PopRange + destination, "); handler.AppendFormatted(dictionary2.Count); handler.AppendLiteral(" arrivals derived"); stringBuilder3.AppendLine(provider, ref handler); string[] planFiles = PlanFiles; foreach (string text3 in planFiles) { stringBuilder2 = stringBuilder; StringBuilder stringBuilder4 = stringBuilder2; invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider2 = invariantCulture; handler = new StringBuilder.AppendInterpolatedStringHandler(9, 2, stringBuilder2, invariantCulture); handler.AppendLiteral(" "); handler.AppendFormatted(text3); handler.AppendLiteral(": "); handler.AppendFormatted(dictionary[text3]); handler.AppendLiteral(" hits"); stringBuilder4.AppendLine(provider2, ref handler); } stringBuilder2 = stringBuilder; StringBuilder stringBuilder5 = stringBuilder2; invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider3 = invariantCulture; handler = new StringBuilder.AppendInterpolatedStringHandler(98, 1, stringBuilder2, invariantCulture); handler.AppendFormatted(list.Count); handler.AppendLiteral(" unresolved (no plan file holds the pop id; Level-sheet fallback may still cover some at runtime):"); stringBuilder5.AppendLine(provider3, ref handler); foreach (string item3 in list) { stringBuilder.AppendLine(item3); } stringBuilder.AppendLine(); stringBuilder2 = stringBuilder; StringBuilder stringBuilder6 = stringBuilder2; invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider4 = invariantCulture; handler = new StringBuilder.AppendInterpolatedStringHandler(25, 2, stringBuilder2, invariantCulture); handler.AppendLiteral("written: "); handler.AppendFormatted(text2); handler.AppendLiteral(" (game version "); handler.AppendFormatted(derivedArrivalFile.GameVersion ?? "?"); handler.AppendLiteral(")"); stringBuilder6.AppendLine(provider4, ref handler); stringBuilder.AppendLine("DEBUG builds read this file directly via DevSourceDirectory - toggle the plugin to see the new arrivals; commit it in SmartNav to ship."); return stringBuilder.ToString(); } private void DrawLayerSetProbe() { if (!ImGui.CollapsingHeader("Layer-Set Attribution Probe (.lvb)###lvbprobe", ImGuiTreeNodeFlags.DefaultOpen)) { return; } ImGui.TextWrapped("Parses the territory's .lvb layer-set table per the offsets claimed in NoireLib's GameData docs (0x1C-byte records, TerritoryType row at +0x0C) and each plan file's per-layer layer-set references. Verify against a known shared-level pair, e.g. Mist 339 vs s1h1_test 136: attributed territory ids must all appear in the 'territories sharing this Bg' line."); ImGui.SetNextItemWidth(120f); ImGui.InputInt("Territory id (0 = current)##lvbterritory", ref _lvbTerritoryId); if (ImGui.Button("Parse##lvb")) { uint territoryId = ((_lvbTerritoryId > 0) ? ((uint)_lvbTerritoryId) : _clientState.TerritoryType); _lvbReport = RunGuarded(() => BuildLayerSetReport(territoryId)); } DrawReport("lvb", ref _lvbReport); } private string BuildLayerSetReport(uint territoryId) { StringBuilder stringBuilder = new StringBuilder(); ExcelSheet excelSheet = _dataManager.GetExcelSheet(); string bg = excelSheet.GetRowOrDefault(territoryId)?.Bg.ExtractText(); if (string.IsNullOrEmpty(bg)) { return $"territory {territoryId} has no Bg path"; } int num = bg.IndexOf("/level/", StringComparison.Ordinal); if (num < 0) { return $"territory {territoryId} Bg '{bg}' has no /level/ segment"; } string text = "bg/" + bg.Substring(0, num + 1) + "level/"; string text2 = bg.Substring(bg.LastIndexOf('/') + 1); StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder3 = stringBuilder2; IFormatProvider invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider = invariantCulture; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(17, 2, stringBuilder2, invariantCulture); handler.AppendLiteral("territory "); handler.AppendFormatted(_territoryData.GetNameAndId(territoryId)); handler.AppendLiteral(", Bg '"); handler.AppendFormatted(bg); handler.AppendLiteral("'"); stringBuilder3.AppendLine(provider, ref handler); List list = (from t in excelSheet where t.RowId != 0 && t.Bg.ExtractText() == bg select t.RowId).ToList(); stringBuilder2 = stringBuilder; StringBuilder stringBuilder4 = stringBuilder2; invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider2 = invariantCulture; handler = new StringBuilder.AppendInterpolatedStringHandler(29, 1, stringBuilder2, invariantCulture); handler.AppendLiteral("territories sharing this Bg: "); handler.AppendFormatted(string.Join(", ", list)); stringBuilder4.AppendLine(provider2, ref handler); List<(uint SetId, uint TerritoryId)> list2 = ParseLvbLayerSets(ReadFileBytes(text + text2 + ".lvb"), stringBuilder); Dictionary> owners = new Dictionary>(); foreach (var item3 in list2) { uint item = item3.SetId; uint item2 = item3.TerritoryId; stringBuilder2 = stringBuilder; StringBuilder stringBuilder5 = stringBuilder2; invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider3 = invariantCulture; handler = new StringBuilder.AppendInterpolatedStringHandler(26, 3, stringBuilder2, invariantCulture); handler.AppendLiteral(" layer set "); handler.AppendFormatted(item); handler.AppendLiteral(" -> territory "); handler.AppendFormatted(item2); handler.AppendFormatted(list.Contains(item2) ? "" : " <-- NOT IN SHARING SET (offsets suspect)"); stringBuilder5.AppendLine(provider3, ref handler); if (item2 != 0) { if (!owners.TryGetValue(item, out List value)) { value = (owners[item] = new List()); } value.Add(item2); } } string[] planFiles = PlanFiles; foreach (string text3 in planFiles) { List> list4 = ParseLgbLayerSetReferences(ReadFileBytes(text + text3)); if (list4.Count == 0) { stringBuilder2 = stringBuilder; StringBuilder stringBuilder6 = stringBuilder2; invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider4 = invariantCulture; handler = new StringBuilder.AppendInterpolatedStringHandler(18, 1, stringBuilder2, invariantCulture); handler.AppendFormatted(text3); handler.AppendLiteral(": no layers parsed"); stringBuilder6.AppendLine(provider4, ref handler); continue; } int value2 = list4.Count((List refs) => refs.Count > 0); List value3; List list5 = (from setId in list4.SelectMany((List refs) => refs).Distinct() select (!owners.TryGetValue(setId, out value3)) ? $"set {setId} unknown" : string.Join("/", value3)).Distinct().ToList(); stringBuilder2 = stringBuilder; StringBuilder stringBuilder7 = stringBuilder2; invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider5 = invariantCulture; handler = new StringBuilder.AppendInterpolatedStringHandler(48, 4, stringBuilder2, invariantCulture); handler.AppendFormatted(text3); handler.AppendLiteral(": "); handler.AppendFormatted(list4.Count); handler.AppendLiteral(" layers, "); handler.AppendFormatted(value2); handler.AppendLiteral(" with layer-set refs; "); handler.AppendLiteral("attributed to: "); handler.AppendFormatted((list5.Count > 0) ? string.Join(", ", list5) : "-"); stringBuilder7.AppendLine(provider5, ref handler); } return stringBuilder.ToString(); } private byte[]? ReadFileBytes(string path) { try { return _dataManager.GetFile(path)?.Data; } catch (Exception exception) { _logger.LogTrace(exception, "Failed to read {Path}", path); return null; } } private static List<(uint SetId, uint TerritoryId)> ParseLvbLayerSets(byte[]? data, StringBuilder sb) { List<(uint, uint)> list = new List<(uint, uint)>(); if (data == null || data.Length < 36 || !HasMagic(data, 0, "LVB1") || !HasMagic(data, 12, "SCN1")) { sb.AppendLine(".lvb: missing or not LVB1/SCN1"); return list; } int num = 20 + BitConverter.ToInt32(data, 32); if (num < 0 || num + 12 > data.Length) { sb.AppendLine(".lvb: layer-set folder offset out of range"); return list; } int num2 = BitConverter.ToInt32(data, num + 4); int num3 = num + 12; bool flag = ((num2 < 0 || num2 > 4096) ? true : false); StringBuilder stringBuilder; IFormatProvider invariantCulture; StringBuilder.AppendInterpolatedStringHandler handler; if (flag || num3 + num2 * 28 > data.Length) { stringBuilder = sb; StringBuilder stringBuilder2 = stringBuilder; invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider = invariantCulture; handler = new StringBuilder.AppendInterpolatedStringHandler(34, 1, stringBuilder, invariantCulture); handler.AppendLiteral(".lvb: implausible layer-set count "); handler.AppendFormatted(num2); stringBuilder2.AppendLine(provider, ref handler); return list; } for (int i = 0; i < num2; i++) { int num4 = num3 + i * 28; list.Add((BitConverter.ToUInt32(data, num4), BitConverter.ToUInt32(data, num4 + 12))); } stringBuilder = sb; StringBuilder stringBuilder3 = stringBuilder; invariantCulture = CultureInfo.InvariantCulture; IFormatProvider provider2 = invariantCulture; handler = new StringBuilder.AppendInterpolatedStringHandler(17, 1, stringBuilder, invariantCulture); handler.AppendLiteral(".lvb: "); handler.AppendFormatted(num2); handler.AppendLiteral(" layer sets"); stringBuilder3.AppendLine(provider2, ref handler); return list; } private static List> ParseLgbLayerSetReferences(byte[]? data) { List> list = new List>(); if (data == null || data.Length < 36 || !HasMagic(data, 0, "LGB1") || !HasMagic(data, 12, "LGP1")) { return list; } int num = 20 + BitConverter.ToInt32(data, 28); int num2 = BitConverter.ToInt32(data, 32); bool flag = ((num2 < 0 || num2 > 100000) ? true : false); if (flag || num < 0 || num + num2 * 4 > data.Length) { return list; } for (int i = 0; i < num2; i++) { List list2 = new List(); list.Add(list2); int num3 = num + BitConverter.ToInt32(data, num + i * 4); if (num3 < 0 || num3 + 36 > data.Length) { continue; } int num4 = num3 + BitConverter.ToInt32(data, num3 + 20); if (num4 <= num3 || num4 + 12 > data.Length) { continue; } int num5 = num4 + BitConverter.ToInt32(data, num4 + 4); int num6 = BitConverter.ToInt32(data, num4 + 8); flag = ((num6 < 1 || num6 > 4096) ? true : false); if (!flag && num5 >= 0 && num5 + num6 * 4 <= data.Length) { for (int j = 0; j < num6; j++) { list2.Add(BitConverter.ToUInt32(data, num5 + j * 4)); } } } return list; } private static bool HasMagic(byte[] data, int at, string magic) { for (int i = 0; i < magic.Length; i++) { if (data[at + i] != (byte)magic[i]) { return false; } } return true; } private string RunGuarded(Func probe) { try { return probe(); } catch (Exception ex) { _logger.LogError(ex, "Data probe failed"); return $"probe failed: {ex}"; } } }