FT SDK: Difference between revisions
No edit summary |
No edit summary |
||
| (One intermediate revision by the same user not shown) | |||
| Line 1: | Line 1: | ||
== Overview == | == 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 <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. | |||
== | The practical goal of this SDK is to make custom client UI possible without adding unsupported client stages. The Fantasy Tennis client uses a fixed stage model, so custom UI should attach to an existing stage owner, popup owner or embedded/custom GUI owner instead of creating an additional global stage. | ||
* <code>StageManager</code> state tracking, | |||
The SDK is intentionally conservative. Names describe current reverse engineering understanding, not original source names. Offsets, virtual slots and function addresses are tied to the currently analyzed Fantasy Tennis client build. | |||
* <code>ScreenRoot</code> | |||
* <code>StageBase</code> XML dialog ownership, child-owner | == Current Focus == | ||
* <code>GameDialogStage</code> built-in popup slots, popup | |||
The currently documented SDK covers: | |||
* | |||
* | * <code>StageManager</code> state tracking, current-stage access and stage switching helpers. | ||
* XML | * <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 == | ||
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 125: | Line 191: | ||
if (!IsValid()) | if (!IsValid()) | ||
return L""; | return L""; | ||
return IsInline() ? inlineBuffer : heapPtr; | return IsInline() ? inlineBuffer : heapPtr; | ||
} | } | ||
| Line 135: | Line 202: | ||
if (!text || !IsValid()) | if (!text || !IsValid()) | ||
return false; | return false; | ||
const size_t textLen = std::wcslen(text); | const size_t textLen = std::wcslen(text); | ||
return textLen == length && std::memcmp(Data(), text, length * sizeof(wchar_t)) == 0; | return textLen == length && std::memcmp(Data(), text, length * sizeof(wchar_t)) == 0; | ||
| Line 210: | Line 278: | ||
uint8_t pad16[2]; | uint8_t pad16[2]; | ||
bool IsNil() const { return isNil != 0; } | bool IsNil() const { | ||
return isNil != 0; | |||
} | |||
}; | }; | ||
| Line 219: | Line 289: | ||
uint32_t count; | uint32_t count; | ||
bool IsEmpty() const { return count == 0; } | bool IsEmpty() const { | ||
uint32_t Size() const { return count; } | return count == 0; | ||
FTTreeNode<Key, Value>* Head() const { return head; } | } | ||
uint32_t Size() const { | |||
return count; | |||
} | |||
FTTreeNode<Key, Value>* Head() const { | |||
return head; | |||
} | |||
FTTreeNode<Key, Value>* Root() const { | FTTreeNode<Key, Value>* Root() const { | ||
| Line 257: | Line 335: | ||
} | } | ||
bool Contains(const Key& key) const { return FindNode(key) != nullptr; } | bool Contains(const Key& key) const { | ||
return FindNode(key) != nullptr; | |||
} | |||
}; | }; | ||
| Line 264: | Line 344: | ||
const char* controlName; | const char* controlName; | ||
int32_t controlTypeId; | int32_t controlTypeId; | ||
}; | |||
struct Point { | |||
int32_t x; | |||
int32_t y; | |||
}; | }; | ||
| Line 280: | Line 365: | ||
int32_t bottom; | int32_t bottom; | ||
}; | }; | ||
struct StageOwnedResource { | struct StageOwnedResource { | ||
| Line 328: | Line 412: | ||
}; | }; | ||
static_assert(sizeof(Point) == 0x08); | |||
static_assert(sizeof(Rect) == 0x10); | static_assert(sizeof(Rect) == 0x10); | ||
static_assert(sizeof(GuiRectStorage) == 0x14); | static_assert(sizeof(GuiRectStorage) == 0x14); | ||
| Line 383: | Line 468: | ||
ListBox = 9, | ListBox = 9, | ||
ScrollBar = 10, | ScrollBar = 10, | ||
ContextMenu = 11 | ContextMenu = 11, | ||
Custom = 12, | |||
FTRankingInfoType = 68, | |||
}; | |||
enum GuiVisualStateName : int32_t { | |||
StateAll = 0, | |||
StateNormal = 1, | |||
StateOver = 2, | |||
StateFocus = 3, | |||
StatePressed = 4, | |||
StateDisabled = 5, | |||
StateHidden = 6 | |||
}; | |||
enum GuiBlendMode : int32_t { | |||
BlendModulate = 0, | |||
BlendAdd = 1 | |||
}; | |||
enum GuiTextFx : int32_t { | |||
TextFxNone = 0, | |||
TextFxShadow = 1, | |||
TextFxOutline = 2 | |||
}; | }; | ||
| Line 392: | Line 501: | ||
MessageBox_TimedYesNo = 4 | MessageBox_TimedYesNo = 4 | ||
}; | }; | ||
struct AduGuiStateSet; | struct AduGuiStateSet; | ||
class AduGuiDialog; | class AduGuiDialog; | ||
class AduGuiControl; | |||
class AduGuiStatic; | |||
class AduGuiButton; | |||
class AduGuiRadioButton; | |||
class AduGuiCheckBox; | |||
class AduGuiComboBox; | |||
class AduGuiEditBox; | |||
class AduGuiIMEEditBox; | |||
class AduGuiListBox; | |||
class AduGuiScrollBar; | |||
class AduGuiSlider; | |||
class AduGuiGauge; | |||
class AduGuiContextMenu; | |||
using GuiCallbackFn = void(FTSDK_STDCALL*)(GuiEventType eventType, int commandId, int param, void* userData); | |||
using CreateCustomGuiControlFn = AduGuiControl * (FTSDK_CDECL*)(AduGuiDialog* dialog, int32_t customTypeId); | |||
struct AduGuiVisualStateInfo { | struct AduGuiVisualStateInfo { | ||
| Line 417: | Line 535: | ||
static_assert(sizeof(AduGuiVisualStateInfo) == 0x28); | static_assert(sizeof(AduGuiVisualStateInfo) == 0x28); | ||
// Dialog side parsed XML/control definition. This is not AduGuiControl storage. | struct AduGuiParsedStateDef { | ||
// | int32_t image[6]; // +0x00 | ||
// AduGuiControl::LoadFromXml / | int32_t color[6]; // +0x18 | ||
int32_t textColor[6]; // +0x30 | |||
int32_t textFxColor[6]; // +0x48 | |||
int32_t blend[6]; // +0x60 | |||
int32_t fx[6]; // +0x78 | |||
uint8_t scale[6]; // +0x90 | |||
uint8_t tiling[6]; // +0x96 | |||
int32_t offsetX[6]; // +0x9C | |||
int32_t offsetY[6]; // +0xB4 | |||
}; | |||
static_assert(sizeof(AduGuiParsedStateDef) == 0xCC); | |||
// Dialog-side parsed XML/control definition. | |||
// This is not AduGuiControl runtime storage. | |||
// | |||
// Confirmed: | |||
// - sub_624F90 initializes this structure. | |||
// - sub_614F60 fills it from one <Control>. | |||
// - AduGuiControl::LoadFromXml consumes it. | |||
// - parsedStates uses 0xCC-byte parser-side state records. | |||
// - eventSoundIds are indexed by GuiEventType and initialized to invalid/NaN-like values. | |||
struct AduGuiParsedControlDef { | struct AduGuiParsedControlDef { | ||
Structs::FTVector<AduGuiParsedStateDef> parsedStates; // +0x0000 | |||
int32_t eventSoundIds[20]; // +0x0010, indexed by GuiEventType | |||
int32_t | uint8_t stateAndImageParseStorage[0x804]; // +0x0060..+0x0863 | ||
uint8_t | |||
wchar_t resolvedText[256]; // + | wchar_t resolvedText[256]; // +0x0864, assigned to AduGuiControl::text | ||
uint8_t | |||
uint8_t enable; // +0x1064, XML Enable, default 1 | uint8_t tooltipAndMenuParseStorage[0x600]; // +0x0A64..+0x1063 | ||
uint8_t enable; // +0x1064, XML Enable, default 1 | |||
uint8_t enabledState; // +0x1065, XML EnabledState, default 1 | uint8_t enabledState; // +0x1065, XML EnabledState, default 1 | ||
uint8_t pad1066[2]; | uint8_t pad1066[2]; | ||
int32_t x; | |||
int32_t y; | int32_t x; // +0x1068, XML x | ||
int32_t width; | int32_t y; // +0x106C, XML y | ||
int32_t height; | int32_t width; // +0x1070, XML w | ||
int32_t alignX; | int32_t height; // +0x1074, XML h | ||
int32_t alignY; | |||
float scaleX; | int32_t alignX; // XML AlignX | ||
float scaleY; | int32_t alignY; // XML AlignY | ||
float rotate; | |||
float centerX; | float scaleX; // XML ScaleX, default 1.0 | ||
float centerY; | float scaleY; // XML ScaleY, default 1.0 | ||
int32_t flipX; | float rotate; // XML Rotate | ||
int32_t textAlignX; | float centerX; // XML CenterX | ||
int32_t textAlignY; | float centerY; // XML CenterY | ||
int32_t textFx; | |||
int32_t textMarginLeft; | int32_t flipX; // XML FlipX | ||
int32_t textAlignX; // XML TextAlignX | |||
int32_t textAlignY; // XML TextAlignY | |||
int32_t textFx; // XML TextFx | |||
int32_t textMarginLeft; // XML TextMargin | |||
int32_t textMarginTop; | int32_t textMarginTop; | ||
int32_t textMarginRight; | int32_t textMarginRight; | ||
int32_t textMarginBottom; | int32_t textMarginBottom; | ||
int32_t wordWrap; | |||
int32_t groupId; | int32_t wordWrap; // XML WordWrap | ||
int32_t subGroupId; | int32_t groupId; // XML GroupID | ||
int32_t subGroupId; // XML SubGroupID | |||
uint8_t unknown10C0[0x24]; | uint8_t unknown10C0[0x24]; | ||
int32_t tabStop; // +0x10E4 | |||
int32_t initialStage; | int32_t tabStop; // +0x10E4, XML TabStop | ||
int32_t bindStage; | int32_t initialStage; // XML Stage | ||
int32_t hotKey; | int32_t bindStage; // XML BindStage | ||
char contextMenuName[256]; // +0x10F4 | int32_t hotKey; // XML HotKey | ||
char contextMenuName[256]; // +0x10F4, XML Menu | |||
}; | }; | ||
static_assert(sizeof(AduGuiParsedControlDef) == 0x11F4); | |||
namespace Constants { | namespace Constants { | ||
constexpr int32_t StageOwnerCount = 24; | constexpr int32_t StageOwnerCount = 24; | ||
constexpr int32_t KnownBuiltinPopupSlotCount = 10; | constexpr int32_t KnownBuiltinPopupSlotCount = 10; | ||
constexpr int32_t GuiEventTypeCount = 20; | |||
constexpr int32_t GuiVisualStateNameCount = 7; | |||
constexpr int32_t ParsedGuiStateSize = 0xCC; | |||
} | } | ||
| Line 483: | Line 635: | ||
} | } | ||
}; | }; | ||
class AduGameObj : public AduBase { | class AduGameObj : public AduBase { | ||
public: | public: | ||
virtual AduGameObj* FTSDK_THISCALL Destroy(uint32_t flags) = 0; | |||
virtual bool FTSDK_THISCALL SetDirtyValue(int32_t value) = 0; | virtual bool FTSDK_THISCALL SetDirtyValue(int32_t value) = 0; | ||
virtual int32_t FTSDK_THISCALL | virtual int32_t FTSDK_THISCALL ResetElapsedTimeRecursive() = 0; | ||
virtual bool FTSDK_THISCALL SetActive(bool value) = 0; | virtual bool FTSDK_THISCALL SetActive(bool value) = 0; | ||
virtual bool FTSDK_THISCALL SetUpdateBlocked(bool value) = 0; | virtual bool FTSDK_THISCALL SetUpdateBlocked(bool value) = 0; | ||
| Line 507: | Line 659: | ||
float elapsedTime; | float elapsedTime; | ||
float deltaTime; | float deltaTime; | ||
public: | public: | ||
| Line 530: | Line 682: | ||
} | } | ||
}; | }; | ||
class AduGuiControl : public AduGameObj { | class AduGuiControl : public AduGameObj { | ||
public: | public: | ||
virtual bool FTSDK_THISCALL RecalculateRect() = 0; // vt+0x20 | virtual bool FTSDK_THISCALL RecalculateRect() = 0; // vt+0x20, rect = {x,y,x+w,y+h} | ||
virtual bool FTSDK_THISCALL LoadFromXml(const char* name, void* xmlParser, AduGuiParsedControlDef* parsedControlDef) = 0; | virtual bool FTSDK_THISCALL LoadFromXml(const char* name, void* xmlParser, AduGuiParsedControlDef* parsedControlDef) = 0; | ||
virtual bool FTSDK_THISCALL PostLoadResolve() = 0; | virtual bool FTSDK_THISCALL PostLoadResolve() = 0; | ||
virtual | virtual AduGuiStatic* FTSDK_THISCALL AsStatic() = 0; // vt+0x2C | ||
virtual | virtual AduGuiButton* FTSDK_THISCALL AsButton() = 0; | ||
virtual | virtual AduGuiRadioButton* FTSDK_THISCALL AsRadioButton() = 0; | ||
virtual | virtual AduGuiCheckBox* FTSDK_THISCALL AsCheckBox() = 0; | ||
virtual | virtual AduGuiComboBox* FTSDK_THISCALL AsComboBox() = 0; | ||
virtual | virtual AduGuiEditBox* FTSDK_THISCALL AsEditBox() = 0; | ||
virtual | virtual AduGuiIMEEditBox* FTSDK_THISCALL AsIMEEditBox() = 0; | ||
virtual | virtual AduGuiListBox* FTSDK_THISCALL AsListBox() = 0; | ||
virtual | virtual AduGuiScrollBar* FTSDK_THISCALL AsScrollBar() = 0; | ||
virtual | virtual AduGuiSlider* FTSDK_THISCALL AsSlider() = 0; | ||
virtual | virtual AduGuiGauge* FTSDK_THISCALL AsGauge() = 0; | ||
virtual AduGuiContextMenu* FTSDK_THISCALL AsContextMenu() = 0; | |||
virtual | virtual bool FTSDK_THISCALL DefaultFalse5C() = 0; | ||
virtual | virtual bool FTSDK_THISCALL IsOwnerOrRelatedObject(void* candidate) = 0; | ||
virtual | virtual bool FTSDK_THISCALL DefaultTrue64() = 0; | ||
virtual int FTSDK_THISCALL | virtual int FTSDK_THISCALL ResetInteractionState() = 0; | ||
virtual bool FTSDK_THISCALL | virtual bool FTSDK_THISCALL HandleKeyboardCharInput(uint32_t msg, int32_t wParam, int32_t lParam) = 0; | ||
virtual bool FTSDK_THISCALL | virtual bool FTSDK_THISCALL HandleKeyboardNavigation(uint32_t msg, int32_t wParam, int32_t lParam) = 0; | ||
virtual | virtual bool FTSDK_THISCALL HandleMouseInput(uint32_t msg, int32_t wParam, int32_t lParam, int32_t a4, int32_t a5) = 0; | ||
virtual | virtual bool FTSDK_THISCALL DefaultFalse78() = 0; | ||
virtual | virtual void FTSDK_THISCALL SetPressedFlag() = 0; | ||
virtual | virtual void FTSDK_THISCALL ClearPressedFlag() = 0; | ||
virtual | virtual void FTSDK_THISCALL SetOverFlag() = 0; | ||
virtual | virtual void FTSDK_THISCALL ClearOverFlag() = 0; | ||
virtual | virtual void FTSDK_THISCALL OnHotKeyMatched() = 0; | ||
virtual | virtual Structs::Rect* FTSDK_THISCALL GetRectPtr() = 0; | ||
virtual | virtual bool FTSDK_THISCALL ContainsPoint(Structs::Point pt) = 0; | ||
virtual | virtual bool FTSDK_THISCALL SetEnabledState(bool value) = 0; | ||
virtual bool FTSDK_THISCALL | virtual bool FTSDK_THISCALL IsEnabledState() = 0; | ||
virtual | virtual bool FTSDK_THISCALL DefaultFalseA0() = 0; | ||
virtual | virtual bool FTSDK_THISCALL HandleHotKey(int32_t hotKeyValue) = 0; | ||
virtual int FTSDK_THISCALL | virtual int FTSDK_THISCALL ApplyStateFieldE7E0(int32_t stateSetIndex, int32_t visualStateIndex, int32_t parsedStateIndex) = 0; | ||
virtual int FTSDK_THISCALL | virtual int FTSDK_THISCALL ApplyStateFieldE770(int32_t stateSetIndex, int32_t visualStateIndex) = 0; | ||
virtual AduGuiStateSet* FTSDK_THISCALL GetStateSet(int32_t | virtual AduGuiStateSet* FTSDK_THISCALL GetStateSet(int32_t stateSetIndex) = 0; | ||
virtual int FTSDK_THISCALL | virtual int FTSDK_THISCALL EnsureStateSetCopy(uint32_t stateSetIndex, const AduGuiStateSet* source) = 0; | ||
virtual int FTSDK_THISCALL | virtual int FTSDK_THISCALL UpdateCurrentVisualState() = 0; | ||
virtual | virtual int FTSDK_THISCALL SetStateFlagE8A0(int32_t visualStateIndex, bool value, int32_t stateSetIndex) = 0; | ||
virtual | virtual int FTSDK_THISCALL SetStateRangeD850(int32_t visualStateIndex, float minValue, float maxValue, int32_t stateSetIndex) = 0; | ||
virtual int FTSDK_THISCALL | virtual int FTSDK_THISCALL ClearStateRuntimeFlag(int32_t visualStateIndex) = 0; | ||
virtual int FTSDK_THISCALL | virtual int FTSDK_THISCALL ClearVisualStateInfo(int32_t visualStateIndex) = 0; | ||
virtual | virtual int FTSDK_THISCALL ConfigureVisualStateInfo(int32_t visualStateIndex, bool enabled, float timing, int32_t alternateStateIndex) = 0; | ||
virtual | virtual int FTSDK_THISCALL SetEventSoundId(int32_t soundId, int32_t eventIndex) = 0; | ||
virtual int FTSDK_THISCALL | virtual int FTSDK_THISCALL GetEventSoundId(int32_t eventIndex) = 0; | ||
public: | public: | ||
AduGuiStateSet** stateSets; | AduGuiStateSet** stateSets; // +0x20 | ||
int32_t stateCount; | int32_t stateCount; | ||
int32_t stateCapacity; | int32_t stateCapacity; | ||
Structs::FTStringA name; | Structs::FTStringA name; // XML Name | ||
int32_t commandId; | int32_t commandId; // +0x48, bind command id, ctor -1 | ||
int32_t groupId; | int32_t groupId; // XML GroupID | ||
int32_t controlTypeId; | int32_t controlTypeId; // XML Type, GuiControlType | ||
int32_t hotKey; | int32_t hotKey; // XML HotKey | ||
int32_t | int32_t subGroupId; // XML SubGroupID, likely | ||
uint8_t | 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 bindStage; | |||
uint8_t debugFlag; | |||
uint8_t overFlag; | uint8_t overFlag; | ||
uint8_t pressedFlag; | uint8_t pressedFlag; | ||
| Line 619: | Line 770: | ||
AduGuiDialog* ownerDialog; | AduGuiDialog* ownerDialog; | ||
void* relatedObject; | |||
int32_t controlIndex; | int32_t controlIndex; | ||
int32_t dx; | int32_t dx; | ||
| Line 627: | Line 778: | ||
Structs::FTStringW text; | Structs::FTStringW text; | ||
AduGuiControl* resolvedContextMenuControl; | AduGuiControl* resolvedContextMenuControl; // resolved from XML Menu, type ContextMenu | ||
Structs::FTStringA contextMenuName; | Structs::FTStringA contextMenuName; // XML Menu | ||
uint8_t field1F8; | uint8_t field1F8; // ctor 1 | ||
uint8_t pad1F9[3]; | uint8_t pad1F9[3]; | ||
public: | public: | ||
| Line 643: | Line 794: | ||
int32_t ControlTypeId() const { return controlTypeId; } | int32_t ControlTypeId() const { return controlTypeId; } | ||
int32_t HotKey() const { return hotKey; } | int32_t HotKey() const { return hotKey; } | ||
int32_t SubGroupId() const { return subGroupId; } | |||
bool IsType(GuiControlType type) const { return controlTypeId == static_cast<int32_t>(type); } | bool IsType(GuiControlType type) const { return controlTypeId == static_cast<int32_t>(type); } | ||
bool | bool IsEnabledStateFlag() const { return enabledStateFlag != 0; } | ||
bool IsInteractionActiveFlag() const { return interactionActiveFlag != 0; } | |||
bool IsFocusFlag() const { return focusFlag != 0; } | |||
bool IsBindStage() const { return bindStage != 0; } | bool IsBindStage() const { return bindStage != 0; } | ||
bool IsDebugFlag() const { return debugFlag != 0; } | bool IsDebugFlag() const { return debugFlag != 0; } | ||
| Line 668: | Line 822: | ||
AduGuiDialog* OwnerDialog() const { return ownerDialog; } | AduGuiDialog* OwnerDialog() const { return ownerDialog; } | ||
void* RelatedObject() const { return relatedObject; } | |||
const Structs::FTStringW& Text() const { return text; } | const Structs::FTStringW& Text() const { return text; } | ||
const Structs::FTStringA& ContextMenuName() const { return contextMenuName; } | const Structs::FTStringA& ContextMenuName() const { return contextMenuName; } | ||
AduGuiControl* ResolvedContextMenuControl() const { return resolvedContextMenuControl; } | AduGuiControl* ResolvedContextMenuControl() const { return resolvedContextMenuControl; } | ||
}; | }; | ||
class AduGuiStatic : public AduGuiControl { | class AduGuiStatic : public AduGuiControl { | ||
public: | public: | ||
virtual int FTSDK_THISCALL RenderVisuals(void* renderCtx, int flags, const Structs::Rect* rect) = 0; // vt+0xD8 | virtual int FTSDK_THISCALL RenderVisuals(void* renderCtx, int flags, const Structs::Rect* rect) = 0; // vt+0xD8 | ||
virtual bool FTSDK_THISCALL RenderText(int visualIndex) = 0; | virtual bool FTSDK_THISCALL RenderText(int visualIndex) = 0; | ||
virtual void FTSDK_THISCALL RenderState(int stateIndex) = 0; | virtual void FTSDK_THISCALL RenderState(int stateIndex) = 0; | ||
public: | public: | ||
| Line 702: | Line 849: | ||
int32_t textScrollOffsetX; | int32_t textScrollOffsetX; | ||
uint32_t | uint32_t clippedTextWidth; | ||
uint8_t clippedTextRenderFlag; | uint8_t clippedTextRenderFlag; | ||
uint8_t pad259[3]; | uint8_t pad259[3]; | ||
public: | public: | ||
| Line 715: | Line 862: | ||
}; | }; | ||
class AduGuiButton : public AduGuiStatic { | |||
public: | |||
int32_t buttonClickSoundId; | |||
int32_t buttonOverSoundId; | |||
public: | |||
int32_t ButtonClickSoundId() const { return buttonClickSoundId; } | |||
int32_t ButtonOverSoundId() const { return buttonOverSoundId; } | |||
}; | |||
struct AduGuiEditTextBuffer { | struct AduGuiEditTextBuffer { | ||
void (FTSDK_CDECL* callback)(int owner); // +0x00 | void (FTSDK_CDECL* callback)(int owner); // +0x00 | ||
void* owner; | void* owner; // +0x04 | ||
void* scriptStringAnalysis; // +0x08 | |||
wchar_t* text; // +0x0C | wchar_t* text; // +0x0C | ||
uint32_t capacity; // +0x10 | uint32_t capacity; // +0x10 | ||
uint32_t field14; | uint32_t field14; // +0x14 | ||
uint8_t | |||
uint8_t dirtyFlag; // +0x18 | |||
uint8_t pad19[3]; | uint8_t pad19[3]; | ||
uint32_t field1C; | |||
uint32_t field20; | uint32_t field1C; // +0x1C | ||
uint32_t field24; | uint32_t field20; // +0x20 | ||
uint32_t field28; | uint32_t field24; // +0x24 | ||
uint8_t | uint32_t field28; // +0x28 | ||
uint8_t hasScriptStringAnalysis; // +0x2C | |||
uint8_t pad2D[3]; | uint8_t pad2D[3]; | ||
int32_t maxLength; // +0x30 | |||
int32_t | |||
const wchar_t* Text() const { return text ? text : L""; } | const wchar_t* Text() const { return text ? text : L""; } | ||
uint32_t Capacity() const { return capacity; } | uint32_t Capacity() const { return capacity; } | ||
int32_t MaxLength() const { return maxLength; } | |||
std::size_t Length() const { return std::wcslen(Text()); } | std::size_t Length() const { return std::wcslen(Text()); } | ||
}; | }; | ||
static_assert(sizeof(AduGuiEditTextBuffer) == | static_assert(sizeof(AduGuiEditTextBuffer) == 0x34); | ||
class AduGuiEditBox : public AduGuiControl { | class AduGuiEditBox : public AduGuiControl { | ||
public: | public: | ||
virtual int FTSDK_THISCALL | virtual int FTSDK_THISCALL SetEditTextColor(int32_t color) = 0; | ||
public: | public: | ||
uint32_t lineCacheUnknown; // +0x1FC | uint32_t lineCacheUnknown; // +0x1FC | ||
void** lineCacheFirst; | void** lineCacheFirst; // +0x200 | ||
void** lineCacheLast; | void** lineCacheLast; // +0x204 | ||
void** lineCacheEnd; | void** lineCacheEnd; // +0x208 | ||
AduGuiEditTextBuffer editText; // +0x20C | AduGuiEditTextBuffer editText; // +0x20C | ||
int32_t outerInset; // +0x240 | |||
int32_t innerInset; // +0x244 | |||
Structs::Rect textRect; // +0x248 | |||
Structs::Rect visualRect; // +0x258 | |||
double caretBlinkInterval; // +0x268 | double caretBlinkInterval; // +0x268 | ||
double lastCaretBlinkTime; // +0x270 | double lastCaretBlinkTime; // +0x270 | ||
uint8_t | uint8_t caretVisibleFlag; // +0x278 | ||
uint8_t pad279[3]; | uint8_t pad279[3]; | ||
int32_t | int32_t caretIndex; // +0x27C | ||
uint8_t insertMode; | uint8_t insertMode; // +0x280 | ||
uint8_t pad281[3]; | uint8_t pad281[3]; | ||
int32_t | int32_t selectionAnchorIndex; // +0x284 | ||
int32_t | int32_t editTextColor; // +0x288 | ||
int32_t field28C; | int32_t field28C; | ||
int32_t | int32_t selectionColorOrField290; | ||
int32_t | int32_t caretColorOrField294; | ||
uint8_t field298; | uint8_t field298; | ||
| Line 779: | Line 941: | ||
int32_t field29C; | int32_t field29C; | ||
uint8_t | uint8_t mouseSelectingFlag; // +0x2A0 | ||
uint8_t | uint8_t preventOverflowFlag; // +0x2A1 | ||
uint8_t multilineFlag; | uint8_t multilineFlag; // +0x2A2 | ||
uint8_t field2A3; | uint8_t field2A3; | ||
int32_t | int32_t lineHeight; // +0x2A4, XML Option/LineHeight | ||
int32_t field2A8; | int32_t field2A8; | ||
int32_t field2AC; | int32_t field2AC; | ||
| Line 794: | Line 956: | ||
int32_t field2B8; | int32_t field2B8; | ||
int32_t | int32_t editEventSoundIds[7]; // +0x2BC, EditBoxEnter..EditBoxKeyDown | ||
public: | public: | ||
| Line 805: | Line 967: | ||
bool IsMultiline() const { return multilineFlag != 0; } | bool IsMultiline() const { return multilineFlag != 0; } | ||
int32_t | int32_t EditEventSoundId(GuiEventType eventType) const { | ||
const int32_t index = static_cast<int32_t>(eventType) - static_cast<int32_t>(EditBoxEnter); | const int32_t index = static_cast<int32_t>(eventType) - static_cast<int32_t>(EditBoxEnter); | ||
if (index < 0 || index >= 7) | if (index < 0 || index >= 7) | ||
return -1; | return -1; | ||
return | |||
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]; | |||
return | |||
} | } | ||
}; | |||
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: | 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 | 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) { | void SetCallback(GuiCallbackFn callback, void* userData) { | ||
this->callback = callback; | |||
this->callbackUserData = userData; | |||
} | } | ||
AduGameObj* ObjectAt(int32_t index) const { | AduGameObj* ObjectAt(int32_t index) const { | ||
auto* arr = | auto* arr = objectArray; | ||
if (!arr || index < 0 || index >= objectCount) | |||
return nullptr; | return nullptr; | ||
| Line 867: | Line 1,444: | ||
AduGuiControl* FindControlByCommandId(int32_t commandId) const { | AduGuiControl* FindControlByCommandId(int32_t commandId) const { | ||
const int32_t count = | const int32_t count = objectCount; | ||
if (count < 0 || count > 4096) | if (count < 0 || count > 4096) | ||
| Line 888: | Line 1,465: | ||
return nullptr; | return nullptr; | ||
const int32_t count = | const int32_t count = objectCount; | ||
if (count < 0 || count > 4096) | |||
return nullptr; | |||
for (int32_t i = 0; i < count; ++i) { | |||
auto* control = ControlAt(i); | |||
if (!control) | |||
continue; | |||
if (control->Name().Equals(name)) | |||
return control; | |||
} | |||
return nullptr; | |||
} | |||
AduGuiControl* FindControlByNameAndType(const char* name, GuiControlType type) const { | |||
if (!name) | |||
return nullptr; | |||
const int32_t count = objectCount; | |||
if (count < 0 || count > 4096) | if (count < 0 || count > 4096) | ||
| Line 896: | Line 1,494: | ||
auto* control = ControlAt(i); | auto* control = ControlAt(i); | ||
if (!control) | if (!control) | ||
continue; | |||
if (control->ControlTypeId() != static_cast<int32_t>(type)) | |||
continue; | continue; | ||
| Line 905: | Line 1,506: | ||
} | } | ||
}; | }; | ||
static_assert(sizeof(AduGuiDialog) == 0xC8); | |||
class AduGuiCustomControlBase : public AduGuiControl { | |||
public: | |||
virtual bool FTSDK_THISCALL LoadEmbeddedGuiXml(const char* xmlName, const Structs::GuiBind* binds, int32_t bindCount, GuiCallbackFn callback) = 0; // vt+0xD8 | |||
virtual int FTSDK_THISCALL OnEmbeddedCommand(int32_t commandId) = 0; | |||
virtual int FTSDK_THISCALL OnEmbeddedEditEnter(int32_t commandId) = 0; | |||
virtual int FTSDK_THISCALL OnEmbeddedEditChange(int32_t commandId) = 0; | |||
virtual int FTSDK_THISCALL OnEmbeddedEditTab(int32_t commandId) = 0; | |||
virtual int FTSDK_THISCALL OnEmbeddedEditEsc(int32_t commandId) = 0; | |||
virtual int FTSDK_THISCALL OnEmbeddedEditKeyUp(int32_t commandId) = 0; | |||
virtual int FTSDK_THISCALL OnEmbeddedEditKeyDown(int32_t commandId) = 0; | |||
virtual int FTSDK_THISCALL OnEmbeddedContextMenuClick(int32_t commandId, int32_t selectedValue, AduGuiControl* contextMenuControl, int32_t selectedData) = 0; | |||
virtual int FTSDK_THISCALL DispatchEmbeddedGuiEvent(GuiEventType eventType, int32_t commandId, int32_t param, AduGuiControl* control) = 0; | |||
public: | |||
AduGuiDialog* embeddedDialog; // +0x1FC | |||
Structs::FTTreeMap<int32_t, AduGuiControl*> embeddedBindMap; // +0x200 | |||
int32_t embeddedField20C; | |||
}; | |||
static_assert(sizeof(AduGuiCustomControlBase) == 0x210); | |||
class FTRankingInfo : public AduGuiCustomControlBase { | |||
public: | |||
AduGuiButton* btClothInfoInner; // +0x210, XML btClothInfo | |||
AduGuiStatic* stRankingInner; // +0x214 | |||
AduGuiStatic* stEmblemMarkInner; // +0x218 | |||
AduGuiStatic* stUserNameInner; // +0x21C | |||
AduGuiStatic* stUserLevelInner; // +0x220 | |||
AduGuiStatic* stUserExpInner; // +0x224 | |||
AduGuiStatic* stWinPercentInner; // +0x228 | |||
AduGuiStatic* stRankingPointInner; // +0x22C | |||
void* rankingRecord; // +0x230, points to 52-byte ranking row/cache record | |||
public: | |||
bool HasRankingRecord() const { | |||
return rankingRecord != nullptr; | |||
} | |||
int32_t RankingPlayerId() const { | |||
return rankingRecord | |||
? *reinterpret_cast<const int32_t*>( | |||
reinterpret_cast<const uint8_t*>(rankingRecord) + 0x04 | |||
) | |||
: 0; | |||
} | |||
}; | |||
static_assert(sizeof(FTRankingInfo) == 0x234); | |||
class StageManager; | class StageManager; | ||
| Line 934: | Line 1,585: | ||
virtual void FTSDK_THISCALL DeactivateProcessing() = 0; | virtual void FTSDK_THISCALL DeactivateProcessing() = 0; | ||
virtual | virtual int FTSDK_THISCALL OnStageCommand(int32_t commandId) = 0; | ||
virtual void FTSDK_THISCALL Reserved58() = 0; | virtual void FTSDK_THISCALL Reserved58() = 0; | ||
virtual void FTSDK_THISCALL Reserved5C() = 0; | virtual void FTSDK_THISCALL Reserved5C() = 0; | ||
| Line 1,015: | Line 1,666: | ||
int32_t cursorX; | int32_t cursorX; | ||
int32_t cursorY; | int32_t cursorY; | ||
uint8_t inputState[0x26C]; | uint8_t inputState[0x26C]; | ||
| Line 1,021: | Line 1,672: | ||
ScreenRoot* hoverOrCapturedOwner; | ScreenRoot* hoverOrCapturedOwner; | ||
char resourceBasePath[0x104]; | char resourceBasePath[0x104]; | ||
public: | public: | ||
bool HasCurrentFocusOwner() const { return currentFocusOwner != nullptr; } | bool HasCurrentFocusOwner() const { return currentFocusOwner != nullptr; } | ||
bool HasCurrentHoverOwner() const { return currentHoverOwner != nullptr; } | bool HasCurrentHoverOwner() const { return currentHoverOwner != nullptr; } | ||
int32_t CursorDeltaX() const { return cursorX - previousCursorX; } | int32_t CursorDeltaX() const { return cursorX - previousCursorX; } | ||
int32_t CursorDeltaY() const { return cursorY - previousCursorY; } | int32_t CursorDeltaY() const { return cursorY - previousCursorY; } | ||
| Line 1,071: | Line 1,720: | ||
virtual int FTSDK_THISCALL HandleStagePacket(void* packet) = 0; | virtual int FTSDK_THISCALL HandleStagePacket(void* packet) = 0; | ||
virtual int FTSDK_THISCALL HandleGuiEvent(GuiEventType eventType, int commandId, int param) = 0; | virtual int FTSDK_THISCALL HandleGuiEvent(GuiEventType eventType, int commandId, int param) = 0; | ||
virtual int FTSDK_THISCALL OnCommandAction( | virtual int FTSDK_THISCALL OnCommandAction(int32_t commandId) = 0; | ||
virtual int FTSDK_THISCALL OnEditBoxEnter( | virtual int FTSDK_THISCALL OnEditBoxEnter(int32_t commandId) = 0; | ||
virtual int FTSDK_THISCALL OnEditBoxChange( | virtual int FTSDK_THISCALL OnEditBoxChange(int32_t commandId) = 0; | ||
virtual int FTSDK_THISCALL OnEditBoxTab( | virtual int FTSDK_THISCALL OnEditBoxTab(int32_t commandId) = 0; | ||
virtual int FTSDK_THISCALL OnEditBoxEsc( | virtual int FTSDK_THISCALL OnEditBoxEsc(int32_t commandId) = 0; | ||
virtual int FTSDK_THISCALL OnEditBoxKeyUp( | virtual int FTSDK_THISCALL OnEditBoxKeyUp(int32_t commandId) = 0; | ||
virtual int FTSDK_THISCALL OnEditBoxKeyDown( | virtual int FTSDK_THISCALL OnEditBoxKeyDown(int32_t commandId) = 0; | ||
virtual int FTSDK_THISCALL OnContextMenuClick( | virtual int FTSDK_THISCALL OnContextMenuClick(int32_t commandId, int selectedValue, AduGuiControl* contextMenuControl, int selectedData) = 0; | ||
public: | public: | ||
StageBase* priorityInputChildOwner; | StageBase* priorityInputChildOwner; | ||
| Line 1,117: | Line 1,767: | ||
void SetPriorityInputChildOwner(StageBase* child) { priorityInputChildOwner = child; } | void SetPriorityInputChildOwner(StageBase* child) { priorityInputChildOwner = child; } | ||
void ClearPriorityInputChildOwnerIf(StageBase* child) { if (PriorityInputChildOwner() == child) SetPriorityInputChildOwner(nullptr); } | |||
void ClearPriorityInputChildOwnerIf(StageBase* child) { | |||
if (PriorityInputChildOwner() == child) | |||
SetPriorityInputChildOwner(nullptr); | |||
} | |||
using AddChildOwnerFn = int(FTSDK_THISCALL*)(StageBase* self, StageBase* child); | using AddChildOwnerFn = int(FTSDK_THISCALL*)(StageBase* self, StageBase* child); | ||
void AddChildOwner(StageBase* child) { reinterpret_cast<AddChildOwnerFn>(0x005AA720)(this, child); } | void AddChildOwner(StageBase* child) { | ||
reinterpret_cast<AddChildOwnerFn>(0x005AA720)(this, child); | |||
} | |||
using BringChildOwnerToFrontFn = int(FTSDK_THISCALL*)(StageBase* self, StageBase* child); | using BringChildOwnerToFrontFn = int(FTSDK_THISCALL*)(StageBase* self, StageBase* child); | ||
void BringChildOwnerToFront(StageBase* child) { reinterpret_cast<BringChildOwnerToFrontFn>(0x005AA780)(this, child); } | void BringChildOwnerToFront(StageBase* child) { | ||
reinterpret_cast<BringChildOwnerToFrontFn>(0x005AA780)(this, child); | |||
} | |||
using IsOwnerReachableFn = int(FTSDK_FASTCALL*)(StageBase* self); | using IsOwnerReachableFn = int(FTSDK_FASTCALL*)(StageBase* self); | ||
bool IsReachableThroughParentChain() const { return reinterpret_cast<IsOwnerReachableFn>(0x005A99E0)(const_cast<StageBase*>(this)) != 0 | bool IsReachableThroughParentChain() const { | ||
return reinterpret_cast<IsOwnerReachableFn>(0x005A99E0)(const_cast<StageBase*>(this)) != 0; | |||
} | } | ||
}; | }; | ||
| Line 1,138: | Line 1,793: | ||
public: | public: | ||
int32_t messageBoxMode; | int32_t messageBoxMode; | ||
int32_t okYesEventType; | int32_t okYesEventType; | ||
| Line 1,243: | Line 1,894: | ||
} | } | ||
}; | }; | ||
static_assert(sizeof(MessageBoxDialogOwner) == 0x21C); | static_assert(sizeof(MessageBoxDialogOwner) == 0x21C); | ||
| Line 1,318: | Line 1,968: | ||
float hourlyPacketTimer; | float hourlyPacketTimer; | ||
int32_t unknown89C; | int32_t unknown89C; | ||
public: | public: | ||
| Line 1,377: | Line 2,027: | ||
using ShowMessageBoxFn = void(FTSDK_THISCALL*)(StageManager* self, const wchar_t* message, int32_t mode, int32_t okYesEventType, int32_t okYesEventData, int32_t okYesEventParam, uint8_t playSound); | using ShowMessageBoxFn = void(FTSDK_THISCALL*)(StageManager* self, const wchar_t* message, int32_t mode, int32_t okYesEventType, int32_t okYesEventData, int32_t okYesEventParam, uint8_t playSound); | ||
void ShowMessageBox(const wchar_t* message, MessageBoxMode mode = MessageBox_OK, int32_t okYesEventType = 0, int32_t okYesEventData = 0, | void ShowMessageBox( | ||
int32_t okYesEventParam = 0, bool playSound = true) { | const wchar_t* message, | ||
MessageBoxMode mode = MessageBox_OK, | |||
int32_t okYesEventType = 0, | |||
int32_t okYesEventData = 0, | |||
int32_t okYesEventParam = 0, | |||
bool playSound = true | |||
) { | |||
reinterpret_cast<ShowMessageBoxFn>(0x004AE9C0)( | reinterpret_cast<ShowMessageBoxFn>(0x004AE9C0)( | ||
this, | this, | ||
| Line 1,399: | Line 2,055: | ||
void ShowYesNoMessage(const wchar_t* message, int32_t okYesEventType = 0, int32_t okYesEventData = 0, int32_t okYesEventParam = 0, bool playSound = true) { | void ShowYesNoMessage(const wchar_t* message, int32_t okYesEventType = 0, int32_t okYesEventData = 0, int32_t okYesEventParam = 0, bool playSound = true) { | ||
ShowMessageBox( | ShowMessageBox(message, MessageBox_YesNo, okYesEventType, okYesEventData, okYesEventParam, playSound); | ||
} | } | ||
void ShowTimedYesNoMessage(const wchar_t* message, int32_t okYesEventType = 0, int32_t okYesEventData = 0, int32_t okYesEventParam = 0, bool playSound = true) { | void ShowTimedYesNoMessage(const wchar_t* message, int32_t okYesEventType = 0, int32_t okYesEventData = 0, int32_t okYesEventParam = 0, bool playSound = true) { | ||
ShowMessageBox( | ShowMessageBox(message, MessageBox_TimedYesNo, okYesEventType, okYesEventData, okYesEventParam, playSound); | ||
} | } | ||
}; | }; | ||
static_assert(sizeof(StageManager) == 0x8A0); | static_assert(sizeof(StageManager) == 0x8A0); | ||
| Line 1,436: | Line 2,077: | ||
inline GameDialogStage* ConstructGameDialogStage(GameDialogStage* memory) { | inline GameDialogStage* ConstructGameDialogStage(GameDialogStage* memory) { | ||
return reinterpret_cast<ConstructGameDialogStageFn>(0x004987C0)(memory); | return reinterpret_cast<ConstructGameDialogStageFn>(0x004987C0)(memory); | ||
} | |||
using SetStaticTextFn = int(FTSDK_THISCALL*)(AduGuiStatic* control, const wchar_t* text); | |||
inline int SetStaticText(AduGuiStatic* control, const wchar_t* text) { | |||
if (!control) | |||
return 0; | |||
return reinterpret_cast<SetStaticTextFn>(0x006185E0)(control, text ? text : L""); | |||
} | |||
namespace GuiCustomControlRegistry { | |||
constexpr uintptr_t RegisterFactoryAddr = 0x006113F0; | |||
constexpr uintptr_t RegisterTypeNameAddr = 0x00617E20; | |||
constexpr uintptr_t DefaultFactoryAddr = 0x005AC950; | |||
constexpr uintptr_t RegisterBuiltinCustomTypesAddr = 0x005AE050; | |||
using RegisterFactoryFn = CreateCustomGuiControlFn(FTSDK_CDECL*)(CreateCustomGuiControlFn factory); | |||
using RegisterTypeNameFn = void (FTSDK_CDECL*)(const char* typeName, int32_t customTypeId); | |||
using RegisterBuiltinCustomTypesFn = int (FTSDK_CDECL*)(); | |||
inline CreateCustomGuiControlFn RegisterFactory(CreateCustomGuiControlFn factory) { | |||
return reinterpret_cast<RegisterFactoryFn>(RegisterFactoryAddr)(factory); | |||
} | |||
inline void RegisterTypeName(const char* typeName, int32_t customTypeId) { | |||
reinterpret_cast<RegisterTypeNameFn>(RegisterTypeNameAddr)(typeName, customTypeId); | |||
} | |||
inline int RegisterBuiltinCustomTypes() { | |||
return reinterpret_cast<RegisterBuiltinCustomTypesFn>(RegisterBuiltinCustomTypesAddr)(); | |||
} | |||
inline AduGuiControl* CreateByDefaultFactory(AduGuiDialog* dialog, int32_t customTypeId) { | |||
return reinterpret_cast<CreateCustomGuiControlFn>(DefaultFactoryAddr)(dialog, customTypeId); | |||
} | |||
} | } | ||
} | } | ||
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:
StageManagerstate tracking, current-stage access and stage switching helpers.ScreenRootownership, parent chains, open state, processing state and input routing.StageBaseXML dialog ownership, GUI callback routing, priority child-owner behavior and event dispatch.GameDialogStagebuilt-in popup slots, popup creation helpers, selected/active popup state and game-dialog lifecycle hooks.AduGuiDialogruntime layout, object array access, command-ID lookup, name lookup and callback/user-data storage.AduGuiControlcommon 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:
AduGuiStaticAduGuiButtonAduGuiCheckBoxAduGuiRadioButtonAduGuiComboBoxAduGuiSliderAduGuiGaugeAduGuiEditBoxAduGuiIMEEditBoxAduGuiListBoxAduGuiScrollBarAduGuiContextMenu
- Generic custom GUI controls through
AduGuiCustomControlBase. - Known concrete custom controls such as
FTRankingInfo. - XML binding through
GuiBindarrays, 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:
- Create or reuse a
GameDialogStage/StageBaseowner attached to an existing game stage or popup owner. - Load a normal XML dialog with
StageBase::LoadXml. - Register additional XML control type names with
GuiCustomControlRegistry::RegisterTypeName. - For simple embedded GUI controls, map a custom XML type name to
GuiControlType::Custom/ type id12. - For each generic custom control instance, call
AduGuiCustomControlBase::LoadEmbeddedGuiXml. - Cache the inner controls from the embedded
AduGuiDialog. - 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::LoadXmlcan create generic custom control instances from XML.- The created generic custom controls appear in
AduGuiDialog::objectArray. AduGuiCustomControlBase::LoadEmbeddedGuiXmlcan load a nestedGuiCtrl_*.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::controlBindMapmay not contain every control even when the dialog loaded correctly. For robust lookup, fall back toAduGuiDialog::FindControlByCommandIdand thenAduGuiDialog::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,processEnabledandparentChainEnabled. - 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);
}
}
}