FT SDK: Difference between revisions

From JFTSE Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 1: Line 1:
== Overview ==
== Overview ==
This page documents the current reverse engineering progress for the Fantasy Tennis client-side SDK used by JFTSE tooling. The SDK maps known client structures, virtual functions, GUI objects, stage management, XML dialog loading, popup ownership and input routing into C++ wrappers that can be used from injected debugging or extension code.


The current focus is the client GUI layer around <code>StageManager</code>, <code>ScreenRoot</code>, <code>StageBase</code>, <code>GameDialogStage</code> and Adu GUI controls. Confirmed areas include the fixed 24-stage model, current-stage lookup, parent-chain/process/open-state behavior, built-in popup slot creation, XML dialog loading, command-ID binding, callback dispatch and Adu object state handling.
This page documents the current reverse engineering baseline for the Fantasy Tennis client-side SDK used by JFTSE tooling, injected debugging code and client UI experiments.


This page is the current reverse engineering handoff for the SDK. Names, offsets and layouts are updated only after they are verified through disassembly, runtime dumps and real client call sites.
The SDK maps the known client GUI/stage layer into C++ wrappers around the original client structures. The current focus is the UI system around <code>StageManager</code>, <code>ScreenRoot</code>, <code>ScreenInputRoot</code>, <code>StageBase</code>, <code>GameDialogStage</code>, <code>AduGuiDialog</code>, <code>AduGuiControl</code> and all known built-in Adu GUI controls.


== Scope ==
The practical goal of this SDK is to make custom client UI possible without adding unsupported client stages. The Fantasy Tennis client uses a fixed stage model, so custom UI should attach to an existing stage owner, popup owner or embedded/custom GUI owner instead of creating an additional global stage.
* <code>StageManager</code> state tracking, stage switching and current-stage access.
 
* Fixed 24-stage owner list and safe current-stage attachment.
The SDK is intentionally conservative. Names describe current reverse engineering understanding, not original source names. Offsets, virtual slots and function addresses are tied to the currently analyzed Fantasy Tennis client build.
* <code>ScreenRoot</code> parent/child ownership, input routing, open state and processing state.
 
* <code>StageBase</code> XML dialog ownership, child-owner lists, priority input child handling and GUI event dispatch.
== Current Focus ==
* <code>GameDialogStage</code> built-in popup slots, popup factory behavior, selected/active popup index and tray-popup related state.
 
* Game-dialog lifecycle hooks, including XML initialization, activation, update, back/default action and popup close paths.
The currently documented SDK covers:
* Adu GUI object hierarchy, including active/update/visible flags, timers, child traversal and recursive processing.
 
* Adu dialog/control layouts, including object arrays, command IDs, control names, control types, state sets and basic runtime flags.
* <code>StageManager</code> state tracking, current-stage access and stage switching helpers.
* XML GUI binding through <code>GuiBind</code> arrays, command IDs and control type IDs.
* <code>ScreenRoot</code> ownership, parent chains, open state, processing state and input routing.
* Default GUI callback routing through <code>AduGuiDialog</code> callback/user-data fields.
* <code>StageBase</code> XML dialog ownership, GUI callback routing, priority child-owner behavior and event dispatch.
* Confirmed event dispatch for buttons, radio buttons, edit boxes, context menus and related GUI events.
* <code>GameDialogStage</code> built-in popup slots, popup creation helpers, selected/active popup state and game-dialog lifecycle hooks.
* Confirmed edit-box text access through the wide-character text pointer at the current <code>AduGuiEditBox</code> layout.
* <code>AduGuiDialog</code> runtime layout, object array access, command-ID lookup, name lookup and callback/user-data storage.
* Group-style control activation behavior used by ranking and popup XML, especially active/visible state synchronization.
* <code>AduGuiControl</code> common runtime fields such as name, command id, type id, group id, subgroup id, rectangle, state sets, text and context menu data.
* Built-in Adu GUI controls:
** <code>AduGuiStatic</code>
** <code>AduGuiButton</code>
** <code>AduGuiCheckBox</code>
** <code>AduGuiRadioButton</code>
** <code>AduGuiComboBox</code>
** <code>AduGuiSlider</code>
** <code>AduGuiGauge</code>
** <code>AduGuiEditBox</code>
** <code>AduGuiIMEEditBox</code>
** <code>AduGuiListBox</code>
** <code>AduGuiScrollBar</code>
** <code>AduGuiContextMenu</code>
* Generic custom GUI controls through <code>AduGuiCustomControlBase</code>.
* Known concrete custom controls such as <code>FTRankingInfo</code>.
* XML binding through <code>GuiBind</code> arrays, command ids and control type ids.
* Runtime text assignment for static controls through <code>FTSDK::SetStaticText</code>.
* Custom type name registration through <code>GuiCustomControlRegistry</code>.
 
== Custom UI Direction ==
 
The preferred custom UI direction is now:
 
# Create or reuse a <code>GameDialogStage</code>/<code>StageBase</code> owner attached to an existing game stage or popup owner.
# Load a normal XML dialog with <code>StageBase::LoadXml</code>.
# Register additional XML control type names with <code>GuiCustomControlRegistry::RegisterTypeName</code>.
# For simple embedded GUI controls, map a custom XML type name to <code>GuiControlType::Custom</code> / type id <code>12</code>.
# For each generic custom control instance, call <code>AduGuiCustomControlBase::LoadEmbeddedGuiXml</code>.
# Cache the inner controls from the embedded <code>AduGuiDialog</code>.
# Update inner controls directly, for example with <code>FTSDK::SetStaticText</code>.
 
This mirrors the real client pattern used by controls such as <code>FTRankingInfo</code>: the outer custom control is created by the dialog loader, then its concrete load function loads a separate <code>GuiCtrl_*.xml</code>, binds/caches the inner controls and refreshes them from runtime data.
 
Important: registering a name such as <code>MyCustomType</code> to type id <code>12</code> makes <code>Type="MyCustomType"</code> resolve to the generic custom control base. This does not create a new factory-backed custom type. Using type ids above the built-in range such as <code>100</code>, requires extending or hooking the custom control factory. Without that, the default factory only creates the known built-in custom control ids.
 
== Current Custom UI Caveats ==
 
The custom UI path is partly proven but not fully finished.
 
Known working pieces:
 
* The client can resolve a custom XML type name to generic custom type <code>12</code>.
* <code>StageBase::LoadXml</code> can create generic custom control instances from XML.
* The created generic custom controls appear in <code>AduGuiDialog::objectArray</code>.
* <code>AduGuiCustomControlBase::LoadEmbeddedGuiXml</code> can load a nested <code>GuiCtrl_*.xml</code>.
* The embedded dialog can be inspected and inner controls can be cached.
* Static text can be written through <code>FTSDK::SetStaticText</code>.
 
Known caveats:
 
* In manually constructed/attached owners, <code>StageBase::controlBindMap</code> may not contain every control even when the dialog loaded correctly. For robust lookup, fall back to <code>AduGuiDialog::FindControlByCommandId</code> and then <code>AduGuiDialog::FindControlByName</code>.
* Setting active/visible state on the outer custom row is not always enough if the embedded child dialog/control tree is not being processed or rendered by the same path as concrete client controls.
* The generic custom control rendering/update path still needs more reversing. The next useful target is the base custom control implementation used by real <code>GuiCtrl_*</code> controls and the concrete GUI popup/stage base classes.
* Closing or hiding existing popups may happen by priority-child/parent-chain changes rather than by clean deactivation. Do not rely only on <code>OnDeactivate</code>.


== Stability Notes ==
== Stability Notes ==
Offsets and function addresses are version specific to the currently analyzed Fantasy Tennis client build. Names describe current understanding, not original source names. Any field marked unknown, maybe or conservative should be treated as subject to change until confirmed by call sites, runtime tests or additional disassembly.


Important caveat: custom GUI work should not add another stage. The client uses a fixed set of 24 stage owners. Custom UI should attach to the current stage owner, an existing popup path or the embedded modal/input owner where appropriate.
All offsets and function addresses are version specific. They are verified against the currently analyzed Fantasy Tennis client build through disassembly, runtime dumps, vtable summaries and real call sites.
 
The SDK should be treated as a reverse engineering aid, not a stable API. Fields marked <code>unknown</code>, <code>field*</code>, <code>maybe</code> or described conservatively are intentionally not finalized.
 
Do not add another global stage. The client uses a fixed 24 stage model. Attach custom UI to an existing stage, popup owner, modal owner or embedded GUI owner.
 
When testing custom UI, prefer defensive lookup and logging:
 
* Check <code>owner->XmlDialog()</code>.
* Dump <code>AduGuiDialog::objectArray</code>.
* Prefer <code>FindControlByCommandId</code>.
* Fall back to <code>FindControlByName</code>.
* Verify <code>ControlTypeId()`.
* Check <code>active</code>, <code>visible</code>, <code>openFlag</code>, <code>processEnabled</code> and <code>parentChainEnabled</code>.
* Verify whether the target owner is reachable through the parent chain.


== Current SDK Header ==
== Current SDK Header ==
Line 125: Line 191:
                 if (!IsValid())
                 if (!IsValid())
                     return L"";
                     return L"";
                 return IsInline() ? inlineBuffer : heapPtr;
                 return IsInline() ? inlineBuffer : heapPtr;
             }
             }
Line 135: Line 202:
                 if (!text || !IsValid())
                 if (!text || !IsValid())
                     return false;
                     return false;
                 const size_t textLen = std::wcslen(text);
                 const size_t textLen = std::wcslen(text);
                 return textLen == length && std::memcmp(Data(), text, length * sizeof(wchar_t)) == 0;
                 return textLen == length && std::memcmp(Data(), text, length * sizeof(wchar_t)) == 0;
Line 210: Line 278:
             uint8_t pad16[2];
             uint8_t pad16[2];


             bool IsNil() const { return isNil != 0; }
             bool IsNil() const {
                return isNil != 0;
            }
         };
         };


Line 219: Line 289:
             uint32_t count;
             uint32_t count;


             bool IsEmpty() const { return count == 0; }
             bool IsEmpty() const {
             uint32_t Size() const { return count; }
                return count == 0;
             FTTreeNode<Key, Value>* Head() const { return head; }
            }
 
             uint32_t Size() const {
                return count;
            }
 
             FTTreeNode<Key, Value>* Head() const {
                return head;
            }


             FTTreeNode<Key, Value>* Root() const {
             FTTreeNode<Key, Value>* Root() const {
Line 257: Line 335:
             }
             }


             bool Contains(const Key& key) const { return FindNode(key) != nullptr; }
             bool Contains(const Key& key) const {
                return FindNode(key) != nullptr;
            }
         };
         };


Line 264: Line 344:
             const char* controlName;
             const char* controlName;
             int32_t controlTypeId;
             int32_t controlTypeId;
        };
        struct Point {
            int32_t x;
            int32_t y;
         };
         };


Line 280: Line 365:
             int32_t bottom;
             int32_t bottom;
         };
         };


         struct StageOwnedResource {
         struct StageOwnedResource {
Line 328: Line 412:
         };
         };


        static_assert(sizeof(Point) == 0x08);
         static_assert(sizeof(Rect) == 0x10);
         static_assert(sizeof(Rect) == 0x10);
         static_assert(sizeof(GuiRectStorage) == 0x14);
         static_assert(sizeof(GuiRectStorage) == 0x14);
Line 383: Line 468:
         ListBox = 9,
         ListBox = 9,
         ScrollBar = 10,
         ScrollBar = 10,
         ContextMenu = 11
         ContextMenu = 11,
Custom = 12,
 
        FTRankingInfoType = 68,
    };
 
    enum GuiVisualStateName : int32_t {
        StateAll = 0,
        StateNormal = 1,
        StateOver = 2,
        StateFocus = 3,
        StatePressed = 4,
        StateDisabled = 5,
        StateHidden = 6
    };
 
    enum GuiBlendMode : int32_t {
        BlendModulate = 0,
        BlendAdd = 1
    };
 
    enum GuiTextFx : int32_t {
        TextFxNone = 0,
        TextFxShadow = 1,
        TextFxOutline = 2
     };
     };


Line 392: Line 501:
         MessageBox_TimedYesNo = 4
         MessageBox_TimedYesNo = 4
     };
     };
    using GuiCallbackFn = void(FTSDK_STDCALL*)(
        GuiEventType eventType,
        int commandId,
        int param,
        void* userData
        );


     struct AduGuiStateSet;
     struct AduGuiStateSet;
     class AduGuiDialog;
     class AduGuiDialog;
    class AduGuiControl;
    class AduGuiStatic;
    class AduGuiButton;
    class AduGuiRadioButton;
    class AduGuiCheckBox;
    class AduGuiComboBox;
    class AduGuiEditBox;
    class AduGuiIMEEditBox;
    class AduGuiListBox;
    class AduGuiScrollBar;
    class AduGuiSlider;
    class AduGuiGauge;
    class AduGuiContextMenu;
    using GuiCallbackFn = void(FTSDK_STDCALL*)(GuiEventType eventType, int commandId, int param, void* userData);
    using CreateCustomGuiControlFn = AduGuiControl * (FTSDK_CDECL*)(AduGuiDialog* dialog, int32_t customTypeId);


     struct AduGuiVisualStateInfo {
     struct AduGuiVisualStateInfo {
Line 417: Line 535:
     static_assert(sizeof(AduGuiVisualStateInfo) == 0x28);
     static_assert(sizeof(AduGuiVisualStateInfo) == 0x28);


     // Dialog side parsed XML/control definition. This is not AduGuiControl storage.
    struct AduGuiParsedStateDef {
     // It is initialized by sub_624F90, populated by sub_614F60, then passed into
        int32_t image[6]; // +0x00
     // AduGuiControl::LoadFromXml / concrete LoadFromXml overrides.
        int32_t color[6]; // +0x18
        int32_t textColor[6]; // +0x30
        int32_t textFxColor[6]; // +0x48
        int32_t blend[6]; // +0x60
        int32_t fx[6]; // +0x78
 
        uint8_t scale[6]; // +0x90
        uint8_t tiling[6]; // +0x96
 
        int32_t offsetX[6]; // +0x9C
        int32_t offsetY[6]; // +0xB4
    };
    static_assert(sizeof(AduGuiParsedStateDef) == 0xCC);
 
     // Dialog-side parsed XML/control definition.
    // This is not AduGuiControl runtime storage.
     //
    // Confirmed:
    // - sub_624F90 initializes this structure.
    // - sub_614F60 fills it from one <Control>.
     // - AduGuiControl::LoadFromXml consumes it.
    // - parsedStates uses 0xCC-byte parser-side state records.
    // - eventSoundIds are indexed by GuiEventType and initialized to invalid/NaN-like values.
     struct AduGuiParsedControlDef {
     struct AduGuiParsedControlDef {
         uint32_t unknown00;
         Structs::FTVector<AduGuiParsedStateDef> parsedStates; // +0x0000
        uint32_t stateBegin; // +0x04, parsed state array begin/pointer-like
 
        uint32_t stateEnd; // +0x08, parsed state array end/pointer-like
         int32_t eventSoundIds[20]; // +0x0010, indexed by GuiEventType
        uint32_t unknown0C; // +0x0C
 
         int32_t eventField[20]; // +0x10, initialized to -1; indexed by GuiEventType
         uint8_t stateAndImageParseStorage[0x804]; // +0x0060..+0x0863
         uint8_t unknown60[0x804]; // +0x60...+0x863
 
         wchar_t resolvedText[256]; // +0x864, assigned to AduGuiControl::text
         wchar_t resolvedText[256]; // +0x0864, assigned to AduGuiControl::text
         uint8_t unknownA64[0x600]; // +0xA64...+0x1063
 
         uint8_t enable; // +0x1064, XML Enable, default 1
         uint8_t tooltipAndMenuParseStorage[0x600]; // +0x0A64..+0x1063
 
         uint8_t enable;       // +0x1064, XML Enable, default 1
         uint8_t enabledState; // +0x1065, XML EnabledState, default 1
         uint8_t enabledState; // +0x1065, XML EnabledState, default 1
         uint8_t pad1066[2]; // +0x1066
         uint8_t pad1066[2];
         int32_t x;
 
         int32_t y;
         int32_t x;     // +0x1068, XML x
         int32_t width;
         int32_t y;     // +0x106C, XML y
         int32_t height;
         int32_t width; // +0x1070, XML w
         int32_t alignX;
         int32_t height; // +0x1074, XML h
         int32_t alignY;
 
         float scaleX;
         int32_t alignX; // XML AlignX
         float scaleY;
         int32_t alignY; // XML AlignY
         float rotate;
 
         float centerX;
         float scaleX; // XML ScaleX, default 1.0
         float centerY;
         float scaleY; // XML ScaleY, default 1.0
         int32_t flipX;
         float rotate; // XML Rotate
         int32_t textAlignX;
         float centerX; // XML CenterX
         int32_t textAlignY;
         float centerY; // XML CenterY
         int32_t textFx;
 
         int32_t textMarginLeft;
         int32_t flipX; // XML FlipX
 
         int32_t textAlignX; // XML TextAlignX
         int32_t textAlignY; // XML TextAlignY
         int32_t textFx;     // XML TextFx
 
         int32_t textMarginLeft;   // XML TextMargin
         int32_t textMarginTop;
         int32_t textMarginTop;
         int32_t textMarginRight;
         int32_t textMarginRight;
         int32_t textMarginBottom;
         int32_t textMarginBottom;
         int32_t wordWrap;
 
         int32_t groupId;
         int32_t wordWrap;   // XML WordWrap
         int32_t subGroupId;
         int32_t groupId;   // XML GroupID
         int32_t subGroupId; // XML SubGroupID
 
         uint8_t unknown10C0[0x24];
         uint8_t unknown10C0[0x24];
         int32_t tabStop; // +0x10E4
 
         int32_t initialStage;
         int32_t tabStop;     // +0x10E4, XML TabStop
         int32_t bindStage;
         int32_t initialStage; // XML Stage
         int32_t hotKey;
         int32_t bindStage;   // XML BindStage
         char contextMenuName[256]; // +0x10F4
         int32_t hotKey;       // XML HotKey
 
         char contextMenuName[256]; // +0x10F4, XML Menu
     };
     };
    static_assert(sizeof(AduGuiParsedControlDef) == 0x11F4);


     namespace Constants {
     namespace Constants {
        // Confirmed by StageManager InitStages / SwitchStateNow bounds.
        // Do not add a custom 25th stage; attach custom UI to an existing owner/modal
         constexpr int32_t StageOwnerCount = 24;
         constexpr int32_t StageOwnerCount = 24;
        // These are built-in popup owner slots at FTGameDialogOwner + 0x1E8
        // Not proof that every stage uses all 10 slots
         constexpr int32_t KnownBuiltinPopupSlotCount = 10;
         constexpr int32_t KnownBuiltinPopupSlotCount = 10;
        constexpr int32_t GuiEventTypeCount = 20;
        constexpr int32_t GuiVisualStateNameCount = 7;
        constexpr int32_t ParsedGuiStateSize = 0xCC;
     }
     }


Line 483: Line 635:
         }
         }
     };
     };
    static_assert(sizeof(AduBase) == 0x08);


     class AduGameObj : public AduBase {
     class AduGameObj : public AduBase {
     public:
     public:
        virtual AduGameObj* FTSDK_THISCALL Destroy(uint32_t flags) = 0;
         virtual bool FTSDK_THISCALL SetDirtyValue(int32_t value) = 0;
         virtual bool FTSDK_THISCALL SetDirtyValue(int32_t value) = 0;
         virtual int32_t FTSDK_THISCALL ResetTimerRecursive() = 0;
         virtual int32_t FTSDK_THISCALL ResetElapsedTimeRecursive() = 0;
         virtual bool FTSDK_THISCALL SetActive(bool value) = 0;
         virtual bool FTSDK_THISCALL SetActive(bool value) = 0;
         virtual bool FTSDK_THISCALL SetUpdateBlocked(bool value) = 0;
         virtual bool FTSDK_THISCALL SetUpdateBlocked(bool value) = 0;
Line 507: Line 659:


         float elapsedTime;
         float elapsedTime;
         float deltaTime; // +0x1C
         float deltaTime;


     public:
     public:
Line 530: Line 682:
         }
         }
     };
     };
    static_assert(sizeof(AduGameObj) == 0x20);


     class AduGuiControl : public AduGameObj {
     class AduGuiControl : public AduGameObj {
     public:
     public:
         virtual bool FTSDK_THISCALL RecalculateRect() = 0; // vt+0x20
         virtual bool FTSDK_THISCALL RecalculateRect() = 0; // vt+0x20, rect = {x,y,x+w,y+h}
         virtual bool FTSDK_THISCALL LoadFromXml(const char* name, void* xmlParser, AduGuiParsedControlDef* parsedControlDef) = 0;
         virtual bool FTSDK_THISCALL LoadFromXml(const char* name, void* xmlParser, AduGuiParsedControlDef* parsedControlDef) = 0;
         virtual bool FTSDK_THISCALL PostLoadResolve() = 0;
         virtual bool FTSDK_THISCALL PostLoadResolve() = 0;
        virtual AduGameObj* FTSDK_THISCALL GetInnerObject() = 0;


         virtual void FTSDK_THISCALL ControlVfunc30() = 0; // vt+0x30
         virtual AduGuiStatic* FTSDK_THISCALL AsStatic() = 0; // vt+0x2C
         virtual void FTSDK_THISCALL ControlVfunc34() = 0; // vt+0x34
         virtual AduGuiButton* FTSDK_THISCALL AsButton() = 0;
         virtual void FTSDK_THISCALL ControlVfunc38() = 0; // vt+0x38
        virtual AduGuiRadioButton* FTSDK_THISCALL AsRadioButton() = 0;
         virtual void FTSDK_THISCALL ControlVfunc3C() = 0; // vt+0x3C
         virtual AduGuiCheckBox* FTSDK_THISCALL AsCheckBox() = 0;
         virtual void FTSDK_THISCALL ControlVfunc40() = 0; // vt+0x40
         virtual AduGuiComboBox* FTSDK_THISCALL AsComboBox() = 0;
         virtual void FTSDK_THISCALL ControlVfunc44() = 0; // vt+0x44
         virtual AduGuiEditBox* FTSDK_THISCALL AsEditBox() = 0;
         virtual void FTSDK_THISCALL ControlVfunc48() = 0; // vt+0x48
         virtual AduGuiIMEEditBox* FTSDK_THISCALL AsIMEEditBox() = 0;
         virtual void FTSDK_THISCALL ControlVfunc4C() = 0; // vt+0x4C
         virtual AduGuiListBox* FTSDK_THISCALL AsListBox() = 0;
         virtual void FTSDK_THISCALL ControlVfunc50() = 0; // vt+0x50
         virtual AduGuiScrollBar* FTSDK_THISCALL AsScrollBar() = 0;
         virtual void FTSDK_THISCALL ControlVfunc54() = 0; // vt+0x54
         virtual AduGuiSlider* FTSDK_THISCALL AsSlider() = 0;
         virtual void FTSDK_THISCALL ControlVfunc58() = 0; // vt+0x58
         virtual AduGuiGauge* FTSDK_THISCALL AsGauge() = 0;
         virtual AduGuiContextMenu* FTSDK_THISCALL AsContextMenu() = 0;


         virtual int FTSDK_THISCALL ControlVfunc5C() = 0; // vt+0x5C
         virtual bool FTSDK_THISCALL DefaultFalse5C() = 0;
         virtual int FTSDK_THISCALL ControlVfunc60() = 0; // vt+0x60
         virtual bool FTSDK_THISCALL IsOwnerOrRelatedObject(void* candidate) = 0;
         virtual int FTSDK_THISCALL ControlVfunc64() = 0; // vt+0x64
         virtual bool FTSDK_THISCALL DefaultTrue64() = 0;
         virtual int FTSDK_THISCALL ControlVfunc68() = 0; // vt+0x68
         virtual int FTSDK_THISCALL ResetInteractionState() = 0;
         virtual bool FTSDK_THISCALL ControlHandleInputA(uint32_t msg, int32_t wParam, int32_t lParam) = 0; // vt+0x6C
         virtual bool FTSDK_THISCALL HandleKeyboardCharInput(uint32_t msg, int32_t wParam, int32_t lParam) = 0;
         virtual bool FTSDK_THISCALL ControlHandleInputB(uint32_t msg, int32_t wParam, int32_t lParam) = 0; // vt+0x70
         virtual bool FTSDK_THISCALL HandleKeyboardNavigation(uint32_t msg, int32_t wParam, int32_t lParam) = 0;
         virtual int FTSDK_THISCALL ControlVfunc74() = 0; // vt+0x74
         virtual bool FTSDK_THISCALL HandleMouseInput(uint32_t msg, int32_t wParam, int32_t lParam, int32_t a4, int32_t a5) = 0;
         virtual int FTSDK_THISCALL ControlVfunc78() = 0; // vt+0x78
         virtual bool FTSDK_THISCALL DefaultFalse78() = 0;
         virtual int FTSDK_THISCALL ControlVfunc7C() = 0; // vt+0x7C
         virtual void FTSDK_THISCALL SetPressedFlag() = 0;
         virtual int FTSDK_THISCALL ControlVfunc80() = 0; // vt+0x80
         virtual void FTSDK_THISCALL ClearPressedFlag() = 0;
         virtual int FTSDK_THISCALL ControlVfunc84() = 0; // vt+0x84
         virtual void FTSDK_THISCALL SetOverFlag() = 0;
         virtual int FTSDK_THISCALL ControlVfunc88() = 0; // vt+0x88
         virtual void FTSDK_THISCALL ClearOverFlag() = 0;
         virtual int FTSDK_THISCALL ControlVfunc8C() = 0; // vt+0x8C
         virtual void FTSDK_THISCALL OnHotKeyMatched() = 0;
         virtual int FTSDK_THISCALL ControlVfunc90() = 0; // vt+0x90
         virtual Structs::Rect* FTSDK_THISCALL GetRectPtr() = 0;
         virtual int FTSDK_THISCALL ControlVfunc94() = 0; // vt+0x94
         virtual bool FTSDK_THISCALL ContainsPoint(Structs::Point pt) = 0;
         virtual int FTSDK_THISCALL ControlVfunc98() = 0; // vt+0x98
         virtual bool FTSDK_THISCALL SetEnabledState(bool value) = 0;
         virtual bool FTSDK_THISCALL SetEnabledState(bool value) = 0; // vt+0x9C
         virtual bool FTSDK_THISCALL IsEnabledState() = 0;
         virtual int FTSDK_THISCALL ControlVfuncA0() = 0; // vt+0xA0
         virtual bool FTSDK_THISCALL DefaultFalseA0() = 0;
         virtual int FTSDK_THISCALL ControlVfuncA4() = 0; // vt+0xA4
         virtual bool FTSDK_THISCALL HandleHotKey(int32_t hotKeyValue) = 0;
         virtual int FTSDK_THISCALL ControlVfuncA8() = 0; // vt+0xA8
         virtual int FTSDK_THISCALL ApplyStateFieldE7E0(int32_t stateSetIndex, int32_t visualStateIndex, int32_t parsedStateIndex) = 0;
         virtual int FTSDK_THISCALL ControlVfuncAC() = 0; // vt+0xAC
         virtual int FTSDK_THISCALL ApplyStateFieldE770(int32_t stateSetIndex, int32_t visualStateIndex) = 0;
         virtual AduGuiStateSet* FTSDK_THISCALL GetStateSet(int32_t stateIndex) = 0; // vt+0xB0
         virtual AduGuiStateSet* FTSDK_THISCALL GetStateSet(int32_t stateSetIndex) = 0;
         virtual int FTSDK_THISCALL ControlVfuncB4() = 0; // vt+0xB4
         virtual int FTSDK_THISCALL EnsureStateSetCopy(uint32_t stateSetIndex, const AduGuiStateSet* source) = 0;
         virtual int FTSDK_THISCALL ControlVfuncB8() = 0; // vt+0xB8
         virtual int FTSDK_THISCALL UpdateCurrentVisualState() = 0;
         virtual bool FTSDK_THISCALL CanAddStateSets() = 0; // vt+0xBC
         virtual int FTSDK_THISCALL SetStateFlagE8A0(int32_t visualStateIndex, bool value, int32_t stateSetIndex) = 0;
         virtual void FTSDK_THISCALL SetStateTilingOrFlag(int32_t stateIndex, float a3, float a4, int32_t stageIndex) = 0; // vt+0xC0
         virtual int FTSDK_THISCALL SetStateRangeD850(int32_t visualStateIndex, float minValue, float maxValue, int32_t stateSetIndex) = 0;
         virtual int FTSDK_THISCALL ControlVfuncC4() = 0; // vt+0xC4
         virtual int FTSDK_THISCALL ClearStateRuntimeFlag(int32_t visualStateIndex) = 0;
         virtual int FTSDK_THISCALL ControlVfuncC8() = 0; // vt+0xC8
         virtual int FTSDK_THISCALL ClearVisualStateInfo(int32_t visualStateIndex) = 0;
         virtual void FTSDK_THISCALL SetStateTimingOrFx(int32_t stateIndex, int32_t enabled, float timing, int32_t alternateStateIndex) = 0; // vt+0xCC
         virtual int FTSDK_THISCALL ConfigureVisualStateInfo(int32_t visualStateIndex, bool enabled, float timing, int32_t alternateStateIndex) = 0;
         virtual void FTSDK_THISCALL SetEventField(int32_t value, int32_t eventIndex) = 0; // vt+0xD0
         virtual int FTSDK_THISCALL SetEventSoundId(int32_t soundId, int32_t eventIndex) = 0;
         virtual int FTSDK_THISCALL ControlVfuncD4() = 0; // vt+0xD4
         virtual int FTSDK_THISCALL GetEventSoundId(int32_t eventIndex) = 0;


     public:
     public:
         AduGuiStateSet** stateSets;
         AduGuiStateSet** stateSets; // +0x20
         int32_t stateCount;
         int32_t stateCount;
         int32_t stateCapacity;
         int32_t stateCapacity;


         Structs::FTStringA name;
         Structs::FTStringA name; // XML Name


         int32_t commandId;
         int32_t commandId;     // +0x48, bind command id, ctor -1
         int32_t groupId;
         int32_t groupId;       // XML GroupID
         int32_t controlTypeId;
         int32_t controlTypeId; // XML Type, GuiControlType
         int32_t hotKey;
         int32_t hotKey;       // XML HotKey
         int32_t field58;
         int32_t subGroupId;   // XML SubGroupID, likely


         uint8_t enabledFlag;
         uint8_t enabledStateFlag; // XML EnabledState via SetEnabledState
        uint8_t interactionActiveFlag;
        uint8_t focusFlag;
        uint8_t pad5F;


         uint8_t field5D;
         Structs::Rect rect; // calculated from x/y/width/height
        uint8_t pad5E[2];


         Structs::Rect rect;
         int32_t currentVisualState; // ctor 5
        int32_t selectedStateIndex; // XML Stage / selected visual stage


        int32_t currentVisualState;
         uint8_t bindStage; // XML BindStage
        int32_t selectedStateIndex;
         uint8_t debugFlag; // XML Debug
 
         uint8_t bindStage;
         uint8_t debugFlag;
         uint8_t overFlag;
         uint8_t overFlag;
         uint8_t pressedFlag;
         uint8_t pressedFlag;
Line 619: Line 770:


         AduGuiDialog* ownerDialog;
         AduGuiDialog* ownerDialog;
         int32_t field94;
         void* relatedObject;
         int32_t controlIndex;
         int32_t controlIndex;
         int32_t dx;
         int32_t dx;
Line 627: Line 778:


         Structs::FTStringW text;
         Structs::FTStringW text;
         AduGuiControl* resolvedContextMenuControl;
         AduGuiControl* resolvedContextMenuControl; // resolved from XML Menu, type ContextMenu
         Structs::FTStringA contextMenuName;
         Structs::FTStringA contextMenuName;       // XML Menu


         uint8_t field1F8;
         uint8_t field1F8; // ctor 1
         uint8_t pad1F9[3]; // +0x1F9
         uint8_t pad1F9[3];


     public:
     public:
Line 643: Line 794:
         int32_t ControlTypeId() const { return controlTypeId; }
         int32_t ControlTypeId() const { return controlTypeId; }
         int32_t HotKey() const { return hotKey; }
         int32_t HotKey() const { return hotKey; }
        int32_t SubGroupId() const { return subGroupId; }


         bool IsType(GuiControlType type) const { return controlTypeId == static_cast<int32_t>(type); }
         bool IsType(GuiControlType type) const { return controlTypeId == static_cast<int32_t>(type); }
         bool IsEnabledFlag() const { return enabledFlag != 0; }
         bool IsEnabledStateFlag() const { return enabledStateFlag != 0; }
        bool IsInteractionActiveFlag() const { return interactionActiveFlag != 0; }
        bool IsFocusFlag() const { return focusFlag != 0; }
         bool IsBindStage() const { return bindStage != 0; }
         bool IsBindStage() const { return bindStage != 0; }
         bool IsDebugFlag() const { return debugFlag != 0; }
         bool IsDebugFlag() const { return debugFlag != 0; }
Line 668: Line 822:


         AduGuiDialog* OwnerDialog() const { return ownerDialog; }
         AduGuiDialog* OwnerDialog() const { return ownerDialog; }
        void* RelatedObject() const { return relatedObject; }


         const Structs::FTStringW& Text() const { return text; }
         const Structs::FTStringW& Text() const { return text; }
         const Structs::FTStringA& ContextMenuName() const { return contextMenuName; }
         const Structs::FTStringA& ContextMenuName() const { return contextMenuName; }
         AduGuiControl* ResolvedContextMenuControl() const { return resolvedContextMenuControl; }
         AduGuiControl* ResolvedContextMenuControl() const { return resolvedContextMenuControl; }
        template <typename T>
        T* InnerObjectAs() { return reinterpret_cast<T*>(GetInnerObject()); }
        template <typename T>
        const T* InnerObjectAs() const { return reinterpret_cast<const T*>(const_cast<AduGuiControl*>(this)->GetInnerObject()); }
     };
     };
    static_assert(sizeof(AduGuiControl) == 0x1FC);


     class AduGuiStatic : public AduGuiControl {
     class AduGuiStatic : public AduGuiControl {
     public:
     public:
         virtual int FTSDK_THISCALL RenderVisuals(void* renderCtx, int flags, const Structs::Rect* rect) = 0; // vt+0xD8
         virtual int FTSDK_THISCALL RenderVisuals(void* renderCtx, int flags, const Structs::Rect* rect) = 0; // vt+0xD8
         virtual bool FTSDK_THISCALL RenderText(int visualIndex) = 0; // vt+0xDC
         virtual bool FTSDK_THISCALL RenderText(int visualIndex) = 0;
         virtual void FTSDK_THISCALL RenderState(int stateIndex) = 0; // vt+0xE0
         virtual void FTSDK_THISCALL RenderState(int stateIndex) = 0;


     public:
     public:
Line 702: Line 849:


         int32_t textScrollOffsetX;
         int32_t textScrollOffsetX;
         uint32_t field254;
         uint32_t clippedTextWidth;
         uint8_t clippedTextRenderFlag;
         uint8_t clippedTextRenderFlag;
         uint8_t pad259[3]; // +0x259
         uint8_t pad259[3];


     public:
     public:
Line 715: Line 862:
     };
     };


     static_assert(sizeof(AduGuiStatic) == 0x25C);
     class AduGuiButton : public AduGuiStatic {
    public:
        int32_t buttonClickSoundId;
        int32_t buttonOverSoundId;
 
    public:
        int32_t ButtonClickSoundId() const { return buttonClickSoundId; }
        int32_t ButtonOverSoundId() const { return buttonOverSoundId; }
    };


     struct AduGuiEditTextBuffer {
     struct AduGuiEditTextBuffer {
         void (FTSDK_CDECL* callback)(int owner); // +0x00, ctor sets sub_610260
         void (FTSDK_CDECL* callback)(int owner); // +0x00
         void* owner;
         void* owner; // +0x04
         wchar_t* secondaryBuffer; // +0x08, used only when hasSecondaryBuffer is set
         void* scriptStringAnalysis; // +0x08
         wchar_t* text; // +0x0C, absolute AduGuiEditBox +0x218
         wchar_t* text; // +0x0C
         uint32_t capacity; // +0x10, absolute AduGuiEditBox +0x21C
         uint32_t capacity; // +0x10
         uint32_t field14;
         uint32_t field14; // +0x14
         uint8_t field18;
 
         uint8_t dirtyFlag; // +0x18
         uint8_t pad19[3];
         uint8_t pad19[3];
         uint32_t field1C;
 
         uint32_t field20;
         uint32_t field1C; // +0x1C
         uint32_t field24;
         uint32_t field20; // +0x20
         uint32_t field28;
         uint32_t field24; // +0x24
         uint8_t hasSecondaryBuffer;
         uint32_t field28; // +0x28
 
         uint8_t hasScriptStringAnalysis; // +0x2C
         uint8_t pad2D[3];
         uint8_t pad2D[3];
        int32_t field30;
 
        int32_t field34;
         int32_t maxLength; // +0x30
         int32_t field38;


         const wchar_t* Text() const { return text ? text : L""; }
         const wchar_t* Text() const { return text ? text : L""; }
         uint32_t Capacity() const { return capacity; }
         uint32_t Capacity() const { return capacity; }
        int32_t MaxLength() const { return maxLength; }
         std::size_t Length() const { return std::wcslen(Text()); }
         std::size_t Length() const { return std::wcslen(Text()); }
     };
     };
     static_assert(sizeof(AduGuiEditTextBuffer) == 0x3C);
     static_assert(sizeof(AduGuiEditTextBuffer) == 0x34);


     class AduGuiEditBox : public AduGuiControl {
     class AduGuiEditBox : public AduGuiControl {
     public:
     public:
         virtual int FTSDK_THISCALL SetEditBoxColorOrField(int32_t value) = 0; // vt+0xD8
         virtual int FTSDK_THISCALL SetEditTextColor(int32_t color) = 0;


     public:
     public:
         uint32_t lineCacheUnknown; // +0x1FC
         uint32_t lineCacheUnknown; // +0x1FC
         void** lineCacheFirst;
         void** lineCacheFirst; // +0x200
         void** lineCacheLast;
         void** lineCacheLast; // +0x204
         void** lineCacheEnd;
         void** lineCacheEnd; // +0x208


         AduGuiEditTextBuffer editText; // +0x20C
         AduGuiEditTextBuffer editText; // +0x20C


         uint8_t field248[0x20]; // +0x248
         int32_t outerInset; // +0x240
        int32_t innerInset; // +0x244
 
        Structs::Rect textRect; // +0x248
        Structs::Rect visualRect; // +0x258


         double caretBlinkInterval; // +0x268, GetCaretBlinkTime() * 0.001
         double caretBlinkInterval; // +0x268
         double lastCaretBlinkTime; // +0x270, performance-counter seconds
         double lastCaretBlinkTime; // +0x270


         uint8_t field278;
         uint8_t caretVisibleFlag; // +0x278
         uint8_t pad279[3];
         uint8_t pad279[3];


         int32_t field27C;
         int32_t caretIndex; // +0x27C


         uint8_t insertMode;
         uint8_t insertMode; // +0x280
         uint8_t pad281[3];
         uint8_t pad281[3];


         int32_t field284;
         int32_t selectionAnchorIndex; // +0x284
         int32_t colorOrField288;
         int32_t editTextColor; // +0x288
         int32_t field28C;
         int32_t field28C;
         int32_t colorOrField290;
         int32_t selectionColorOrField290;
         int32_t colorOrField294;
         int32_t caretColorOrField294;


         uint8_t field298;
         uint8_t field298;
Line 779: Line 941:
         int32_t field29C;
         int32_t field29C;


         uint8_t field2A0;
         uint8_t mouseSelectingFlag; // +0x2A0
         uint8_t field2A1;
         uint8_t preventOverflowFlag; // +0x2A1
         uint8_t multilineFlag;
         uint8_t multilineFlag; // +0x2A2
         uint8_t field2A3;
         uint8_t field2A3;


         int32_t field2A4;
         int32_t lineHeight; // +0x2A4, XML Option/LineHeight
         int32_t field2A8;
         int32_t field2A8;
         int32_t field2AC;
         int32_t field2AC;
Line 794: Line 956:
         int32_t field2B8;
         int32_t field2B8;


         int32_t editEventFields[7]; // +0x2BC, GuiEventType 8..14
         int32_t editEventSoundIds[7]; // +0x2BC, EditBoxEnter..EditBoxKeyDown


     public:
     public:
Line 805: Line 967:
         bool IsMultiline() const { return multilineFlag != 0; }
         bool IsMultiline() const { return multilineFlag != 0; }


         int32_t EditEventField(GuiEventType eventType) const {
         int32_t EditEventSoundId(GuiEventType eventType) const {
             const int32_t index = static_cast<int32_t>(eventType) - static_cast<int32_t>(EditBoxEnter);
             const int32_t index = static_cast<int32_t>(eventType) - static_cast<int32_t>(EditBoxEnter);
             if (index < 0 || index >= 7)
             if (index < 0 || index >= 7)
                 return -1;
                 return -1;
             return editEventFields[index];
 
             return editEventSoundIds[index];
         }
         }
        const Structs::Rect& TextRect() const { return textRect; }
        const Structs::Rect& VisualRect() const { return visualRect; }
        int32_t OuterInset() const { return outerInset; }
        int32_t InnerInset() const { return innerInset; }
        int32_t CaretIndex() const { return caretIndex; }
        int32_t SelectionAnchorIndex() const { return selectionAnchorIndex; }
        bool HasSelection() const { return caretIndex != selectionAnchorIndex; }
    };
    class AduGuiIMEEditBox : public AduGuiEditBox {
    public:
        virtual bool FTSDK_THISCALL HandleImeComposition(int a2, int a3, uint16_t compositionFlags) = 0; // vt+0xD8
        virtual bool FTSDK_THISCALL ResetImeComposition(int a1, int a2) = 0;
        virtual bool FTSDK_THISCALL HandleImeCandidateList(int notifyType, int param) = 0;
        virtual int FTSDK_THISCALL RenderImeCandidateOrReadingWindow(int a2, int a3, bool readingWindow) = 0;
        virtual int FTSDK_THISCALL RenderImeCompositionString(int a2, int a3) = 0;
        virtual void FTSDK_THISCALL RenderImeStatusIndicator(int a2, int a3) = 0;
        virtual int FTSDK_THISCALL ApplyImeTextStyle(int styleValue) = 0;
    public:
        int32_t imeCandidatePageBackgroundColor; // +0x2D8
        int32_t imeCandidateTextColor; // +0x2DC
        int32_t imeCandidateSelectedTextColor; // +0x2E0
        int32_t imeCandidateSelectedBackColor; // +0x2E4
        int32_t imeCandidateBorderColor; // +0x2E8
        int32_t imeCompositionTextColor; // +0x2EC
        int32_t imeCompositionBackColor; // +0x2F0
        int32_t imeCompositionBorderColor; // +0x2F4
        int32_t imeReadingTextColor; // +0x2F8
        int32_t imeReadingBackColor; // +0x2FC
        int32_t imeReadingBorderColor; // +0x300
        int32_t imeField304;
        int32_t imeField308;
        int32_t imeField30C;
        int32_t imeField310;
        int32_t imeField314;
        int32_t imeField318;
        int32_t imeField31C;
        int32_t imeField320;
        int32_t imeField324;
        uint8_t imeActiveFlag; // +0x328
        uint8_t pad329[3];
        uint8_t pad32C[4];
    };
    class AduGuiCheckBox : public AduGuiButton {
    public:
        virtual void FTSDK_THISCALL SetChecked(bool checked, bool dispatchEvent) = 0; // vt+0xE4
    public:
        uint8_t hasSeparateCheckImage; // +0x264
        uint8_t checkedFlag; // +0x265
        uint8_t pad266[2];
        Structs::Rect checkImageRect; // +0x268
        Structs::Rect checkLabelHitRect; // +0x278
        Structs::FTVector<int32_t> linkedCheckCommandIds; // +0x288
    public:
        bool IsChecked() const { return checkedFlag != 0; }
        bool HasSeparateCheckImage() const { return hasSeparateCheckImage != 0; }
        const Structs::Rect& CheckImageRect() const { return checkImageRect; }
        const Structs::Rect& CheckLabelHitRect() const { return checkLabelHitRect; }
        Structs::FTVector<int32_t>& LinkedCheckCommandIds() { return linkedCheckCommandIds; }
        const Structs::FTVector<int32_t>& LinkedCheckCommandIds() const { return linkedCheckCommandIds; }
    };
    static_assert(sizeof(AduGuiCheckBox) == 0x298);
    class AduGuiRadioButton : public AduGuiCheckBox {
    public:
        virtual void FTSDK_THISCALL SetRadioChecked(bool checked, bool clearGroup, bool dispatchEvent) = 0; // vt+0xE8
    public:
        uint8_t allowRightMouseToggleFlag; // +0x298
        uint8_t pad299[3];
    public:
        bool AllowsRightMouseToggle() const { return allowRightMouseToggleFlag != 0; }
    };
    class AduGuiSlider : public AduGuiControl {
    public:
        int32_t value; // +0x1FC, XML Value, ctor 50
        int32_t minValue; // +0x200, XML Range min, ctor 0
        int32_t maxValue; // +0x204, XML Range max, ctor 100
        int32_t dragStartX; // +0x208
        int32_t dragOffsetX; // +0x20C
        int32_t thumbOffsetX; // +0x210, calculated from value/range
        Structs::Rect thumbRect; // +0x214
        int32_t sliderValueChangeSoundId; // +0x224, GuiEventType::SliderValueChange
    public:
        int32_t Value() const { return value; }
        int32_t MinValue() const { return minValue; }
        int32_t MaxValue() const { return maxValue; }
        const Structs::Rect& ThumbRect() const { return thumbRect; }
        int32_t SliderValueChangeSoundId() const { return sliderValueChangeSoundId; }
    };
    class AduGuiGauge : public AduGuiStatic {
    public:
        virtual int32_t FTSDK_THISCALL GetRange() = 0; // vt+0xE4
        virtual int32_t FTSDK_THISCALL GetValue() = 0;
        virtual int FTSDK_THISCALL SetRange(int32_t range) = 0;
        virtual int FTSDK_THISCALL SetValue(int32_t value) = 0;
    public:
        int32_t range; // +0x25C, XML Range, ctor 100
        int32_t value; // +0x260, XML Value, ctor 50
    public:
        int32_t Range() const { return range; }
        int32_t Value() const { return value; }
    };
    class AduGuiScrollBar : public AduGuiControl {
    public:
        virtual int32_t FTSDK_THISCALL ContainsScrollHitRect(Structs::Point pt) = 0; // vt+0xD8
    public:
        uint8_t scrollEnabledFlag; // +0x1FC
        uint8_t thumbDraggingFlag; // +0x1FD
        uint8_t pad1FE[2];
        Structs::Rect upButtonRect; // +0x200
        Structs::Rect downButtonRect; // +0x210
        Structs::Rect trackRect; // +0x220
        Structs::Rect thumbRect; // +0x230
        int32_t scrollValue; // +0x240
        int32_t pageSize; // +0x244
        int32_t minValue; // +0x248
        int32_t maxValue; // +0x24C
        Structs::Point lastMousePoint; // +0x250
        int32_t repeatMode; // +0x258, 0 none, 1 up initial, 2 down initial, 3 up repeat, 4 down repeat
        double lastRepeatTime; // +0x260
        uint8_t suppressScrollChangeEvent; // +0x268
        uint8_t pad269[3];
        int32_t checkWidth; // +0x26C, XML Option/CheckWidth
        Structs::Rect scrollHitRect; // +0x270
        uint8_t field280; // +0x280
        uint8_t normalDirectionFlag; // +0x281
        uint8_t pad282[2];
        int32_t scrollChangeSoundId; // +0x284, GuiEventType::ScrollBarChange
    public:
        bool IsScrollEnabled() const { return scrollEnabledFlag != 0; }
        bool IsThumbDragging() const { return thumbDraggingFlag != 0; }
        int32_t ScrollValue() const { return scrollValue; }
        int32_t PageSize() const { return pageSize; }
        int32_t MinValue() const { return minValue; }
        int32_t MaxValue() const { return maxValue; }
        const Structs::Rect& UpButtonRect() const { return upButtonRect; }
        const Structs::Rect& DownButtonRect() const { return downButtonRect; }
        const Structs::Rect& TrackRect() const { return trackRect; }
        const Structs::Rect& ThumbRect() const { return thumbRect; }
        const Structs::Rect& ScrollHitRect() const { return scrollHitRect; }
        int32_t ScrollChangeSoundId() const { return scrollChangeSoundId; }
    };
    struct AduGuiListBoxItem {
        int32_t textColor; // +0x000
        wchar_t text[256]; // +0x004
        int32_t itemData; // +0x204
        uint8_t unknown208[0x10];
        uint8_t selectedFlag; // +0x218
        uint8_t pad219[3];
        const wchar_t* Text() const { return text; }
        bool IsSelected() const { return selectedFlag != 0; }
     };
     };
    static_assert(sizeof(AduGuiListBoxItem) == 0x21C);
    class AduGuiListBox : public AduGuiControl {
    public:
        Structs::Rect textClipRect; // +0x1FC
        Structs::Rect itemVisualRect; // +0x20C


     static_assert(sizeof(AduGuiEditBox) == 0x2D8);
        AduGuiScrollBar* scrollBar; // +0x21C
 
        int32_t rightReservedWidth; // +0x220, ctor 16
        int32_t outerPadding; // +0x224, ctor 6
        int32_t textHorizontalPadding; // +0x228, ctor 5
        int32_t fontHeight; // +0x22C
 
        int32_t selectionFlags; // +0x230, bit 0 enables multi/range selection behavior
        int32_t selectedIndex; // +0x234, ctor -1
        int32_t selectionAnchorIndex; // +0x238
 
        uint8_t mouseSelectingFlag; // +0x23C
        uint8_t autoScrollToBottomFlag; // +0x23D
        uint8_t listEnabledFlag; // +0x23E
        uint8_t pad23F;
 
        int32_t rowHeight; // +0x240, fontHeight + rowSpacing
        int32_t rowSpacing; // +0x244, ctor 2
        int32_t field248; // +0x248, ctor 1
 
        AduGuiListBoxItem** items; // +0x24C
        int32_t itemCount; // +0x250
        int32_t itemCapacity; // +0x254
 
     public:
        AduGuiScrollBar* ScrollBar() const { return scrollBar; }
 
        const Structs::Rect& TextClipRect() const { return textClipRect; }
        const Structs::Rect& ItemVisualRect() const { return itemVisualRect; }
 
        int32_t SelectedIndex() const { return selectedIndex; }
        int32_t SelectionAnchorIndex() const { return selectionAnchorIndex; }
 
        bool IsMultiSelectEnabled() const { return (selectionFlags & 1) != 0; }
        bool IsMouseSelecting() const { return mouseSelectingFlag != 0; }
        bool IsAutoScrollToBottomEnabled() const { return autoScrollToBottomFlag != 0; }
        bool IsListEnabled() const { return listEnabledFlag != 0; }
 
        int32_t ItemCount() const { return itemCount; }
 
        AduGuiListBoxItem* ItemAt(int32_t index) const {
            if (!items || index < 0 || index >= itemCount)
                return nullptr;


    class AduGuiDialog : public AduGameObj {
             return items[index];
    protected:
        template <typename T>
        T ReadField(size_t offset) const {
             return *reinterpret_cast<const T*>(
                reinterpret_cast<const uint8_t*>(this) + offset
                );
         }
         }
    };
    struct AduGuiComboBoxItem {
        wchar_t text[256]; // +0x000
        int32_t itemData; // +0x200
        Structs::Rect itemRect; // +0x204
        uint8_t visibleFlag; // +0x214
        uint8_t pad215[3];
        const wchar_t* Text() const { return text; }
        bool IsVisible() const { return visibleFlag != 0; }
    };
    static_assert(sizeof(AduGuiComboBoxItem) == 0x218);
    class AduGuiComboBox : public AduGuiButton {
    public:
        virtual int FTSDK_THISCALL ApplyComboBoxStateStyle(int styleValue) = 0; // vt+0xE4
    public:
        int32_t selectedIndex; // +0x264
        int32_t hoverOrPreviewIndex; // +0x268
        int32_t dropHeight; // +0x26C, XML DropHeight
        AduGuiScrollBar* scrollBar; // +0x270
        int32_t dropButtonWidth; // +0x274, ctor 16
        uint8_t dropdownOpenFlag; // +0x278
        uint8_t showDropButtonFlag; // +0x279, XML ShowDropButton
        uint8_t pad27A[2];
        Structs::Rect selectedTextRect; // +0x27C
        Structs::Rect dropButtonRect; // +0x28C
        Structs::Rect dropdownOuterRect; // +0x29C
        Structs::Rect dropdownInnerRect; // +0x2AC
        int32_t rowHeight; // +0x2BC
        int32_t rowSpacing; // +0x2C0, ctor 2
        int32_t comboBoxCloseSoundId; // +0x2C4, GuiEventType::ComboBoxClose
        AduGuiComboBoxItem** items; // +0x2C8
        int32_t itemCount; // +0x2CC
        int32_t itemCapacity; // +0x2D0
    public:
        int32_t SelectedIndex() const { return selectedIndex; }
        int32_t HoverOrPreviewIndex() const { return hoverOrPreviewIndex; }
        bool IsDropdownOpen() const { return dropdownOpenFlag != 0; }
        bool ShowsDropButton() const { return showDropButtonFlag != 0; }
        AduGuiScrollBar* ScrollBar() const { return scrollBar; }
        int32_t ItemCount() const { return itemCount; }
        AduGuiComboBoxItem* ItemAt(int32_t index) const {
            if (!items || index < 0 || index >= itemCount)
                return nullptr;


        template <typename T>
             return items[index];
        void WriteField(size_t offset, T value) {
             *reinterpret_cast<T*>(
                reinterpret_cast<uint8_t*>(this) + offset
                ) = value;
         }
         }
        int32_t ComboBoxOpenSoundId() const { return buttonClickSoundId; }
        int32_t ComboBoxSelectChangeSoundId() const { return buttonOverSoundId; }
        int32_t ComboBoxCloseSoundId() const { return comboBoxCloseSoundId; }
    };
    struct AduGuiContextMenuItem {
        int32_t itemData; // +0x00
        Structs::FTStringW text; // +0x04
        const wchar_t* Text() const { return text.Data(); }
        int32_t ItemData() const { return itemData; }
    };
    static_assert(sizeof(AduGuiContextMenuItem) == 0x20);
    class AduGuiContextMenu : public AduGuiControl {
    public:
        int32_t field1FC; // +0x1FC, ctor 0
        Structs::Rect itemAreaRect; // +0x200
        int32_t itemPaddingX; // +0x210, ctor 5
        int32_t itemPaddingY; // +0x214, ctor 3
        int32_t maxTextWidth; // +0x218
        int32_t fontHeight; // +0x21C
        int32_t menuContentWidth; // +0x220
        int32_t itemHeight; // +0x224
        int32_t hoverItemIndex; // +0x228, ctor -1
        int32_t selectedItemIndex; // +0x22C, ctor -1
        int32_t contextMenuClickSoundId; // +0x230, GuiEventType::ContextMenuClick
        AduGuiContextMenuItem** items; // +0x234
        int32_t itemCount; // +0x238
        int32_t itemCapacity; // +0x23C


     public:
     public:
         AduGameObj** ObjectArray() const {
         int32_t HoverItemIndex() const { return hoverItemIndex; }
             return ReadField<AduGameObj**>(0x8C);
        int32_t SelectedItemIndex() const { return selectedItemIndex; }
 
        int32_t ItemCount() const { return itemCount; }
 
        AduGuiContextMenuItem* ItemAt(int32_t index) const {
             if (!items || index < 0 || index >= itemCount)
                return nullptr;
 
            return items[index];
         }
         }


         int32_t ObjectCount() const {
         int32_t ContextMenuClickSoundId() const { return contextMenuClickSoundId; }
            return ReadField<int32_t>(0x90);
    };
         }
   
    static_assert(sizeof(AduBase) == 0x08);
    static_assert(sizeof(AduGameObj) == 0x20);
    static_assert(sizeof(AduGuiControl) == 0x1FC);
    static_assert(sizeof(AduGuiStatic) == 0x25C);
    static_assert(sizeof(AduGuiButton) == 0x264);
    static_assert(sizeof(AduGuiCheckBox) == 0x298);
    static_assert(sizeof(AduGuiRadioButton) == 0x29C);
    static_assert(sizeof(AduGuiComboBox) == 0x2D4);
    static_assert(sizeof(AduGuiSlider) == 0x228);
    static_assert(sizeof(AduGuiGauge) == 0x264);
    static_assert(sizeof(AduGuiEditBox) == 0x2D8);
    static_assert(sizeof(AduGuiIMEEditBox) == 0x330);
    static_assert(sizeof(AduGuiListBox) == 0x258);
    static_assert(sizeof(AduGuiScrollBar) == 0x288);
    static_assert(sizeof(AduGuiContextMenu) == 0x240);
 
    class AduGuiDialog : public AduGameObj {
    public:
    public:
        int32_t dialogIdOrField20; // +0x20, ctor 0xFFFF
        int32_t field24;
 
        double lastPeriodicUpdateTime; // +0x28
 
        AduGuiControl* tooltipOrDelayedTextControl; // +0x30
 
        uint8_t field34;
        uint8_t field35;
        uint8_t pad36[2];
 
        int32_t field38;
        int32_t field3C;
        int32_t dialogX;      // +0x40, XML Dialog x
        int32_t dialogY;      // +0x44, XML Dialog y
        int32_t dialogWidth;  // +0x48, XML Dialog w
        int32_t dialogHeight; // +0x4C, XML Dialog h
 
        float scaleX; // +0x50, ctor 1.0
        float scaleY; // +0x54, ctor 1.0
 
        int32_t field58;
        int32_t field5C;
        int32_t field60;
        int32_t field64;
 
        void* renderContext; // +0x68
 
        GuiCallbackFn callback; // +0x6C
        void* callbackUserData; // +0x70
 
        void** imageResources; // +0x74
        int32_t imageResourceCount; // +0x78
        int32_t imageResourceCapacity; // +0x7C
 
        Structs::FTTreeMap<int32_t, AduGuiControl*> controlMap; // +0x80
 
        AduGameObj** objectArray; // +0x8C
        int32_t objectCount; // +0x90
        int32_t objectCapacity; // +0x94
 
        void** runtimeEntryArray; // +0x98
        int32_t runtimeEntryCount; // +0x9C
        int32_t runtimeEntryCapacity; // +0xA0
 
        AduGuiDialog* selfA4; // +0xA4
        AduGuiDialog* selfA8; // +0xA8
 
        uint8_t rotateFocusFlag; // +0xAC, XML RotateFocus, ctor 1
        uint8_t padAD[3];
 
        int32_t delayedTextX; // +0xB0
        int32_t delayedTextY; // +0xB4
 
        uint8_t delayedTextEnabled; // +0xB8
        uint8_t padB9[3];
 
         float delayedTextTimer; // +0xBC


         GuiCallbackFn Callback() const {
         uint8_t dispatchEventsFlag; // +0xC0
            return ReadField<GuiCallbackFn>(0x6C);
        uint8_t keepCaptureAfterEventFlag; // +0xC1
         }
        uint8_t fieldC2; // +0xC2, ctor 1
         uint8_t padC3;


         void* CallbackUserData() const {
         float fieldC4;
            return ReadField<void*>(0x70);
        }


    public:
         void SetCallback(GuiCallbackFn callback, void* userData) {
         void SetCallback(GuiCallbackFn callback, void* userData) {
            WriteField<GuiCallbackFn>(0x6C, callback);
this->callback = callback;
            WriteField<void*>(0x70, userData);
this->callbackUserData = userData;
         }
         }


         AduGameObj* ObjectAt(int32_t index) const {
         AduGameObj* ObjectAt(int32_t index) const {
             auto* arr = ObjectArray();
             auto* arr = objectArray;


            if (!arr || index < 0 || index >= ObjectCount())
if (!arr || index < 0 || index >= objectCount)
                 return nullptr;
                 return nullptr;


Line 867: Line 1,444:


         AduGuiControl* FindControlByCommandId(int32_t commandId) const {
         AduGuiControl* FindControlByCommandId(int32_t commandId) const {
             const int32_t count = ObjectCount();
             const int32_t count = objectCount;


             if (count < 0 || count > 4096)
             if (count < 0 || count > 4096)
Line 888: Line 1,465:
                 return nullptr;
                 return nullptr;


             const int32_t count = ObjectCount();
             const int32_t count = objectCount;
 
            if (count < 0 || count > 4096)
                return nullptr;
 
            for (int32_t i = 0; i < count; ++i) {
                auto* control = ControlAt(i);
                if (!control)
                    continue;
 
                if (control->Name().Equals(name))
                    return control;
            }
 
            return nullptr;
        }
 
        AduGuiControl* FindControlByNameAndType(const char* name, GuiControlType type) const {
            if (!name)
                return nullptr;
 
            const int32_t count = objectCount;


             if (count < 0 || count > 4096)
             if (count < 0 || count > 4096)
Line 896: Line 1,494:
                 auto* control = ControlAt(i);
                 auto* control = ControlAt(i);
                 if (!control)
                 if (!control)
                    continue;
                if (control->ControlTypeId() != static_cast<int32_t>(type))
                     continue;
                     continue;


Line 905: Line 1,506:
         }
         }
     };
     };
static_assert(sizeof(AduGuiDialog) == 0xC8);
    class AduGuiCustomControlBase : public AduGuiControl {
    public:
        virtual bool FTSDK_THISCALL LoadEmbeddedGuiXml(const char* xmlName, const Structs::GuiBind* binds, int32_t bindCount, GuiCallbackFn callback) = 0; // vt+0xD8
        virtual int FTSDK_THISCALL OnEmbeddedCommand(int32_t commandId) = 0;
        virtual int FTSDK_THISCALL OnEmbeddedEditEnter(int32_t commandId) = 0;
        virtual int FTSDK_THISCALL OnEmbeddedEditChange(int32_t commandId) = 0;
        virtual int FTSDK_THISCALL OnEmbeddedEditTab(int32_t commandId) = 0;
        virtual int FTSDK_THISCALL OnEmbeddedEditEsc(int32_t commandId) = 0;
        virtual int FTSDK_THISCALL OnEmbeddedEditKeyUp(int32_t commandId) = 0;
        virtual int FTSDK_THISCALL OnEmbeddedEditKeyDown(int32_t commandId) = 0;
        virtual int FTSDK_THISCALL OnEmbeddedContextMenuClick(int32_t commandId, int32_t selectedValue, AduGuiControl* contextMenuControl, int32_t selectedData) = 0;
        virtual int FTSDK_THISCALL DispatchEmbeddedGuiEvent(GuiEventType eventType, int32_t commandId, int32_t param, AduGuiControl* control) = 0;
    public:
        AduGuiDialog* embeddedDialog; // +0x1FC
        Structs::FTTreeMap<int32_t, AduGuiControl*> embeddedBindMap; // +0x200
        int32_t embeddedField20C;
    };
    static_assert(sizeof(AduGuiCustomControlBase) == 0x210);
    class FTRankingInfo : public AduGuiCustomControlBase {
    public:
        AduGuiButton* btClothInfoInner; // +0x210, XML btClothInfo
        AduGuiStatic* stRankingInner; // +0x214
        AduGuiStatic* stEmblemMarkInner; // +0x218
        AduGuiStatic* stUserNameInner; // +0x21C
        AduGuiStatic* stUserLevelInner; // +0x220
        AduGuiStatic* stUserExpInner; // +0x224
        AduGuiStatic* stWinPercentInner; // +0x228
        AduGuiStatic* stRankingPointInner; // +0x22C
        void* rankingRecord; // +0x230, points to 52-byte ranking row/cache record
    public:
        bool HasRankingRecord() const {
            return rankingRecord != nullptr;
        }
        int32_t RankingPlayerId() const {
            return rankingRecord
                ? *reinterpret_cast<const int32_t*>(
                    reinterpret_cast<const uint8_t*>(rankingRecord) + 0x04
                    )
                : 0;
        }
    };
    static_assert(sizeof(FTRankingInfo) == 0x234);


     class StageManager;
     class StageManager;
Line 934: Line 1,585:
         virtual void FTSDK_THISCALL DeactivateProcessing() = 0;
         virtual void FTSDK_THISCALL DeactivateProcessing() = 0;


         virtual void FTSDK_THISCALL OnStageCommand(int commandId) = 0;
         virtual int FTSDK_THISCALL OnStageCommand(int32_t commandId) = 0;
         virtual void FTSDK_THISCALL Reserved58() = 0;
         virtual void FTSDK_THISCALL Reserved58() = 0;
         virtual void FTSDK_THISCALL Reserved5C() = 0;
         virtual void FTSDK_THISCALL Reserved5C() = 0;
Line 1,015: Line 1,666:


         int32_t cursorX;
         int32_t cursorX;
         int32_t cursorY; // +0x1E8
         int32_t cursorY;


         uint8_t inputState[0x26C];
         uint8_t inputState[0x26C];
Line 1,021: Line 1,672:
         ScreenRoot* hoverOrCapturedOwner;
         ScreenRoot* hoverOrCapturedOwner;


         char resourceBasePath[0x104]; // +0x45C
         char resourceBasePath[0x104];


     public:
     public:
         bool HasCurrentFocusOwner() const { return currentFocusOwner != nullptr; }
         bool HasCurrentFocusOwner() const { return currentFocusOwner != nullptr; }
         bool HasCurrentHoverOwner() const { return currentHoverOwner != nullptr; }
         bool HasCurrentHoverOwner() const { return currentHoverOwner != nullptr; }


         int32_t CursorDeltaX() const { return cursorX - previousCursorX; }
         int32_t CursorDeltaX() const { return cursorX - previousCursorX; }
         int32_t CursorDeltaY() const { return cursorY - previousCursorY; }
         int32_t CursorDeltaY() const { return cursorY - previousCursorY; }


Line 1,071: Line 1,720:
         virtual int FTSDK_THISCALL HandleStagePacket(void* packet) = 0;
         virtual int FTSDK_THISCALL HandleStagePacket(void* packet) = 0;
         virtual int FTSDK_THISCALL HandleGuiEvent(GuiEventType eventType, int commandId, int param) = 0;
         virtual int FTSDK_THISCALL HandleGuiEvent(GuiEventType eventType, int commandId, int param) = 0;
         virtual int FTSDK_THISCALL OnCommandAction(int commandId) = 0;
         virtual int FTSDK_THISCALL OnCommandAction(int32_t commandId) = 0;
         virtual int FTSDK_THISCALL OnEditBoxEnter(int commandId) = 0;
         virtual int FTSDK_THISCALL OnEditBoxEnter(int32_t commandId) = 0;
         virtual int FTSDK_THISCALL OnEditBoxChange(int commandId) = 0;
         virtual int FTSDK_THISCALL OnEditBoxChange(int32_t commandId) = 0;
         virtual int FTSDK_THISCALL OnEditBoxTab(int commandId) = 0;
         virtual int FTSDK_THISCALL OnEditBoxTab(int32_t commandId) = 0;
         virtual int FTSDK_THISCALL OnEditBoxEsc(int commandId) = 0;
         virtual int FTSDK_THISCALL OnEditBoxEsc(int32_t commandId) = 0;
         virtual int FTSDK_THISCALL OnEditBoxKeyUp(int commandId) = 0;
         virtual int FTSDK_THISCALL OnEditBoxKeyUp(int32_t commandId) = 0;
         virtual int FTSDK_THISCALL OnEditBoxKeyDown(int commandId) = 0;
         virtual int FTSDK_THISCALL OnEditBoxKeyDown(int32_t commandId) = 0;
         virtual int FTSDK_THISCALL OnContextMenuClick(int commandId, int selectedValue, AduGuiControl* contextMenuControl, int selectedData) = 0;
         virtual int FTSDK_THISCALL OnContextMenuClick(int32_t commandId, int selectedValue, AduGuiControl* contextMenuControl, int selectedData) = 0;
 
     public:
     public:
         StageBase* priorityInputChildOwner;
         StageBase* priorityInputChildOwner;
Line 1,117: Line 1,767:


         void SetPriorityInputChildOwner(StageBase* child) { priorityInputChildOwner = child; }
         void SetPriorityInputChildOwner(StageBase* child) { priorityInputChildOwner = child; }
         void ClearPriorityInputChildOwnerIf(StageBase* child) { if (PriorityInputChildOwner() == child) SetPriorityInputChildOwner(nullptr); }
 
         void ClearPriorityInputChildOwnerIf(StageBase* child) {
            if (PriorityInputChildOwner() == child)
                SetPriorityInputChildOwner(nullptr);
        }


         using AddChildOwnerFn = int(FTSDK_THISCALL*)(StageBase* self, StageBase* child);
         using AddChildOwnerFn = int(FTSDK_THISCALL*)(StageBase* self, StageBase* child);
         void AddChildOwner(StageBase* child) { reinterpret_cast<AddChildOwnerFn>(0x005AA720)(this, child); }
         void AddChildOwner(StageBase* child) {
            reinterpret_cast<AddChildOwnerFn>(0x005AA720)(this, child);
        }


         using BringChildOwnerToFrontFn = int(FTSDK_THISCALL*)(StageBase* self, StageBase* child);
         using BringChildOwnerToFrontFn = int(FTSDK_THISCALL*)(StageBase* self, StageBase* child);
         void BringChildOwnerToFront(StageBase* child) { reinterpret_cast<BringChildOwnerToFrontFn>(0x005AA780)(this, child); }
         void BringChildOwnerToFront(StageBase* child) {
            reinterpret_cast<BringChildOwnerToFrontFn>(0x005AA780)(this, child);
        }


         using IsOwnerReachableFn = int(FTSDK_FASTCALL*)(StageBase* self);
         using IsOwnerReachableFn = int(FTSDK_FASTCALL*)(StageBase* self);
         bool IsReachableThroughParentChain() const { return reinterpret_cast<IsOwnerReachableFn>(0x005A99E0)(const_cast<StageBase*>(this)) != 0; }
         bool IsReachableThroughParentChain() const {
 
            return reinterpret_cast<IsOwnerReachableFn>(0x005A99E0)(const_cast<StageBase*>(this)) != 0;
        AduGameObj* GetControlInnerControl(int32_t commandId) {
            auto* control = FindBoundControl(commandId);
            return control ? control->GetInnerObject() : nullptr;
         }
         }
     };
     };
Line 1,138: Line 1,793:
     public:
     public:
         int32_t messageBoxMode;
         int32_t messageBoxMode;
        // 1 = OK
        // 2 = timed OK
        // 3 = Yes/No
        // 4 = timed Yes/No


         int32_t okYesEventType;
         int32_t okYesEventType;
Line 1,243: Line 1,894:
         }
         }
     };
     };
     static_assert(sizeof(MessageBoxDialogOwner) == 0x21C);
     static_assert(sizeof(MessageBoxDialogOwner) == 0x21C);


Line 1,318: Line 1,968:


         float hourlyPacketTimer;
         float hourlyPacketTimer;
         int32_t unknown89C; // +0x89C
         int32_t unknown89C;


     public:
     public:
Line 1,377: Line 2,027:


         using ShowMessageBoxFn = void(FTSDK_THISCALL*)(StageManager* self, const wchar_t* message, int32_t mode, int32_t okYesEventType, int32_t okYesEventData, int32_t okYesEventParam, uint8_t playSound);
         using ShowMessageBoxFn = void(FTSDK_THISCALL*)(StageManager* self, const wchar_t* message, int32_t mode, int32_t okYesEventType, int32_t okYesEventData, int32_t okYesEventParam, uint8_t playSound);
         void ShowMessageBox(const wchar_t* message, MessageBoxMode mode = MessageBox_OK, int32_t okYesEventType = 0, int32_t okYesEventData = 0,
         void ShowMessageBox(
             int32_t okYesEventParam = 0, bool playSound = true) {
            const wchar_t* message,
            MessageBoxMode mode = MessageBox_OK,
            int32_t okYesEventType = 0,
            int32_t okYesEventData = 0,
             int32_t okYesEventParam = 0,
            bool playSound = true
        ) {
             reinterpret_cast<ShowMessageBoxFn>(0x004AE9C0)(
             reinterpret_cast<ShowMessageBoxFn>(0x004AE9C0)(
                 this,
                 this,
Line 1,399: Line 2,055:


         void ShowYesNoMessage(const wchar_t* message, int32_t okYesEventType = 0, int32_t okYesEventData = 0, int32_t okYesEventParam = 0, bool playSound = true) {
         void ShowYesNoMessage(const wchar_t* message, int32_t okYesEventType = 0, int32_t okYesEventData = 0, int32_t okYesEventParam = 0, bool playSound = true) {
             ShowMessageBox(
             ShowMessageBox(message, MessageBox_YesNo, okYesEventType, okYesEventData, okYesEventParam, playSound);
                message,
                MessageBox_YesNo,
                okYesEventType,
                okYesEventData,
                okYesEventParam,
                playSound
            );
         }
         }


         void ShowTimedYesNoMessage(const wchar_t* message, int32_t okYesEventType = 0, int32_t okYesEventData = 0, int32_t okYesEventParam = 0, bool playSound = true) {
         void ShowTimedYesNoMessage(const wchar_t* message, int32_t okYesEventType = 0, int32_t okYesEventData = 0, int32_t okYesEventParam = 0, bool playSound = true) {
             ShowMessageBox(
             ShowMessageBox(message, MessageBox_TimedYesNo, okYesEventType, okYesEventData, okYesEventParam, playSound);
                message,
                MessageBox_TimedYesNo,
                okYesEventType,
                okYesEventData,
                okYesEventParam,
                playSound
            );
         }
         }
     };
     };
     static_assert(sizeof(StageManager) == 0x8A0);
     static_assert(sizeof(StageManager) == 0x8A0);


Line 1,436: Line 2,077:
     inline GameDialogStage* ConstructGameDialogStage(GameDialogStage* memory) {
     inline GameDialogStage* ConstructGameDialogStage(GameDialogStage* memory) {
         return reinterpret_cast<ConstructGameDialogStageFn>(0x004987C0)(memory);
         return reinterpret_cast<ConstructGameDialogStageFn>(0x004987C0)(memory);
    }
    using SetStaticTextFn = int(FTSDK_THISCALL*)(AduGuiStatic* control, const wchar_t* text);
    inline int SetStaticText(AduGuiStatic* control, const wchar_t* text) {
        if (!control)
return 0;
       
return reinterpret_cast<SetStaticTextFn>(0x006185E0)(control, text ? text : L"");
}
    namespace GuiCustomControlRegistry {
        constexpr uintptr_t RegisterFactoryAddr = 0x006113F0;
        constexpr uintptr_t RegisterTypeNameAddr = 0x00617E20;
        constexpr uintptr_t DefaultFactoryAddr = 0x005AC950;
        constexpr uintptr_t RegisterBuiltinCustomTypesAddr = 0x005AE050;
        using RegisterFactoryFn = CreateCustomGuiControlFn(FTSDK_CDECL*)(CreateCustomGuiControlFn factory);
        using RegisterTypeNameFn = void (FTSDK_CDECL*)(const char* typeName, int32_t customTypeId);
        using RegisterBuiltinCustomTypesFn = int (FTSDK_CDECL*)();
        inline CreateCustomGuiControlFn RegisterFactory(CreateCustomGuiControlFn factory) {
            return reinterpret_cast<RegisterFactoryFn>(RegisterFactoryAddr)(factory);
        }
        inline void RegisterTypeName(const char* typeName, int32_t customTypeId) {
            reinterpret_cast<RegisterTypeNameFn>(RegisterTypeNameAddr)(typeName, customTypeId);
        }
        inline int RegisterBuiltinCustomTypes() {
            return reinterpret_cast<RegisterBuiltinCustomTypesFn>(RegisterBuiltinCustomTypesAddr)();
        }
        inline AduGuiControl* CreateByDefaultFactory(AduGuiDialog* dialog, int32_t customTypeId) {
            return reinterpret_cast<CreateCustomGuiControlFn>(DefaultFactoryAddr)(dialog, customTypeId);
        }
     }
     }
}
}

Revision as of 08:34, 20 May 2026

Overview

This page documents the current reverse engineering baseline for the Fantasy Tennis client-side SDK used by JFTSE tooling, injected debugging code and client UI experiments.

The SDK maps the known client GUI/stage layer into C++ wrappers around the original client structures. The current focus is the UI system around StageManager, ScreenRoot, ScreenInputRoot, StageBase, GameDialogStage, AduGuiDialog, AduGuiControl and all known built-in Adu GUI controls.

The practical goal of this SDK is to make custom client UI possible without adding unsupported client stages. The Fantasy Tennis client uses a fixed stage model, so custom UI should attach to an existing stage owner, popup owner or embedded/custom GUI owner instead of creating an additional global stage.

The SDK is intentionally conservative. Names describe current reverse engineering understanding, not original source names. Offsets, virtual slots and function addresses are tied to the currently analyzed Fantasy Tennis client build.

Current Focus

The currently documented SDK covers:

  • StageManager state tracking, current-stage access and stage switching helpers.
  • ScreenRoot ownership, parent chains, open state, processing state and input routing.
  • StageBase XML dialog ownership, GUI callback routing, priority child-owner behavior and event dispatch.
  • GameDialogStage built-in popup slots, popup creation helpers, selected/active popup state and game-dialog lifecycle hooks.
  • AduGuiDialog runtime layout, object array access, command-ID lookup, name lookup and callback/user-data storage.
  • AduGuiControl common runtime fields such as name, command id, type id, group id, subgroup id, rectangle, state sets, text and context menu data.
  • Built-in Adu GUI controls:
    • AduGuiStatic
    • AduGuiButton
    • AduGuiCheckBox
    • AduGuiRadioButton
    • AduGuiComboBox
    • AduGuiSlider
    • AduGuiGauge
    • AduGuiEditBox
    • AduGuiIMEEditBox
    • AduGuiListBox
    • AduGuiScrollBar
    • AduGuiContextMenu
  • Generic custom GUI controls through AduGuiCustomControlBase.
  • Known concrete custom controls such as FTRankingInfo.
  • XML binding through GuiBind arrays, command ids and control type ids.
  • Runtime text assignment for static controls through FTSDK::SetStaticText.
  • Custom type name registration through GuiCustomControlRegistry.

Custom UI Direction

The preferred custom UI direction is now:

  1. Create or reuse a GameDialogStage/StageBase owner attached to an existing game stage or popup owner.
  2. Load a normal XML dialog with StageBase::LoadXml.
  3. Register additional XML control type names with GuiCustomControlRegistry::RegisterTypeName.
  4. For simple embedded GUI controls, map a custom XML type name to GuiControlType::Custom / type id 12.
  5. For each generic custom control instance, call AduGuiCustomControlBase::LoadEmbeddedGuiXml.
  6. Cache the inner controls from the embedded AduGuiDialog.
  7. Update inner controls directly, for example with FTSDK::SetStaticText.

This mirrors the real client pattern used by controls such as FTRankingInfo: the outer custom control is created by the dialog loader, then its concrete load function loads a separate GuiCtrl_*.xml, binds/caches the inner controls and refreshes them from runtime data.

Important: registering a name such as MyCustomType to type id 12 makes Type="MyCustomType" resolve to the generic custom control base. This does not create a new factory-backed custom type. Using type ids above the built-in range such as 100, requires extending or hooking the custom control factory. Without that, the default factory only creates the known built-in custom control ids.

Current Custom UI Caveats

The custom UI path is partly proven but not fully finished.

Known working pieces:

  • The client can resolve a custom XML type name to generic custom type 12.
  • StageBase::LoadXml can create generic custom control instances from XML.
  • The created generic custom controls appear in AduGuiDialog::objectArray.
  • AduGuiCustomControlBase::LoadEmbeddedGuiXml can load a nested GuiCtrl_*.xml.
  • The embedded dialog can be inspected and inner controls can be cached.
  • Static text can be written through FTSDK::SetStaticText.

Known caveats:

  • In manually constructed/attached owners, StageBase::controlBindMap may not contain every control even when the dialog loaded correctly. For robust lookup, fall back to AduGuiDialog::FindControlByCommandId and then AduGuiDialog::FindControlByName.
  • Setting active/visible state on the outer custom row is not always enough if the embedded child dialog/control tree is not being processed or rendered by the same path as concrete client controls.
  • The generic custom control rendering/update path still needs more reversing. The next useful target is the base custom control implementation used by real GuiCtrl_* controls and the concrete GUI popup/stage base classes.
  • Closing or hiding existing popups may happen by priority-child/parent-chain changes rather than by clean deactivation. Do not rely only on OnDeactivate.

Stability Notes

All offsets and function addresses are version specific. They are verified against the currently analyzed Fantasy Tennis client build through disassembly, runtime dumps, vtable summaries and real call sites.

The SDK should be treated as a reverse engineering aid, not a stable API. Fields marked unknown, field*, maybe or described conservatively are intentionally not finalized.

Do not add another global stage. The client uses a fixed 24 stage model. Attach custom UI to an existing stage, popup owner, modal owner or embedded GUI owner.

When testing custom UI, prefer defensive lookup and logging:

  • Check owner->XmlDialog().
  • Dump AduGuiDialog::objectArray.
  • Prefer FindControlByCommandId.
  • Fall back to FindControlByName.
  • Verify ControlTypeId()`.
  • Check active, visible, openFlag, processEnabled and parentChainEnabled.
  • Verify whether the target owner is reachable through the parent chain.

Current SDK Header

#pragma once

#include <cstdint>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <cwchar>
#include <string>
#include <type_traits>

#define FTSDK_THISCALL __thiscall
#define FTSDK_CDECL    __cdecl
#define FTSDK_STDCALL  __stdcall
#define FTSDK_FASTCALL __fastcall

namespace FTSDK {

    namespace Structs {
        struct FTStringA {
            uint32_t unknown_00;

            union {
                char inlineBuffer[16];
                char* heapPtr;
            };

            uint32_t length;
            uint32_t capacity;

            bool IsInline() const {
                return capacity < 16;
            }

            bool IsValid() const {
                if (length > capacity || length > 4096)
                    return false;

                if (capacity > 0xFFFFFFFEu)
                    return false;

                if (!IsInline() && !heapPtr)
                    return false;

                return true;
            }

            const char* Data() const {
                if (!IsValid())
                    return "";

                return IsInline() ? inlineBuffer : heapPtr;
            }

            int32_t Length() const {
                return IsValid() ? static_cast<int32_t>(length) : 0;
            }

            bool Equals(const char* text) const {
                if (!text || !IsValid())
                    return false;

                const size_t textLen = std::strlen(text);
                return textLen == length && std::memcmp(Data(), text, length) == 0;
            }
        };

        struct FTStringW {
            uint32_t unknown_00;

            union {
                wchar_t inlineBuffer[8];
                wchar_t* heapPtr;
            };

            uint32_t length;
            uint32_t capacity;

            bool IsInline() const {
                return capacity < 8;
            }

            bool IsValid() const {
                if (length > capacity || length > 4096)
                    return false;

                if (capacity > 0x7FFFFFFEu)
                    return false;

                if (!IsInline() && !heapPtr)
                    return false;

                return true;
            }

            const wchar_t* Data() const {
                if (!IsValid())
                    return L"";

                return IsInline() ? inlineBuffer : heapPtr;
            }

            int32_t Length() const {
                return IsValid() ? static_cast<int32_t>(length) : 0;
            }

            bool Equals(const wchar_t* text) const {
                if (!text || !IsValid())
                    return false;

                const size_t textLen = std::wcslen(text);
                return textLen == length && std::memcmp(Data(), text, length * sizeof(wchar_t)) == 0;
            }
        };

        template <typename T>
        struct FTVector {
            uint32_t unknown_00;
            T* first;
            T* last;
            T* end;

            int32_t Size() const {
                if (!first || !last || last < first)
                    return 0;

                return static_cast<int32_t>(last - first);
            }

            int32_t Capacity() const {
                if (!first || !end || end < first)
                    return 0;

                return static_cast<int32_t>(end - first);
            }

            bool IsEmpty() const {
                return Size() == 0;
            }

            T& operator[](size_t index) {
                return first[index];
            }

            const T& operator[](size_t index) const {
                return first[index];
            }

            T* Begin() {
                return first;
            }

            T* End() {
                return last;
            }

            const T* Begin() const {
                return first;
            }

            const T* End() const {
                return last;
            }
        };

        template <typename T>
        struct FTList {
            uint32_t unknown_00;
            T* next;
            uint32_t count;
        };

        template <typename Key, typename Value>
        struct FTTreeNode {
            FTTreeNode<Key, Value>* left;
            FTTreeNode<Key, Value>* parent;
            FTTreeNode<Key, Value>* right;

            Key key;
            Value value;

            uint8_t rbColor;
            uint8_t isNil;
            uint8_t pad16[2];

            bool IsNil() const {
                return isNil != 0;
            }
        };

        template <typename Key, typename Value>
        struct FTTreeMap {
            uint32_t unknown_00;
            FTTreeNode<Key, Value>* head;
            uint32_t count;

            bool IsEmpty() const {
                return count == 0;
            }

            uint32_t Size() const {
                return count;
            }

            FTTreeNode<Key, Value>* Head() const {
                return head;
            }

            FTTreeNode<Key, Value>* Root() const {
                if (!head || head->IsNil())
                    return nullptr;

                auto* root = head->parent;
                return root && !root->IsNil() ? root : nullptr;
            }

            FTTreeNode<Key, Value>* FindNode(const Key& key) const {
                auto* node = Root();

                while (node) {
                    if (node->IsNil())
                        return nullptr;

                    if (key < node->key) {
                        node = node->left;
                    }
                    else if (node->key < key) {
                        node = node->right;
                    }
                    else {
                        return node;
                    }
                }

                return nullptr;
            }

            Value Find(const Key& key) const {
                auto* node = FindNode(key);
                return node ? node->value : Value{};
            }

            bool Contains(const Key& key) const {
                return FindNode(key) != nullptr;
            }
        };

        struct GuiBind {
            int32_t commandId;
            const char* controlName;
            int32_t controlTypeId;
        };

        struct Point {
            int32_t x;
            int32_t y;
        };

        struct Rect {
            int32_t left;
            int32_t top;
            int32_t right;
            int32_t bottom;
        };

        struct GuiRectStorage {
            void* vtable;
            int32_t left;
            int32_t top;
            int32_t right;
            int32_t bottom;
        };

        struct StageOwnedResource {
            int32_t activeOrTransitionFlag;
            int32_t enabled;
            float baseOrDuration;
            float progressOrAlpha;
            float colorR_orParam10;
            float colorG_orParam14;
            float colorB_orParam18;
            void* resourceHandle;
        };

        struct StageOverlayVertex {
            float x;
            float y;
            float z;
            float rhw;
            uint32_t color;
            float u;
            float v;
        };

        struct StageOverlayQuad {
            StageOverlayVertex vertices[4];

            int32_t unknown70;
            int32_t unknown74;
            int32_t widthInt;
            int32_t heightInt;

            float x;
            float y;
            float width;
            float height;

            float scaleX;
            float alpha;
            float rotationOrAngle;

            uint8_t unknown9C;
            uint8_t unknown9D;
            uint8_t visibleOrEnabled;
            uint8_t pad9F;

            uint8_t unknownA0[0x0C];
        };

        static_assert(sizeof(Point) == 0x08);
        static_assert(sizeof(Rect) == 0x10);
        static_assert(sizeof(GuiRectStorage) == 0x14);
        static_assert(sizeof(StageOwnedResource) == 0x20);
        static_assert(sizeof(FTStringA) == 0x1C);
        static_assert(sizeof(FTStringW) == 0x1C);
        static_assert(sizeof(GuiBind) == 0x0C);
        static_assert(sizeof(FTVector<void*>) == 0x10);
        static_assert(sizeof(StageOverlayQuad) == 0xAC);
        static_assert(sizeof(StageOverlayVertex) == 0x1C);
        static_assert(sizeof(FTList<void*>) == 0x0C);
        static_assert(sizeof(FTTreeNode<int32_t, void*>) == 0x18);
        static_assert(sizeof(FTTreeMap<int32_t, void*>) == 0x0C);
    }

    enum GuiEventType : int32_t {
        ButtonClick = 0,
        ButtonOver = 1,

        ComboBoxOpen = 2,
        ComboBoxClose = 3,
        ComboBoxSelectChange = 4,

        RadioButtonChange = 5,
        CheckBoxChange = 6,

        SliderValueChange = 7,

        EditBoxEnter = 8,
        EditBoxChange = 9,
        EditBoxTAB = 10,
        EditBoxESC = 11,
        EditBoxCharLimit = 12,
        EditBoxKeyUp = 13,
        EditBoxKeyDown = 14,

        ListBoxDBClick = 15,
        ListBoxSelect = 16,
        ListBoxSelectEnd = 17,

        ContextMenuClick = 18,
        ScrollBarPosChange = 19
    };

    enum GuiControlType : int32_t {
        Static = 0,
        Button = 1,
        CheckBox = 2,
        RadioButton = 3,
        ComboBox = 4,
        Slider = 5,
        Gauge = 6,
        EditBox = 7,
        IMEEditBox = 8,
        ListBox = 9,
        ScrollBar = 10,
        ContextMenu = 11,
		Custom = 12,

        FTRankingInfoType = 68,
    };

    enum GuiVisualStateName : int32_t {
        StateAll = 0,
        StateNormal = 1,
        StateOver = 2,
        StateFocus = 3,
        StatePressed = 4,
        StateDisabled = 5,
        StateHidden = 6
    };

    enum GuiBlendMode : int32_t {
        BlendModulate = 0,
        BlendAdd = 1
    };

    enum GuiTextFx : int32_t {
        TextFxNone = 0,
        TextFxShadow = 1,
        TextFxOutline = 2
    };

    enum MessageBoxMode : int32_t {
        MessageBox_OK = 1,
        MessageBox_TimedOK = 2,
        MessageBox_YesNo = 3,
        MessageBox_TimedYesNo = 4
    };

    struct AduGuiStateSet;
    class AduGuiDialog;
    class AduGuiControl;
    class AduGuiStatic;
    class AduGuiButton;
    class AduGuiRadioButton;
    class AduGuiCheckBox;
    class AduGuiComboBox;
    class AduGuiEditBox;
    class AduGuiIMEEditBox;
    class AduGuiListBox;
    class AduGuiScrollBar;
    class AduGuiSlider;
    class AduGuiGauge;
    class AduGuiContextMenu;

    using GuiCallbackFn = void(FTSDK_STDCALL*)(GuiEventType eventType, int commandId, int param, void* userData);
    using CreateCustomGuiControlFn = AduGuiControl * (FTSDK_CDECL*)(AduGuiDialog* dialog, int32_t customTypeId);

    struct AduGuiVisualStateInfo {
        int32_t kind;
        int32_t field04;
        int32_t field08;
        int32_t field0C;
        float timing;
        int32_t field14;
        int32_t field18;
        int32_t field1C;
        int32_t alternateStateIndex;
        int32_t field24;
    };
    static_assert(sizeof(AduGuiVisualStateInfo) == 0x28);

    struct AduGuiParsedStateDef {
        int32_t image[6]; // +0x00
        int32_t color[6]; // +0x18
        int32_t textColor[6]; // +0x30
        int32_t textFxColor[6]; // +0x48
        int32_t blend[6]; // +0x60
        int32_t fx[6]; // +0x78

        uint8_t scale[6]; // +0x90
        uint8_t tiling[6]; // +0x96

        int32_t offsetX[6]; // +0x9C
        int32_t offsetY[6]; // +0xB4
    };
    static_assert(sizeof(AduGuiParsedStateDef) == 0xCC);

    // Dialog-side parsed XML/control definition.
    // This is not AduGuiControl runtime storage.
    //
    // Confirmed:
    // - sub_624F90 initializes this structure.
    // - sub_614F60 fills it from one <Control>.
    // - AduGuiControl::LoadFromXml consumes it.
    // - parsedStates uses 0xCC-byte parser-side state records.
    // - eventSoundIds are indexed by GuiEventType and initialized to invalid/NaN-like values.
    struct AduGuiParsedControlDef {
        Structs::FTVector<AduGuiParsedStateDef> parsedStates; // +0x0000

        int32_t eventSoundIds[20]; // +0x0010, indexed by GuiEventType

        uint8_t stateAndImageParseStorage[0x804]; // +0x0060..+0x0863

        wchar_t resolvedText[256]; // +0x0864, assigned to AduGuiControl::text

        uint8_t tooltipAndMenuParseStorage[0x600]; // +0x0A64..+0x1063

        uint8_t enable;       // +0x1064, XML Enable, default 1
        uint8_t enabledState; // +0x1065, XML EnabledState, default 1
        uint8_t pad1066[2];

        int32_t x;      // +0x1068, XML x
        int32_t y;      // +0x106C, XML y
        int32_t width;  // +0x1070, XML w
        int32_t height; // +0x1074, XML h

        int32_t alignX; // XML AlignX
        int32_t alignY; // XML AlignY

        float scaleX;  // XML ScaleX, default 1.0
        float scaleY;  // XML ScaleY, default 1.0
        float rotate;  // XML Rotate
        float centerX; // XML CenterX
        float centerY; // XML CenterY

        int32_t flipX; // XML FlipX

        int32_t textAlignX; // XML TextAlignX
        int32_t textAlignY; // XML TextAlignY
        int32_t textFx;     // XML TextFx

        int32_t textMarginLeft;   // XML TextMargin
        int32_t textMarginTop;
        int32_t textMarginRight;
        int32_t textMarginBottom;

        int32_t wordWrap;   // XML WordWrap
        int32_t groupId;    // XML GroupID
        int32_t subGroupId; // XML SubGroupID

        uint8_t unknown10C0[0x24];

        int32_t tabStop;      // +0x10E4, XML TabStop
        int32_t initialStage; // XML Stage
        int32_t bindStage;    // XML BindStage
        int32_t hotKey;       // XML HotKey

        char contextMenuName[256]; // +0x10F4, XML Menu
    };
    static_assert(sizeof(AduGuiParsedControlDef) == 0x11F4);

    namespace Constants {
        constexpr int32_t StageOwnerCount = 24;
        constexpr int32_t KnownBuiltinPopupSlotCount = 10;
        constexpr int32_t GuiEventTypeCount = 20;
        constexpr int32_t GuiVisualStateNameCount = 7;
        constexpr int32_t ParsedGuiStateSize = 0xCC;
    }

    class AduBase {
    public:
        virtual AduBase* FTSDK_THISCALL Destroy(uint32_t flags) = 0;

    public:
        int32_t unk04;

    public:
        bool Unk04Enabled() const {
            return unk04 != 0;
        }
    };

    class AduGameObj : public AduBase {
    public:
        virtual AduGameObj* FTSDK_THISCALL Destroy(uint32_t flags) = 0;
        virtual bool FTSDK_THISCALL SetDirtyValue(int32_t value) = 0;
        virtual int32_t FTSDK_THISCALL ResetElapsedTimeRecursive() = 0;
        virtual bool FTSDK_THISCALL SetActive(bool value) = 0;
        virtual bool FTSDK_THISCALL SetUpdateBlocked(bool value) = 0;
        virtual bool FTSDK_THISCALL SetVisible(bool value) = 0;
        virtual bool FTSDK_THISCALL UpdateRecursive(float dt) = 0;
        virtual int32_t FTSDK_THISCALL ProcessVisibleRecursive() = 0;

    public:
        int32_t dirtyValue; // +0x08

        AduGameObj* firstChild;
        AduGameObj* nextSibling;

        uint8_t dirtyFlag;
        uint8_t active;
        uint8_t updateBlocked;
        uint8_t visible;

        float elapsedTime;
        float deltaTime;

    public:
        bool IsDirty() const { return dirtyFlag != 0; }
        bool IsActive() const { return active != 0; }
        bool IsUpdateBlocked() const { return updateBlocked != 0; }
        bool IsVisible() const { return visible != 0; }

        AduGameObj* FirstChild() const { return firstChild; }
        AduGameObj* NextSibling() const { return nextSibling; }

        template <typename Callback>
        void ForEachChild(Callback&& callback) {
            for (AduGameObj* child = firstChild; child; child = child->nextSibling)
                callback(child);
        }

        template <typename Callback>
        void ForEachChild(Callback&& callback) const {
            for (AduGameObj* child = firstChild; child; child = child->nextSibling)
                callback(child);
        }
    };

    class AduGuiControl : public AduGameObj {
    public:
        virtual bool FTSDK_THISCALL RecalculateRect() = 0; // vt+0x20, rect = {x,y,x+w,y+h}
        virtual bool FTSDK_THISCALL LoadFromXml(const char* name, void* xmlParser, AduGuiParsedControlDef* parsedControlDef) = 0;
        virtual bool FTSDK_THISCALL PostLoadResolve() = 0;

        virtual AduGuiStatic* FTSDK_THISCALL AsStatic() = 0; // vt+0x2C
        virtual AduGuiButton* FTSDK_THISCALL AsButton() = 0;
        virtual AduGuiRadioButton* FTSDK_THISCALL AsRadioButton() = 0;
        virtual AduGuiCheckBox* FTSDK_THISCALL AsCheckBox() = 0;
        virtual AduGuiComboBox* FTSDK_THISCALL AsComboBox() = 0;
        virtual AduGuiEditBox* FTSDK_THISCALL AsEditBox() = 0;
        virtual AduGuiIMEEditBox* FTSDK_THISCALL AsIMEEditBox() = 0;
        virtual AduGuiListBox* FTSDK_THISCALL AsListBox() = 0;
        virtual AduGuiScrollBar* FTSDK_THISCALL AsScrollBar() = 0;
        virtual AduGuiSlider* FTSDK_THISCALL AsSlider() = 0;
        virtual AduGuiGauge* FTSDK_THISCALL AsGauge() = 0;
        virtual AduGuiContextMenu* FTSDK_THISCALL AsContextMenu() = 0;

        virtual bool FTSDK_THISCALL DefaultFalse5C() = 0;
        virtual bool FTSDK_THISCALL IsOwnerOrRelatedObject(void* candidate) = 0;
        virtual bool FTSDK_THISCALL DefaultTrue64() = 0;
        virtual int FTSDK_THISCALL ResetInteractionState() = 0;
        virtual bool FTSDK_THISCALL HandleKeyboardCharInput(uint32_t msg, int32_t wParam, int32_t lParam) = 0;
        virtual bool FTSDK_THISCALL HandleKeyboardNavigation(uint32_t msg, int32_t wParam, int32_t lParam) = 0;
        virtual bool FTSDK_THISCALL HandleMouseInput(uint32_t msg, int32_t wParam, int32_t lParam, int32_t a4, int32_t a5) = 0;
        virtual bool FTSDK_THISCALL DefaultFalse78() = 0;
        virtual void FTSDK_THISCALL SetPressedFlag() = 0;
        virtual void FTSDK_THISCALL ClearPressedFlag() = 0;
        virtual void FTSDK_THISCALL SetOverFlag() = 0;
        virtual void FTSDK_THISCALL ClearOverFlag() = 0;
        virtual void FTSDK_THISCALL OnHotKeyMatched() = 0;
        virtual Structs::Rect* FTSDK_THISCALL GetRectPtr() = 0;
        virtual bool FTSDK_THISCALL ContainsPoint(Structs::Point pt) = 0;
        virtual bool FTSDK_THISCALL SetEnabledState(bool value) = 0;
        virtual bool FTSDK_THISCALL IsEnabledState() = 0;
        virtual bool FTSDK_THISCALL DefaultFalseA0() = 0;
        virtual bool FTSDK_THISCALL HandleHotKey(int32_t hotKeyValue) = 0;
        virtual int FTSDK_THISCALL ApplyStateFieldE7E0(int32_t stateSetIndex, int32_t visualStateIndex, int32_t parsedStateIndex) = 0;
        virtual int FTSDK_THISCALL ApplyStateFieldE770(int32_t stateSetIndex, int32_t visualStateIndex) = 0;
        virtual AduGuiStateSet* FTSDK_THISCALL GetStateSet(int32_t stateSetIndex) = 0;
        virtual int FTSDK_THISCALL EnsureStateSetCopy(uint32_t stateSetIndex, const AduGuiStateSet* source) = 0;
        virtual int FTSDK_THISCALL UpdateCurrentVisualState() = 0;
        virtual int FTSDK_THISCALL SetStateFlagE8A0(int32_t visualStateIndex, bool value, int32_t stateSetIndex) = 0;
        virtual int FTSDK_THISCALL SetStateRangeD850(int32_t visualStateIndex, float minValue, float maxValue, int32_t stateSetIndex) = 0;
        virtual int FTSDK_THISCALL ClearStateRuntimeFlag(int32_t visualStateIndex) = 0;
        virtual int FTSDK_THISCALL ClearVisualStateInfo(int32_t visualStateIndex) = 0;
        virtual int FTSDK_THISCALL ConfigureVisualStateInfo(int32_t visualStateIndex, bool enabled, float timing, int32_t alternateStateIndex) = 0;
        virtual int FTSDK_THISCALL SetEventSoundId(int32_t soundId, int32_t eventIndex) = 0;
        virtual int FTSDK_THISCALL GetEventSoundId(int32_t eventIndex) = 0;

    public:
        AduGuiStateSet** stateSets; // +0x20
        int32_t stateCount;
        int32_t stateCapacity;

        Structs::FTStringA name; // XML Name

        int32_t commandId;     // +0x48, bind command id, ctor -1
        int32_t groupId;       // XML GroupID
        int32_t controlTypeId; // XML Type, GuiControlType
        int32_t hotKey;        // XML HotKey
        int32_t subGroupId;    // XML SubGroupID, likely

        uint8_t enabledStateFlag; // XML EnabledState via SetEnabledState
        uint8_t interactionActiveFlag;
        uint8_t focusFlag;
        uint8_t pad5F;

        Structs::Rect rect; // calculated from x/y/width/height

        int32_t currentVisualState; // ctor 5
        int32_t selectedStateIndex; // XML Stage / selected visual stage

        uint8_t bindStage; // XML BindStage
        uint8_t debugFlag; // XML Debug
        uint8_t overFlag;
        uint8_t pressedFlag;
        uint8_t field7C;
        uint8_t pad7D[3];

        int32_t x;
        int32_t y;
        int32_t width;
        int32_t height;

        AduGuiDialog* ownerDialog;
        void* relatedObject;
        int32_t controlIndex;
        int32_t dx;
        int32_t dy;

        AduGuiVisualStateInfo visualStateInfos[7];

        Structs::FTStringW text;
        AduGuiControl* resolvedContextMenuControl; // resolved from XML Menu, type ContextMenu
        Structs::FTStringA contextMenuName;        // XML Menu

        uint8_t field1F8; // ctor 1
        uint8_t pad1F9[3];

    public:
        AduGuiStateSet** StateSets() const { return stateSets; }
        int32_t StateCount() const { return stateCount; }
        int32_t StateCapacity() const { return stateCapacity; }

        const Structs::FTStringA& Name() const { return name; }
        int32_t CommandId() const { return commandId; }
        int32_t GroupId() const { return groupId; }
        int32_t ControlTypeId() const { return controlTypeId; }
        int32_t HotKey() const { return hotKey; }
        int32_t SubGroupId() const { return subGroupId; }

        bool IsType(GuiControlType type) const { return controlTypeId == static_cast<int32_t>(type); }
        bool IsEnabledStateFlag() const { return enabledStateFlag != 0; }
        bool IsInteractionActiveFlag() const { return interactionActiveFlag != 0; }
        bool IsFocusFlag() const { return focusFlag != 0; }
        bool IsBindStage() const { return bindStage != 0; }
        bool IsDebugFlag() const { return debugFlag != 0; }
        bool IsOverFlag() const { return overFlag != 0; }
        bool IsPressedFlag() const { return pressedFlag != 0; }

        const Structs::Rect& Rect() const { return rect; }
        Structs::Rect& Rect() { return rect; }

        int32_t CurrentVisualState() const { return currentVisualState; }
        int32_t SelectedStateIndex() const { return selectedStateIndex; }

        int32_t X() const { return x; }
        int32_t Y() const { return y; }
        int32_t Width() const { return width; }
        int32_t Height() const { return height; }
        int32_t Right() const { return x + width; }
        int32_t Bottom() const { return y + height; }

        int32_t Dx() const { return dx; }
        int32_t Dy() const { return dy; }

        AduGuiDialog* OwnerDialog() const { return ownerDialog; }
        void* RelatedObject() const { return relatedObject; }

        const Structs::FTStringW& Text() const { return text; }
        const Structs::FTStringA& ContextMenuName() const { return contextMenuName; }
        AduGuiControl* ResolvedContextMenuControl() const { return resolvedContextMenuControl; }
    };

    class AduGuiStatic : public AduGuiControl {
    public:
        virtual int FTSDK_THISCALL RenderVisuals(void* renderCtx, int flags, const Structs::Rect* rect) = 0; // vt+0xD8
        virtual bool FTSDK_THISCALL RenderText(int visualIndex) = 0;
        virtual void FTSDK_THISCALL RenderState(int stateIndex) = 0;

    public:
        Structs::FTStringW sourceTextOrTextId;
        Structs::FTStringW resolvedDisplayText;

        uint8_t textDirtyFlag;
        uint8_t resolveTextFlag;
        uint8_t pad236[2];

        int32_t field238;
        int32_t textFx;

        Structs::Rect textMargin;

        int32_t textScrollOffsetX;
        uint32_t clippedTextWidth;
        uint8_t clippedTextRenderFlag;
        uint8_t pad259[3];

    public:
        const Structs::FTStringW& SourceTextOrTextId() const { return sourceTextOrTextId; }
        const Structs::FTStringW& ResolvedDisplayText() const { return resolvedDisplayText; }
        const Structs::Rect& TextMargin() const { return textMargin; }
        bool IsTextDirty() const { return textDirtyFlag != 0; }
        bool UsesTextResolve() const { return resolveTextFlag != 0; }
        bool UsesClippedTextRender() const { return clippedTextRenderFlag != 0; }
    };

    class AduGuiButton : public AduGuiStatic {
    public:
        int32_t buttonClickSoundId;
        int32_t buttonOverSoundId;

    public:
        int32_t ButtonClickSoundId() const { return buttonClickSoundId; }
        int32_t ButtonOverSoundId() const { return buttonOverSoundId; }
    };

    struct AduGuiEditTextBuffer {
        void (FTSDK_CDECL* callback)(int owner); // +0x00
        void* owner; // +0x04
        void* scriptStringAnalysis; // +0x08
        wchar_t* text; // +0x0C
        uint32_t capacity; // +0x10
        uint32_t field14; // +0x14

        uint8_t dirtyFlag; // +0x18
        uint8_t pad19[3];

        uint32_t field1C; // +0x1C
        uint32_t field20; // +0x20
        uint32_t field24; // +0x24
        uint32_t field28; // +0x28

        uint8_t hasScriptStringAnalysis; // +0x2C
        uint8_t pad2D[3];

        int32_t maxLength; // +0x30

        const wchar_t* Text() const { return text ? text : L""; }
        uint32_t Capacity() const { return capacity; }
        int32_t MaxLength() const { return maxLength; }
        std::size_t Length() const { return std::wcslen(Text()); }
    };
    static_assert(sizeof(AduGuiEditTextBuffer) == 0x34);

    class AduGuiEditBox : public AduGuiControl {
    public:
        virtual int FTSDK_THISCALL SetEditTextColor(int32_t color) = 0;

    public:
        uint32_t lineCacheUnknown; // +0x1FC
        void** lineCacheFirst; // +0x200
        void** lineCacheLast; // +0x204
        void** lineCacheEnd; // +0x208

        AduGuiEditTextBuffer editText; // +0x20C

        int32_t outerInset; // +0x240
        int32_t innerInset; // +0x244

        Structs::Rect textRect; // +0x248
        Structs::Rect visualRect; // +0x258

        double caretBlinkInterval; // +0x268
        double lastCaretBlinkTime; // +0x270

        uint8_t caretVisibleFlag; // +0x278
        uint8_t pad279[3];

        int32_t caretIndex; // +0x27C

        uint8_t insertMode; // +0x280
        uint8_t pad281[3];

        int32_t selectionAnchorIndex; // +0x284
        int32_t editTextColor; // +0x288
        int32_t field28C;
        int32_t selectionColorOrField290;
        int32_t caretColorOrField294;

        uint8_t field298;
        uint8_t numericOnlyFlag;
        uint8_t pad29A[2];

        int32_t field29C;

        uint8_t mouseSelectingFlag; // +0x2A0
        uint8_t preventOverflowFlag; // +0x2A1
        uint8_t multilineFlag; // +0x2A2
        uint8_t field2A3;

        int32_t lineHeight; // +0x2A4, XML Option/LineHeight
        int32_t field2A8;
        int32_t field2AC;

        uint8_t field2B0;
        uint8_t pad2B1[3];

        int32_t field2B4;
        int32_t field2B8;

        int32_t editEventSoundIds[7]; // +0x2BC, EditBoxEnter..EditBoxKeyDown

    public:
        const wchar_t* TextW() const { return editText.Text(); }
        std::size_t TextLength() const { return editText.Length(); }
        uint32_t TextCapacity() const { return editText.Capacity(); }

        bool IsInsertMode() const { return insertMode != 0; }
        bool IsNumericOnly() const { return numericOnlyFlag != 0; }
        bool IsMultiline() const { return multilineFlag != 0; }

        int32_t EditEventSoundId(GuiEventType eventType) const {
            const int32_t index = static_cast<int32_t>(eventType) - static_cast<int32_t>(EditBoxEnter);
            if (index < 0 || index >= 7)
                return -1;

            return editEventSoundIds[index];
        }

        const Structs::Rect& TextRect() const { return textRect; }
        const Structs::Rect& VisualRect() const { return visualRect; }

        int32_t OuterInset() const { return outerInset; }
        int32_t InnerInset() const { return innerInset; }

        int32_t CaretIndex() const { return caretIndex; }
        int32_t SelectionAnchorIndex() const { return selectionAnchorIndex; }
        bool HasSelection() const { return caretIndex != selectionAnchorIndex; }
    };

    class AduGuiIMEEditBox : public AduGuiEditBox {
    public:
        virtual bool FTSDK_THISCALL HandleImeComposition(int a2, int a3, uint16_t compositionFlags) = 0; // vt+0xD8
        virtual bool FTSDK_THISCALL ResetImeComposition(int a1, int a2) = 0;
        virtual bool FTSDK_THISCALL HandleImeCandidateList(int notifyType, int param) = 0;
        virtual int FTSDK_THISCALL RenderImeCandidateOrReadingWindow(int a2, int a3, bool readingWindow) = 0;
        virtual int FTSDK_THISCALL RenderImeCompositionString(int a2, int a3) = 0;
        virtual void FTSDK_THISCALL RenderImeStatusIndicator(int a2, int a3) = 0;
        virtual int FTSDK_THISCALL ApplyImeTextStyle(int styleValue) = 0;

    public:
        int32_t imeCandidatePageBackgroundColor; // +0x2D8
        int32_t imeCandidateTextColor; // +0x2DC
        int32_t imeCandidateSelectedTextColor; // +0x2E0
        int32_t imeCandidateSelectedBackColor; // +0x2E4
        int32_t imeCandidateBorderColor; // +0x2E8
        int32_t imeCompositionTextColor; // +0x2EC
        int32_t imeCompositionBackColor; // +0x2F0
        int32_t imeCompositionBorderColor; // +0x2F4
        int32_t imeReadingTextColor; // +0x2F8
        int32_t imeReadingBackColor; // +0x2FC
        int32_t imeReadingBorderColor; // +0x300
        int32_t imeField304;
        int32_t imeField308;
        int32_t imeField30C;
        int32_t imeField310;
        int32_t imeField314;
        int32_t imeField318;
        int32_t imeField31C;
        int32_t imeField320;
        int32_t imeField324;

        uint8_t imeActiveFlag; // +0x328
        uint8_t pad329[3];
        uint8_t pad32C[4];
    };

    class AduGuiCheckBox : public AduGuiButton {
    public:
        virtual void FTSDK_THISCALL SetChecked(bool checked, bool dispatchEvent) = 0; // vt+0xE4

    public:
        uint8_t hasSeparateCheckImage; // +0x264
        uint8_t checkedFlag; // +0x265
        uint8_t pad266[2];

        Structs::Rect checkImageRect; // +0x268
        Structs::Rect checkLabelHitRect; // +0x278

        Structs::FTVector<int32_t> linkedCheckCommandIds; // +0x288

    public:
        bool IsChecked() const { return checkedFlag != 0; }
        bool HasSeparateCheckImage() const { return hasSeparateCheckImage != 0; }

        const Structs::Rect& CheckImageRect() const { return checkImageRect; }
        const Structs::Rect& CheckLabelHitRect() const { return checkLabelHitRect; }

        Structs::FTVector<int32_t>& LinkedCheckCommandIds() { return linkedCheckCommandIds; }
        const Structs::FTVector<int32_t>& LinkedCheckCommandIds() const { return linkedCheckCommandIds; }
    };
    static_assert(sizeof(AduGuiCheckBox) == 0x298);

    class AduGuiRadioButton : public AduGuiCheckBox {
    public:
        virtual void FTSDK_THISCALL SetRadioChecked(bool checked, bool clearGroup, bool dispatchEvent) = 0; // vt+0xE8

    public:
        uint8_t allowRightMouseToggleFlag; // +0x298
        uint8_t pad299[3];

    public:
        bool AllowsRightMouseToggle() const { return allowRightMouseToggleFlag != 0; }
    };

    class AduGuiSlider : public AduGuiControl {
    public:
        int32_t value; // +0x1FC, XML Value, ctor 50
        int32_t minValue; // +0x200, XML Range min, ctor 0
        int32_t maxValue; // +0x204, XML Range max, ctor 100

        int32_t dragStartX; // +0x208
        int32_t dragOffsetX; // +0x20C

        int32_t thumbOffsetX; // +0x210, calculated from value/range

        Structs::Rect thumbRect; // +0x214

        int32_t sliderValueChangeSoundId; // +0x224, GuiEventType::SliderValueChange

    public:
        int32_t Value() const { return value; }
        int32_t MinValue() const { return minValue; }
        int32_t MaxValue() const { return maxValue; }

        const Structs::Rect& ThumbRect() const { return thumbRect; }

        int32_t SliderValueChangeSoundId() const { return sliderValueChangeSoundId; }
    };

    class AduGuiGauge : public AduGuiStatic {
    public:
        virtual int32_t FTSDK_THISCALL GetRange() = 0; // vt+0xE4
        virtual int32_t FTSDK_THISCALL GetValue() = 0;
        virtual int FTSDK_THISCALL SetRange(int32_t range) = 0;
        virtual int FTSDK_THISCALL SetValue(int32_t value) = 0;

    public:
        int32_t range; // +0x25C, XML Range, ctor 100
        int32_t value; // +0x260, XML Value, ctor 50

    public:
        int32_t Range() const { return range; }
        int32_t Value() const { return value; }
    };

    class AduGuiScrollBar : public AduGuiControl {
    public:
        virtual int32_t FTSDK_THISCALL ContainsScrollHitRect(Structs::Point pt) = 0; // vt+0xD8

    public:
        uint8_t scrollEnabledFlag; // +0x1FC
        uint8_t thumbDraggingFlag; // +0x1FD
        uint8_t pad1FE[2];

        Structs::Rect upButtonRect; // +0x200
        Structs::Rect downButtonRect; // +0x210
        Structs::Rect trackRect; // +0x220
        Structs::Rect thumbRect; // +0x230

        int32_t scrollValue; // +0x240
        int32_t pageSize; // +0x244
        int32_t minValue; // +0x248
        int32_t maxValue; // +0x24C

        Structs::Point lastMousePoint; // +0x250

        int32_t repeatMode; // +0x258, 0 none, 1 up initial, 2 down initial, 3 up repeat, 4 down repeat
        double lastRepeatTime; // +0x260

        uint8_t suppressScrollChangeEvent; // +0x268
        uint8_t pad269[3];

        int32_t checkWidth; // +0x26C, XML Option/CheckWidth

        Structs::Rect scrollHitRect; // +0x270

        uint8_t field280; // +0x280
        uint8_t normalDirectionFlag; // +0x281
        uint8_t pad282[2];

        int32_t scrollChangeSoundId; // +0x284, GuiEventType::ScrollBarChange

    public:
        bool IsScrollEnabled() const { return scrollEnabledFlag != 0; }
        bool IsThumbDragging() const { return thumbDraggingFlag != 0; }

        int32_t ScrollValue() const { return scrollValue; }
        int32_t PageSize() const { return pageSize; }
        int32_t MinValue() const { return minValue; }
        int32_t MaxValue() const { return maxValue; }

        const Structs::Rect& UpButtonRect() const { return upButtonRect; }
        const Structs::Rect& DownButtonRect() const { return downButtonRect; }
        const Structs::Rect& TrackRect() const { return trackRect; }
        const Structs::Rect& ThumbRect() const { return thumbRect; }
        const Structs::Rect& ScrollHitRect() const { return scrollHitRect; }

        int32_t ScrollChangeSoundId() const { return scrollChangeSoundId; }
    };

    struct AduGuiListBoxItem {
        int32_t textColor; // +0x000
        wchar_t text[256]; // +0x004
        int32_t itemData; // +0x204

        uint8_t unknown208[0x10];

        uint8_t selectedFlag; // +0x218
        uint8_t pad219[3];

        const wchar_t* Text() const { return text; }
        bool IsSelected() const { return selectedFlag != 0; }
    };
    static_assert(sizeof(AduGuiListBoxItem) == 0x21C);

    class AduGuiListBox : public AduGuiControl {
    public:
        Structs::Rect textClipRect; // +0x1FC
        Structs::Rect itemVisualRect; // +0x20C

        AduGuiScrollBar* scrollBar; // +0x21C

        int32_t rightReservedWidth; // +0x220, ctor 16
        int32_t outerPadding; // +0x224, ctor 6
        int32_t textHorizontalPadding; // +0x228, ctor 5
        int32_t fontHeight; // +0x22C

        int32_t selectionFlags; // +0x230, bit 0 enables multi/range selection behavior
        int32_t selectedIndex; // +0x234, ctor -1
        int32_t selectionAnchorIndex; // +0x238

        uint8_t mouseSelectingFlag; // +0x23C
        uint8_t autoScrollToBottomFlag; // +0x23D
        uint8_t listEnabledFlag; // +0x23E
        uint8_t pad23F;

        int32_t rowHeight; // +0x240, fontHeight + rowSpacing
        int32_t rowSpacing; // +0x244, ctor 2
        int32_t field248; // +0x248, ctor 1

        AduGuiListBoxItem** items; // +0x24C
        int32_t itemCount; // +0x250
        int32_t itemCapacity; // +0x254

    public:
        AduGuiScrollBar* ScrollBar() const { return scrollBar; }

        const Structs::Rect& TextClipRect() const { return textClipRect; }
        const Structs::Rect& ItemVisualRect() const { return itemVisualRect; }

        int32_t SelectedIndex() const { return selectedIndex; }
        int32_t SelectionAnchorIndex() const { return selectionAnchorIndex; }

        bool IsMultiSelectEnabled() const { return (selectionFlags & 1) != 0; }
        bool IsMouseSelecting() const { return mouseSelectingFlag != 0; }
        bool IsAutoScrollToBottomEnabled() const { return autoScrollToBottomFlag != 0; }
        bool IsListEnabled() const { return listEnabledFlag != 0; }

        int32_t ItemCount() const { return itemCount; }

        AduGuiListBoxItem* ItemAt(int32_t index) const {
            if (!items || index < 0 || index >= itemCount)
                return nullptr;

            return items[index];
        }
    };

    struct AduGuiComboBoxItem {
        wchar_t text[256]; // +0x000
        int32_t itemData; // +0x200
        Structs::Rect itemRect; // +0x204
        uint8_t visibleFlag; // +0x214
        uint8_t pad215[3];

        const wchar_t* Text() const { return text; }
        bool IsVisible() const { return visibleFlag != 0; }
    };
    static_assert(sizeof(AduGuiComboBoxItem) == 0x218);

    class AduGuiComboBox : public AduGuiButton {
    public:
        virtual int FTSDK_THISCALL ApplyComboBoxStateStyle(int styleValue) = 0; // vt+0xE4

    public:
        int32_t selectedIndex; // +0x264
        int32_t hoverOrPreviewIndex; // +0x268

        int32_t dropHeight; // +0x26C, XML DropHeight
        AduGuiScrollBar* scrollBar; // +0x270

        int32_t dropButtonWidth; // +0x274, ctor 16

        uint8_t dropdownOpenFlag; // +0x278
        uint8_t showDropButtonFlag; // +0x279, XML ShowDropButton
        uint8_t pad27A[2];

        Structs::Rect selectedTextRect; // +0x27C
        Structs::Rect dropButtonRect; // +0x28C
        Structs::Rect dropdownOuterRect; // +0x29C
        Structs::Rect dropdownInnerRect; // +0x2AC

        int32_t rowHeight; // +0x2BC
        int32_t rowSpacing; // +0x2C0, ctor 2

        int32_t comboBoxCloseSoundId; // +0x2C4, GuiEventType::ComboBoxClose

        AduGuiComboBoxItem** items; // +0x2C8
        int32_t itemCount; // +0x2CC
        int32_t itemCapacity; // +0x2D0

    public:
        int32_t SelectedIndex() const { return selectedIndex; }
        int32_t HoverOrPreviewIndex() const { return hoverOrPreviewIndex; }

        bool IsDropdownOpen() const { return dropdownOpenFlag != 0; }
        bool ShowsDropButton() const { return showDropButtonFlag != 0; }

        AduGuiScrollBar* ScrollBar() const { return scrollBar; }

        int32_t ItemCount() const { return itemCount; }

        AduGuiComboBoxItem* ItemAt(int32_t index) const {
            if (!items || index < 0 || index >= itemCount)
                return nullptr;

            return items[index];
        }

        int32_t ComboBoxOpenSoundId() const { return buttonClickSoundId; }
        int32_t ComboBoxSelectChangeSoundId() const { return buttonOverSoundId; }
        int32_t ComboBoxCloseSoundId() const { return comboBoxCloseSoundId; }
    };

    struct AduGuiContextMenuItem {
        int32_t itemData; // +0x00
        Structs::FTStringW text; // +0x04

        const wchar_t* Text() const { return text.Data(); }
        int32_t ItemData() const { return itemData; }
    };
    static_assert(sizeof(AduGuiContextMenuItem) == 0x20);

    class AduGuiContextMenu : public AduGuiControl {
    public:
        int32_t field1FC; // +0x1FC, ctor 0

        Structs::Rect itemAreaRect; // +0x200

        int32_t itemPaddingX; // +0x210, ctor 5
        int32_t itemPaddingY; // +0x214, ctor 3
        int32_t maxTextWidth; // +0x218
        int32_t fontHeight; // +0x21C

        int32_t menuContentWidth; // +0x220
        int32_t itemHeight; // +0x224

        int32_t hoverItemIndex; // +0x228, ctor -1
        int32_t selectedItemIndex; // +0x22C, ctor -1

        int32_t contextMenuClickSoundId; // +0x230, GuiEventType::ContextMenuClick

        AduGuiContextMenuItem** items; // +0x234
        int32_t itemCount; // +0x238
        int32_t itemCapacity; // +0x23C

    public:
        int32_t HoverItemIndex() const { return hoverItemIndex; }
        int32_t SelectedItemIndex() const { return selectedItemIndex; }

        int32_t ItemCount() const { return itemCount; }

        AduGuiContextMenuItem* ItemAt(int32_t index) const {
            if (!items || index < 0 || index >= itemCount)
                return nullptr;

            return items[index];
        }

        int32_t ContextMenuClickSoundId() const { return contextMenuClickSoundId; }
    };
    
    static_assert(sizeof(AduBase) == 0x08);
    static_assert(sizeof(AduGameObj) == 0x20);
    static_assert(sizeof(AduGuiControl) == 0x1FC);
    static_assert(sizeof(AduGuiStatic) == 0x25C);
    static_assert(sizeof(AduGuiButton) == 0x264);
    static_assert(sizeof(AduGuiCheckBox) == 0x298);
    static_assert(sizeof(AduGuiRadioButton) == 0x29C);
    static_assert(sizeof(AduGuiComboBox) == 0x2D4);
    static_assert(sizeof(AduGuiSlider) == 0x228);
    static_assert(sizeof(AduGuiGauge) == 0x264);
    static_assert(sizeof(AduGuiEditBox) == 0x2D8);
    static_assert(sizeof(AduGuiIMEEditBox) == 0x330);
    static_assert(sizeof(AduGuiListBox) == 0x258);
    static_assert(sizeof(AduGuiScrollBar) == 0x288);
    static_assert(sizeof(AduGuiContextMenu) == 0x240);

    class AduGuiDialog : public AduGameObj {
    public:
    public:
        int32_t dialogIdOrField20; // +0x20, ctor 0xFFFF
        int32_t field24;

        double lastPeriodicUpdateTime; // +0x28

        AduGuiControl* tooltipOrDelayedTextControl; // +0x30

        uint8_t field34;
        uint8_t field35;
        uint8_t pad36[2];

        int32_t field38;
        int32_t field3C;
        int32_t dialogX;      // +0x40, XML Dialog x
        int32_t dialogY;      // +0x44, XML Dialog y
        int32_t dialogWidth;  // +0x48, XML Dialog w
        int32_t dialogHeight; // +0x4C, XML Dialog h

        float scaleX; // +0x50, ctor 1.0
        float scaleY; // +0x54, ctor 1.0

        int32_t field58;
        int32_t field5C;
        int32_t field60;
        int32_t field64;

        void* renderContext; // +0x68

        GuiCallbackFn callback; // +0x6C
        void* callbackUserData; // +0x70

        void** imageResources; // +0x74
        int32_t imageResourceCount; // +0x78
        int32_t imageResourceCapacity; // +0x7C

        Structs::FTTreeMap<int32_t, AduGuiControl*> controlMap; // +0x80

        AduGameObj** objectArray; // +0x8C
        int32_t objectCount; // +0x90
        int32_t objectCapacity; // +0x94

        void** runtimeEntryArray; // +0x98
        int32_t runtimeEntryCount; // +0x9C
        int32_t runtimeEntryCapacity; // +0xA0

        AduGuiDialog* selfA4; // +0xA4
        AduGuiDialog* selfA8; // +0xA8

        uint8_t rotateFocusFlag; // +0xAC, XML RotateFocus, ctor 1
        uint8_t padAD[3];

        int32_t delayedTextX; // +0xB0
        int32_t delayedTextY; // +0xB4

        uint8_t delayedTextEnabled; // +0xB8
        uint8_t padB9[3];

        float delayedTextTimer; // +0xBC

        uint8_t dispatchEventsFlag; // +0xC0
        uint8_t keepCaptureAfterEventFlag; // +0xC1
        uint8_t fieldC2; // +0xC2, ctor 1
        uint8_t padC3;

        float fieldC4;

    public:
        void SetCallback(GuiCallbackFn callback, void* userData) {
			this->callback = callback;
			this->callbackUserData = userData;
        }

        AduGameObj* ObjectAt(int32_t index) const {
            auto* arr = objectArray;

			if (!arr || index < 0 || index >= objectCount)
                return nullptr;

            return arr[index];
        }

        AduGuiControl* ControlAt(int32_t index) const {
            return reinterpret_cast<AduGuiControl*>(ObjectAt(index));
        }

        AduGuiControl* FindControlByCommandId(int32_t commandId) const {
            const int32_t count = objectCount;

            if (count < 0 || count > 4096)
                return nullptr;

            for (int32_t i = 0; i < count; ++i) {
                auto* control = ControlAt(i);
                if (!control)
                    continue;

                if (control->CommandId() == commandId)
                    return control;
            }

            return nullptr;
        }

        AduGuiControl* FindControlByName(const char* name) const {
            if (!name)
                return nullptr;

            const int32_t count = objectCount;

            if (count < 0 || count > 4096)
                return nullptr;

            for (int32_t i = 0; i < count; ++i) {
                auto* control = ControlAt(i);
                if (!control)
                    continue;

                if (control->Name().Equals(name))
                    return control;
            }

            return nullptr;
        }

        AduGuiControl* FindControlByNameAndType(const char* name, GuiControlType type) const {
            if (!name)
                return nullptr;

            const int32_t count = objectCount;

            if (count < 0 || count > 4096)
                return nullptr;

            for (int32_t i = 0; i < count; ++i) {
                auto* control = ControlAt(i);
                if (!control)
                    continue;

                if (control->ControlTypeId() != static_cast<int32_t>(type))
                    continue;

                if (control->Name().Equals(name))
                    return control;
            }

            return nullptr;
        }
    };
	static_assert(sizeof(AduGuiDialog) == 0xC8);

    class AduGuiCustomControlBase : public AduGuiControl {
    public:
        virtual bool FTSDK_THISCALL LoadEmbeddedGuiXml(const char* xmlName, const Structs::GuiBind* binds, int32_t bindCount, GuiCallbackFn callback) = 0; // vt+0xD8
        virtual int FTSDK_THISCALL OnEmbeddedCommand(int32_t commandId) = 0;
        virtual int FTSDK_THISCALL OnEmbeddedEditEnter(int32_t commandId) = 0;
        virtual int FTSDK_THISCALL OnEmbeddedEditChange(int32_t commandId) = 0;
        virtual int FTSDK_THISCALL OnEmbeddedEditTab(int32_t commandId) = 0;
        virtual int FTSDK_THISCALL OnEmbeddedEditEsc(int32_t commandId) = 0;
        virtual int FTSDK_THISCALL OnEmbeddedEditKeyUp(int32_t commandId) = 0;
        virtual int FTSDK_THISCALL OnEmbeddedEditKeyDown(int32_t commandId) = 0;
        virtual int FTSDK_THISCALL OnEmbeddedContextMenuClick(int32_t commandId, int32_t selectedValue, AduGuiControl* contextMenuControl, int32_t selectedData) = 0;
        virtual int FTSDK_THISCALL DispatchEmbeddedGuiEvent(GuiEventType eventType, int32_t commandId, int32_t param, AduGuiControl* control) = 0;

    public:
        AduGuiDialog* embeddedDialog; // +0x1FC
        Structs::FTTreeMap<int32_t, AduGuiControl*> embeddedBindMap; // +0x200
        int32_t embeddedField20C;
    };
    static_assert(sizeof(AduGuiCustomControlBase) == 0x210);

    class FTRankingInfo : public AduGuiCustomControlBase {
    public:
        AduGuiButton* btClothInfoInner; // +0x210, XML btClothInfo

        AduGuiStatic* stRankingInner; // +0x214
        AduGuiStatic* stEmblemMarkInner; // +0x218
        AduGuiStatic* stUserNameInner; // +0x21C
        AduGuiStatic* stUserLevelInner; // +0x220
        AduGuiStatic* stUserExpInner; // +0x224
        AduGuiStatic* stWinPercentInner; // +0x228
        AduGuiStatic* stRankingPointInner; // +0x22C

        void* rankingRecord; // +0x230, points to 52-byte ranking row/cache record

    public:
        bool HasRankingRecord() const {
            return rankingRecord != nullptr;
        }

        int32_t RankingPlayerId() const {
            return rankingRecord
                ? *reinterpret_cast<const int32_t*>(
                    reinterpret_cast<const uint8_t*>(rankingRecord) + 0x04
                    )
                : 0;
        }
    };
    static_assert(sizeof(FTRankingInfo) == 0x234);

    class StageManager;
    class AduEngine;

    class ScreenRoot {
    public:
        virtual void FTSDK_THISCALL Reserved00() = 0;
        virtual void FTSDK_THISCALL Reserved04() = 0;
        virtual void FTSDK_THISCALL Reserved08() = 0;
        virtual void FTSDK_THISCALL Reserved0C() = 0;
        virtual void FTSDK_THISCALL Reserved10() = 0;
        virtual void FTSDK_THISCALL Reserved14() = 0;
        virtual void FTSDK_THISCALL Reserved18() = 0;
        virtual void FTSDK_THISCALL Reserved1C() = 0;
        virtual void FTSDK_THISCALL Reserved20() = 0;
        virtual void FTSDK_THISCALL Reserved24() = 0;
        virtual void FTSDK_THISCALL Reserved28() = 0;

        virtual int FTSDK_THISCALL OnFocusEnter() = 0;
        virtual int FTSDK_THISCALL OnFocusLeave() = 0;
        virtual void* FTSDK_THISCALL Destroy(uint32_t flags) = 0;
        virtual void FTSDK_THISCALL AttachRaw(ScreenRoot* parentOrManager, int32_t rectVtableOrTemp, int32_t left, int32_t top, int32_t right, int32_t bottom, int32_t id) = 0;
        virtual int FTSDK_THISCALL Shutdown() = 0;
        virtual int FTSDK_THISCALL Update() = 0;
        virtual int FTSDK_THISCALL OffsetRect(int32_t dx, int32_t dy) = 0;
        virtual void FTSDK_THISCALL CenterRectInParent() = 0;
        virtual ScreenRoot* FTSDK_THISCALL ActivateProcessing() = 0;
        virtual void FTSDK_THISCALL DeactivateProcessing() = 0;

        virtual int FTSDK_THISCALL OnStageCommand(int32_t commandId) = 0;
        virtual void FTSDK_THISCALL Reserved58() = 0;
        virtual void FTSDK_THISCALL Reserved5C() = 0;
        virtual void FTSDK_THISCALL Reserved60() = 0;
        virtual void FTSDK_THISCALL Reserved64() = 0;
        virtual void FTSDK_THISCALL Reserved68() = 0;
        virtual void FTSDK_THISCALL Reserved6C() = 0;
        virtual void FTSDK_THISCALL Reserved70() = 0;
        virtual void FTSDK_THISCALL Reserved74() = 0;
        virtual void FTSDK_THISCALL Reserved78() = 0;
        virtual void FTSDK_THISCALL Reserved7C() = 0;
        virtual void FTSDK_THISCALL Reserved80() = 0;

        virtual void FTSDK_THISCALL SetOpenFlag() = 0;
        virtual void FTSDK_THISCALL ClearOpenFlag() = 0;
        virtual void FTSDK_STDCALL RectCleanupThunk(int32_t a1, uint8_t rectObj, int32_t a3, int32_t a4) = 0;
        virtual void FTSDK_THISCALL HandleSystemEvent(int32_t eventType, void* data, int32_t param) = 0;
        virtual void FTSDK_STDCALL RectCleanupThunk2(uint8_t rectObj, int32_t a2, int32_t a3) = 0;
        virtual bool FTSDK_THISCALL HandleInput(uint32_t msg, int32_t wParam, int32_t lParam) = 0;

    public:
        int32_t id;
        float field08;
        float field0C;

        uint8_t unknown10[0x80];

        Structs::GuiRectStorage rect;

        Structs::FTVector<ScreenRoot*> children;
        ScreenRoot* parent;

        int32_t parentChainEnabled;
        int32_t processEnabled;
        int32_t openFlag;
        int32_t rectOffsetEnabled;

        uint8_t unknownC8[0x70];
        char resourceName[0x80];
        void* lazyResource;
        uint8_t lazyResourceAllowed;
        uint8_t pad1BD[3];

    public:
        bool IsParentChainEnabled() const { return parentChainEnabled != 0; }
        bool IsProcessingEnabled() const { return processEnabled != 0; }
        bool IsOpen() const { return openFlag != 0; }
        bool IsRectOffsetEnabled() const { return rectOffsetEnabled != 0; }
        bool HasParent() const { return parent != nullptr; }
        bool HasLazyResource() const { return lazyResource != nullptr; }

        const Structs::GuiRectStorage& RectStorage() const { return rect; }
        Structs::GuiRectStorage& RectStorage() { return rect; }

        Structs::FTVector<ScreenRoot*>& Children() { return children; }
        const Structs::FTVector<ScreenRoot*>& Children() const { return children; }

        int32_t Left() const { return rect.left; }
        int32_t Top() const { return rect.top; }
        int32_t Right() const { return rect.right; }
        int32_t Bottom() const { return rect.bottom; }
        int32_t Width() const { return rect.right - rect.left; }
        int32_t Height() const { return rect.bottom - rect.top; }
    };
    static_assert(sizeof(ScreenRoot) == 0x1C0);

    class ScreenInputRoot : public ScreenRoot {
    public:
        void* inputHwnd;
        int32_t startupParamOrMode;
        AduEngine* engine;

        ScreenRoot* previousFocusOwner;
        ScreenRoot* currentFocusOwner;
        ScreenRoot* previousHoverOwner;
        ScreenRoot* currentHoverOwner;

        int32_t previousCursorX;
        int32_t previousCursorY;

        int32_t cursorX;
        int32_t cursorY;

        uint8_t inputState[0x26C];

        ScreenRoot* hoverOrCapturedOwner;

        char resourceBasePath[0x104];

    public:
        bool HasCurrentFocusOwner() const { return currentFocusOwner != nullptr; }
        bool HasCurrentHoverOwner() const { return currentHoverOwner != nullptr; }

        int32_t CursorDeltaX() const { return cursorX - previousCursorX; }
        int32_t CursorDeltaY() const { return cursorY - previousCursorY; }

        using SetFocusOwnerFn = ScreenRoot * (FTSDK_THISCALL*)(ScreenInputRoot* self, ScreenRoot* owner);
        ScreenRoot* SetFocusOwner(ScreenRoot* owner) {
            return reinterpret_cast<SetFocusOwnerFn>(0x005AAF70)(this, owner);
        }

        using DispatchInputMessageFn = bool(FTSDK_FASTCALL*)(ScreenInputRoot* self, int edx0, uint32_t msg, int32_t wParam, int32_t lParam);
        bool DispatchInputMessage(uint32_t msg, int32_t wParam, int32_t lParam) {
            return reinterpret_cast<DispatchInputMessageFn>(0x005AB1F0)(this, 0, msg, wParam, lParam);
        }

        using UpdateInputRootFn = int(FTSDK_THISCALL*)(ScreenInputRoot* self);
        int UpdateInputRoot() {
            return reinterpret_cast<UpdateInputRootFn>(0x005AB180)(this);
        }

        using InitInputRootFn = char* (FTSDK_THISCALL*)(ScreenInputRoot* self, void* hwnd, AduEngine* aduEngine, int32_t startupParamOrMode,
            const char* resourceBasePath, int cursorResourceA, int cursorResourceB);
        char* InitInputRoot(void* hwndValue, AduEngine* aduEngineValue, int32_t startupParam, const char* basePath, int cursorResourceA, int cursorResourceB) {
            return reinterpret_cast<InitInputRootFn>(0x005AAEA0)(this, hwndValue, aduEngineValue, startupParam, basePath, cursorResourceA, cursorResourceB);
        }

        using SetCursorModeFn = uint32_t(FTSDK_THISCALL*)(ScreenInputRoot* self, int32_t mode);
        uint32_t SetCursorMode(int32_t mode) {
            return reinterpret_cast<SetCursorModeFn>(0x005AAD30)(this, mode);
        }
    };
    static_assert(sizeof(ScreenInputRoot) == 0x560);

    class StageBase : public ScreenRoot {
    public:
        virtual void FTSDK_THISCALL AttachOwnerWithDefaultRect(ScreenRoot* parent, int32_t id) = 0;
        virtual int FTSDK_THISCALL InitDialogWithParentOwner(StageBase* parent) = 0;
        virtual bool FTSDK_THISCALL LoadXml(const char* xmlName, const Structs::GuiBind* binds, int32_t bindCount, GuiCallbackFn callback, bool reload) = 0;
        virtual int FTSDK_THISCALL OnActivate() = 0;
        virtual int FTSDK_THISCALL OnDeactivate() = 0;
        virtual int FTSDK_THISCALL OnUpdate() = 0;
        virtual bool FTSDK_THISCALL ProcessChildOwners(float dt) = 0;
        virtual int FTSDK_THISCALL HandleStagePacket(void* packet) = 0;
        virtual int FTSDK_THISCALL HandleGuiEvent(GuiEventType eventType, int commandId, int param) = 0;
        virtual int FTSDK_THISCALL OnCommandAction(int32_t commandId) = 0;
        virtual int FTSDK_THISCALL OnEditBoxEnter(int32_t commandId) = 0;
        virtual int FTSDK_THISCALL OnEditBoxChange(int32_t commandId) = 0;
        virtual int FTSDK_THISCALL OnEditBoxTab(int32_t commandId) = 0;
        virtual int FTSDK_THISCALL OnEditBoxEsc(int32_t commandId) = 0;
        virtual int FTSDK_THISCALL OnEditBoxKeyUp(int32_t commandId) = 0;
        virtual int FTSDK_THISCALL OnEditBoxKeyDown(int32_t commandId) = 0;
        virtual int FTSDK_THISCALL OnContextMenuClick(int32_t commandId, int selectedValue, AduGuiControl* contextMenuControl, int selectedData) = 0;

    public:
        StageBase* priorityInputChildOwner;
        uint8_t inputConsumedFlag;
        uint8_t padOrUnknown1C5[3];
        AduGuiDialog* xmlDialog;

        Structs::FTTreeMap<int32_t, AduGuiControl*> controlBindMap;
        Structs::FTVector<StageBase*> processingChildOwners;

    public:
        bool InputConsumedFlag() const { return inputConsumedFlag != 0; }
        AduGuiDialog* XmlDialog() const { return xmlDialog; }
        bool HasXmlDialog() const { return xmlDialog != nullptr; }
        StageBase* PriorityInputChildOwner() const { return priorityInputChildOwner; }

        Structs::FTTreeMap<int32_t, AduGuiControl*>& ControlBindMap() { return controlBindMap; }
        const Structs::FTTreeMap<int32_t, AduGuiControl*>& ControlBindMap() const { return controlBindMap; }

        AduGuiControl* FindBoundControl(int32_t commandId) const { return controlBindMap.Find(commandId); }

        AduGuiControl* FindBoundControl(int32_t commandId, GuiControlType expectedType) const {
            auto* control = FindBoundControl(commandId);

            if (!control)
                return nullptr;

            if (control->ControlTypeId() != static_cast<int32_t>(expectedType))
                return nullptr;

            return control;
        }

        bool HasBoundControl(int32_t commandId) const { return controlBindMap.Contains(commandId); }

        Structs::FTVector<StageBase*>& ProcessingChildOwners() { return processingChildOwners; }
        const Structs::FTVector<StageBase*>& ProcessingChildOwners() const { return processingChildOwners; }

        void SetPriorityInputChildOwner(StageBase* child) { priorityInputChildOwner = child; }

        void ClearPriorityInputChildOwnerIf(StageBase* child) {
            if (PriorityInputChildOwner() == child)
                SetPriorityInputChildOwner(nullptr);
        }

        using AddChildOwnerFn = int(FTSDK_THISCALL*)(StageBase* self, StageBase* child);
        void AddChildOwner(StageBase* child) {
            reinterpret_cast<AddChildOwnerFn>(0x005AA720)(this, child);
        }

        using BringChildOwnerToFrontFn = int(FTSDK_THISCALL*)(StageBase* self, StageBase* child);
        void BringChildOwnerToFront(StageBase* child) {
            reinterpret_cast<BringChildOwnerToFrontFn>(0x005AA780)(this, child);
        }

        using IsOwnerReachableFn = int(FTSDK_FASTCALL*)(StageBase* self);
        bool IsReachableThroughParentChain() const {
            return reinterpret_cast<IsOwnerReachableFn>(0x005A99E0)(const_cast<StageBase*>(this)) != 0;
        }
    };
    static_assert(sizeof(StageBase) == 0x1E8);

    class MessageBoxDialogOwner : public StageBase {
    public:
        int32_t messageBoxMode;

        int32_t okYesEventType;
        int32_t okYesEventData;
        int32_t okYesEventParam;

        int32_t noEventType;
        int32_t noEventData;
        int32_t noEventParam;

        void* stMessageInnerObject;
        AduGuiControl* btOKControl;
        AduGuiControl* btYesControl;
        AduGuiControl* btNoControl;
        void* stTimeLeftInnerObject;

        float delayTimer;

    public:
        bool IsTimedMode() const {
            return messageBoxMode == MessageBox_TimedOK || messageBoxMode == MessageBox_TimedYesNo;
        }

        bool IsYesNoMode() const {
            return messageBoxMode == MessageBox_YesNo || messageBoxMode == MessageBox_TimedYesNo;
        }

        bool IsOkOnlyMode() const {
            return messageBoxMode == MessageBox_OK || messageBoxMode == MessageBox_TimedOK;
        }

        bool IsShowing() const {
            return processEnabled != 0;
        }

        using ShowMessageBoxOwnerFn = MessageBoxDialogOwner * (FTSDK_THISCALL*)(MessageBoxDialogOwner* self, const wchar_t* message, int32_t mode, const int32_t* okYesEventArgs, const int32_t* noEventArgs);
        MessageBoxDialogOwner* Show(const wchar_t* message, MessageBoxMode mode, const int32_t* okYesEventArgs = nullptr, const int32_t* noEventArgs = nullptr) {
            return reinterpret_cast<ShowMessageBoxOwnerFn>(0x0056DAE0)(
                this,
                message,
                static_cast<int32_t>(mode),
                okYesEventArgs,
                noEventArgs
                );
        }

        MessageBoxDialogOwner* ShowOK(const wchar_t* message) {
            return Show(message, MessageBox_OK);
        }

        MessageBoxDialogOwner* ShowTimedOK(const wchar_t* message) {
            return Show(message, MessageBox_TimedOK);
        }

        MessageBoxDialogOwner* ShowYesNo(
            const wchar_t* message,
            int32_t okYesEventType = 0,
            int32_t okYesEventData = 0,
            int32_t okYesEventParam = 0,
            int32_t noEventType = 0,
            int32_t noEventData = 0,
            int32_t noEventParam = 0
        ) {
            const int32_t okYesArgs[3] = {
                okYesEventType,
                okYesEventData,
                okYesEventParam
            };

            const int32_t noArgs[3] = {
                noEventType,
                noEventData,
                noEventParam
            };

            return Show(message, MessageBox_YesNo, okYesArgs, noArgs);
        }

        MessageBoxDialogOwner* ShowTimedYesNo(
            const wchar_t* message,
            int32_t okYesEventType = 0,
            int32_t okYesEventData = 0,
            int32_t okYesEventParam = 0,
            int32_t noEventType = 0,
            int32_t noEventData = 0,
            int32_t noEventParam = 0
        ) {
            const int32_t okYesArgs[3] = {
                okYesEventType,
                okYesEventData,
                okYesEventParam
            };

            const int32_t noArgs[3] = {
                noEventType,
                noEventData,
                noEventParam
            };

            return Show(message, MessageBox_TimedYesNo, okYesArgs, noArgs);
        }
    };
    static_assert(sizeof(MessageBoxDialogOwner) == 0x21C);

    class GameDialogStage : public StageBase {
    public:
        virtual void FTSDK_THISCALL AttachOwnerAndCreateUserNotice(ScreenRoot* parent, int32_t id) = 0;
        virtual int FTSDK_THISCALL InitStageXml() = 0;
        virtual int FTSDK_THISCALL SetSelectedOrActivePopupIndex(int32_t index) = 0;
        virtual int FTSDK_THISCALL CanLeaveStageOrPopup(int32_t reason) = 0;
        virtual void FTSDK_THISCALL ReservedF0() = 0;
        virtual int FTSDK_THISCALL OnBackOrDefaultAction() = 0;

    public:
        StageBase* builtinPopupOwners[10];
        uint8_t xmlLoadedFlag;
        uint8_t pad211[3];

        int32_t selectedOrActivePopupIndex;
        void* trayPopupOwner;

    public:
        bool IsXmlLoaded() const {
            return xmlLoadedFlag != 0;
        }

        StageBase* BuiltinPopupOwnerAt(int32_t index) const {
            if (index < 0 || index >= 10)
                return nullptr;

            return builtinPopupOwners[index];
        }

        using GetOrCreateBuiltinPopupOwnerFn = StageBase * (FTSDK_THISCALL*)(StageBase* self, uint32_t popupIndex);
        StageBase* GetOrCreateBuiltinPopupOwner(uint32_t popupIndex) {
            if (popupIndex >= Constants::KnownBuiltinPopupSlotCount)
                return nullptr;

            return reinterpret_cast<GetOrCreateBuiltinPopupOwnerFn>(0x00498890)(this, popupIndex);
        }
    };

    class StageManager : public ScreenInputRoot {
    public:
        void* stageHwnd;

        int32_t previousStageId;
        int32_t pendingStageId;
        int32_t currentStageId;
        int32_t transitionStageScratchId;

        GameDialogStage* currentStage;
        uint8_t messageBoxDialog[0x21C];

        Structs::FTVector<GameDialogStage*> stages;

        void* currentOverlayRaw;
        Structs::StageOverlayQuad overlayQuad;

        float overlayDrawTimer;

        Structs::StageOwnedResource stageTransitionResource;

        int32_t frameUpdating;

        uint8_t unknown87C[0x10];

        uint8_t delayedReconnectFlag;
        uint8_t pad88D[3];

        float periodicActionTimer;

        uint8_t hourlyPacketReadyFlag;
        uint8_t pad895[3];

        float hourlyPacketTimer;
        int32_t unknown89C;

    public:
        MessageBoxDialogOwner* MessageBoxDialog() {
            return reinterpret_cast<MessageBoxDialogOwner*>(messageBoxDialog);
        }

        const MessageBoxDialogOwner* MessageBoxDialog() const {
            return reinterpret_cast<const MessageBoxDialogOwner*>(messageBoxDialog);
        }

        GameDialogStage* CurrentStage() const {
            if (currentStage)
                return currentStage;

            if (currentStageId < 0 || currentStageId >= stages.Size())
                return nullptr;

            return stages[currentStageId];
        }

        bool IsValidStageIndex(uint32_t index) const {
            return index < Constants::StageOwnerCount;
        }

        bool IsFrameUpdating() const {
            return frameUpdating != 0;
        }

        bool HasOverlay() const {
            return currentOverlayRaw != nullptr;
        }

        bool IsHourlyPacketReady() const {
            return hourlyPacketReadyFlag != 0;
        }

        using RequestStateFn = void(FTSDK_THISCALL*)(StageManager* self, uint32_t state);
        void RequestState(uint32_t state) {
            if (!IsValidStageIndex(state))
                return;

            reinterpret_cast<RequestStateFn>(0x004AE9A0)(this, state);
        }

        using SwitchStateNowFn = void(FTSDK_THISCALL*)(StageManager* self, uint32_t state);
        void SwitchStateNow(uint32_t state) {
            if (!IsValidStageIndex(state))
                return;

            reinterpret_cast<SwitchStateNowFn>(0x004AD7D0)(this, state);
        }

        using InitStagesFn = bool(FTSDK_THISCALL*)(StageManager* self, void* hwnd, int startupParam);
        bool InitStages(void* hwnd, int startupParam) {
            return reinterpret_cast<InitStagesFn>(0x004B0590)(this, hwnd, startupParam);
        }

        using ShowMessageBoxFn = void(FTSDK_THISCALL*)(StageManager* self, const wchar_t* message, int32_t mode, int32_t okYesEventType, int32_t okYesEventData, int32_t okYesEventParam, uint8_t playSound);
        void ShowMessageBox(
            const wchar_t* message,
            MessageBoxMode mode = MessageBox_OK,
            int32_t okYesEventType = 0,
            int32_t okYesEventData = 0,
            int32_t okYesEventParam = 0,
            bool playSound = true
        ) {
            reinterpret_cast<ShowMessageBoxFn>(0x004AE9C0)(
                this,
                message,
                static_cast<int32_t>(mode),
                okYesEventType,
                okYesEventData,
                okYesEventParam,
                playSound ? 1 : 0
                );
        }

        void ShowOKMessage(const wchar_t* message, bool playSound = true) {
            ShowMessageBox(message, MessageBox_OK, 0, 0, 0, playSound);
        }

        void ShowTimedOKMessage(const wchar_t* message, bool playSound = true) {
            ShowMessageBox(message, MessageBox_TimedOK, 0, 0, 0, playSound);
        }

        void ShowYesNoMessage(const wchar_t* message, int32_t okYesEventType = 0, int32_t okYesEventData = 0, int32_t okYesEventParam = 0, bool playSound = true) {
            ShowMessageBox(message, MessageBox_YesNo, okYesEventType, okYesEventData, okYesEventParam, playSound);
        }

        void ShowTimedYesNoMessage(const wchar_t* message, int32_t okYesEventType = 0, int32_t okYesEventData = 0, int32_t okYesEventParam = 0, bool playSound = true) {
            ShowMessageBox(message, MessageBox_TimedYesNo, okYesEventType, okYesEventData, okYesEventParam, playSound);
        }
    };
    static_assert(sizeof(StageManager) == 0x8A0);

    using GetStageManagerFn = StageManager * (FTSDK_CDECL*)();
    inline StageManager* GetStageManager() {
        return reinterpret_cast<GetStageManagerFn>(0x004B0520)();
    }

    using ConstructGuiOwnerBaseFn = StageBase * (FTSDK_THISCALL*)(StageBase* self);
    inline StageBase* ConstructStageBase(StageBase* memory) {
        return reinterpret_cast<ConstructGuiOwnerBaseFn>(0x005AEC40)(memory);
    }

    using ConstructGameDialogStageFn = GameDialogStage * (FTSDK_THISCALL*)(GameDialogStage* self);
    inline GameDialogStage* ConstructGameDialogStage(GameDialogStage* memory) {
        return reinterpret_cast<ConstructGameDialogStageFn>(0x004987C0)(memory);
    }

    using SetStaticTextFn = int(FTSDK_THISCALL*)(AduGuiStatic* control, const wchar_t* text);
    inline int SetStaticText(AduGuiStatic* control, const wchar_t* text) {
        if (!control)
			return 0;
        
		return reinterpret_cast<SetStaticTextFn>(0x006185E0)(control, text ? text : L"");
	}

    namespace GuiCustomControlRegistry {
        constexpr uintptr_t RegisterFactoryAddr = 0x006113F0;
        constexpr uintptr_t RegisterTypeNameAddr = 0x00617E20;
        constexpr uintptr_t DefaultFactoryAddr = 0x005AC950;
        constexpr uintptr_t RegisterBuiltinCustomTypesAddr = 0x005AE050;

        using RegisterFactoryFn = CreateCustomGuiControlFn(FTSDK_CDECL*)(CreateCustomGuiControlFn factory);
        using RegisterTypeNameFn = void (FTSDK_CDECL*)(const char* typeName, int32_t customTypeId);
        using RegisterBuiltinCustomTypesFn = int (FTSDK_CDECL*)();

        inline CreateCustomGuiControlFn RegisterFactory(CreateCustomGuiControlFn factory) {
            return reinterpret_cast<RegisterFactoryFn>(RegisterFactoryAddr)(factory);
        }

        inline void RegisterTypeName(const char* typeName, int32_t customTypeId) {
            reinterpret_cast<RegisterTypeNameFn>(RegisterTypeNameAddr)(typeName, customTypeId);
        }

        inline int RegisterBuiltinCustomTypes() {
            return reinterpret_cast<RegisterBuiltinCustomTypesFn>(RegisterBuiltinCustomTypesAddr)();
        }

        inline AduGuiControl* CreateByDefaultFactory(AduGuiDialog* dialog, int32_t customTypeId) {
            return reinterpret_cast<CreateCustomGuiControlFn>(DefaultFactoryAddr)(dialog, customTypeId);
        }
    }
}

Example Usage