FT SDK
Overview
This page documents the current reverse engineering progress for the Fantasy Tennis client-side SDK used by JFTSE tooling. The SDK maps known client structures, virtual functions, GUI objects, stage management, XML dialog loading, popup ownership and input routing into C++ wrappers that can be used from injected debugging or extension code.
The current focus is the client GUI layer around StageManager, ScreenRoot, StageBase, GameDialogStage and Adu GUI controls. Confirmed areas include the fixed 24-stage model, current-stage lookup, parent-chain/process/open-state behavior, built-in popup slot creation, XML dialog loading, command-ID binding, callback dispatch and Adu object state handling.
This page is the current reverse engineering handoff for the SDK. Names, offsets and layouts are updated only after they are verified through disassembly, runtime dumps and real client call sites.
Scope
StageManagerstate tracking, stage switching and current-stage access.- Fixed 24-stage owner list and safe current-stage attachment.
ScreenRootparent/child ownership, input routing, open state and processing state.StageBaseXML dialog ownership, child-owner lists, priority input child handling and GUI event dispatch.GameDialogStagebuilt-in popup slots, popup factory behavior, selected/active popup index and tray-popup related state.- Game-dialog lifecycle hooks, including XML initialization, activation, update, back/default action and popup close paths.
- Adu GUI object hierarchy, including active/update/visible flags, timers, child traversal and recursive processing.
- Adu dialog/control layouts, including object arrays, command IDs, control names, control types, state sets and basic runtime flags.
- XML GUI binding through
GuiBindarrays, command IDs and control type IDs. - Default GUI callback routing through
AduGuiDialogcallback/user-data fields. - Confirmed event dispatch for buttons, radio buttons, edit boxes, context menus and related GUI events.
- Confirmed edit-box text access through the wide-character text pointer at the current
AduGuiEditBoxlayout. - Group-style control activation behavior used by ranking and popup XML, especially active/visible state synchronization.
Stability Notes
Offsets and function addresses are version specific to the currently analyzed Fantasy Tennis client build. Names describe current understanding, not original source names. Any field marked unknown, maybe or conservative should be treated as subject to change until confirmed by call sites, runtime tests or additional disassembly.
Important caveat: custom GUI work should not add another stage. The client uses a fixed set of 24 stage owners. Custom UI should attach to the current stage owner, an existing popup path or the embedded modal/input owner where appropriate.
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 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(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
};
enum MessageBoxMode : int32_t {
MessageBox_OK = 1,
MessageBox_TimedOK = 2,
MessageBox_YesNo = 3,
MessageBox_TimedYesNo = 4
};
using GuiCallbackFn = void(FTSDK_STDCALL*)(
GuiEventType eventType,
int commandId,
int param,
void* userData
);
struct AduGuiStateSet;
class AduGuiDialog;
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);
// Dialog side parsed XML/control definition. This is not AduGuiControl storage.
// It is initialized by sub_624F90, populated by sub_614F60, then passed into
// AduGuiControl::LoadFromXml / concrete LoadFromXml overrides.
struct AduGuiParsedControlDef {
uint32_t unknown00;
uint32_t stateBegin; // +0x04, parsed state array begin/pointer-like
uint32_t stateEnd; // +0x08, parsed state array end/pointer-like
uint32_t unknown0C; // +0x0C
int32_t eventField[20]; // +0x10, initialized to -1; indexed by GuiEventType
uint8_t unknown60[0x804]; // +0x60...+0x863
wchar_t resolvedText[256]; // +0x864, assigned to AduGuiControl::text
uint8_t unknownA64[0x600]; // +0xA64...+0x1063
uint8_t enable; // +0x1064, XML Enable, default 1
uint8_t enabledState; // +0x1065, XML EnabledState, default 1
uint8_t pad1066[2]; // +0x1066
int32_t x;
int32_t y;
int32_t width;
int32_t height;
int32_t alignX;
int32_t alignY;
float scaleX;
float scaleY;
float rotate;
float centerX;
float centerY;
int32_t flipX;
int32_t textAlignX;
int32_t textAlignY;
int32_t textFx;
int32_t textMarginLeft;
int32_t textMarginTop;
int32_t textMarginRight;
int32_t textMarginBottom;
int32_t wordWrap;
int32_t groupId;
int32_t subGroupId;
uint8_t unknown10C0[0x24];
int32_t tabStop; // +0x10E4
int32_t initialStage;
int32_t bindStage;
int32_t hotKey;
char contextMenuName[256]; // +0x10F4
};
namespace Constants {
// Confirmed by StageManager InitStages / SwitchStateNow bounds.
// Do not add a custom 25th stage; attach custom UI to an existing owner/modal
constexpr int32_t StageOwnerCount = 24;
// These are built-in popup owner slots at FTGameDialogOwner + 0x1E8
// Not proof that every stage uses all 10 slots
constexpr int32_t KnownBuiltinPopupSlotCount = 10;
}
class AduBase {
public:
virtual AduBase* FTSDK_THISCALL Destroy(uint32_t flags) = 0;
public:
int32_t unk04;
public:
bool Unk04Enabled() const {
return unk04 != 0;
}
};
static_assert(sizeof(AduBase) == 0x08);
class AduGameObj : public AduBase {
public:
virtual bool FTSDK_THISCALL SetDirtyValue(int32_t value) = 0;
virtual int32_t FTSDK_THISCALL ResetTimerRecursive() = 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; // +0x1C
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);
}
};
static_assert(sizeof(AduGameObj) == 0x20);
class AduGuiControl : public AduGameObj {
public:
virtual bool FTSDK_THISCALL RecalculateRect() = 0; // vt+0x20
virtual bool FTSDK_THISCALL LoadFromXml(const char* name, void* xmlParser, AduGuiParsedControlDef* parsedControlDef) = 0;
virtual bool FTSDK_THISCALL PostLoadResolve() = 0;
virtual AduGameObj* FTSDK_THISCALL GetInnerObject() = 0;
virtual void FTSDK_THISCALL ControlVfunc30() = 0; // vt+0x30
virtual void FTSDK_THISCALL ControlVfunc34() = 0; // vt+0x34
virtual void FTSDK_THISCALL ControlVfunc38() = 0; // vt+0x38
virtual void FTSDK_THISCALL ControlVfunc3C() = 0; // vt+0x3C
virtual void FTSDK_THISCALL ControlVfunc40() = 0; // vt+0x40
virtual void FTSDK_THISCALL ControlVfunc44() = 0; // vt+0x44
virtual void FTSDK_THISCALL ControlVfunc48() = 0; // vt+0x48
virtual void FTSDK_THISCALL ControlVfunc4C() = 0; // vt+0x4C
virtual void FTSDK_THISCALL ControlVfunc50() = 0; // vt+0x50
virtual void FTSDK_THISCALL ControlVfunc54() = 0; // vt+0x54
virtual void FTSDK_THISCALL ControlVfunc58() = 0; // vt+0x58
virtual int FTSDK_THISCALL ControlVfunc5C() = 0; // vt+0x5C
virtual int FTSDK_THISCALL ControlVfunc60() = 0; // vt+0x60
virtual int FTSDK_THISCALL ControlVfunc64() = 0; // vt+0x64
virtual int FTSDK_THISCALL ControlVfunc68() = 0; // vt+0x68
virtual bool FTSDK_THISCALL ControlHandleInputA(uint32_t msg, int32_t wParam, int32_t lParam) = 0; // vt+0x6C
virtual bool FTSDK_THISCALL ControlHandleInputB(uint32_t msg, int32_t wParam, int32_t lParam) = 0; // vt+0x70
virtual int FTSDK_THISCALL ControlVfunc74() = 0; // vt+0x74
virtual int FTSDK_THISCALL ControlVfunc78() = 0; // vt+0x78
virtual int FTSDK_THISCALL ControlVfunc7C() = 0; // vt+0x7C
virtual int FTSDK_THISCALL ControlVfunc80() = 0; // vt+0x80
virtual int FTSDK_THISCALL ControlVfunc84() = 0; // vt+0x84
virtual int FTSDK_THISCALL ControlVfunc88() = 0; // vt+0x88
virtual int FTSDK_THISCALL ControlVfunc8C() = 0; // vt+0x8C
virtual int FTSDK_THISCALL ControlVfunc90() = 0; // vt+0x90
virtual int FTSDK_THISCALL ControlVfunc94() = 0; // vt+0x94
virtual int FTSDK_THISCALL ControlVfunc98() = 0; // vt+0x98
virtual bool FTSDK_THISCALL SetEnabledState(bool value) = 0; // vt+0x9C
virtual int FTSDK_THISCALL ControlVfuncA0() = 0; // vt+0xA0
virtual int FTSDK_THISCALL ControlVfuncA4() = 0; // vt+0xA4
virtual int FTSDK_THISCALL ControlVfuncA8() = 0; // vt+0xA8
virtual int FTSDK_THISCALL ControlVfuncAC() = 0; // vt+0xAC
virtual AduGuiStateSet* FTSDK_THISCALL GetStateSet(int32_t stateIndex) = 0; // vt+0xB0
virtual int FTSDK_THISCALL ControlVfuncB4() = 0; // vt+0xB4
virtual int FTSDK_THISCALL ControlVfuncB8() = 0; // vt+0xB8
virtual bool FTSDK_THISCALL CanAddStateSets() = 0; // vt+0xBC
virtual void FTSDK_THISCALL SetStateTilingOrFlag(int32_t stateIndex, float a3, float a4, int32_t stageIndex) = 0; // vt+0xC0
virtual int FTSDK_THISCALL ControlVfuncC4() = 0; // vt+0xC4
virtual int FTSDK_THISCALL ControlVfuncC8() = 0; // vt+0xC8
virtual void FTSDK_THISCALL SetStateTimingOrFx(int32_t stateIndex, int32_t enabled, float timing, int32_t alternateStateIndex) = 0; // vt+0xCC
virtual void FTSDK_THISCALL SetEventField(int32_t value, int32_t eventIndex) = 0; // vt+0xD0
virtual int FTSDK_THISCALL ControlVfuncD4() = 0; // vt+0xD4
public:
AduGuiStateSet** stateSets;
int32_t stateCount;
int32_t stateCapacity;
Structs::FTStringA name;
int32_t commandId;
int32_t groupId;
int32_t controlTypeId;
int32_t hotKey;
int32_t field58;
uint8_t enabledFlag;
uint8_t field5D;
uint8_t pad5E[2];
Structs::Rect rect;
int32_t currentVisualState;
int32_t selectedStateIndex;
uint8_t bindStage;
uint8_t debugFlag;
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;
int32_t field94;
int32_t controlIndex;
int32_t dx;
int32_t dy;
AduGuiVisualStateInfo visualStateInfos[7];
Structs::FTStringW text;
AduGuiControl* resolvedContextMenuControl;
Structs::FTStringA contextMenuName;
uint8_t field1F8;
uint8_t pad1F9[3]; // +0x1F9
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; }
bool IsType(GuiControlType type) const { return controlTypeId == static_cast<int32_t>(type); }
bool IsEnabledFlag() const { return enabledFlag != 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; }
const Structs::FTStringW& Text() const { return text; }
const Structs::FTStringA& ContextMenuName() const { return contextMenuName; }
AduGuiControl* ResolvedContextMenuControl() const { return resolvedContextMenuControl; }
template <typename T>
T* InnerObjectAs() { return reinterpret_cast<T*>(GetInnerObject()); }
template <typename T>
const T* InnerObjectAs() const { return reinterpret_cast<const T*>(const_cast<AduGuiControl*>(this)->GetInnerObject()); }
};
static_assert(sizeof(AduGuiControl) == 0x1FC);
class AduGuiStatic : public AduGuiControl {
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; // vt+0xDC
virtual void FTSDK_THISCALL RenderState(int stateIndex) = 0; // vt+0xE0
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 field254;
uint8_t clippedTextRenderFlag;
uint8_t pad259[3]; // +0x259
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; }
};
static_assert(sizeof(AduGuiStatic) == 0x25C);
struct AduGuiEditTextBuffer {
void (FTSDK_CDECL* callback)(int owner); // +0x00, ctor sets sub_610260
void* owner;
wchar_t* secondaryBuffer; // +0x08, used only when hasSecondaryBuffer is set
wchar_t* text; // +0x0C, absolute AduGuiEditBox +0x218
uint32_t capacity; // +0x10, absolute AduGuiEditBox +0x21C
uint32_t field14;
uint8_t field18;
uint8_t pad19[3];
uint32_t field1C;
uint32_t field20;
uint32_t field24;
uint32_t field28;
uint8_t hasSecondaryBuffer;
uint8_t pad2D[3];
int32_t field30;
int32_t field34;
int32_t field38;
const wchar_t* Text() const { return text ? text : L""; }
uint32_t Capacity() const { return capacity; }
std::size_t Length() const { return std::wcslen(Text()); }
};
static_assert(sizeof(AduGuiEditTextBuffer) == 0x3C);
class AduGuiEditBox : public AduGuiControl {
public:
virtual int FTSDK_THISCALL SetEditBoxColorOrField(int32_t value) = 0; // vt+0xD8
public:
uint32_t lineCacheUnknown; // +0x1FC
void** lineCacheFirst;
void** lineCacheLast;
void** lineCacheEnd;
AduGuiEditTextBuffer editText; // +0x20C
uint8_t field248[0x20]; // +0x248
double caretBlinkInterval; // +0x268, GetCaretBlinkTime() * 0.001
double lastCaretBlinkTime; // +0x270, performance-counter seconds
uint8_t field278;
uint8_t pad279[3];
int32_t field27C;
uint8_t insertMode;
uint8_t pad281[3];
int32_t field284;
int32_t colorOrField288;
int32_t field28C;
int32_t colorOrField290;
int32_t colorOrField294;
uint8_t field298;
uint8_t numericOnlyFlag;
uint8_t pad29A[2];
int32_t field29C;
uint8_t field2A0;
uint8_t field2A1;
uint8_t multilineFlag;
uint8_t field2A3;
int32_t field2A4;
int32_t field2A8;
int32_t field2AC;
uint8_t field2B0;
uint8_t pad2B1[3];
int32_t field2B4;
int32_t field2B8;
int32_t editEventFields[7]; // +0x2BC, GuiEventType 8..14
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 EditEventField(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 editEventFields[index];
}
};
static_assert(sizeof(AduGuiEditBox) == 0x2D8);
class AduGuiDialog : public AduGameObj {
protected:
template <typename T>
T ReadField(size_t offset) const {
return *reinterpret_cast<const T*>(
reinterpret_cast<const uint8_t*>(this) + offset
);
}
template <typename T>
void WriteField(size_t offset, T value) {
*reinterpret_cast<T*>(
reinterpret_cast<uint8_t*>(this) + offset
) = value;
}
public:
AduGameObj** ObjectArray() const {
return ReadField<AduGameObj**>(0x8C);
}
int32_t ObjectCount() const {
return ReadField<int32_t>(0x90);
}
GuiCallbackFn Callback() const {
return ReadField<GuiCallbackFn>(0x6C);
}
void* CallbackUserData() const {
return ReadField<void*>(0x70);
}
void SetCallback(GuiCallbackFn callback, void* userData) {
WriteField<GuiCallbackFn>(0x6C, callback);
WriteField<void*>(0x70, 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;
}
};
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 void FTSDK_THISCALL OnStageCommand(int 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; // +0x1E8
uint8_t inputState[0x26C];
ScreenRoot* hoverOrCapturedOwner;
char resourceBasePath[0x104]; // +0x45C
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(int commandId) = 0;
virtual int FTSDK_THISCALL OnEditBoxEnter(int commandId) = 0;
virtual int FTSDK_THISCALL OnEditBoxChange(int commandId) = 0;
virtual int FTSDK_THISCALL OnEditBoxTab(int commandId) = 0;
virtual int FTSDK_THISCALL OnEditBoxEsc(int commandId) = 0;
virtual int FTSDK_THISCALL OnEditBoxKeyUp(int commandId) = 0;
virtual int FTSDK_THISCALL OnEditBoxKeyDown(int commandId) = 0;
virtual int FTSDK_THISCALL OnContextMenuClick(int 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; }
AduGameObj* GetControlInnerControl(int32_t commandId) {
auto* control = FindBoundControl(commandId);
return control ? control->GetInnerObject() : nullptr;
}
};
static_assert(sizeof(StageBase) == 0x1E8);
class MessageBoxDialogOwner : public StageBase {
public:
int32_t messageBoxMode;
// 1 = OK
// 2 = timed OK
// 3 = Yes/No
// 4 = timed Yes/No
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; // +0x89C
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);
}
}