using System.IO; using AcDream.App.UI; using AcDream.App.UI.Layout; using AcDream.Content; using DatReaderWriter; using DatReaderWriter.Options; namespace AcDream.App.Tests.UI.Layout; /// /// Installed-retail-DAT acceptance gate for Campaign CC slice CC4. Opt in /// with ACDREAM_PROBE_LIVE_MOUNT=1; ACDREAM_DAT_DIR can /// override the ordinary Documents/Asheron's Call location. Mirrors /// 's pattern: sweeps the /// authored master-shell and page ids the campaign plan and CC4's own /// decomp research cite, pinning them against the real installed layout. /// public sealed class CharacterCreationLiveDatTests { private static string DatDirectory => Environment.GetEnvironmentVariable("ACDREAM_DAT_DIR") ?? Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Documents", "Asheron's Call"); [InstalledDatFact] public void EnumTable5_ResolvesTheMasterShellRootAndChildren() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); Assert.NotEqual(0u, layoutId); Console.WriteLine( $"[CC4-DAT] category=5 enum=0x10000039 -> DID=0x{layoutId:X8}"); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); Assert.Equal( CharacterCreationUiController.RootElementId, screen.Root.DatElementId); Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.ProgressBarElementId)); AssertButton(screen, CharacterCreationUiController.BackElementId); AssertButton(screen, CharacterCreationUiController.NextElementId); AssertButton(screen, CharacterCreationUiController.FinishElementId); AssertButton(screen, CharacterCreationUiController.HelpElementId); AssertButton(screen, CharacterCreationUiController.ExitElementId); AssertButton(screen, CharacterCreationUiController.RandomElementId); Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.MasterPageElementId)); foreach (uint pageId in new[] { CharacterCreationUiController.HeritagePageElementId, CharacterCreationUiController.ProfessionPageElementId, CharacterCreationUiController.SkillsPageElementId, CharacterCreationUiController.AppearancePageElementId, CharacterCreationUiController.TownPageElementId, CharacterCreationUiController.SummaryPageElementId, }) { Assert.IsAssignableFrom(screen.FindElement(pageId)); } AssertButton(screen, CharacterCreationUiController.HeritageTabElementId); AssertButton(screen, CharacterCreationUiController.ProfessionTabElementId); AssertButton(screen, CharacterCreationUiController.SkillsTabElementId); AssertButton(screen, CharacterCreationUiController.AppearanceTabElementId); AssertButton(screen, CharacterCreationUiController.TownTabElementId); AssertButton(screen, CharacterCreationUiController.SummaryTabElementId); } [InstalledDatFact] public void MountsThroughTheControllerAgainstLiveResources() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); var host = new UiRoot(); var dialogs = MakeDialogFactory(dats, host); var bindings = new CharacterCreationRuntimeBindings( () => null, _ => default, _ => default, _ => default, (_, _) => default, (_, _) => default, _ => default, _ => default, _ => default, _ => default, _ => default, () => { }); UiElement? ResolveTemplate(uint templateLayoutId, uint templateElementId) => LayoutImporter.Import( dats, templateLayoutId, templateElementId, _ => (0u, 0, 0), null)?.Root; CharacterCreationUiController? controller = CharacterCreationUiController.CreateDetached( host, screen, ResolveTemplate, dialogs, bindings, new CharacterCreationUiController.DialogStrings("Are you sure?")); Assert.NotNull(controller); controller!.AttachAndTick(); controller.Dispose(); dialogs.Dispose(); } /// 13 heritage buttons, the description text, all present as /// authored (Heritage page — gmCGHeritagePage::InitializePage @ /// 0x00483a10). [InstalledDatFact] public void HeritagePage_HasAllThirteenRaceButtonsAndDescriptionText() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); UiElement heritageRoot = Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.HeritagePageElementId)); uint[] heritageButtonIds = [ 0x100003BFu, 0x100003C1u, 0x100003C2u, 0x100003C3u, 0x10000590u, 0x100005A9u, 0x100005E8u, 0x100005F1u, 0x100005C4u, 0x10000591u, 0x100005BFu, 0x100005C7u, 0x100005C8u, ]; foreach (uint buttonId in heritageButtonIds) { Assert.IsType( UiElement.FindDescendant(heritageRoot, buttonId)); } Assert.IsType( UiElement.FindDescendant(heritageRoot, 0x100003C4u)); } /// Seven template buttons, six attribute sliders (each with a /// lock button + scrollbar + value text), and the four derived /// displays (Profession page — /// gmCGProfessionPage::InitializePage @ 0x00482d50). [InstalledDatFact] public void ProfessionPage_HasTemplateButtonsSlidersAndDisplays() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); UiElement professionRoot = Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.ProfessionPageElementId)); uint[] templateButtonIds = [ 0x100003D9u, 0x100003DAu, 0x100003DBu, 0x100003DCu, 0x100003DDu, 0x100003DEu, 0x100003DFu, ]; foreach (uint buttonId in templateButtonIds) { Assert.IsType( UiElement.FindDescendant(professionRoot, buttonId)); } uint[] sliderContainerIds = [ 0x100003E6u, 0x100003E7u, 0x100003E8u, 0x100003E9u, 0x100003EAu, 0x100003EBu, ]; foreach (uint containerId in sliderContainerIds) { UiElement container = Assert.IsAssignableFrom( UiElement.FindDescendant(professionRoot, containerId)); Assert.IsType( UiElement.FindDescendant(container, 0x100002EEu)); // The value display authors as an editable Type-12 (retail's // NumberInputFilter) — DatWidgetFactory maps that to UiField, // not UiText. See CharacterCreationProfessionPage's ctor comment. Assert.IsType( UiElement.FindDescendant(container, 0x100002EFu)); } // Avail/health/stamina/mana each author as a Button whose Type-12 // value child is swallowed by UiButton.ConsumesDatChildren — the // same substitution the Skills page's credits meter needed. See // CharacterCreationProfessionPage's ctor comment. foreach (uint containerId in new[] { 0x100003E2u, 0x100003E3u, 0x100003E4u, 0x100003E5u }) { Assert.IsType( UiElement.FindDescendant(professionRoot, containerId)); } } /// Skills listbox, credits meter, info panes (Skills page — /// gmCGSkillsPage::InitializePage @ 0x00481dd0). [InstalledDatFact] public void SkillsPage_HasListboxCreditsAndInfoPanes() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); UiElement skillsRoot = Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.SkillsPageElementId)); Assert.IsType( UiElement.FindDescendant(skillsRoot, 0x100003F7u)); // The credits meter (decomp id 0x100002f3) authors as a raw dat // child of button 0x100003f9; UiButton.ConsumesDatChildren swallows // it before it becomes an addressable widget, so the faithful // acdream substitute is the button's own Label — see // CharacterCreationSkillsPage's ctor comment. Assert.IsType( UiElement.FindDescendant(skillsRoot, 0x100003F9u)); Assert.IsType( UiElement.FindDescendant(skillsRoot, 0x100003FBu)); Assert.IsType( UiElement.FindDescendant(skillsRoot, 0x100003FCu)); } /// Four town buttons + description text (Town page — /// gmCGTownPage::InitializePage @ 0x0047c6d0). [InstalledDatFact] public void TownPage_HasFourStarterAreaButtons() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); uint layoutId = RetailDataIdResolver.Resolve( dats, CharacterCreationUiController.RootEnum, 5u); ImportedLayout screen = BuildSelected( dats, layoutId, CharacterCreationUiController.RootElementId); UiElement townRoot = Assert.IsAssignableFrom( screen.FindElement(CharacterCreationUiController.TownPageElementId)); foreach (uint buttonId in new[] { 0x1000040Bu, 0x1000040Du, 0x1000040Eu, 0x1000040Fu }) { Assert.IsType( UiElement.FindDescendant(townRoot, buttonId)); } Assert.IsType( UiElement.FindDescendant(townRoot, 0x10000409u)); } /// The exit-warning + all per-heritage/per-town DAT string /// keys this slice cites actually resolve in the installed table. /// [InstalledDatFact] public void RequiredChargenStringsResolve() { using var dats = new DatCollection(DatDirectory, DatAccessType.Read); var strings = new DatStringResolver(dats); const uint table = 0x23000002u; string[] keys = [ "ID_CharGen_ExitWarning", "ID_CharGen_Heritage_StartingSkills_Header", "ID_CharGen_Heritage_StartingSkills", "ID_CharGen_Heritage_BonusSkills_Trained_Header", "ID_CharGen_AluvianText_BonusSkills_Trained", "ID_CharGen_GaruText_BonusSkills_Trained", "ID_CharGen_ShoText_BonusSkills_Trained", "ID_CharGen_ViaText_BonusSkills_Trained", "ID_CharGen_ShadText_BonusSkills_Trained", "ID_CharGen_GearText_BonusSkills_Trained", "ID_CharGen_AunTText_BonusSkills_Trained", "ID_CharGen_EmpText_BonusSkills_Trained", "ID_CharGen_UndText_BonusSkills_Trained", "ID_CharGen_TownHowTo", "ID_CharGen_HoltText", "ID_CharGen_ShoushiText", "ID_CharGen_YaraqText", "ID_CharGen_SanamarText", ]; foreach (string key in keys) { string? resolved = strings.Resolve(table, DatStringResolver.ComputeHash(key)); Assert.True(resolved is not null, $"missing string: {key}"); } } private static RetailDialogFactory MakeDialogFactory(IDatReaderWriter dats, UiRoot host) { uint dialogDid = RetailDataIdResolver.Resolve(dats, 2u, 5u); ImportedLayout? CreateLayout(RetailDialogType type) { uint rootElementId = RetailDialogFactory.RootElementId(type); return rootElementId == 0u ? null : LayoutImporter.Import( dats, dialogDid, rootElementId, _ => (0u, 0, 0), null); } return new RetailDialogFactory(host, CreateLayout); } private static void AssertButton(ImportedLayout layout, uint elementId) => Assert.IsType(layout.FindElement(elementId)); private static ImportedLayout BuildSelected( IDatReaderWriter dats, uint layoutDid, uint rootId) { ElementInfo info = Assert.IsType( LayoutImporter.ImportInfos(dats, layoutDid, rootId)); return LayoutImporter.Build( info, _ => (0u, 0, 0), null, null, new DatStringResolver(dats).Resolve); } }