FT SDK: Difference between revisions

From JFTSE Wiki
Jump to navigation Jump to search
Created page with "== 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 and StageManager layer. Confirmed areas include the fixed 24-stage owner model,..."
 
No edit summary
 
(3 intermediate revisions by the same user not shown)
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 and StageManager layer. Confirmed areas include the fixed 24-stage owner model, current-stage GUI attachment, embedded modal/input owner behavior, built-in popup factory behavior, owner lifecycle functions, ADU GUI object and control layouts, XML control binding, callback dispatch, StageManager-based input forwarding and EditBox text extraction.
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 as behavior is verified through disassembly, runtime tests 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.
* StageManager state, input routing and owner lifecycle.
 
* Fixed stage owner list and current-stage owner access.
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.
* Safe custom GUI attachment to the current stage owner.
 
* Built-in popup owner factory and popup slot behavior.
== Current Focus ==
* ADU GUI object, dialog and control layouts.
 
* XML GUI binding through command IDs and control types.
The currently documented SDK covers:
* GUI callback dispatch, including callback user data and event parameters.
 
* Confirmed edit-box text access through the wide-character text buffer.
* <code>StageManager</code> state tracking, current-stage access and stage switching helpers.
* Utility wrappers for dumping owners, dialogs, controls and popup state.
* <code>ScreenRoot</code> ownership, parent chains, open state, processing state and input routing.
* <code>StageBase</code> XML dialog ownership, GUI callback routing, priority child-owner behavior and event dispatch.
* <code>GameDialogStage</code> built-in popup slots, popup creation helpers, selected/active popup state and game-dialog lifecycle hooks.
* <code>AduGuiDialog</code> runtime layout, object array access, command-ID lookup, name lookup and callback/user-data storage.
* <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()</code>.
* 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 30: Line 100:
#include <cstdio>
#include <cstdio>
#include <cstring>
#include <cstring>
#include <cwchar>
#include <string>
#include <type_traits>
#include <type_traits>


Line 39: Line 111:
namespace FTSDK {
namespace FTSDK {


     struct GuiBind {
     namespace Structs {
        int32_t commandId;
        struct FTStringA {
        const char* controlName;
            uint32_t unknown_00;
         int32_t controlTypeId;
 
     };
            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 {
     enum GuiEventType : int32_t {
Line 86: 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
    };
 
    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);


     using GuiCallbackFn = void(FTSDK_STDCALL*)(
     struct AduGuiParsedStateDef {
         GuiEventType eventType,
         int32_t image[6]; // +0x00
         int commandId,
         int32_t color[6]; // +0x18
         int param,
         int32_t textColor[6]; // +0x30
         void* userData
         int32_t textFxColor[6]; // +0x48
         );
        int32_t blend[6]; // +0x60
         int32_t fx[6]; // +0x78


    struct RectI {
         uint8_t scale[6]; // +0x90
         int32_t left;
         uint8_t tiling[6]; // +0x96
         int32_t top;
 
         int32_t right;
         int32_t offsetX[6]; // +0x9C
         int32_t bottom;
         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


    struct FTString {
        int32_t alignX; // XML AlignX
         uint32_t unknown_00;
         int32_t alignY; // XML AlignY


         union {
         float scaleX;  // XML ScaleX, default 1.0
            char inlineBuffer[16];
        float scaleY;  // XML ScaleY, default 1.0
            char* heapPtr;
        float rotate;  // XML Rotate
         };
        float centerX; // XML CenterX
        float centerY; // XML CenterY
 
         int32_t flipX; // XML FlipX


         uint32_t length;
         int32_t textAlignX; // XML TextAlignX
         uint32_t capacity;
         int32_t textAlignY; // XML TextAlignY
        int32_t textFx;    // XML TextFx


         bool IsInline() const {
         int32_t textMarginLeft;  // XML TextMargin
            return capacity <= 15;
        int32_t textMarginTop;
         }
        int32_t textMarginRight;
         int32_t textMarginBottom;


         bool IsValid() const {
         int32_t wordWrap;  // XML WordWrap
            if (length > capacity || length >= 4096)
        int32_t groupId;    // XML GroupID
                return false;
        int32_t subGroupId; // XML SubGroupID


            if (!IsInline() && !heapPtr)
        uint8_t unknown10C0[0x24];
                return false;


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


         const char* Data() const {
         char contextMenuName[256]; // +0x10F4, XML Menu
            if (!IsValid())
    };
                return "";
    static_assert(sizeof(AduGuiParsedControlDef) == 0x11F4);


            return IsInline() ? inlineBuffer : heapPtr;
    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;
    }


        int32_t Length() const {
    class AduBase {
            return IsValid() ? static_cast<int32_t>(length) : 0;
    public:
        }
        virtual AduBase* FTSDK_THISCALL Destroy(uint32_t flags) = 0;


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


            const size_t textLen = std::strlen(text);
    public:
             return textLen == length && std::memcmp(Data(), text, length) == 0;
        bool Unk04Enabled() const {
             return unk04 != 0;
         }
         }
     };
     };


     static_assert(sizeof(GuiBind) == 0x0C, "GuiBind must be 0x0C");
     class AduGameObj : public AduBase {
    static_assert(sizeof(RectI) == 0x10, "RectI must be 0x10");
    public:
    static_assert(sizeof(FTString) == 0x1C, "FTString must be 0x1C");
        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


    struct AduGuiStateSet;
        AduGameObj* firstChild;
        AduGameObj* nextSibling;


    template <typename T>
         uint8_t dirtyFlag;
    struct FTContainerVector {
         uint8_t active;
         void* containerBase;
         uint8_t updateBlocked;
         T* first;
         uint8_t visible;
         T* last;
         T* capacityEnd;


         int32_t size() const {
         float elapsedTime;
            if (!first || !last || last < first)
        float deltaTime;
                return 0;


            return static_cast<int32_t>(last - first);
    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; }


         int32_t capacity() const {
         AduGameObj* FirstChild() const { return firstChild; }
            if (!first || !capacityEnd || capacityEnd < first)
        AduGameObj* NextSibling() const { return nextSibling; }
                return 0;


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


         bool empty() const {
         template <typename Callback>
             return size() == 0;
        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;


         bool validIndex(std::size_t index) const {
    public:
            return index < static_cast<std::size_t>(size());
         AduGuiStateSet** stateSets; // +0x20
         }
        int32_t stateCount;
         int32_t stateCapacity;


         T& operator[](std::size_t index) {
         Structs::FTStringA name; // XML Name
            return first[index];
        }


         const T& operator[](std::size_t index) const {
         int32_t commandId;    // +0x48, bind command id, ctor -1
            return first[index];
        int32_t groupId;       // XML GroupID
         }
         int32_t controlTypeId; // XML Type, GuiControlType
        int32_t hotKey;        // XML HotKey
        int32_t subGroupId;    // XML SubGroupID, likely


         T* begin() {
         uint8_t enabledStateFlag; // XML EnabledState via SetEnabledState
            return first;
        uint8_t interactionActiveFlag;
         }
        uint8_t focusFlag;
         uint8_t pad5F;


         T* end() {
         Structs::Rect rect; // calculated from x/y/width/height
            return last;
        }


         const T* begin() const {
         int32_t currentVisualState; // ctor 5
            return first;
         int32_t selectedStateIndex; // XML Stage / selected visual stage
         }


         const T* end() const {
         uint8_t bindStage; // XML BindStage
            return last;
        uint8_t debugFlag; // XML Debug
         }
        uint8_t overFlag;
    };
        uint8_t pressedFlag;
         uint8_t field7C;
        uint8_t pad7D[3];


    static_assert(sizeof(FTContainerVector<void*>) == 0x10, "FTContainerVector must be 0x10");
        int32_t x;
        int32_t y;
        int32_t width;
        int32_t height;


    template <typename T>
        AduGuiDialog* ownerDialog;
    struct FTVector3 {
        void* relatedObject;
         T* first;
         int32_t controlIndex;
         T* last;
         int32_t dx;
         T* capacityEnd;
         int32_t dy;


         int32_t size() const {
         AduGuiVisualStateInfo visualStateInfos[7];
            if (!first || !last || last < first)
                return 0;


            return static_cast<int32_t>(last - first);
        Structs::FTStringW text;
         }
         AduGuiControl* resolvedContextMenuControl; // resolved from XML Menu, type ContextMenu
        Structs::FTStringA contextMenuName;        // XML Menu


         int32_t capacity() const {
         uint8_t field1F8; // ctor 1
            if (!first || !capacityEnd || capacityEnd < first)
        uint8_t pad1F9[3];
                return 0;


            return static_cast<int32_t>(capacityEnd - first);
    public:
         }
        AduGuiStateSet** StateSets() const { return stateSets; }
        int32_t StateCount() const { return stateCount; }
         int32_t StateCapacity() const { return stateCapacity; }


         bool empty() const {
         const Structs::FTStringA& Name() const { return name; }
            return size() == 0;
        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 validIndex(std::size_t index) const {
         bool IsType(GuiControlType type) const { return controlTypeId == static_cast<int32_t>(type); }
            return index < static_cast<std::size_t>(size());
        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; }


         T& operator[](std::size_t index) {
         const Structs::Rect& Rect() const { return rect; }
            return first[index];
        Structs::Rect& Rect() { return rect; }
        }


         const T& operator[](std::size_t index) const {
         int32_t CurrentVisualState() const { return currentVisualState; }
            return first[index];
         int32_t SelectedStateIndex() const { return selectedStateIndex; }
         }


         T* begin() {
         int32_t X() const { return x; }
            return first;
        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; }


         T* end() {
         int32_t Dx() const { return dx; }
            return last;
         int32_t Dy() const { return dy; }
         }


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


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


     static_assert(sizeof(FTVector3<void*>) == 0x0C, "FTVector3 must be 0x0C");
     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;


     template <typename T>
     public:
    struct FTProcessingOwnerVector {
         Structs::FTStringW sourceTextOrTextId;
        void* unknown; // +0x00 / owner +0x1D8
         Structs::FTStringW resolvedDisplayText;
         T* first;     // +0x04 / owner +0x1DC
         T* last;       // +0x08 / owner +0x1E0


         int32_t size() const {
         uint8_t textDirtyFlag;
            if (!first || !last || last < first)
        uint8_t resolveTextFlag;
                return 0;
        uint8_t pad236[2];
            return static_cast<int32_t>(last - first);
 
         }
        int32_t field238;
         int32_t textFx;


         bool validIndex(std::size_t index) const {
         Structs::Rect textMargin;
            return index < static_cast<std::size_t>(size());
        }


         T& operator[](std::size_t index) {
         int32_t textScrollOffsetX;
            return first[index];
        uint32_t clippedTextWidth;
        }
        uint8_t clippedTextRenderFlag;
        uint8_t pad259[3];


         const T& operator[](std::size_t index) const {
    public:
            return first[index];
         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; }
     };
     };


     namespace Constants {
     class AduGuiButton : public AduGuiStatic {
        // Confirmed by StageManager InitStages / SwitchStateNow bounds.
    public:
        // Do not add a custom 25th stage; attach custom UI to an existing owner/modal.
         int32_t buttonClickSoundId;
        constexpr int32_t StageOwnerCount = 24;
         int32_t buttonOverSoundId;
        // 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 uintptr_t BuiltinPopupOwnerSlotsOffset = 0x1E8;
        constexpr int32_t BuiltinPopupOwnerSlotBaseIndex = 122;
    }


    class AduBase {
     public:
     public:
         virtual AduBase* FTSDK_THISCALL Destroy(uint32_t flags) = 0;
         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 AduGameObj : public AduBase {
     class AduGuiEditBox : public AduGuiControl {
     public:
     public:
         virtual AduGameObj* FTSDK_THISCALL Destroy(uint32_t flags) = 0;
         virtual int FTSDK_THISCALL SetEditTextColor(int32_t color) = 0;
        virtual bool FTSDK_THISCALL SetField08AndMark(int32_t value) = 0;
        virtual int32_t FTSDK_THISCALL ResetElapsedRecursive() = 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:
     public:
         void* VTable() const {
        uint32_t lineCacheUnknown; // +0x1FC
            return *reinterpret_cast<void* const*>(this);
         void** lineCacheFirst; // +0x200
         }
        void** lineCacheLast; // +0x204
         void** lineCacheEnd; // +0x208


         int32_t Field04() const {
         AduGuiEditTextBuffer editText; // +0x20C
            return Read<int32_t>(0x04);
        }


         int32_t Field08() const {
         int32_t outerInset; // +0x240
            return Read<int32_t>(0x08);
        int32_t innerInset; // +0x244
        }


         AduGameObj* FirstChild() const {
         Structs::Rect textRect; // +0x248
            return Read<AduGameObj*>(0x0C);
         Structs::Rect visualRect; // +0x258
         }


         AduGameObj* NextSibling() const {
         double caretBlinkInterval; // +0x268
            return Read<AduGameObj*>(0x10);
         double lastCaretBlinkTime; // +0x270
         }


         bool IsMarked() const {
         uint8_t caretVisibleFlag; // +0x278
            return ReadBool(0x14);
         uint8_t pad279[3];
         }


         bool IsActive() const {
         int32_t caretIndex; // +0x27C
            return ReadBool(0x15);
        }


         bool IsUpdateBlocked() const {
         uint8_t insertMode; // +0x280
            return ReadBool(0x16);
         uint8_t pad281[3];
         }


         bool IsVisible() const {
         int32_t selectionAnchorIndex; // +0x284
            return ReadBool(0x17);
        int32_t editTextColor; // +0x288
         }
         int32_t field28C;
        int32_t selectionColorOrField290;
        int32_t caretColorOrField294;


         float ElapsedOrTime() const {
         uint8_t field298;
            return Read<float>(0x18);
        uint8_t numericOnlyFlag;
         }
         uint8_t pad29A[2];


         float LastDelta() const {
         int32_t field29C;
            return Read<float>(0x1C);
        }


         template <typename Callback>
         uint8_t mouseSelectingFlag; // +0x2A0
         void ForEachChild(Callback&& callback) {
         uint8_t preventOverflowFlag; // +0x2A1
            for (AduGameObj* child = FirstChild(); child; child = child->NextSibling())
        uint8_t multilineFlag; // +0x2A2
                callback(child);
         uint8_t field2A3;
         }


         template <typename Callback>
         int32_t lineHeight; // +0x2A4, XML Option/LineHeight
         void ForEachChild(Callback&& callback) const {
         int32_t field2A8;
            for (AduGameObj* child = FirstChild(); child; child = child->NextSibling())
        int32_t field2AC;
                callback(child);
        }


    protected:
         uint8_t field2B0;
         template <typename T>
         uint8_t pad2B1[3];
         T Read(uintptr_t offset) const {
            return *reinterpret_cast<const T*>(reinterpret_cast<uintptr_t>(this) + offset);
        }


         template <typename T>
         int32_t field2B4;
         void Write(uintptr_t offset, T value) {
         int32_t field2B8;
            *reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(this) + offset) = value;
        }


         bool ReadBool(uintptr_t offset) const {
         int32_t editEventSoundIds[7]; // +0x2BC, EditBoxEnter..EditBoxKeyDown
            return Read<uint8_t>(offset) != 0;
        }
    };


    class AduGuiControl : public AduGameObj {
     public:
     public:
         AduGuiStateSet** StateSets() const {
         const wchar_t* TextW() const { return editText.Text(); }
            return Read<AduGuiStateSet**>(0x20);
        std::size_t TextLength() const { return editText.Length(); }
         }
         uint32_t TextCapacity() const { return editText.Capacity(); }


         int32_t StateCount() const {
         bool IsInsertMode() const { return insertMode != 0; }
            return Read<int32_t>(0x24);
        bool IsNumericOnly() const { return numericOnlyFlag != 0; }
         }
         bool IsMultiline() const { return multilineFlag != 0; }


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


        const FTString& Name() const {
             return editEventSoundIds[index];
             return *reinterpret_cast<const FTString*>(
                reinterpret_cast<uintptr_t>(this) + 0x2C
                );
         }
         }


         int32_t CommandId() const {
         const Structs::Rect& TextRect() const { return textRect; }
            return Read<int32_t>(0x48);
        const Structs::Rect& VisualRect() const { return visualRect; }
        }


         uint32_t Field4C() const {
         int32_t OuterInset() const { return outerInset; }
            return Read<uint32_t>(0x4C);
        int32_t InnerInset() const { return innerInset; }
        }


         int32_t ControlTypeId() const {
         int32_t CaretIndex() const { return caretIndex; }
            return Read<int32_t>(0x50);
        int32_t SelectionAnchorIndex() const { return selectionAnchorIndex; }
         }
         bool HasSelection() const { return caretIndex != selectionAnchorIndex; }
    };


         bool IsControlEnabledFlag() const {
    class AduGuiIMEEditBox : public AduGuiEditBox {
            return Read<uint8_t>(0x5C) != 0;
    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;


         bool IsHoverOrState3Flag() const {
    public:
            return Read<uint8_t>(0x5D) != 0;
        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;


         const RectI& Rect() const {
         uint8_t imeActiveFlag; // +0x328
            return *reinterpret_cast<const RectI*>(
        uint8_t pad329[3];
                reinterpret_cast<uintptr_t>(this) + 0x60
         uint8_t pad32C[4];
                );
    };
         }


        int32_t CurrentVisualState() const {
    class AduGuiCheckBox : public AduGuiButton {
            return Read<int32_t>(0x70);
    public:
        }
        virtual void FTSDK_THISCALL SetChecked(bool checked, bool dispatchEvent) = 0; // vt+0xE4


         int32_t DefaultStateIndex() const {
    public:
            return Read<int32_t>(0x74);
         uint8_t hasSeparateCheckImage; // +0x264
         }
        uint8_t checkedFlag; // +0x265
         uint8_t pad266[2];


         bool IsDebugFlag() const {
         Structs::Rect checkImageRect; // +0x268
            return Read<uint8_t>(0x79) != 0;
         Structs::Rect checkLabelHitRect; // +0x278
         }


         bool IsOverFlag() const {
         Structs::FTVector<int32_t> linkedCheckCommandIds; // +0x288
            return Read<uint8_t>(0x7A) != 0;
        }


         bool IsPressedFlag() const {
    public:
            return Read<uint8_t>(0x7B) != 0;
         bool IsChecked() const { return checkedFlag != 0; }
        }
        bool HasSeparateCheckImage() const { return hasSeparateCheckImage != 0; }


         int32_t X() const {
         const Structs::Rect& CheckImageRect() const { return checkImageRect; }
            return Read<int32_t>(0x80);
        const Structs::Rect& CheckLabelHitRect() const { return checkLabelHitRect; }
        }


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


        int32_t Width() const {
    class AduGuiRadioButton : public AduGuiCheckBox {
            return Read<int32_t>(0x88);
    public:
        }
        virtual void FTSDK_THISCALL SetRadioChecked(bool checked, bool clearGroup, bool dispatchEvent) = 0; // vt+0xE8


         int32_t Height() const {
    public:
            return Read<int32_t>(0x8C);
         uint8_t allowRightMouseToggleFlag; // +0x298
         }
         uint8_t pad299[3];


         float ControlTimer() const {
    public:
            return Read<float>(0x18);
         bool AllowsRightMouseToggle() const { return allowRightMouseToggleFlag != 0; }
        }
     };
     };


     class AduGuiEditBox : public AduGuiControl {
     class AduGuiSlider : public AduGuiControl {
     public:
     public:
         const wchar_t* TextW() const {
         int32_t value; // +0x1FC, XML Value, ctor 50
            const wchar_t* text = Read<const wchar_t*>(0x218);
        int32_t minValue; // +0x200, XML Range min, ctor 0
            return text ? text : L"";
        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


         std::size_t TextLength() const {
    public:
            return std::wcslen(TextW());
         int32_t Value() const { return value; }
         }
        int32_t MinValue() const { return minValue; }
         int32_t MaxValue() const { return maxValue; }


         uint32_t TextField21C() const {
         const Structs::Rect& ThumbRect() const { return thumbRect; }
            return Read<uint32_t>(0x21C);
        }


         void DumpText() const {
         int32_t SliderValueChangeSoundId() const { return sliderValueChangeSoundId; }
            std::printf(
                "[EditBox] this=%p textPtr=%p strlen=%u field21C=%u text=\"%ls\"\n",
                this,
                TextW(),
                static_cast<unsigned>(TextLength()),
                TextField21C(),
                TextW()
            );
        }
     };
     };


     class AduGuiDialog : public AduGameObj {
     class AduGuiGauge : public AduGuiStatic {
     public:
     public:
         AduGameObj** ObjectArray() const {
         virtual int32_t FTSDK_THISCALL GetRange() = 0; // vt+0xE4
            return Read<AduGameObj**>(0x8C);
        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;


         int32_t ObjectCount() const {
    public:
            return Read<int32_t>(0x90);
         int32_t range; // +0x25C, XML Range, ctor 100
        }
        int32_t value; // +0x260, XML Value, ctor 50


         AduGameObj* ObjectAt(int32_t index) const {
    public:
            auto arr = ObjectArray();
         int32_t Range() const { return range; }
        int32_t Value() const { return value; }
    };


            if (!arr || index < 0 || index >= ObjectCount())
    class AduGuiScrollBar : public AduGuiControl {
                return nullptr;
    public:
        virtual int32_t FTSDK_THISCALL ContainsScrollHitRect(Structs::Point pt) = 0; // vt+0xD8


            return arr[index];
    public:
        }
        uint8_t scrollEnabledFlag; // +0x1FC
        uint8_t thumbDraggingFlag; // +0x1FD
        uint8_t pad1FE[2];


         AduGuiControl* ControlAt(int32_t index) const {
         Structs::Rect upButtonRect; // +0x200
            return reinterpret_cast<AduGuiControl*>(ObjectAt(index));
        Structs::Rect downButtonRect; // +0x210
         }
        Structs::Rect trackRect; // +0x220
         Structs::Rect thumbRect; // +0x230


         AduGuiControl* FindControlByCommandId(int32_t commandId) const {
         int32_t scrollValue; // +0x240
            const int32_t count = ObjectCount();
        int32_t pageSize; // +0x244
        int32_t minValue; // +0x248
        int32_t maxValue; // +0x24C


            for (int32_t i = 0; i < count; ++i) {
        Structs::Point lastMousePoint; // +0x250
                auto* control = ControlAt(i);
                if (!control)
                    continue;


                if (control->CommandId() == commandId)
        int32_t repeatMode; // +0x258, 0 none, 1 up initial, 2 down initial, 3 up repeat, 4 down repeat
                    return control;
        double lastRepeatTime; // +0x260
            }


            return nullptr;
        uint8_t suppressScrollChangeEvent; // +0x268
         }
         uint8_t pad269[3];


         AduGuiControl* FindControlByName(const char* name) const {
         int32_t checkWidth; // +0x26C, XML Option/CheckWidth
            if (!name)
                return nullptr;


            const int32_t count = ObjectCount();
        Structs::Rect scrollHitRect; // +0x270


            for (int32_t i = 0; i < count; ++i) {
        uint8_t field280; // +0x280
                auto* control = ControlAt(i);
        uint8_t normalDirectionFlag; // +0x281
                if (!control)
        uint8_t pad282[2];
                    continue;


                if (control->Name().Equals(name))
        int32_t scrollChangeSoundId; // +0x284, GuiEventType::ScrollBarChange
                    return control;
            }


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


         void SetCallback(GuiCallbackFn callback, void* userData) {
         int32_t ScrollValue() const { return scrollValue; }
            Write<GuiCallbackFn>(27 * 4, callback);
        int32_t PageSize() const { return pageSize; }
            Write<void*>(28 * 4, userData);
        int32_t MinValue() const { return minValue; }
         }
         int32_t MaxValue() const { return maxValue; }


         GuiCallbackFn Callback() const {
         const Structs::Rect& UpButtonRect() const { return upButtonRect; }
            return Read<GuiCallbackFn>(27 * 4);
        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; }


         void* CallbackUserData() const {
         int32_t ScrollChangeSoundId() const { return scrollChangeSoundId; }
            return Read<void*>(28 * 4);
        }
     };
     };


     class StageManager;
     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 FTGuiOwnerBase {
     class AduGuiListBox : public AduGuiControl {
     public:
     public:
         void* VTable() const {
         Structs::Rect textClipRect; // +0x1FC
            return *reinterpret_cast<void* const*>(this);
        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 StateIndex() const {
         int32_t rowHeight; // +0x240, fontHeight + rowSpacing
            return Read<int32_t>(0x04);
        int32_t rowSpacing; // +0x244, ctor 2
         }
         int32_t field248; // +0x248, ctor 1


         uintptr_t Address() const {
         AduGuiListBoxItem** items; // +0x24C
            return reinterpret_cast<uintptr_t>(this);
        int32_t itemCount; // +0x250
         }
         int32_t itemCapacity; // +0x254


         const char* OwnerName() const {
    public:
            return reinterpret_cast<const char*>(
         AduGuiScrollBar* ScrollBar() const { return scrollBar; }
                reinterpret_cast<uintptr_t>(this) + 0x10
                );
        }


         RectI OwnerRect() const {
         const Structs::Rect& TextClipRect() const { return textClipRect; }
            RectI r{};
        const Structs::Rect& ItemVisualRect() const { return itemVisualRect; }
            r.left = Left();
            r.top = Top();
            r.right = Right();
            r.bottom = Bottom();
            return r;
        }


         int32_t Width() const {
         int32_t SelectedIndex() const { return selectedIndex; }
            return Right() - Left();
        int32_t SelectionAnchorIndex() const { return selectionAnchorIndex; }
        }


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


         int32_t Left() const {
         int32_t ItemCount() const { return itemCount; }
            return Read<int32_t>(0x94);
        }


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


        int32_t Right() const {
             return items[index];
             return Read<int32_t>(0x9C);
         }
         }
    };


         int32_t Bottom() const {
    struct AduGuiComboBoxItem {
            return Read<int32_t>(0xA0);
        wchar_t text[256]; // +0x000
         }
         int32_t itemData; // +0x200
        Structs::Rect itemRect; // +0x204
        uint8_t visibleFlag; // +0x214
         uint8_t pad215[3];


         FTContainerVector<FTGuiOwnerBase*>& ActiveChildOwners() {
         const wchar_t* Text() const { return text; }
            return *reinterpret_cast<FTContainerVector<FTGuiOwnerBase*>*>(
        bool IsVisible() const { return visibleFlag != 0; }
                reinterpret_cast<uintptr_t>(this) + 0xA4
    };
                );
    static_assert(sizeof(AduGuiComboBoxItem) == 0x218);
        }


        const FTContainerVector<FTGuiOwnerBase*>& ActiveChildOwners() const {
    class AduGuiComboBox : public AduGuiButton {
            return *reinterpret_cast<const FTContainerVector<FTGuiOwnerBase*>*>(
    public:
                reinterpret_cast<uintptr_t>(this) + 0xA4
        virtual int FTSDK_THISCALL ApplyComboBoxStateStyle(int styleValue) = 0; // vt+0xE4
                );
        }


         FTGuiOwnerBase* UnknownB0_ActiveChildCapacityEndAlias() const {
    public:
            return Read<FTGuiOwnerBase*>(0xB0);
         int32_t selectedIndex; // +0x264
         }
         int32_t hoverOrPreviewIndex; // +0x268


         FTGuiOwnerBase* ParentOwner() const {
         int32_t dropHeight; // +0x26C, XML DropHeight
            return Read<FTGuiOwnerBase*>(0xB4);
        AduGuiScrollBar* scrollBar; // +0x270
        }


         void* OwnerManager() const {
         int32_t dropButtonWidth; // +0x274, ctor 16
            return ParentOwner();
        }


         bool IsParentChainEnabled() const {
         uint8_t dropdownOpenFlag; // +0x278
            return Read<uint32_t>(0xB8) != 0;
        uint8_t showDropButtonFlag; // +0x279, XML ShowDropButton
         }
         uint8_t pad27A[2];


         bool IsProcessEnabled() const {
         Structs::Rect selectedTextRect; // +0x27C
            return Read<uint32_t>(0xBC) != 0;
        Structs::Rect dropButtonRect; // +0x28C
         }
        Structs::Rect dropdownOuterRect; // +0x29C
         Structs::Rect dropdownInnerRect; // +0x2AC


         bool IsRegisteredForProcessing() const {
         int32_t rowHeight; // +0x2BC
            return IsProcessEnabled();
         int32_t rowSpacing; // +0x2C0, ctor 2
         }


         bool IsOwnerActiveFlag() const {
         int32_t comboBoxCloseSoundId; // +0x2C4, GuiEventType::ComboBoxClose
            return IsRegisteredForProcessing();
        }


         bool IsOwnerModalOrOpenFlag() const {
         AduGuiComboBoxItem** items; // +0x2C8
            return Read<uint32_t>(0xC0) != 0;
        int32_t itemCount; // +0x2CC
         }
         int32_t itemCapacity; // +0x2D0


         bool IsRectOffsetEnabled() const {
    public:
            return Read<uint32_t>(0xC4) != 0;
         int32_t SelectedIndex() const { return selectedIndex; }
        }
        int32_t HoverOrPreviewIndex() const { return hoverOrPreviewIndex; }


         bool InputConsumedFlag() const {
         bool IsDropdownOpen() const { return dropdownOpenFlag != 0; }
            return Read<uint8_t>(0x1C4) != 0;
        bool ShowsDropButton() const { return showDropButtonFlag != 0; }
        }


         AduGuiDialog* XmlDialog() const {
         AduGuiScrollBar* ScrollBar() const { return scrollBar; }
            return Read<AduGuiDialog*>(0x1C8);
        }


         bool HasXmlDialog() const {
         int32_t ItemCount() const { return itemCount; }
            return XmlDialog() != nullptr;
        }


         FTProcessingOwnerVector<FTGuiOwnerBase*>& ProcessingChildOwners() {
         AduGuiComboBoxItem* ItemAt(int32_t index) const {
             return *reinterpret_cast<FTProcessingOwnerVector<FTGuiOwnerBase*>*>(
             if (!items || index < 0 || index >= itemCount)
                reinterpret_cast<uintptr_t>(this) + 0x1D8
                 return nullptr;
                 );
        }


        const FTProcessingOwnerVector<FTGuiOwnerBase*>& ProcessingChildOwners() const {
             return items[index];
             return *reinterpret_cast<const FTProcessingOwnerVector<FTGuiOwnerBase*>*>(
                reinterpret_cast<uintptr_t>(this) + 0x1D8
                );
         }
         }


         FTGuiOwnerBase* Unknown1D0_SelfOnEmbeddedModalInit() const {
         int32_t ComboBoxOpenSoundId() const { return buttonClickSoundId; }
            return Read<FTGuiOwnerBase*>(0x1D0);
        int32_t ComboBoxSelectChangeSoundId() const { return buttonOverSoundId; }
        }
        int32_t ComboBoxCloseSoundId() const { return comboBoxCloseSoundId; }
    };


         AduGuiControl* FindControlByCommandId(int32_t commandId) const {
    struct AduGuiContextMenuItem {
            auto* xml = XmlDialog();
         int32_t itemData; // +0x00
            return xml ? xml->FindControlByCommandId(commandId) : nullptr;
        Structs::FTStringW text; // +0x04
        }


         AduGuiControl* FindControlByName(const char* name) const {
         const wchar_t* Text() const { return text.Data(); }
            auto* xml = XmlDialog();
        int32_t ItemData() const { return itemData; }
            return xml ? xml->FindControlByName(name) : nullptr;
    };
        }
    static_assert(sizeof(AduGuiContextMenuItem) == 0x20);


        void SetOwnerRect(int32_t left, int32_t top, int32_t right, int32_t bottom) {
    class AduGuiContextMenu : public AduGuiControl {
            Write<int32_t>(0x94, left);
    public:
            Write<int32_t>(0x98, top);
        int32_t field1FC; // +0x1FC, ctor 0
            Write<int32_t>(0x9C, right);
            Write<int32_t>(0xA0, bottom);
        }


         using DestroyOwnerFn = void* (FTSDK_THISCALL*)(
         Structs::Rect itemAreaRect; // +0x200
            FTGuiOwnerBase* self,
            uint32_t flags
            );


         void* DestroyOwner(uint32_t flags) {
         int32_t itemPaddingX; // +0x210, ctor 5
            auto vt = *reinterpret_cast<void***>(this);
        int32_t itemPaddingY; // +0x214, ctor 3
            auto fn = reinterpret_cast<DestroyOwnerFn>(vt[0x34 / 4]);
        int32_t maxTextWidth; // +0x218
            return fn(this, flags);
         int32_t fontHeight; // +0x21C
         }


         using AddChildOwnerFn = int(FTSDK_THISCALL*)(
         int32_t menuContentWidth; // +0x220
            FTGuiOwnerBase* self,
        int32_t itemHeight; // +0x224
            FTGuiOwnerBase* child
            );


         void AddChildOwner(FTGuiOwnerBase* child) {
         int32_t hoverItemIndex; // +0x228, ctor -1
            reinterpret_cast<AddChildOwnerFn>(0x005AA720)(this, child);
         int32_t selectedItemIndex; // +0x22C, ctor -1
         }


         using BringChildOwnerToFrontFn = int(FTSDK_THISCALL*)(
         int32_t contextMenuClickSoundId; // +0x230, GuiEventType::ContextMenuClick
            FTGuiOwnerBase* self,
            FTGuiOwnerBase* child
            );


         void BringChildOwnerToFront(FTGuiOwnerBase* child) {
         AduGuiContextMenuItem** items; // +0x234
            reinterpret_cast<BringChildOwnerToFrontFn>(0x005AA780)(this, child);
        int32_t itemCount; // +0x238
         }
         int32_t itemCapacity; // +0x23C


         using IsOwnerReachableFn = int(FTSDK_FASTCALL*)(
    public:
            FTGuiOwnerBase* self
         int32_t HoverItemIndex() const { return hoverItemIndex; }
            );
        int32_t SelectedItemIndex() const { return selectedItemIndex; }


         bool IsReachableThroughParentChain() const {
         int32_t ItemCount() const { return itemCount; }
            return reinterpret_cast<IsOwnerReachableFn>(0x005A99E0)(
                const_cast<FTGuiOwnerBase*>(this)
                ) != 0;
        }


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


        // vtable + 0x4C.
             return items[index];
        FTGuiOwnerBase* ActivateOwnerProcessing() {
             auto vt = *reinterpret_cast<void***>(this);
            auto fn = reinterpret_cast<ActivateOwnerProcessingFn>(vt[0x4C / 4]);
            return fn(this);
         }
         }


         using OnPostGuiLoadFn = void(FTSDK_THISCALL*)(
         int32_t ContextMenuClickSoundId() const { return contextMenuClickSoundId; }
            FTGuiOwnerBase* self
    };
            );
   
    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);


        void OnPostGuiLoad() {
    class AduGuiDialog : public AduGameObj {
            auto vt = *reinterpret_cast<void***>(this);
    public:
            auto fn = reinterpret_cast<OnPostGuiLoadFn>(vt[0x50 / 4]);
    public:
            fn(this);
        int32_t dialogIdOrField20; // +0x20, ctor 0xFFFF
         }
         int32_t field24;


         using SetOpenFlagFn = void(FTSDK_THISCALL*)(
         double lastPeriodicUpdateTime; // +0x28
            FTGuiOwnerBase* self
            );


         void OnOwnerOpened() {
         AduGuiControl* tooltipOrDelayedTextControl; // +0x30
            auto vt = *reinterpret_cast<void***>(this);
            auto fn = reinterpret_cast<SetOpenFlagFn>(vt[0x84 / 4]);
            fn(this);
        }


         void OnOwnerClosed() {
         uint8_t field34;
            auto vt = *reinterpret_cast<void***>(this);
        uint8_t field35;
            auto fn = reinterpret_cast<SetOpenFlagFn>(vt[0x88 / 4]);
        uint8_t pad36[2];
            fn(this);
        }


         using OpenOwnerFn = int(FTSDK_THISCALL*)(
         int32_t field38;
            FTGuiOwnerBase* self
        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


         int OpenOwner() {
         float scaleX; // +0x50, ctor 1.0
            auto vt = *reinterpret_cast<void***>(this);
        float scaleY; // +0x54, ctor 1.0
            auto fn = reinterpret_cast<OpenOwnerFn>(vt[0x2C / 4]);
            return fn(this);
        }


         int CloseOwner() {
         int32_t field58;
            auto vt = *reinterpret_cast<void***>(this);
        int32_t field5C;
            auto fn = reinterpret_cast<OpenOwnerFn>(vt[0x30 / 4]);
        int32_t field60;
            return fn(this);
         int32_t field64;
         }


         using LoadGuiFn = bool(FTSDK_THISCALL*)(
         void* renderContext; // +0x68
            FTGuiOwnerBase* self,
            const char* xmlName,
            const GuiBind* binds,
            int32_t bindCount,
            GuiCallbackFn callback,
            bool reload
            );


         bool LoadGui(
         GuiCallbackFn callback; // +0x6C
            const char* xmlName,
         void* callbackUserData; // +0x70
            const GuiBind* binds,
            int32_t bindCount,
            GuiCallbackFn callback = nullptr,
            bool reload = false
         ) {
            auto vt = *reinterpret_cast<void***>(this);
            auto fn = reinterpret_cast<LoadGuiFn>(vt[0xA4 / 4]);
            return fn(this, xmlName, binds, bindCount, callback, reload);
        }


         using InitWithParentOwnerFn = void(FTSDK_THISCALL*)(
         void** imageResources; // +0x74
            FTGuiOwnerBase* self,
        int32_t imageResourceCount; // +0x78
            FTGuiOwnerBase* parentOwner
        int32_t imageResourceCapacity; // +0x7C
            );


         // Base implementation vfunc_1757376:
         Structs::FTTreeMap<int32_t, AduGuiControl*> controlMap; // +0x80
        //  - attaches this owner to parent with a zero rect
        //  - sets ParentOwner at +0xB4
        //  - adds this to parent->ActiveChildOwners
        //
        // Popup subclasses override this
        void InitWithParentOwner(FTGuiOwnerBase* parentOwner) {
            auto vt = *reinterpret_cast<void***>(this);
            auto fn = reinterpret_cast<InitWithParentOwnerFn>(vt[0xA0 / 4]);
            fn(this, parentOwner);
        }


         using DispatchGuiEventFn = int(FTSDK_THISCALL*)(
         AduGameObj** objectArray; // +0x8C
            FTGuiOwnerBase* self,
        int32_t objectCount; // +0x90
            GuiEventType eventType,
        int32_t objectCapacity; // +0x94
            int commandId,
            int param
            );


         int DispatchGuiEvent(GuiEventType eventType, int commandId, int param) {
         void** runtimeEntryArray; // +0x98
            auto vt = *reinterpret_cast<void***>(this);
        int32_t runtimeEntryCount; // +0x9C
            auto fn = reinterpret_cast<DispatchGuiEventFn>(vt[0xBC / 4]);
         int32_t runtimeEntryCapacity; // +0xA0
            return fn(this, eventType, commandId, param);
         }


         using SetOwnerRectCenteredInParentFn = void(FTSDK_THISCALL*)(
         AduGuiDialog* selfA4; // +0xA4
            FTGuiOwnerBase* self
        AduGuiDialog* selfA8; // +0xA8
            );


         void SetOwnerRectCenteredInParent() {
         uint8_t rotateFocusFlag; // +0xAC, XML RotateFocus, ctor 1
            auto vt = *reinterpret_cast<void***>(this);
        uint8_t padAD[3];
            auto fn = reinterpret_cast<SetOwnerRectCenteredInParentFn>(vt[0x48 / 4]);
            fn(this);
        }


         using GetControlByCommandIdFn = AduGuiControl * (FTSDK_THISCALL*)(
         int32_t delayedTextX; // +0xB0
            FTGuiOwnerBase* self,
        int32_t delayedTextY; // +0xB4
            int32_t commandId,
            int32_t expectedType
            );


         AduGuiControl* GetControlByCommandId(int32_t commandId, int32_t expectedType = -1) {
         uint8_t delayedTextEnabled; // +0xB8
            return reinterpret_cast<GetControlByCommandIdFn>(0x005AE7D0)(
         uint8_t padB9[3];
                this,
                commandId,
                expectedType
                );
         }


         using GetControlInnerObjectFn = void* (FTSDK_THISCALL*)(
         float delayedTextTimer; // +0xBC
            FTGuiOwnerBase* self,
            int32_t commandId
            );


         void* GetControlInnerObject(int32_t commandId) {
         uint8_t dispatchEventsFlag; // +0xC0
            return reinterpret_cast<GetControlInnerObjectFn>(0x005AE840)(
        uint8_t keepCaptureAfterEventFlag; // +0xC1
                this,
        uint8_t fieldC2; // +0xC2, ctor 1
                commandId
        uint8_t padC3;
                );
        }


         using HandleInputFn = bool(FTSDK_THISCALL*)(
         float fieldC4;
            FTGuiOwnerBase* self,
            uint32_t msg,
            int32_t wParam,
            int32_t lParam
            );


        // vtable + 0x98.
    public:
        //
         void SetCallback(GuiCallbackFn callback, void* userData) {
        // Base/game-dialog behavior vfunc_1758720:
this->callback = callback;
         //  - delegates to focused/input owner if present
this->callbackUserData = userData;
        //  - handles IME/edit path if present
        //  - walks ProcessingChildOwners at +0x1D8
        //  - calls child->HandleInput(...) when child +0xBC is enabled
        bool HandleInput(uint32_t msg, int32_t wParam, int32_t lParam) {
            auto vt = *reinterpret_cast<void***>(this);
            auto fn = reinterpret_cast<HandleInputFn>(vt[0x98 / 4]);
            return fn(this, msg, wParam, lParam);
         }
         }


         // +0x1C0 / this[112].
         AduGameObj* ObjectAt(int32_t index) const {
        //
             auto* arr = objectArray;
        // Used by vt+0x98 HandleInput and vt+0xB4 update traversal before
        // falling back to the +0x1D8 processing child vector.
        //
        // If set and reachable/process-enabled, this owner receives input first.
        FTGuiOwnerBase* PriorityInputChildOwner() const {
             return Read<FTGuiOwnerBase*>(0x1C0);
        }


        void SetPriorityInputChildOwner(FTGuiOwnerBase* child) {
if (!arr || index < 0 || index >= objectCount)
            Write<FTGuiOwnerBase*>(0x1C0, child);
                return nullptr;
        }


        void ClearPriorityInputChildOwnerIf(FTGuiOwnerBase* child) {
             return arr[index];
             if (PriorityInputChildOwner() == child)
                SetPriorityInputChildOwner(nullptr);
         }
         }


         void SetParentChainEnabled(bool value) {
         AduGuiControl* ControlAt(int32_t index) const {
             Write<uint32_t>(0xB8, value ? 1u : 0u);
             return reinterpret_cast<AduGuiControl*>(ObjectAt(index));
         }
         }


         uint8_t ReadU8(uintptr_t offset) const {
         AduGuiControl* FindControlByCommandId(int32_t commandId) const {
             return Read<uint8_t>(offset);
             const int32_t count = objectCount;
        }


        uint32_t ReadU32(uintptr_t offset) const {
            if (count < 0 || count > 4096)
            return Read<uint32_t>(offset);
                return nullptr;
        }


        void* ReadPtr(uintptr_t offset) const {
            for (int32_t i = 0; i < count; ++i) {
            return Read<void*>(offset);
                auto* control = ControlAt(i);
        }
                if (!control)
                    continue;


    protected:
                if (control->CommandId() == commandId)
        template <typename T>
                    return control;
        T Read(uintptr_t offset) const {
            }
            return *reinterpret_cast<const T*>(reinterpret_cast<uintptr_t>(this) + offset);
        }


        template <typename T>
             return nullptr;
        void Write(uintptr_t offset, T value) {
             *reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(this) + offset) = value;
         }
         }
    };


    class FTGameDialogOwner : public FTGuiOwnerBase {
         AduGuiControl* FindControlByName(const char* name) const {
    public:
             if (!name)
         bool IsXmlLoadedGameFlag() const {
                return nullptr;
             return Read<uint8_t>(0x210) != 0;
        }


        bool IsXmlLoaded() const {
            const int32_t count = objectCount;
            return IsXmlLoadedGameFlag();
        }


        // Valid only for stage/current dialog owners that own the built-in popup table.
             if (count < 0 || count > 4096)
        // Do not call this on arbitrary popup owner instances.
        //
        // sub_498890 uses parentOwner[122 + index] as the built-in popup slot table.
        // Ranking popup constructor also writes to [122], proving that offset 0x1E8
        // is subclass-specific on popup objects and not universally a popup table.
        FTGameDialogOwner* StageBuiltinPopupOwnerAt(int32_t popupIndex) const {
             if (popupIndex < 0 || popupIndex >= Constants::KnownBuiltinPopupSlotCount)
                 return nullptr;
                 return nullptr;


             return Read<FTGameDialogOwner*>(
             for (int32_t i = 0; i < count; ++i) {
                 Constants::BuiltinPopupOwnerSlotsOffset + popupIndex * 4
                auto* control = ControlAt(i);
            );
                 if (!control)
        }
                    continue;
 
                if (control->Name().Equals(name))
                    return control;
            }


        // Compatibility alias. Use only on stage/current dialog owners.
             return nullptr;
        FTGameDialogOwner* BuiltinPopupOwnerAt(int32_t popupIndex) const {
             return StageBuiltinPopupOwnerAt(popupIndex);
         }
         }


         // Compatibility alias. Use only on stage/current dialog owners.
         AduGuiControl* FindControlByNameAndType(const char* name, GuiControlType type) const {
        FTGameDialogOwner* PopupOwnerAt(int32_t popupIndex) const {
             if (!name)
             return StageBuiltinPopupOwnerAt(popupIndex);
                return nullptr;
        }


        AduGuiDialog* StageBuiltinPopupXmlDialogAt(int32_t popupIndex) const {
            const int32_t count = objectCount;
            auto* popup = StageBuiltinPopupOwnerAt(popupIndex);
            return popup ? popup->XmlDialog() : nullptr;
        }


        AduGuiDialog* BuiltinPopupXmlDialogAt(int32_t popupIndex) const {
            if (count < 0 || count > 4096)
            return StageBuiltinPopupXmlDialogAt(popupIndex);
                return nullptr;
        }


        AduGuiDialog* PopupXmlDialogAt(int32_t popupIndex) const {
            for (int32_t i = 0; i < count; ++i) {
            return StageBuiltinPopupXmlDialogAt(popupIndex);
                auto* control = ControlAt(i);
        }
                if (!control)
                    continue;


        bool HasStageBuiltinPopupOwner(int32_t popupIndex) const {
                if (control->ControlTypeId() != static_cast<int32_t>(type))
            return StageBuiltinPopupOwnerAt(popupIndex) != nullptr;
                    continue;
        }


        bool HasAnyStageBuiltinPopupOwner() const {
                if (control->Name().Equals(name))
            for (int32_t i = 0; i < Constants::KnownBuiltinPopupSlotCount; ++i) {
                     return control;
                if (StageBuiltinPopupOwnerAt(i))
                     return true;
             }
             }


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


         using GetOrCreateBuiltinPopupOwnerFn = FTGameDialogOwner * (FTSDK_THISCALL*)(
    class AduGuiCustomControlBase : public AduGuiControl {
            FTGameDialogOwner* self,
    public:
            uint32_t popupIndex
         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;


         // sub_498890 / 0x00498890.
    public:
         //
         AduGuiDialog* embeddedDialog; // +0x1FC
         // Valid for stage/current dialog owners.
        Structs::FTTreeMap<int32_t, AduGuiControl*> embeddedBindMap; // +0x200
         //
        int32_t embeddedField20C;
         // Creates one of the known built-in popup owner types if the slot is empty,
    };
         // stores it at parentOwner[122 + popupIndex], then calls the popup owner's
    static_assert(sizeof(AduGuiCustomControlBase) == 0x210);
         // vt+0xA0 init/attach method with this owner as parent.
 
         FTGameDialogOwner* GetOrCreateBuiltinPopupOwner(uint32_t popupIndex) {
    class FTRankingInfo : public AduGuiCustomControlBase {
            if (popupIndex >= Constants::KnownBuiltinPopupSlotCount)
    public:
                return nullptr;
         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


            return reinterpret_cast<GetOrCreateBuiltinPopupOwnerFn>(0x00498890)(
    public:
                this,
        bool HasRankingRecord() const {
                popupIndex
            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 StageManager {
     class ScreenRoot {
     public:
     public:
         virtual void FTSDK_THISCALL Reserved00() = 0;
         virtual void FTSDK_THISCALL Reserved00() = 0;
Line 1,068: Line 1,574:
         virtual void FTSDK_THISCALL Reserved28() = 0;
         virtual void FTSDK_THISCALL Reserved28() = 0;


         virtual int FTSDK_THISCALL OpenOwner() = 0;
         virtual int FTSDK_THISCALL OnFocusEnter() = 0;
         virtual int FTSDK_THISCALL CloseOwner() = 0;
         virtual int FTSDK_THISCALL OnFocusLeave() = 0;
 
         virtual void* FTSDK_THISCALL Destroy(uint32_t flags) = 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 void FTSDK_THISCALL AttachOwnerRaw(
         virtual int FTSDK_THISCALL OnStageCommand(int32_t commandId) = 0;
            void* parentOrManager,
            int32_t rectVtableOrTemp,
            int32_t left,
            int32_t top,
            int32_t right,
            int32_t bottom,
            int32_t stateIndex
        ) = 0;
 
        virtual int FTSDK_THISCALL ShutdownStages() = 0;
         virtual int FTSDK_THISCALL UpdateStageManager() = 0;
 
        virtual int FTSDK_THISCALL OffsetOwnerRect(int32_t dx, int32_t dy) = 0;
        virtual void FTSDK_THISCALL CenterOwnerRectInParent() = 0;
 
        virtual StageManager* FTSDK_THISCALL RegisterProcessEnabled() = 0;
        virtual void FTSDK_THISCALL ResetProcessEnabled() = 0;
 
        virtual void FTSDK_THISCALL Reserved54() = 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,105: Line 1,598:
         virtual void FTSDK_THISCALL Reserved80() = 0;
         virtual void FTSDK_THISCALL Reserved80() = 0;


         virtual void FTSDK_THISCALL OnOwnerOpened() = 0;
         virtual void FTSDK_THISCALL SetOpenFlag() = 0;
         virtual void FTSDK_THISCALL OnOwnerClosed() = 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;


         virtual void FTSDK_STDCALL RectCleanupThunk(
    public:
            int32_t a1,
         int32_t id;
            uint8_t rectObj,
        float field08;
            int32_t a3,
         float field0C;
            int32_t a4
         ) = 0;


         virtual void FTSDK_THISCALL HandleSystemEvent(
         uint8_t unknown10[0x80];
            int32_t eventType,
            void* data,
            int32_t param
        ) = 0;


         virtual void FTSDK_STDCALL RectCleanupThunk2(
         Structs::GuiRectStorage rect;
            uint8_t rectObj,
            int32_t a2,
            int32_t a3
        ) = 0;


         virtual bool FTSDK_THISCALL HandleInput(
         Structs::FTVector<ScreenRoot*> children;
            uint32_t msg,
        ScreenRoot* parent;
            int32_t wParam,
 
            int32_t lParam
        int32_t parentChainEnabled;
         ) = 0;
        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:
     public:
         void* VTable() const {
         bool IsParentChainEnabled() const { return parentChainEnabled != 0; }
            return *reinterpret_cast<void* const*>(this);
        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);
         }
         }


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


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


         int32_t PendingState() const {
         using InitInputRootFn = char* (FTSDK_THISCALL*)(ScreenInputRoot* self, void* hwnd, AduEngine* aduEngine, int32_t startupParamOrMode,
             return Read<int32_t>(0x568);
            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);
         }
         }


         int32_t CurrentState() const {
         using SetCursorModeFn = uint32_t(FTSDK_THISCALL*)(ScreenInputRoot* self, int32_t mode);
             return Read<int32_t>(0x56C);
        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;


        int32_t TransitionStateScratch() const {
             return control;
             return Read<int32_t>(0x570);
         }
         }


         FTGameDialogOwner* CurrentStageOwner() const {
         bool HasBoundControl(int32_t commandId) const { return controlBindMap.Contains(commandId); }
            return Read<FTGameDialogOwner*>(0x574);
 
        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);
         }
         }


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


         AduGuiDialog* CurrentDialog() const {
         using BringChildOwnerToFrontFn = int(FTSDK_THISCALL*)(StageBase* self, StageBase* child);
            auto* owner = CurrentStageOwner();
        void BringChildOwnerToFront(StageBase* child) {
             return owner ? owner->XmlDialog() : nullptr;
             reinterpret_cast<BringChildOwnerToFrontFn>(0x005AA780)(this, child);
         }
         }


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


         FTGuiOwnerBase* ModalDialogOwner() {
    class MessageBoxDialogOwner : public StageBase {
             return reinterpret_cast<FTGuiOwnerBase*>(
    public:
                reinterpret_cast<uintptr_t>(this) + 0x578
         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;
         }
         }


         const FTGuiOwnerBase* ModalDialogOwner() const {
         bool IsOkOnlyMode() const {
             return reinterpret_cast<const FTGuiOwnerBase*>(
             return messageBoxMode == MessageBox_OK || messageBoxMode == MessageBox_TimedOK;
                reinterpret_cast<uintptr_t>(this) + 0x578
                );
         }
         }


         FTContainerVector<FTGameDialogOwner*>& StageOwners() {
         bool IsShowing() const {
             return *reinterpret_cast<FTContainerVector<FTGameDialogOwner*>*>(
             return processEnabled != 0;
                reinterpret_cast<uintptr_t>(this) + 0x794
                );
         }
         }


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


         FTContainerVector<FTGameDialogOwner*>& DialogOwners() {
         MessageBoxDialogOwner* ShowOK(const wchar_t* message) {
             return StageOwners();
             return Show(message, MessageBox_OK);
         }
         }


         const FTContainerVector<FTGameDialogOwner*>& DialogOwners() const {
         MessageBoxDialogOwner* ShowTimedOK(const wchar_t* message) {
             return StageOwners();
             return Show(message, MessageBox_TimedOK);
         }
         }


         FTGameDialogOwner* StageOwnerAt(int32_t index) const {
         MessageBoxDialogOwner* ShowYesNo(
             const auto& owners = StageOwners();
            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
            };


             if (index < 0 || !owners.validIndex(static_cast<std::size_t>(index)))
             const int32_t noArgs[3] = {
                 return nullptr;
                 noEventType,
                noEventData,
                noEventParam
            };


             return owners[static_cast<std::size_t>(index)];
             return Show(message, MessageBox_YesNo, okYesArgs, noArgs);
         }
         }


         FTGameDialogOwner* DialogOwnerAt(int32_t index) const {
         MessageBoxDialogOwner* ShowTimedYesNo(
             return StageOwnerAt(index);
            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];


         void* CurrentOverlayRaw() const {
        int32_t selectedOrActivePopupIndex;
             return Read<void*>(0x7A4);
         void* trayPopupOwner;
 
    public:
        bool IsXmlLoaded() const {
             return xmlLoadedFlag != 0;
         }
         }


         float OverlayTimer() const {
         StageBase* BuiltinPopupOwnerAt(int32_t index) const {
             return Read<float>(0x854);
             if (index < 0 || index >= 10)
                return nullptr;
 
            return builtinPopupOwners[index];
         }
         }


         bool IsFrameUpdating() const {
         using GetOrCreateBuiltinPopupOwnerFn = StageBase * (FTSDK_THISCALL*)(StageBase* self, uint32_t popupIndex);
             return Read<uint32_t>(0x878) != 0;
        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;


         uint8_t DelayedReconnectFlag() const {
    public:
             return Read<uint8_t>(0x88C);
         MessageBoxDialogOwner* MessageBoxDialog() {
             return reinterpret_cast<MessageBoxDialogOwner*>(messageBoxDialog);
         }
         }


         float PeriodicTimer() const {
         const MessageBoxDialogOwner* MessageBoxDialog() const {
             return Read<float>(0x890);
             return reinterpret_cast<const MessageBoxDialogOwner*>(messageBoxDialog);
         }
         }


         uint8_t HourlyPacketReadyFlag() const {
         GameDialogStage* CurrentStage() const {
             return Read<uint8_t>(0x894);
             if (currentStage)
                return currentStage;
 
            if (currentStageId < 0 || currentStageId >= stages.Size())
                return nullptr;
 
            return stages[currentStageId];
         }
         }


         float HourlyPacketTimer() const {
         bool IsValidStageIndex(uint32_t index) const {
             return Read<float>(0x898);
             return index < Constants::StageOwnerCount;
         }
         }


         bool IsValidStageIndex(uint32_t state) const {
         bool IsFrameUpdating() const {
             return state < Constants::StageOwnerCount;
             return frameUpdating != 0;
         }
         }


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


         using RequestStateFn = void(FTSDK_THISCALL*)(
         bool IsHourlyPacketReady() const {
             StageManager* self,
             return hourlyPacketReadyFlag != 0;
            uint32_t state
        }
            );


        using RequestStateFn = void(FTSDK_THISCALL*)(StageManager* self, uint32_t state);
         void RequestState(uint32_t state) {
         void RequestState(uint32_t state) {
             if (!IsValidStageIndex(state))
             if (!IsValidStageIndex(state))
Line 1,268: Line 2,013:
         }
         }


         using SwitchStateNowFn = void(FTSDK_THISCALL*)(
         using SwitchStateNowFn = void(FTSDK_THISCALL*)(StageManager* self, uint32_t state);
            StageManager* self,
            uint32_t state
            );
 
         void SwitchStateNow(uint32_t state) {
         void SwitchStateNow(uint32_t state) {
             if (!IsValidStageIndex(state))
             if (!IsValidStageIndex(state))
Line 1,280: Line 2,021:
         }
         }


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


    private:
        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);
         template <typename T>
        void ShowMessageBox(
         T Read(uintptr_t offset) const {
            const wchar_t* message,
             return *reinterpret_cast<const T*>(reinterpret_cast<uintptr_t>(this) + offset);
            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*)();
     using GetStageManagerFn = StageManager * (FTSDK_CDECL*)();
     inline StageManager* GetStageManager() {
     inline StageManager* GetStageManager() {
         return reinterpret_cast<GetStageManagerFn>(0x004B0520)();
         return reinterpret_cast<GetStageManagerFn>(0x004B0520)();
     }
     }


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


     inline FTGuiOwnerBase* ConstructGuiOwnerBase(FTGuiOwnerBase* memory) {
    using ConstructGameDialogStageFn = GameDialogStage * (FTSDK_THISCALL*)(GameDialogStage* self);
         return reinterpret_cast<ConstructGuiOwnerBaseFn>(0x005AEC40)(memory);
     inline GameDialogStage* ConstructGameDialogStage(GameDialogStage* memory) {
         return reinterpret_cast<ConstructGameDialogStageFn>(0x004987C0)(memory);
     }
     }
   
    using ConstructGameDialogOwnerFn = FTGameDialogOwner * (FTSDK_THISCALL*)(
        FTGameDialogOwner* self
        );


     inline FTGameDialogOwner* ConstructGameDialogOwner(FTGameDialogOwner* memory) {
    using SetStaticTextFn = int(FTSDK_THISCALL*)(AduGuiStatic* control, const wchar_t* text);
         return reinterpret_cast<ConstructGameDialogOwnerFn>(0x004987C0)(memory);
     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);
        }
     }
     }
}
}

Latest revision as of 08:43, 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