using System.IO; using AcDream.App.UI.Layout; using DatReaderWriter; using DatReaderWriter.Options; namespace AcDream.App.Tests.UI.Layout; /// /// 2026-08-14 gate probe (the ACDREAM_PROBE_LIVE_MOUNT pattern): dumps the /// authored per-state strings of the floaty powerbar layout (0x21000072 — /// gmPowerbarUI's mode states 0x10000042 jump / 0x10000043 melee / /// 0x10000044 missile / 0x10000045 DDD, RecvNotice_BeginPowerbar /// @ 0x004DA730) and the combat panel's Power label (0x21000073 element /// 0x10000052, root states 0x10000003 melee / 0x10000004 missile) from the /// INSTALLED DAT, so the "Height"/"Power"/"Accuracy" fix binds exactly what /// retail authors instead of guessing. /// public sealed class PowerbarLayoutProbeTests { [Fact] public void ProbePowerbarAuthoredStrings() { if (Environment.GetEnvironmentVariable("ACDREAM_PROBE_POWERBAR") != "1") return; var datDir = Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR") ?? Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Documents", "Asheron's Call"); using var dats = new DatCollection(datDir, DatAccessType.Read); var strings = new DatStringResolver(dats); Dump(dats, strings, JumpPowerbarController.LayoutId, "floaty-powerbar"); Dump(dats, strings, CombatUiController.LayoutId, "combat-panel"); } /// 2026-08-14 friends gate: dump the friends row TEMPLATE tree /// (ListBox 0x10000517's authored template list) so the status cell binds /// the exact authored element instead of a guess. [Fact] public void ProbeFriendsRowTemplate() { if (Environment.GetEnvironmentVariable("ACDREAM_PROBE_POWERBAR") != "1") return; var datDir = Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR") ?? Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Documents", "Asheron's Call"); using var dats = new DatCollection(datDir, DatAccessType.Read); var strings = new DatStringResolver(dats); ElementInfo? root = LayoutImporter.ImportInfos( dats, SocialPanelController.HostLayoutId, SocialPanelController.SlotElementId); Assert.NotNull(root); ElementInfo? listBox = FindById(root!, 0x10000517u); Assert.NotNull(listBox); Console.WriteLine( $"[pbprobe] friends listbox 0x10000517 templates={listBox!.TemplateList.Count}"); foreach (UiTemplateListEntry template in listBox.TemplateList) { Console.WriteLine( $"[pbprobe] template layout=0x{template.TemplateLayoutId:X8} " + $"element=0x{template.TemplateElementId:X8}"); ElementInfo? row = LayoutImporter.ImportInfos( dats, template.TemplateLayoutId, template.TemplateElementId); if (row is not null) DumpElement(strings, row, 1); } } private static ElementInfo? FindById(ElementInfo element, uint id) { if (element.Id == id) return element; foreach (ElementInfo child in element.Children) if (FindById(child, id) is { } found) return found; return null; } private static void Dump( DatCollection dats, DatStringResolver strings, uint layoutId, string label) { ElementInfo? root = LayoutImporter.ImportInfos(dats, layoutId); if (root is null) { Console.WriteLine($"[pbprobe] {label} 0x{layoutId:X8}: import FAILED"); return; } Console.WriteLine($"[pbprobe] === {label} 0x{layoutId:X8} ==="); DumpElement(strings, root, 0); } private static void DumpElement(DatStringResolver strings, ElementInfo e, int depth) { string indent = new(' ', depth * 2); Console.WriteLine( $"[pbprobe] {indent}0x{e.Id:X8} type={e.Type} ({e.X},{e.Y} {e.Width}x{e.Height}) " + $"defaultState=0x{e.DefaultStateId:X8}('{e.DefaultStateName}') states={e.States.Count} " + $"hjustify={e.HJustify} fontColor={(e.FontColor is { } fc ? fc.ToString() : "none")}"); foreach (var (stateId, state) in e.States) { string text = ""; if (state.Properties.Values.TryGetValue(0x17u, out var p) && p.Kind == UiPropertyKind.StringInfo) { text = strings.Resolve(p.StringInfoValue) ?? ""; text = $" text='{text}'"; } string properties = string.Join( " ", state.Properties.Values .OrderBy(static pair => pair.Key) .Select(static pair => $"0x{pair.Key:X2}:{pair.Value.Kind}=0x{pair.Value.UnsignedValue:X}")); Console.WriteLine( $"[pbprobe] {indent} state 0x{stateId:X8} '{state.Name}'" + $" passToChildren={state.PassToChildren}{text} props[{properties}]"); } foreach (ElementInfo child in e.Children) DumpElement(strings, child, depth + 1); } }