initial commit; cv 0.0.3

This commit is contained in:
2026-09-07 19:33:58 -04:00
commit abf065ddde
63 changed files with 8816 additions and 0 deletions

196
Api/BoxPalList.hpp Normal file
View File

@@ -0,0 +1,196 @@
#pragma once
#include <optional>
#include <Unreal/NameTypes.hpp>
#include "UnrealWrapper.hpp"
#include "UmgTypes.hpp"
#include "Widget.hpp"
#include "UserWidget.hpp"
#include "WidgetTree.hpp"
#include "CanvasPanel.hpp"
#include "TextBlock.hpp"
namespace PalUIExtension::Api
{
// ==================================================================
// THE PALBOX GRID CHAIN
//
// PalBoxWidget (the screen)
// -> WBP_BoxPalList_C THIS FILE
// -> WBP_BoxPalListBase_C THIS FILE
// -> WBP_BoxPalScrollList
// == WBP_PalCharacterScrollList_C
// -> the slot tiles
// ==================================================================
class BoxPalListWidget : public UserWidget
{
public:
static auto ClassPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/Common/"
"WBP_BoxPalList.WBP_BoxPalList_C");
}
static auto DebugTag() -> const TCHAR* { return STR("BoxPalList"); }
BoxPalListWidget() = default;
explicit BoxPalListWidget(UObject* Obj) : UserWidget(Obj, ClassOf<BoxPalListWidget>(), DebugTag()) {}
auto BaseObject() const -> UObject* { return BoundObject(STR("WBP_BoxPalListBase")); }
auto CanDragDrop() const -> std::optional<bool>
{
return PropertyBoolValue(STR("CanDragDrop"));
}
auto DisplaysBanMarkInExpedition() const -> std::optional<bool>
{
return PropertyBoolValue(STR("bDisplayBanMarkInExpedition"));
}
// ---- hooks ---------------------------------------------------
static auto UpdateSlotFilteringPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/Common/"
"WBP_BoxPalList.WBP_BoxPalList_C:UpdateSlotFiltering");
}
static auto UpdateAllSlotFilteringPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/Common/"
"WBP_BoxPalList.WBP_BoxPalList_C:UpdateAllSlotFiltering");
}
static auto SetupPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/Common/"
"WBP_BoxPalList.WBP_BoxPalList_C:Setup");
}
static auto DestructPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/Common/"
"WBP_BoxPalList.WBP_BoxPalList_C:Destruct");
}
struct SlotButtonFrame { UObject* SlotButton; };
};
class BoxPalListBaseWidget : public UserWidget
{
public:
static auto ClassPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/Common/"
"WBP_BoxPalListBase.WBP_BoxPalListBase_C");
}
static auto DebugTag() -> const TCHAR* { return STR("BoxPalListBase"); }
BoxPalListBaseWidget() = default;
explicit BoxPalListBaseWidget(UObject* Obj)
: UserWidget(Obj, ClassOf<BoxPalListBaseWidget>(), DebugTag()) {}
auto ScrollList() const -> UObject* { return BoundObject(STR("WBP_BoxPalScrollList")); }
auto LastSelectedPage() const -> std::optional<int32> { return PropertyValue<int32>(STR("LastSelectedPageNum")); }
auto MaxPageNum() const -> std::optional<int32> { return PropertyValue<int32>(STR("MaxPageNum")); }
auto SkipPageNum() const -> std::optional<int32> { return PropertyValue<int32>(STR("SkipPageNum")); }
auto IsValidPageAction() const -> std::optional<bool> { return PropertyBoolValue(STR("bIsValidPageAction")); }
auto IsSkippingPage() const -> std::optional<bool> { return PropertyBoolValue(STR("bSkipPage")); }
// ---- chrome, promoted -----------------------------------------
auto CanvasSliderValue() const -> CanvasPanel
{
UObject* O = BoundObject(STR("Canvas_SliderValue"));
return O ? CanvasPanel{ O } : CanvasPanel{};
}
auto TextBoxName() const -> TextBlock { return WrapText(STR("Text_BoxName")); }
auto TextSliderValue() const -> TextBlock { return WrapText(STR("Text_SliderValue")); }
auto Slider() const -> UObject* { return BoundObject(STR("Slider")); }
auto SearchButton() const -> UObject* { return BoundObject(STR("WBP_CommonButton_Activation_Search")); }
auto SortButton() const -> UObject* { return BoundObject(STR("WBP_CommonButton_Sort")); }
auto NumArrowLeft() const -> UObject* { return BoundObject(STR("WBP_Common_NumArrow_L")); }
auto NumArrowRight() const -> UObject* { return BoundObject(STR("WBP_Common_NumArrow_R")); }
static auto OnCreatedNewSlotPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/Common/"
"WBP_BoxPalListBase.WBP_BoxPalListBase_C:OnCreatedNewSlot__DelegateSignature");
}
static auto OnUpdateSlotPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/Common/"
"WBP_BoxPalListBase.WBP_BoxPalListBase_C:OnUpdateSlot__DelegateSignature");
}
static auto OnSlotSyncedPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/Common/"
"WBP_BoxPalListBase.WBP_BoxPalListBase_C:OnSlotSynced__DelegateSignature");
}
static auto OnUpdatedPagePath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/Common/"
"WBP_BoxPalListBase.WBP_BoxPalListBase_C:OnUpdatedPage__DelegateSignature");
}
static auto SetCharacterSlotsPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/Common/"
"WBP_BoxPalListBase.WBP_BoxPalListBase_C:SetCharacterSlots");
}
static auto SetCurrentPagePath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/Common/"
"WBP_BoxPalListBase.WBP_BoxPalListBase_C:SetCurrentPage");
}
static auto DestructPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/Common/"
"WBP_BoxPalListBase.WBP_BoxPalListBase_C:Destruct");
}
struct SlotWidgetFrame { UObject* Widget; };
struct SlotClickFrame
{
UObject* Widget; // 0x00
uint8 PressType; // 0x08
};
struct PageFrame { int32 NowPage; }; // OnUpdatedPage, OnSelectPalBoxPage
struct SetCharacterSlotsFrame
{
UObject** Data;
int32 Num;
int32 Max;
};
static_assert(sizeof(SetCharacterSlotsFrame) == 16,
"SetCharacterSlots frame is 16 bytes on the dumped build");
auto GetCurrentPage() -> std::optional<int32>
{
struct Frame { int32 CurrentPageNum; } p{};
if (!Call(STR("GetCurrentPage"), p)) return std::nullopt;
return p.CurrentPageNum;
}
private:
auto WrapText(const TCHAR* Name) const -> TextBlock
{
UObject* O = BoundObject(Name);
return O ? TextBlock{ O } : TextBlock{};
}
protected:
BoxPalListBaseWidget(UObject* Obj, UClass* Expected, const TCHAR* Tag)
: UserWidget(Obj, Expected, Tag) {}
};
}

0
Api/Canvas.hpp Normal file
View File

32
Api/CanvasPanel.hpp Normal file
View File

@@ -0,0 +1,32 @@
#pragma once
#include "PanelWidget.hpp"
#include "CanvasPanelSlot.hpp"
namespace PalUIExtension::Api
{
// UCanvasPanel adds exactly one function of its own. Everything else
// it offers comes from UPanelWidget and UWidget, which is now inherited
// rather than re-declared.
class CanvasPanel : public PanelWidget
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/UMG.CanvasPanel"); }
static auto DebugTag() -> const TCHAR* { return STR("Canvas"); }
CanvasPanel() = default;
explicit CanvasPanel(UObject* Obj)
: PanelWidget(Obj, ClassOf<CanvasPanel>(), DebugTag()) {}
// Returns an invalid CanvasPanelSlot if the call failed or the
// engine handed back null. Check it before using it -- the wrapper
// will refuse to dispatch and log, but silently doing nothing is
// not what you want out of a layout call.
auto AddChildToCanvas(UObject* Content) -> CanvasPanelSlot
{
struct P { UObject* Content; UObject* ReturnValue; } p{ Content, nullptr };
auto Result = CallForReturn(STR("AddChildToCanvas"), p, &P::ReturnValue);
return (Result && *Result) ? CanvasPanelSlot{ *Result } : CanvasPanelSlot{};
}
};
}

91
Api/CanvasPanelSlot.hpp Normal file
View File

@@ -0,0 +1,91 @@
#pragma once
#include "PanelSlot.hpp"
namespace PalUIExtension::Api
{
class CanvasPanelSlot : public PanelSlot
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/UMG.CanvasPanelSlot"); }
static auto DebugTag() -> const TCHAR* { return STR("CanvasSlot"); }
CanvasPanelSlot() = default;
explicit CanvasPanelSlot(UObject* Obj)
: PanelSlot(Obj, ClassOf<CanvasPanelSlot>(), DebugTag()) {}
// ---- setters (write through the UFUNCTION so SynchronizeProperties runs)
auto SetZOrder(int32 InZOrder) -> bool
{
struct P { int32 InZOrder; } p{ InZOrder };
return Call(STR("SetZOrder"), p);
}
auto SetSize(FVector2D_ InSize) -> bool
{
struct P { FVector2D_ InSize; } p{ InSize };
return Call(STR("SetSize"), p);
}
auto SetPosition(FVector2D_ InPosition) -> bool
{
struct P { FVector2D_ InPosition; } p{ InPosition };
return Call(STR("SetPosition"), p);
}
auto SetOffsets(FMargin_ InOffset) -> bool
{
struct P { FMargin_ InOffset; } p{ InOffset };
return Call(STR("SetOffsets"), p);
}
auto SetMinimum(FVector2D_ InMinimumAnchors) -> bool
{
struct P { FVector2D_ InMinimumAnchors; } p{ InMinimumAnchors };
return Call(STR("SetMinimum"), p);
}
auto SetMaximum(FVector2D_ InMaximumAnchors) -> bool
{
struct P { FVector2D_ InMaximumAnchors; } p{ InMaximumAnchors };
return Call(STR("SetMaximum"), p);
}
auto SetAnchors(FAnchors_ InAnchors) -> bool
{
struct P { FAnchors_ InAnchors; } p{ InAnchors };
return Call(STR("SetAnchors"), p);
}
auto SetAlignment(FVector2D_ InAlignment) -> bool
{
struct P { FVector2D_ InAlignment; } p{ InAlignment };
return Call(STR("SetAlignment"), p);
}
auto SetAutoSize(bool InbAutoSize) -> bool
{
struct P { bool InbAutoSize; } p{ InbAutoSize };
return Call(STR("SetAutoSize"), p);
}
// Engine signature is (const FAnchorData&); Blueprint const-ref params
// are still laid out by value in the ProcessEvent frame.
// FAnchorData_ field order is the guess flagged in UmgTypes.hpp --
// prefer the granular setters until you have dumped the struct.
auto SetLayout(const FAnchorData_& InLayoutData) -> bool
{
struct P { FAnchorData_ InLayoutData; } p{ InLayoutData };
return Call(STR("SetLayout"), p);
}
// ---- getters --------------------------------------------------------
//
// ZOrder, bAutoSize and LayoutData are plain UPROPERTYs, so these
// three read the memory directly instead of building a ProcessEvent
// frame. The rest have no backing property of their own (they are
// views into LayoutData) and go through the UFUNCTION.
auto GetZOrder() const -> std::optional<int32> { return PropertyValue<int32>(STR("ZOrder")); }
auto GetAutoSize() const -> std::optional<bool> { return PropertyValue<bool>(STR("bAutoSize")); }
auto GetLayout() const -> std::optional<FAnchorData_> { return PropertyValue<FAnchorData_>(STR("LayoutData")); }
auto GetSize() -> std::optional<FVector2D_> { return CallForReturn<FVector2D_>(STR("GetSize")); }
auto GetPosition() -> std::optional<FVector2D_> { return CallForReturn<FVector2D_>(STR("GetPosition")); }
auto GetAlignment() -> std::optional<FVector2D_> { return CallForReturn<FVector2D_>(STR("GetAlignment")); }
auto GetOffsets() -> std::optional<FMargin_> { return CallForReturn<FMargin_>(STR("GetOffsets")); }
auto GetAnchors() -> std::optional<FAnchors_> { return CallForReturn<FAnchors_>(STR("GetAnchors")); }
};
}

29
Api/HorizontalBox.hpp Normal file
View File

@@ -0,0 +1,29 @@
#pragma once
#include "PanelWidget.hpp"
#include "HorizontalBoxSlot.hpp"
namespace PalUIExtension::Api
{
// UHorizontalBox : public UPanelWidget directly -- same shape as
// Overlay. Every generic panel operation is already on PanelWidget;
// AddChildToHorizontalBox exists only to hand back the concrete slot
// type instead of the generic UPanelSlot* AddChild() returns.
class HorizontalBox : public PanelWidget
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/UMG.HorizontalBox"); }
static auto DebugTag() -> const TCHAR* { return STR("HorizontalBox"); }
HorizontalBox() = default;
explicit HorizontalBox(UObject* Obj)
: PanelWidget(Obj, ClassOf<HorizontalBox>(), DebugTag()) {}
auto AddChildToHorizontalBox(UObject* Content) -> HorizontalBoxSlot
{
struct P { UObject* Content; UObject* ReturnValue; } p{ Content, nullptr };
auto Result = CallForReturn(STR("AddChildToHorizontalBox"), p, &P::ReturnValue);
return (Result && *Result) ? HorizontalBoxSlot{ *Result } : HorizontalBoxSlot{};
}
};
}

52
Api/HorizontalBoxSlot.hpp Normal file
View File

@@ -0,0 +1,52 @@
#pragma once
#include "PanelSlot.hpp"
namespace PalUIExtension::Api
{
// Same four-member surface as VerticalBoxSlot (Size + Padding + H/V
// alignment). Different engine class, different ClassPath -- kept
// separate rather than shared, matching how CanvasPanelSlot,
// SizeBoxSlot, and OverlaySlot each stand alone in this tree despite
// overlapping surfaces.
class HorizontalBoxSlot : public PanelSlot
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/UMG.HorizontalBoxSlot"); }
static auto DebugTag() -> const TCHAR* { return STR("HorizontalBoxSlot"); }
HorizontalBoxSlot() = default;
explicit HorizontalBoxSlot(UObject* Obj)
: PanelSlot(Obj, ClassOf<HorizontalBoxSlot>(), DebugTag()) {}
// ---- setters (write through the UFUNCTION so SynchronizeProperties runs)
auto SetSize(FSlateChildSize_ InSize) -> bool
{
struct P { FSlateChildSize_ InSize; } p{ InSize };
return Call(STR("SetSize"), p);
}
auto SetPadding(FMargin_ InPadding) -> bool
{
struct P { FMargin_ InPadding; } p{ InPadding };
return Call(STR("SetPadding"), p);
}
auto SetHorizontalAlignment(EHorizontalAlignment_ InHorizontalAlignment) -> bool
{
struct P { EHorizontalAlignment_ InHorizontalAlignment; } p{ InHorizontalAlignment };
return Call(STR("SetHorizontalAlignment"), p);
}
auto SetVerticalAlignment(EVerticalAlignment_ InVerticalAlignment) -> bool
{
struct P { EVerticalAlignment_ InVerticalAlignment; } p{ InVerticalAlignment };
return Call(STR("SetVerticalAlignment"), p);
}
// ---- property reads
auto GetSize() const -> std::optional<FSlateChildSize_> { return PropertyValue<FSlateChildSize_>(STR("Size")); }
auto GetPadding() const -> std::optional<FMargin_> { return PropertyValue<FMargin_>(STR("Padding")); }
auto GetHorizontalAlignment() const -> std::optional<EHorizontalAlignment_> { return PropertyValue<EHorizontalAlignment_>(STR("HorizontalAlignment")); }
auto GetVerticalAlignment() const -> std::optional<EVerticalAlignment_> { return PropertyValue<EVerticalAlignment_>(STR("VerticalAlignment")); }
};
}

246
Api/MainMenuPalWidget.hpp Normal file
View File

@@ -0,0 +1,246 @@
#pragma once
#include <optional>
#include <vector>
#include <Unreal/NameTypes.hpp>
#include "UnrealWrapper.hpp"
#include "UmgTypes.hpp"
#include "Widget.hpp"
#include "UserWidget.hpp"
#include "WidgetTree.hpp"
#include "CanvasPanel.hpp"
#include "Overlay.hpp"
#include "VerticalBox.hpp"
#include "TextBlock.hpp"
#include <Api/Pal/PalIndividual.hpp>
namespace PalUIExtension::Api
{
class MainMenuPalWidget : public UserWidget
{
public:
static auto ClassPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/MainMenu/Pal/"
"WBP_MainMenu_Pal_00.WBP_MainMenu_Pal_00_C");
}
static auto DebugTag() -> const TCHAR* { return STR("MainMenuPal"); }
MainMenuPalWidget() = default;
explicit MainMenuPalWidget(UObject* Obj)
: UserWidget(Obj, ClassOf<MainMenuPalWidget>(), DebugTag()) {
}
auto CanvasPalName() const -> CanvasPanel { return WrapCanvas(STR("Canvas_PalName")); }
auto CanvasPalStatus() const -> CanvasPanel { return WrapCanvas(STR("Canvas_PalStatus")); }
auto CanvasSkillInfo() const -> CanvasPanel { return WrapCanvas(STR("Canvas_SkillInfo")); }
auto CanvasPartnerSkill() const -> CanvasPanel { return WrapCanvas(STR("CanvasPanel_PartnerSkill")); }
auto CanvasPotencial() const -> CanvasPanel { return WrapCanvas(STR("CanvasPanel_Potencial")); } // engine spelling
auto CanvasDebuff() const -> CanvasPanel { return WrapCanvas(STR("CanvasPanelDebuff")); }
auto CanvasAwakening() const -> CanvasPanel { return WrapCanvas(STR("Canvas_Awakening")); }
auto CanvasDropGuide() const -> CanvasPanel { return WrapCanvas(STR("Canvas_DropGuide")); }
auto CanvasLocked() const -> CanvasPanel { return WrapCanvas(STR("Canvas_Locked")); }
auto CanvasUnlocked() const -> CanvasPanel { return WrapCanvas(STR("Canvas_Unlocked")); }
auto CanvasLock1() const -> CanvasPanel { return WrapCanvas(STR("CanvasPanelLock_1")); }
auto CanvasLockText() const -> CanvasPanel { return WrapCanvas(STR("CanvasPanelLockText")); }
auto CanvasLockText1() const -> CanvasPanel { return WrapCanvas(STR("CanvasPanelLockText_1")); }
auto BoxPalList() const -> VerticalBox { return WrapVBox(STR("VerticalBox_Pal_List")); }
auto BoxSkill() const -> VerticalBox { return WrapVBox(STR("VerticalBox_Skill")); }
auto OverlayCircle() const -> Overlay { return WrapOverlay(STR("Overlay_Circle")); }
auto OverlayIconCircle() const -> Overlay { return WrapOverlay(STR("Overlay_Icon_Circle")); }
auto OverlayIconSquare() const -> Overlay { return WrapOverlay(STR("Overlay_Icon_sq")); }
auto OverlaySquare() const -> Overlay { return WrapOverlay(STR("Overlay_sq")); }
auto GridSuitability() const -> UObject* { return BoundObject(STR("UniformGrid_Suitability")); }
auto FindCanvas(const TCHAR* Name) -> CanvasPanel { return Child<CanvasPanel>(Name); }
auto CachedHandle() const -> UObject* { return BoundObject(STR("CachedIndividualHandle")); }
auto CachedSpecies() const -> std::optional<FName>
{
return Pal::PalIndividual::CharacterIdFromHandle(CachedHandle());
}
auto CachedIdentity() const -> Pal::PalIdentity
{
return Pal::PalIndividual::Identify(CachedHandle());
}
// ---- screen state --------------------------------------------
auto IsShowingStatusWithList() const -> std::optional<bool> { return PropertyBoolValue(STR("isShowStatusWithList")); }
auto IsLocked() const -> std::optional<bool> { return PropertyBoolValue(STR("Is Lock")); }
auto IsEditingNickName() const -> std::optional<bool> { return PropertyBoolValue(STR("IsEditingNickName")); }
// ==============================================================
// WIDGET ARRAYS
// ==============================================================
auto PalPanels() const -> std::vector<UObject*> { return ReadObjectArray(STR("PalPanels")); }
auto ActiveSkillPanels() const -> std::vector<UObject*> { return ReadObjectArray(STR("ActiveSkillPanelArray")); }
auto PassiveSkillPanels() const -> std::vector<UObject*> { return ReadObjectArray(STR("PassiveSkillPanelArray")); }
auto ConditionWidgets() const -> std::vector<UObject*> { return ReadObjectArray(STR("ConditionWidgetArray")); }
auto RarityWidgets() const -> std::vector<UObject*> { return ReadObjectArray(STR("RarityWidgetArray")); }
// The engine's spelling of "SideInfo" is "SindeInfo". Lol.
auto SideInfoConditionWidgets() const -> std::vector<UObject*> { return ReadObjectArray(STR("SindeInfoConditionWidgetArray")); }
// ==============================================================
// TEXT
// ==============================================================
auto TextNowHP() const -> TextBlock { return WrapText(STR("Text_NowHP")); }
auto TextMaxHP() const -> TextBlock { return WrapText(STR("Text_MaxHP")); }
auto TextNowHunger() const -> TextBlock { return WrapText(STR("Text_NowHunger")); }
auto TextMaxHunger() const -> TextBlock { return WrapText(STR("Text_MaxHunger")); }
auto TextNowSanity() const -> TextBlock { return WrapText(STR("Text_NowSanity")); }
auto TextLevel() const -> TextBlock { return WrapText(STR("Text_LevelValue")); }
auto TextNextExp() const -> TextBlock { return WrapText(STR("Text_NextExp")); }
auto TextDefense() const -> TextBlock { return WrapText(STR("Text_DefenseValue")); }
auto TextRangeAttack() const -> TextBlock { return WrapText(STR("Text_RangeAttackValue")); }
auto TextWorkSpeed() const -> TextBlock { return WrapText(STR("Text_WorkSpeedValue")); }
auto TextFriendship() const -> TextBlock { return WrapText(STR("Text_FriendshipRank_Num")); }
auto TextNickName() const -> TextBlock { return WrapText(STR("BP_PalTextBlock_NickName")); }
auto TextLvLabel() const -> TextBlock { return WrapText(STR("BP_PalTextBlock_LvText")); }
auto TextSoulRank() const -> TextBlock { return WrapText(STR("BP_PalTextBlock_SoulRank")); }
auto RichTextPartnerSkillName() const -> UObject* { return BoundObject(STR("RichText_PartnerSkillName")); }
auto RichTextPartnerSkillDesc() const -> UObject* { return BoundObject(STR("RichText_PartnerSkillDesc")); }
// ---- gauges, images, nested widgets: raw ---------------------
auto GaugeHP() const -> UObject* { return BoundObject(STR("Gauge_HP")); }
auto GaugeHunger() const -> UObject* { return BoundObject(STR("Gauge_Hunger")); }
auto GaugeExp() const -> UObject* { return BoundObject(STR("Gauge_Exp")); }
auto GaugeFriendship() const -> UObject* { return BoundObject(STR("Gauge_Friendship")); }
auto ImageRare() const -> UObject* { return BoundObject(STR("Image_Rare")); }
auto ImageStrong() const -> UObject* { return BoundObject(STR("Image_Strong")); }
auto ImageMutant() const -> UObject* { return BoundObject(STR("Image_Mutant")); }
auto ImageLocked() const -> UObject* { return BoundObject(STR("Image_Icon_Locked")); }
auto ImageGlobalImport() const -> UObject* { return BoundObject(STR("Image_Icon_GlobalInport")); } // engine spelling
auto FoodAmountWidget() const -> UObject* { return BoundObject(STR("WBP_MainMenu_Pal_FoodAmount")); }
auto GenderIcon() const -> UObject* { return BoundObject(STR("WBP_PalGenderIcon")); }
auto SkillInfoWidget() const -> UObject* { return BoundObject(STR("WBP_MainMenu_PalSkillInfo")); }
auto PotencialWidget() const -> UObject* { return BoundObject(STR("WBP_Menu_Pal_Potencial")); } // engine spelling
auto NoDataWidget() const -> UObject* { return BoundObject(STR("WBP_NoData")); }
auto BuffTimers() const -> UObject* { return BoundObject(STR("WBP_StatusBuffTimerContainer")); }
auto KeyGuideIcon() const -> UObject* { return BoundObject(STR("WBP_PalKeyGuideIcon")); }
// ==============================================================
// HOOK TARGETS
// ==============================================================
static auto BindFromHandlePath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/MainMenu/Pal/"
"WBP_MainMenu_Pal_00.WBP_MainMenu_Pal_00_C:BindFromHandle");
}
static auto SetPalHandlesPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/MainMenu/Pal/"
"WBP_MainMenu_Pal_00.WBP_MainMenu_Pal_00_C:Set Pal Handles");
}
static auto SetupStatusPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/MainMenu/Pal/"
"WBP_MainMenu_Pal_00.WBP_MainMenu_Pal_00_C:Setup Status");
}
static auto ListToStatusPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/MainMenu/Pal/"
"WBP_MainMenu_Pal_00.WBP_MainMenu_Pal_00_C:ListToStatus");
}
static auto OnClickedPalButtonPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/MainMenu/Pal/"
"WBP_MainMenu_Pal_00.WBP_MainMenu_Pal_00_C:OnClickedPalButtonEvent");
}
static auto OnHoveredPalButtonPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/MainMenu/Pal/"
"WBP_MainMenu_Pal_00.WBP_MainMenu_Pal_00_C:OnHoveredPalButtonEvent");
}
// The honest bookends for "this screen exists" and "your cached
// pointers are now garbage." If you cache anything off this
// class, hook Destruct.
static auto ConstructPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/MainMenu/Pal/"
"WBP_MainMenu_Pal_00.WBP_MainMenu_Pal_00_C:Construct");
}
static auto DestructPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/MainMenu/Pal/"
"WBP_MainMenu_Pal_00.WBP_MainMenu_Pal_00_C:Destruct");
}
// ---- frames --------------------------------------------------
struct BindFromHandleFrame { UObject* targetHandle; };
struct HandleFrame { UObject* Handle; };
struct SetPalHandlesFrame
{
UObject** Data;
int32 Num;
int32 Max;
};
private:
auto WrapCanvas(const TCHAR* Name) const -> CanvasPanel
{
UObject* O = BoundObject(Name);
return O ? CanvasPanel{ O } : CanvasPanel{};
}
auto WrapVBox(const TCHAR* Name) const -> VerticalBox
{
UObject* O = BoundObject(Name);
return O ? VerticalBox{ O } : VerticalBox{};
}
auto WrapOverlay(const TCHAR* Name) const -> Overlay
{
UObject* O = BoundObject(Name);
return O ? Overlay{ O } : Overlay{};
}
auto WrapText(const TCHAR* Name) const -> TextBlock
{
UObject* O = BoundObject(Name);
return O ? TextBlock{ O } : TextBlock{};
}
static constexpr int32 kMaxPlausibleEntries = 1024;
auto ReadObjectArray(const TCHAR* PropertyName) const -> std::vector<UObject*>
{
std::vector<UObject*> Out;
auto* Arr = TryPropertyPtr<TArrayRead_<UObject*>>(PropertyName);
if (!Arr) return Out;
if (!Arr->IsPlausible(kMaxPlausibleEntries))
{
Output::send<LogLevel::Warning>(
STR("[{}] '{}' does not look like a live TArray (Num={}, Max={})\n"),
m_tag, PropertyName, Arr->Num, Arr->Max);
return Out;
}
Out.reserve(static_cast<size_t>(Arr->Num));
for (int32 i = 0; i < Arr->Num; ++i)
{
if (UObject* Entry = Arr->Data[i]) Out.push_back(Entry);
}
return Out;
}
protected:
MainMenuPalWidget(UObject* Obj, UClass* Expected, const TCHAR* Tag)
: UserWidget(Obj, Expected, Tag) {
}
};
}

View File

@@ -0,0 +1,72 @@
#pragma once
#include <optional>
#include <cstring>
#include <Unreal/FText.hpp>
#include <Unreal/NameTypes.hpp>
#include <Api/UmgTypes.hpp>
#include <Api/UnrealWrapper.hpp>
#include <Api/Widget.hpp>
#include <Api/UserWidget.hpp>
#include <Api/WidgetTree.hpp>
#include <Api/TextBlock.hpp>
#include <Api/CanvasPanel.hpp>
#include <Api/Pal/PalIndividual.hpp>
#include <Api/Pal/PalIndividualCharacterHandle.hpp>
#include <Api/Pal/PalIndividualCharacterParameter.hpp>
using namespace PalUIExtension::Api::Pal;
namespace PalUIExtension::Api::Mod
{
class PalDetail_PalIconNightEye : public UserWidget
{
public:
constexpr static auto ClassPath() -> const TCHAR* {
return STR("/Game/Mods/PalUIExtension/PalDetail_PalIconNightEye.PalDetail_PalIconNightEye_C");
}
constexpr static auto WidgetTreePath() -> const TCHAR* {
return STR("/Game/Mods/PalUIExtension/PalDetail_PalIconNightEye.PalDetail_PalIconNightEye_C:WidgetTree");
}
static auto DebugTag() -> const TCHAR* { return STR("PalDetail_PalIconNightEye"); }
PalDetail_PalIconNightEye() = default;
explicit PalDetail_PalIconNightEye(UObject* Obj) : UserWidget(Obj, ClassOf<PalDetail_PalIconNightEye>(), DebugTag()) {
}
auto GetIconVisibility() -> std::optional<bool>
{
return CallForReturn<bool>(STR("GetIconVisibility"));
}
// ---- calls ---------------------------------------------------
struct SetIconVisibilityFrame { bool visibility; bool localVar1; };
auto SetIconVisibility(bool newState) -> bool {
SetIconVisibilityFrame p{
newState,
newState
};
if (!Call(STR("SetIconVisibility"), p)){
Output::send<LogLevel::Error>(
STR("[SetIconVisibility] Call to method failed.\n"));
return false;
}
return true;
}
protected:
PalDetail_PalIconNightEye(UObject* Obj, UClass* Expected, const TCHAR* Tag)
: UserWidget(Obj, Expected, Tag) {
}
};
}

View File

@@ -0,0 +1,101 @@
#pragma once
#include <optional>
#include <cstring>
#include <Unreal/FText.hpp>
#include <Unreal/NameTypes.hpp>
#include <Api/UmgTypes.hpp>
#include <Api/UnrealWrapper.hpp>
#include <Api/Widget.hpp>
#include <Api/UserWidget.hpp>
#include <Api/WidgetTree.hpp>
#include <Api/TextBlock.hpp>
#include <Api/CanvasPanel.hpp>
#include <Api/Pal/PalIndividual.hpp>
#include <Api/Pal/PalIndividualCharacterHandle.hpp>
#include <Api/Pal/PalIndividualCharacterParameter.hpp>
using namespace PalUIExtension::Api::Pal;
namespace PalUIExtension::Api::Mod
{
class PalDetail_PalNumberWidget : public UserWidget
{
public:
static auto ClassPath() -> const TCHAR* {
return STR("/Game/Mods/PalUIExtension/PalDetail_PalNumber.PalDetail_PalNumber_C");
}
static auto WidgetTreePath() -> const TCHAR* {
return STR("/Game/Mods/PalUIExtension/PalDetail_PalNumber.PalDetail_PalNumber_C:WidgetTree");
}
static auto DebugTag() -> const TCHAR* { return STR("PalDetail_PalNumber"); }
PalDetail_PalNumberWidget() = default;
explicit PalDetail_PalNumberWidget(UObject* Obj) : UserWidget(Obj, ClassOf<PalDetail_PalNumberWidget>(), DebugTag()) {
}
auto GetBindedHandle() -> UObject*
{
struct P { UObject* returnValue; } p{ nullptr };
if (!CallForReturn(STR("GetBindedHandle"), p, &P::returnValue)) return nullptr;
return p.returnValue;
}
auto GetBindedName() -> std::optional<FName>
{
struct P { FName returnValue; } p{ FName{} };
if (!CallForReturn(STR("GetBindedName"), p, &P::returnValue)) return std::nullopt;
return p.returnValue;
}
auto CurrentHandle() -> PalIndividualCharacterHandle
{
UObject* H = GetBindedHandle();
return H ? PalIndividualCharacterHandle{ H } : PalIndividualCharacterHandle{};
}
auto HasBinding() -> bool
{
return CallForReturn<bool>(STR("GetHasBinding")).value_or(false);
}
// ---- calls ---------------------------------------------------
struct SetPalNumberTextFrame { FString text; };
auto SetPalNumberText(const FString& text) -> bool {
SetPalNumberTextFrame p{text};
if (!Call(STR("SetPalNumberText"), p)) return false;
return true;
}
auto SetPalBinding(FPalInstanceID _target) -> std::optional<FName>{
struct P {
FPalInstanceID TargetInstanceId;
FName ResolvedName;
UObject* GetIndivCharacterParamReturnVal;
bool IsValid;
FName GetCharacterIdReturnVal;
} p{ _target, FName{} };
if (!CallForReturn(STR("SetPalBinding"), p, &P::ResolvedName)) return std::nullopt;
return p.ResolvedName;
}
protected:
PalDetail_PalNumberWidget(UObject* Obj, UClass* Expected, const TCHAR* Tag)
: UserWidget(Obj, Expected, Tag) {
}
};
}

View File

@@ -0,0 +1,101 @@
#pragma once
#include <optional>
#include <cstring>
#include <Unreal/FText.hpp>
#include <Unreal/NameTypes.hpp>
#include <Api/UmgTypes.hpp>
#include <Api/UnrealWrapper.hpp>
#include <Api/Widget.hpp>
#include <Api/UserWidget.hpp>
#include <Api/WidgetTree.hpp>
#include <Api/TextBlock.hpp>
#include <Api/CanvasPanel.hpp>
#include <Api/Pal/PalIndividual.hpp>
#include <Api/Pal/PalIndividualCharacterHandle.hpp>
#include <Api/Pal/PalIndividualCharacterParameter.hpp>
using namespace PalUIExtension::Api::Pal;
namespace PalUIExtension::Api::Mod
{
class PalStatus_PalNumberWidget : public UserWidget
{
public:
static auto ClassPath() -> const TCHAR* {
return STR("/Game/Mods/PalUIExtension/PalStatus_PalNumber.PalStatus_PalNumber_C");
}
static auto WidgetTreePath() -> const TCHAR* {
return STR("/Game/Mods/PalUIExtension/PalStatus_PalNumber.PalStatus_PalNumber_C:WidgetTree");
}
static auto DebugTag() -> const TCHAR* { return STR("PalStatus_PalNumber"); }
PalStatus_PalNumberWidget() = default;
explicit PalStatus_PalNumberWidget(UObject* Obj) : UserWidget(Obj, ClassOf<PalStatus_PalNumberWidget>(), DebugTag()) {
}
auto GetBindedHandle() -> UObject*
{
struct P { UObject* returnValue; } p{ nullptr };
if (!CallForReturn(STR("GetBindedHandle"), p, &P::returnValue)) return nullptr;
return p.returnValue;
}
auto GetBindedName() -> std::optional<FName>
{
struct P { FName returnValue; } p{ FName{} };
if (!CallForReturn(STR("GetBindedName"), p, &P::returnValue)) return std::nullopt;
return p.returnValue;
}
auto CurrentHandle() -> PalIndividualCharacterHandle
{
UObject* H = GetBindedHandle();
return H ? PalIndividualCharacterHandle{ H } : PalIndividualCharacterHandle{};
}
auto HasBinding() -> bool
{
return CallForReturn<bool>(STR("GetHasBinding")).value_or(false);
}
// ---- calls ---------------------------------------------------
struct SetPalNumberTextFrame { FString text; };
auto SetPalNumberText(const FString& text) -> bool {
SetPalNumberTextFrame p{text};
if (!Call(STR("SetPalNumberText"), p)) return false;
return true;
}
auto SetPalBinding(FPalInstanceID _target) -> std::optional<FName>{
struct P {
FPalInstanceID TargetInstanceId;
FName ResolvedName;
UObject* GetIndivCharacterParamReturnVal;
bool IsValid;
FName GetCharacterIdReturnVal;
} p{ _target, FName{} };
if (!CallForReturn(STR("SetPalBinding"), p, &P::ResolvedName)) return std::nullopt;
return p.ResolvedName;
}
protected:
PalStatus_PalNumberWidget(UObject* Obj, UClass* Expected, const TCHAR* Tag)
: UserWidget(Obj, Expected, Tag) {
}
};
}

34
Api/Overlay.hpp Normal file
View File

@@ -0,0 +1,34 @@
#pragma once
#include "PanelWidget.hpp"
#include "OverlaySlot.hpp"
namespace PalUIExtension::Api
{
// UOverlay : public UPanelWidget directly (confirmed by the header --
// no ContentWidget rung to skip here, unlike SizeBox). Every generic
// panel operation -- AddChild, GetChildAt, GetAllChildren,
// GetChildObjects -- is already on PanelWidget and works unchanged;
// AddChildToOverlay below is the one thing Overlay adds beyond that,
// and it exists only because the engine returns the concrete
// UOverlaySlot* type instead of the generic UPanelSlot* AddChild()
// returns, so you get typed access to Padding/Alignment without an
// extra cast.
class Overlay : public PanelWidget
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/UMG.Overlay"); }
static auto DebugTag() -> const TCHAR* { return STR("Overlay"); }
Overlay() = default;
explicit Overlay(UObject* Obj)
: PanelWidget(Obj, ClassOf<Overlay>(), DebugTag()) {}
auto AddChildToOverlay(UObject* Content) -> OverlaySlot
{
struct P { UObject* Content; UObject* ReturnValue; } p{ Content, nullptr };
auto Result = CallForReturn(STR("AddChildToOverlay"), p, &P::ReturnValue);
return (Result && *Result) ? OverlaySlot{ *Result } : OverlaySlot{};
}
};
}

45
Api/OverlaySlot.hpp Normal file
View File

@@ -0,0 +1,45 @@
#pragma once
#include "PanelSlot.hpp"
namespace PalUIExtension::Api
{
// Same three-member surface as SizeBoxSlot (Padding + H/V alignment).
// Different engine class, different ClassPath -- not merged into one
// wrapper, matching how CanvasPanelSlot stands alone rather than
// sharing with PanelSlot's other concrete subclasses.
class OverlaySlot : public PanelSlot
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/UMG.OverlaySlot"); }
static auto DebugTag() -> const TCHAR* { return STR("OverlaySlot"); }
OverlaySlot() = default;
explicit OverlaySlot(UObject* Obj)
: PanelSlot(Obj, ClassOf<OverlaySlot>(), DebugTag()) {}
// ---- setters (write through the UFUNCTION so SynchronizeProperties runs)
auto SetPadding(FMargin_ InPadding) -> bool
{
struct P { FMargin_ InPadding; } p{ InPadding };
return Call(STR("SetPadding"), p);
}
auto SetHorizontalAlignment(EHorizontalAlignment_ InHorizontalAlignment) -> bool
{
struct P { EHorizontalAlignment_ InHorizontalAlignment; } p{ InHorizontalAlignment };
return Call(STR("SetHorizontalAlignment"), p);
}
auto SetVerticalAlignment(EVerticalAlignment_ InVerticalAlignment) -> bool
{
struct P { EVerticalAlignment_ InVerticalAlignment; } p{ InVerticalAlignment };
return Call(STR("SetVerticalAlignment"), p);
}
// ---- property reads
auto GetPadding() const -> std::optional<FMargin_> { return PropertyValue<FMargin_>(STR("Padding")); }
auto GetHorizontalAlignment()const -> std::optional<EHorizontalAlignment_> { return PropertyValue<EHorizontalAlignment_>(STR("HorizontalAlignment")); }
auto GetVerticalAlignment() const -> std::optional<EVerticalAlignment_> { return PropertyValue<EVerticalAlignment_>(STR("VerticalAlignment")); }
};
}

136
Api/Pal/PalCharacter.hpp Normal file
View File

@@ -0,0 +1,136 @@
#pragma once
#include <optional>
#include <Unreal/NameTypes.hpp>
#include <Api/UnrealWrapper.hpp>
#include "PalIndividual.hpp"
#include "PalTypes.hpp"
namespace PalUIExtension::Api::Pal
{
class PalCharacter : public UnrealWrapper
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/Pal.PalCharacter"); }
static auto DebugTag() -> const TCHAR* { return STR("PalCharacter"); }
PalCharacter() = default;
explicit PalCharacter(UObject* Obj) : UnrealWrapper(Obj, ClassOf<PalCharacter>(), DebugTag()) {}
// ---- components ------------------------------------------------
auto GetCharacterParameterComponent() -> UObject* { return ObjectGetter(STR("GetCharacterParameterComponent")); }
auto GetActionComponent() -> UObject* { return ObjectGetter(STR("GetActionComponent")); }
auto GetPalCharacterMovementComponent() -> UObject* { return ObjectGetter(STR("GetPalCharacterMovementComponent")); }
auto GetMainMesh() -> UObject* { return ObjectGetter(STR("GetMainMesh")); }
auto GetCurrentReservedMontage() -> UObject* { return ObjectGetter(STR("GetCurrentReservedMontage")); }
auto GetOverrideFaceMesh() -> UObject* { return ObjectGetter(STR("GetOverrideFaceMesh")); }
auto GetHandAttachMesh() -> UObject* { return ObjectGetter(STR("GetHandAttachMesh")); }
// ---- identity --------------------------------------------------
auto GetIndividualParameterObject() -> UObject*
{
UObject* Comp = GetCharacterParameterComponent();
if (!Comp) return nullptr;
LooseObject C{ Comp, STR("PalCharacterParameterComponent") };
return C.CallForReturn<UObject*>(STR("GetIndividualParameter")).value_or(nullptr);
}
auto Identify() -> PalIdentity
{
return PalIndividual::IdentifyFromParameter(GetIndividualParameterObject());
}
auto CharacterId() -> std::optional<FName>
{
return PalIndividual::CharacterId(GetIndividualParameterObject());
}
auto BlueprintClassName() const -> std::optional<StringType>
{
if (!m_obj) return std::nullopt;
UClass* Cls = m_obj->GetClassPrivate();
if (!Cls) return std::nullopt;
return Cls->GetName();
}
// ---- state queries ---------------------------------------------
auto IsInitialized() -> std::optional<bool> { return CallForReturn<bool>(STR("IsInitialized")); }
auto IsPart() -> std::optional<bool> { return CallForReturn<bool>(STR("IsPart")); }
auto IsCooping() -> std::optional<bool> { return CallForReturn<bool>(STR("IsCooping")); }
auto IsPreCooping() -> std::optional<bool> { return CallForReturn<bool>(STR("IsPreCooping")); }
auto IsLocalHiddenForCutscene() -> std::optional<bool> { return CallForReturn<bool>(STR("IsLocalHiddenForCutscene")); }
auto IsUseCustomAutoAimTarget() -> std::optional<bool> { return CallForReturn<bool>(STR("IsUseCustomAutoAimTarget")); }
auto IsAllActiveSkillCooldownFinished() -> std::optional<bool> { return CallForReturn<bool>(STR("IsAllActiveSkillCooldownFinished")); }
auto GetBattleMode() -> std::optional<bool> { return CallForReturn<bool>(STR("GetBattleMode")); }
auto GetTalkMode() -> std::optional<bool> { return CallForReturn<bool>(STR("GetTalkMode")); }
auto GetActiveActorFlag() -> std::optional<bool> { return CallForReturn<bool>(STR("GetActiveActorFlag")); }
auto IsActiveSkillCooldownFinished(EPalWazaID_ WazaID) -> std::optional<bool>
{
struct P { EPalWazaID_ WazaID; bool ReturnValue; } p{ WazaID, false };
return CallForReturn(STR("IsActiveSkillCooldownFinished"), p, &P::ReturnValue);
}
// ---- local-only mutators ---------------------------------------
auto ForceResetJumpState() -> bool { return CallVoid(STR("ForceResetJumpState")); }
auto RequestJump() -> bool { return CallVoid(STR("RequestJump")); }
auto ResetTickInterval() -> bool { return CallVoid(STR("ResetTickInterval")); }
auto LocalInitialized() -> bool { return CallVoid(STR("LocalInitialized")); }
auto RefreshDungeonLightingChannels() -> bool { return CallVoid(STR("RefreshDungeonLightingChannels")); }
auto RequestExecuteTickNextFrameForAction() -> bool { return CallVoid(STR("RequestExecuteTickNextFrameForAction")); }
auto BroadcastOnCompleteInitializeParameter() -> bool { return CallVoid(STR("BroadcastOnCompleteInitializeParameter")); }
auto UpdateGroundRayCast(bool bImmediateApply) -> bool
{
struct P { bool bImmediateApply; } p{ bImmediateApply };
return Call(STR("UpdateGroundRayCast"), p);
}
auto SetVisibleCharacterMesh(bool Active) -> bool
{
struct P { bool Active; } p{ Active };
return Call(STR("SetVisibleCharacterMesh"), p);
}
auto SetVisibleHandAttachMesh(bool Active) -> bool
{
struct P { bool Active; } p{ Active };
return Call(STR("SetVisibleHandAttachMesh"), p);
}
auto SetLocalHiddenForCutscene(bool bHide) -> bool
{
struct P { bool bHide; } p{ bHide };
return Call(STR("SetLocalHiddenForCutscene"), p);
}
auto SetActiveCollisionMovement(bool Active) -> bool
{
struct P { bool Active; } p{ Active };
return Call(STR("SetActiveCollisionMovement"), p);
}
auto ChangeBattleModeFlag(bool IsBattle) -> bool
{
struct P { bool IsBattle; } p{ IsBattle };
return Call(STR("ChangeBattleModeFlag"), p);
}
auto SetDisableChangeIntervalByImportance(FName FlagName, bool bIsDisable) -> bool
{
struct P { FName flagName; bool isDisable; } p{ FlagName, bIsDisable };
return Call(STR("SetDisableChangeIntervalByImportance"), p);
}
private:
auto ObjectGetter(const TCHAR* FunctionName) -> UObject*
{
struct P { UObject* ReturnValue; } p{ nullptr };
auto Result = CallForReturn(FunctionName, p, &P::ReturnValue);
return (Result && *Result) ? *Result : nullptr;
}
};
}

View File

@@ -0,0 +1,369 @@
#pragma once
#include <optional>
#include <Unreal/FText.hpp>
#include <Unreal/NameTypes.hpp>
#include <Api/UnrealWrapper.hpp>
#include <Static/LogGate.hpp>
#include "PalIndividual.hpp"
#include "PalTypes.hpp"
namespace PalUIExtension::Api::Pal
{
class PalDatabaseCharacterParameter : public UnrealWrapper
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/Pal.PalDatabaseCharacterParameter"); }
static auto DebugTag() -> const TCHAR* { return STR("PalDatabase"); }
PalDatabaseCharacterParameter() = default;
explicit PalDatabaseCharacterParameter(UObject* Obj) : UnrealWrapper(Obj, ClassOf<PalDatabaseCharacterParameter>(), DebugTag()) {}
static auto StripVariantPrefix(FName CharacterId) -> std::optional<FName>
{
const StringType Id = CharacterId.ToString();
static const StringType Prefixes[] = {
STR("BOSS_"), STR("PREDATOR_"), STR("RAID_"), STR("GYM_"),
};
for (const StringType& Prefix : Prefixes)
{
if (Id.size() <= Prefix.size()) continue;
if (!StartsWithNoCase(Id, Prefix)) continue;
return FName(StringViewType(Id).substr(Prefix.size()), FNAME_Add);
}
return std::nullopt;
}
static auto ResolveBaseRowName(FName CharacterId) -> FName
{
return StripVariantPrefix(CharacterId).value_or(CharacterId);
}
// ---- row lookups by species FName -----------------------------
auto GetZukanIndex(FName RowName) -> std::optional<int32>
{
struct P { FName RowName; int32 ReturnValue; } p{ RowName, 0 };
return CallForReturn(STR("GetZukanIndex"), p, &P::ReturnValue);
}
auto GetZukanIndexResolved(FName CharacterId) -> std::optional<int32>
{
if (auto Direct = GetZukanIndex(CharacterId); Direct.has_value() && *Direct > 0)
{
return Direct;
}
auto Base = StripVariantPrefix(CharacterId);
if (!Base.has_value()) return std::nullopt;
auto Fallback = GetZukanIndex(*Base);
if (!Fallback.has_value() || *Fallback <= 0) return std::nullopt;
if constexpr (Static::kVerboseLogging)
{
Output::send<LogLevel::Verbose>(
STR("[PalDatabase] ZukanIndex: '{}' had none, resolved via base '{}' -> {}\n"),
CharacterId.ToString(), Base->ToString(), *Fallback);
}
return Fallback;
}
auto GetLocalizedBaseCharacterName(FName CharacterId) -> std::optional<FText>
{
return GetLocalizedCharacterName(ResolveBaseRowName(CharacterId));
}
auto GetTribe(FName RowName) -> std::optional<EPalTribeID_>
{
struct P { FName RowName; EPalTribeID_ ReturnValue; } p{ RowName, 0 };
return CallForReturn(STR("GetTribe"), p, &P::ReturnValue);
}
auto GetSize(FName RowName) -> std::optional<EPalSizeType_>
{
struct P { FName RowName; EPalSizeType_ ReturnValue; } p{ RowName, EPalSizeType_::None };
return CallForReturn(STR("GetSize"), p, &P::ReturnValue);
}
auto GetGenusCategory(FName RowName) -> std::optional<EPalGenusCategoryType_>
{
struct P { FName RowName; EPalGenusCategoryType_ ReturnValue; } p{ RowName, EPalGenusCategoryType_::None };
return CallForReturn(STR("GetGenusCategory"), p, &P::ReturnValue);
}
auto GetOrganizationType(FName RowName) -> std::optional<EPalOrganizationType_>
{
struct P { FName RowName; EPalOrganizationType_ ReturnValue; } p{ RowName, EPalOrganizationType_::None };
return CallForReturn(STR("GetOrganizationType"), p, &P::ReturnValue);
}
auto GetWeaponType(FName RowName) -> std::optional<EPalWeaponType_>
{
struct P { FName RowName; EPalWeaponType_ ReturnValue; } p{ RowName, EPalWeaponType_::None };
return CallForReturn(STR("GetWeaponType"), p, &P::ReturnValue);
}
auto GetBattleBGM(FName RowName) -> std::optional<EPalBattleBGMType_>
{
struct P { FName RowName; EPalBattleBGMType_ ReturnValue; } p{ RowName, EPalBattleBGMType_::None };
return CallForReturn(STR("GetBattleBGM"), p, &P::ReturnValue);
}
auto GetPalAwakeningItemElement(FName ItemId) -> std::optional<EPalElementType_>
{
struct P { FName ItemId; EPalElementType_ ReturnValue; } p{ ItemId, EPalElementType_::None };
return CallForReturn(STR("GetPalAwakeningItemElement"), p, &P::ReturnValue);
}
struct ElementTypePair { EPalElementType_ Primary{}; EPalElementType_ Secondary{}; };
// void return, two out-params -- not CallForReturn's shape.
auto GetElementType(FName RowName) -> std::optional<ElementTypePair>
{
struct P { FName RowName; EPalElementType_ Element1; EPalElementType_ Element2; }
p{ RowName, EPalElementType_::None, EPalElementType_::None };
if (!Call(STR("GetElementType"), p)) return std::nullopt;
return ElementTypePair{ p.Element1, p.Element2 };
}
// void return, one out-param.
auto GetBestWorkSuitability(FName RowName) -> std::optional<EPalWorkSuitability_>
{
struct P { FName RowName; EPalWorkSuitability_ BestWorkSuitability; }
p{ RowName, EPalWorkSuitability_::None };
if (!Call(STR("GetBestWorkSuitability"), p)) return std::nullopt;
return p.BestWorkSuitability;
}
auto GetWeaponEquip(FName RowName) -> std::optional<bool>
{
struct P { FName RowName; bool ReturnValue; } p{ RowName, false };
return CallForReturn(STR("GetWeaponEquip"), p, &P::ReturnValue);
}
auto GetUseBossHPGauge(FName RowName) -> std::optional<bool>
{
struct P { FName RowName; bool ReturnValue; } p{ RowName, false };
return CallForReturn(STR("GetUseBossHPGauge"), p, &P::ReturnValue);
}
auto GetNocturnal(FName RowName) -> std::optional<bool>
{
struct P { FName RowName; bool ReturnValue; } p{ RowName, false };
return CallForReturn(STR("GetNocturnal"), p, &P::ReturnValue);
}
auto GetIsTowerBoss(FName RowName) -> std::optional<bool>
{
struct P { FName RowName; bool ReturnValue; } p{ RowName, false };
return CallForReturn(STR("GetIsTowerBoss"), p, &P::ReturnValue);
}
auto GetIsRaidBoss(FName RowName) -> std::optional<bool>
{
struct P { FName RowName; bool ReturnValue; } p{ RowName, false };
return CallForReturn(STR("GetIsRaidBoss"), p, &P::ReturnValue);
}
auto GetIsPredatorBoss(FName RowName) -> std::optional<bool>
{
struct P { FName RowName; bool ReturnValue; } p{ RowName, false };
return CallForReturn(STR("GetIsPredatorBoss"), p, &P::ReturnValue);
}
auto GetIsPal(FName RowName) -> std::optional<bool>
{
struct P { FName RowName; bool ReturnValue; } p{ RowName, false };
return CallForReturn(STR("GetIsPal"), p, &P::ReturnValue);
}
auto GetIsLegend(FName RowName) -> std::optional<bool>
{
struct P { FName RowName; bool ReturnValue; } p{ RowName, false };
return CallForReturn(STR("GetIsLegend"), p, &P::ReturnValue);
}
auto GetIsBoss(FName RowName) -> std::optional<bool>
{
struct P { FName RowName; bool ReturnValue; } p{ RowName, false };
return CallForReturn(STR("GetIsBoss"), p, &P::ReturnValue);
}
auto IsArenaUnusableItem(FName ItemId) -> std::optional<bool>
{
struct P { FName ItemId; bool ReturnValue; } p{ ItemId, false };
return CallForReturn(STR("IsArenaUnusableItem"), p, &P::ReturnValue);
}
auto GetViewingDistance_cm(FName RowName) -> std::optional<int32>
{
struct P { FName RowName; int32 ReturnValue; } p{ RowName, 0 };
return CallForReturn(STR("GetViewingDistance_cm"), p, &P::ReturnValue);
}
auto GetViewingAngle_Degree(FName RowName) -> std::optional<int32>
{
struct P { FName RowName; int32 ReturnValue; } p{ RowName, 0 };
return CallForReturn(STR("GetViewingAngle_Degree"), p, &P::ReturnValue);
}
auto GetRarity(FName RowName) -> std::optional<int32>
{
struct P { FName RowName; int32 ReturnValue; } p{ RowName, 0 };
return CallForReturn(STR("GetRarity"), p, &P::ReturnValue);
}
auto GetFoodAmount(FName RowName) -> std::optional<int32>
{
struct P { FName RowName; int32 ReturnValue; } p{ RowName, 0 };
return CallForReturn(STR("GetFoodAmount"), p, &P::ReturnValue);
}
auto GetPriceRate(FName RowName) -> std::optional<float>
{
struct P { FName RowName; float ReturnValue; } p{ RowName, 0.f };
return CallForReturn(STR("GetPriceRate"), p, &P::ReturnValue);
}
auto GetHearingRate(FName RowName) -> std::optional<float>
{
struct P { FName RowName; float ReturnValue; } p{ RowName, 0.f };
return CallForReturn(STR("GetHearingRate"), p, &P::ReturnValue);
}
auto GetExpRatio(FName RowName) -> std::optional<float>
{
struct P { FName RowName; float ReturnValue; } p{ RowName, 0.f };
return CallForReturn(STR("GetExpRatio"), p, &P::ReturnValue);
}
auto GetCaptureRateCorrect(FName RowName) -> std::optional<float>
{
struct P { FName RowName; float ReturnValue; } p{ RowName, 0.f };
return CallForReturn(STR("GetCaptureRateCorrect"), p, &P::ReturnValue);
}
auto GetBPClassName(FName RowName) -> std::optional<FName>
{
struct P { FName RowName; FName ReturnValue; } p{ RowName, {} };
return CallForReturn(STR("GetBPClassName"), p, &P::ReturnValue);
}
auto GetFirstDefeatRewardItemID(FName CharacterID) -> std::optional<FName>
{
struct P { FName CharacterID; FName ReturnValue; } p{ CharacterID, {} };
return CallForReturn(STR("GetFirstDefeatRewardItemID"), p, &P::ReturnValue);
}
// void return, FName out-param -- not a FString, no ownership question, just an index pair like every other FName here.
auto GetPrefixNameMsgID(FName CharacterID) -> std::optional<FName>
{
struct P { FName CharacterID; FName OutMsgID; } p{ CharacterID, {} };
if (!Call(STR("GetPrefixNameMsgID"), p)) return std::nullopt;
return p.OutMsgID;
}
auto GetLocalizedCharacterName(FName CharacterID) -> std::optional<FText>
{
struct P { FName CharacterID; FText OutText; } p{ CharacterID, {} };
if (!Call(STR("GetLocalizedCharacterName"), p)) return std::nullopt;
return p.OutText;
}
auto GetLocalizedUniqueNPCName(FName UniqueNPCID) -> std::optional<FText>
{
struct P { FName UniqueNPCID; FText OutText; } p{ UniqueNPCID, {} };
if (!Call(STR("GetLocalizedUniqueNPCName"), p)) return std::nullopt;
return p.OutText;
}
// ---- friendship ------------------------------------------------
auto GetMinFriendshipRank() -> std::optional<int32> { return CallForReturn<int32>(STR("GetMinFriendshipRank")); }
auto GetMaxFriendshipRank() -> std::optional<int32> { return CallForReturn<int32>(STR("GetMaxFriendshipRank")); }
auto GetFriendshipRank(int32 FriendshipPoint) -> std::optional<int32>
{
struct P { int32 FriendshipPoint; int32 ReturnValue; } p{ FriendshipPoint, 0 };
return CallForReturn(STR("GetFriendshipRank"), p, &P::ReturnValue);
}
auto CalcFriendshipProgress(int32 FriendshipPoint) -> std::optional<float>
{
struct P { int32 FriendshipPoint; float ReturnValue; } p{ FriendshipPoint, 0.f };
return CallForReturn(STR("CalcFriendshipProgress"), p, &P::ReturnValue);
}
// bool return + int32& out-param.
auto GetFriendshipRequiredPointByRank(int32 FriendshipRank) -> std::optional<int32>
{
struct P { int32 FriendshipRank; int32 OutRequiredPoint; bool ReturnValue; }
p{ FriendshipRank, 0, false };
if (!Call(STR("GetFriendshipRequiredPointByRank"), p) || !p.ReturnValue) return std::nullopt;
return p.OutRequiredPoint;
}
// ---- derived from a live UPalIndividualCharacterParameter* ---------
auto GetHP(UObject* IndividualParameter) -> std::optional<int32>
{
struct P { UObject* IndividualParameter; int32 ReturnValue; } p{ IndividualParameter, 0 };
return CallForReturn(STR("GetHP"), p, &P::ReturnValue);
}
auto GetMeleeAttack(UObject* IndividualParameter) -> std::optional<int32>
{
struct P { UObject* IndividualParameter; int32 ReturnValue; } p{ IndividualParameter, 0 };
return CallForReturn(STR("GetMeleeAttack"), p, &P::ReturnValue);
}
auto GetShotAttack(UObject* IndividualParameter) -> std::optional<int32>
{
struct P { UObject* IndividualParameter; int32 ReturnValue; } p{ IndividualParameter, 0 };
return CallForReturn(STR("GetShotAttack"), p, &P::ReturnValue);
}
auto GetDefense(UObject* IndividualParameter) -> std::optional<int32>
{
struct P { UObject* IndividualParameter; int32 ReturnValue; } p{ IndividualParameter, 0 };
return CallForReturn(STR("GetDefense"), p, &P::ReturnValue);
}
auto GetCraftSpeed(UObject* IndividualParameter) -> std::optional<int32>
{
struct P { UObject* IndividualParameter; int32 ReturnValue; } p{ IndividualParameter, 0 };
return CallForReturn(STR("GetCraftSpeed"), p, &P::ReturnValue);
}
auto GetSupport(UObject* IndividualParameter) -> std::optional<int32>
{
struct P { UObject* IndividualParameter; int32 ReturnValue; } p{ IndividualParameter, 0 };
return CallForReturn(STR("GetSupport"), p, &P::ReturnValue);
}
auto HigherLevelOtomoFromTrainer(UObject* IndividualCharacterParameter) -> std::optional<int32>
{
struct P { UObject* IndividualCharacterParameter; int32 ReturnValue; } p{ IndividualCharacterParameter, 0 };
return CallForReturn(STR("HigherLevelOtomoFromTrainer"), p, &P::ReturnValue);
}
auto UpdateApplyDatabaseToIndividualParameter(UObject* IndividualParameter) -> bool
{
struct P { UObject* IndividualParameter; } p{ IndividualParameter };
return Call(STR("UpdateApplyDatabaseToIndividualParameter"), p);
}
// Raw UObject* return -- UPalPettingPresset isn't modeled anywhere
auto GetPettingPreset() -> UObject*
{
struct P { UObject* ReturnValue; } p{ nullptr };
auto Result = CallForReturn(STR("GetPettingPreset"), p, &P::ReturnValue);
return (Result && *Result) ? *Result : nullptr;
}
// WorldContextObject is a plain UObject* at the native signature
// level -- the meta=(WorldContext=...) annotation only affects
// the Blueprint editor node, not the ProcessEvent frame. Pass
// any live UObject with a valid world (a player controller, an
// actor, etc.).
auto CalcCorrectedLevel(int32 TrainerLevel, int32 TargetCharacterLevel, UObject* WorldContextObject) -> std::optional<int32>
{
struct P { int32 TrainerLevel; int32 TargetCharacterLevel; UObject* WorldContextObject; int32 ReturnValue; }
p{ TrainerLevel, TargetCharacterLevel, WorldContextObject, 0 };
return CallForReturn(STR("CalcCorrectedLevel"), p, &P::ReturnValue);
}
private:
static auto StartsWithNoCase(const StringType& Str, const StringType& Prefix) -> bool
{
if (Str.size() < Prefix.size()) return false;
for (size_t i = 0; i < Prefix.size(); ++i)
{
CharType A = Str[i];
CharType B = Prefix[i];
if (A >= STR('a') && A <= STR('z')) A = static_cast<CharType>(A - STR('a') + STR('A'));
if (B >= STR('a') && B <= STR('z')) B = static_cast<CharType>(B - STR('a') + STR('A'));
if (A != B) return false;
}
return true;
}
};
}

301
Api/Pal/PalGameMode.hpp Normal file
View File

@@ -0,0 +1,301 @@
#pragma once
#include <optional>
#include <Unreal/FString.hpp>
#include <Unreal/NameTypes.hpp>
#include <Api/UnrealWrapper.hpp>
namespace PalUIExtension::Api::Pal
{
using namespace RC;
using namespace RC::Unreal;
// ==================================================================
// EPalGameModeType -- confirmed from the dump, UENUM(BlueprintType)
// over uint8, two values. Mirrored here for the same reason
// ESlateVisibility_ is: a params struct needs a concrete type of the
// right width, and pulling in the game's generated header is not an
// option.
// ==================================================================
enum class EPalGameModeType_ : uint8
{
Title = 0,
InGame = 1,
};
// ==================================================================
// APalGameModeBase.
//
// Two things exist on this class and one of them matters:
// GameModeType, an FEnumProperty over uint8. Read it to tell the
// title screen apart from a live world without inspecting the map
// name.
//
// ---- THE THING TO INTERNALISE ABOUT GAME MODES -------------------
//
// A GameMode exists ONLY ON THE AUTHORITY. On a listen host and in
// single-player that is the local process, so this class is
// reachable. On a client connected to a dedicated server, no
// APalGameMode is ever instantiated locally and nothing in this file
// will ever resolve to anything.
//
// That is not a limitation of this wrapper, it is what a game mode
// IS, and it decides what you may and may not build on top of it. A
// client-side UI mod that depends on a game-mode hook for
// correctness is broken for every dedicated-server player. Use these
// as a supplement to an engine-level signal, never as the only one.
// ==================================================================
class PalGameModeBase : public UnrealWrapper
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/Pal.PalGameModeBase"); }
static auto DebugTag() -> const TCHAR* { return STR("PalGameModeBase"); }
PalGameModeBase() = default;
explicit PalGameModeBase(UObject* Obj)
: UnrealWrapper(Obj, ClassOf<PalGameModeBase>(), DebugTag()) {}
// FEnumProperty over uint8 -- GetSize() reports 1, and
// EPalGameModeType_ is 1 byte, so the size check in PropertyPtr is
// meaningful here rather than vacuous.
auto GameModeType() const -> std::optional<EPalGameModeType_>
{
return PropertyValue<EPalGameModeType_>(STR("GameModeType"));
}
auto IsInGame() const -> bool
{
const auto T = GameModeType();
return T.has_value() && *T == EPalGameModeType_::InGame;
}
auto IsTitle() const -> bool
{
const auto T = GameModeType();
return T.has_value() && *T == EPalGameModeType_::Title;
}
protected:
PalGameModeBase(UObject* Obj, UClass* Expected, const TCHAR* Tag)
: UnrealWrapper(Obj, Expected, Tag) {}
};
// ==================================================================
// APalGameMode -- the in-world game mode.
//
// ---- WHAT IS WORTH HOOKING HERE, AND WHAT IS NOT -----------------
//
// RestartGame is the one this tree cares about: it is the game
// deciding the current world is finished, fired BEFORE the travel
// that destroys it. That ordering is the whole value -- a cache of
// widget pointers wants to be dropped while those pointers are still
// merely useless, not after they are dangling.
//
// READ THE CAVEAT BLOCK ON RestartGamePath() BEFORE RELYING ON IT.
// It has two failure modes that are invisible in testing if you only
// test single-player.
//
// The session/auth callbacks (OnUpdateSession, OnCompleteAuth,
// OnCompleteCreateSession, OnEOSLoginDedicatedServerComplete) are
// dedicated-server plumbing. They are wrapped as PATHS ONLY, with
// frames, because they are occasionally useful for diagnosing "why
// did my server mod not initialise" -- but nothing in a client-side
// UI mod should be reading them, and none of them fire in
// single-player at all.
//
// ---- WHY THERE IS NO Current() -----------------------------------
//
// The obvious convenience -- a static that hands back the live game
// mode -- is deliberately absent. Every way to get one without a
// UWorld in hand goes through UObjectGlobals::FindObject or
// FindObjects, and for a Blueprint-reachable native class that is a
// linear walk of the entire GUObjectArray (see the note in
// PalBoxNightWorkIndicator on what that cost actually is). A helper
// that looks like a property read and is really a million-object scan
// is the kind of convenience that ends up on a per-frame path.
//
// Get the instance from a hook that already has it instead: every
// callback below receives it as Ctx.Context, and
// Hook::RegisterInitGameStatePostCallback hands over an
// AGameModeBase* directly at world start.
// ==================================================================
class PalGameMode : public PalGameModeBase
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/Pal.PalGameMode"); }
static auto DebugTag() -> const TCHAR* { return STR("PalGameMode"); }
PalGameMode() = default;
explicit PalGameMode(UObject* Obj)
: PalGameModeBase(Obj, ClassOf<PalGameMode>(), DebugTag()) {}
// ---- properties ------------------------------------------------
// APlayerStart*. No wrapper for it in this tree, so the raw
// pointer is the honest return type.
auto CachePlayerStart() const -> UObject*
{
return TryObjectProperty(STR("CachePlayerStart"));
}
// ==============================================================
// HOOK TARGETS
// ==============================================================
// ==============================================================
// RestartGame. READ THIS BEFORE BUILDING ON IT.
//
// Two failure modes, both silent, both invisible if you only
// test single-player:
//
// 1. AUTHORITY ONLY. No APalGameMode is instantiated on a client
// connected to a dedicated server, so this hook never installs
// and never fires there. For a client-side UI mod that is the
// majority multiplayer case.
//
// 2. REFLECTION ONLY. This is a native UFUNCTION(BlueprintCallable).
// UE4SS hooks UFunction execution, which catches invocations
// that go through ProcessEvent -- a Blueprint node, a console
// exec, a reflected call. It does NOT catch native C++ code
// calling APalGameMode::RestartGame() directly, because that
// path never touches the reflection system. Whether Palworld
// restarts via Blueprint or via C++ is not answerable from a
// header dump; it has to be observed.
//
// The practical consequence: treat a RestartGame callback as an
// OPPORTUNISTIC EARLY WARNING, not as the teardown signal. The
// load-bearing one is Hook::RegisterLoadMapPreCallback, which is
// engine-level, fires on every map transition regardless of what
// triggered it, and works identically on clients and hosts.
//
// Wiring both is correct and costs nothing: teardown must be
// idempotent anyway, since a restart that goes through both paths
// will call it twice.
// ==============================================================
static auto RestartGamePath() -> const TCHAR*
{
return STR("/Script/Pal.PalGameMode:RestartGame");
}
static auto InitDedicatedServerPath() -> const TCHAR*
{
return STR("/Script/Pal.PalGameMode:InitDedicatedServer");
}
static auto OnServerLobbyUpdatePath() -> const TCHAR*
{
return STR("/Script/Pal.PalGameMode:OnServerLobbyUpdate");
}
static auto OnUpdateSessionPath() -> const TCHAR*
{
return STR("/Script/Pal.PalGameMode:OnUpdateSession");
}
static auto OnCompleteAuthPath() -> const TCHAR*
{
return STR("/Script/Pal.PalGameMode:OnCompleteAuth");
}
static auto OnCompleteCreateSessionPath() -> const TCHAR*
{
return STR("/Script/Pal.PalGameMode:OnCompleteCreateSession");
}
static auto OnEOSLoginDedicatedServerCompletePath() -> const TCHAR*
{
return STR("/Script/Pal.PalGameMode:OnEOSLoginDedicatedServerComplete");
}
static auto CreateSessionPath() -> const TCHAR*
{
return STR("/Script/Pal.PalGameMode:CreateSession");
}
static auto FindPlayerStartWithTagPath() -> const TCHAR*
{
return STR("/Script/Pal.PalGameMode:FindPlayerStartWithTag");
}
// ---- frames ---------------------------------------------------
//
// Leading parameters only, matching the convention in
// PalCommonCharacterSlot.hpp: hook frames are NOT size-checked, so
// reading past the declared parameters reads engine locals. On a
// PRE hook those are uninitialised.
//
// The FString members are the part to be careful with. A
// `const FString&` UFUNCTION parameter still occupies a full
// FString in the frame (TArray<TCHAR>: 8-byte data pointer +
// int32 Num + int32 Max = 16 bytes), so the layout below is what
// the engine writes. It is NOT owned by you -- read it, do not
// move from it, do not free it.
//
// RestartGame takes nothing, which is why there is no frame for
// it and why its callback should not call Ctx.GetParams at all.
struct HttpResponseFrame
{
FString ResponseBody; // 0x00
bool bResponseOK; // 0x10
int32 ResponseCode; // 0x14 (after 3 bytes of padding)
};
struct EOSLoginCompleteFrame
{
UObject* UserInfo; // 0x00 const UPocketpairUserInfo*
bool bSuccess; // 0x08
FString ErrorStr; // 0x10 (after 7 bytes of padding)
};
struct CreateSessionFrame { FString Address; };
struct FindPlayerStartWithTagFrame { FName Tag; UObject* ReturnValue; };
protected:
PalGameMode(UObject* Obj, UClass* Expected, const TCHAR* Tag)
: PalGameModeBase(Obj, Expected, Tag) {}
};
// ==================================================================
// APalGameModeLogin -- the title-screen game mode.
//
// Included because it is the OTHER half of the world-lifecycle
// picture and it is easy to forget it exists: going from a save back
// to the title is a map transition into THIS game mode, not a
// RestartGame on the previous one. If teardown only fires on
// APalGameMode, quitting to title leaves every cached pointer alive
// and dangling.
//
// GoToTitle is BlueprintImplementableEvent -- no native body. It
// dispatches through the object's class chain like anything else, so
// it is hookable, but if no Blueprint subclass overrides it there is
// nothing to hook and the registry will defer forever.
// ==================================================================
class PalGameModeLogin : public PalGameModeBase
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/Pal.PalGameModeLogin"); }
static auto DebugTag() -> const TCHAR* { return STR("PalGameModeLogin"); }
PalGameModeLogin() = default;
explicit PalGameModeLogin(UObject* Obj)
: PalGameModeBase(Obj, ClassOf<PalGameModeLogin>(), DebugTag()) {}
static auto OnCompletePath() -> const TCHAR*
{
return STR("/Script/Pal.PalGameModeLogin:OnComplete");
}
static auto GoToTitlePath() -> const TCHAR*
{
return STR("/Script/Pal.PalGameModeLogin:GoToTitle");
}
struct OnCompleteFrame { bool bCanPlayMulti; };
protected:
PalGameModeLogin(UObject* Obj, UClass* Expected, const TCHAR* Tag)
: PalGameModeBase(Obj, Expected, Tag) {}
};
}

127
Api/Pal/PalIndividual.hpp Normal file
View File

@@ -0,0 +1,127 @@
#pragma once
#include <optional>
#include <cstring>
#include <Unreal/FText.hpp>
#include <Unreal/NameTypes.hpp>
#include <Api\UmgTypes.hpp>
#include <Api\UnrealWrapper.hpp>
#include "PalTypes.hpp"
namespace PalUIExtension::Api::Pal
{
enum class EPalGenderType_ : uint8 { None = 0, Male = 1, Female = 2 };
enum class EPalElementType_ : uint8
{
None = 0, Normal = 1, Fire = 2, Water = 3, Leaf = 4,
Electricity = 5, Ice = 6, Earth = 7, Dark = 8, Dragon = 9, MAX = 10,
};
enum class EPalWorkSuitability_ : uint8
{
None = 0, EmitFlame = 1, Watering = 2, Seeding = 3,
GenerateElectricity = 4, Handcraft = 5, Collection = 6, Deforest = 7,
Mining = 8, OilExtraction = 9, ProductMedicine = 10, Cool = 11,
Transport = 12, MonsterFarm = 13, Anyone = 14, MAX = 15,
};
class LooseObject : public UnrealWrapper
{
public:
LooseObject() = default;
explicit LooseObject(UObject* Obj, const TCHAR* Tag = STR("Loose"))
: UnrealWrapper(Obj, UncheckedTag{}, Tag) {
}
using UnrealWrapper::Call;
using UnrealWrapper::CallVoid;
using UnrealWrapper::CallForReturn;
using UnrealWrapper::PropertyPtr;
using UnrealWrapper::PropertyValue;
};
// this struct is now redundant. watch it never get removed.
struct FPalInstanceID {
FGuid_ PlayerUID;
FGuid_ InstanceID;
FString DebugName;
};
struct PalIdentity
{
UObject* Handle{};
UObject* IndividualParameter{};
std::optional<FName> CharacterId; // the species. "SheepBall", etc.
std::optional<int32> Level;
std::optional<int32> Rank;
std::optional<EPalGenderType_> Gender;
std::optional<bool> bRare;
explicit operator bool() const { return CharacterId.has_value(); }
};
class PalIndividual
{
public:
static auto ParameterFromHandle(UObject* Handle) -> UObject*
{
if (!Handle) return nullptr;
LooseObject H{ Handle, STR("PalIndividualCharacter") };
return H.CallForReturn<UObject*>(STR("TryGetIndividualParameter")).value_or(nullptr);
}
static auto CharacterId(UObject* Parameter) -> std::optional<FName>
{
if (!Parameter) return std::nullopt;
LooseObject P{ Parameter, STR("PalIndividualCharacterParameter") };
return P.CallForReturn<FName>(STR("GetCharacterID"));
}
static auto CharacterIdFromHandle(UObject* Handle) -> std::optional<FName>
{
return CharacterId(ParameterFromHandle(Handle));
}
static auto IdentifyFromParameter(UObject* Parameter) -> PalIdentity
{
PalIdentity Out{};
Out.IndividualParameter = Parameter;
if (!Parameter) return Out;
LooseObject P{ Parameter, STR("PalIndividualCharacterParameter") };
Out.CharacterId = P.CallForReturn<FName>(STR("GetCharacterID"));
Out.Level = P.CallForReturn<int32>(STR("GetLevel"));
Out.Rank = P.CallForReturn<int32>(STR("GetRank"));
Out.Gender = P.CallForReturn<EPalGenderType_>(STR("GetGenderType"));
Out.bRare = P.CallForReturn<bool>(STR("IsRarePal"));
return Out;
}
static auto Identify(UObject* Handle) -> PalIdentity
{
PalIdentity Out = IdentifyFromParameter(ParameterFromHandle(Handle));
Out.Handle = Handle;
return Out;
}
static auto GetInstanceIds(UObject* Handle) -> std::optional<FPalInstanceID>
{
if (!Handle) return std::nullopt;
// FPalInstanceID
LooseObject H{ Handle, STR("PalIndividualCharacterHandle") };
auto _Out = H.CallForReturn<FPalInstanceID>(STR("GetIndividualID"));
if (!_Out.has_value()) return std::nullopt;
return FPalInstanceID{ _Out.value().PlayerUID, _Out.value().InstanceID, _Out.value().DebugName };
}
};
}

View File

@@ -0,0 +1,56 @@
#pragma once
#include <Api/UnrealWrapper.hpp>
#include "PalIndividual.hpp"
#include "PalCharacter.hpp"
namespace PalUIExtension::Api::Pal
{
class PalIndividualCharacterHandle : public UnrealWrapper
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/Pal.PalIndividualCharacterHandle"); }
static auto DebugTag() -> const TCHAR* { return STR("PalIndividualCharacterHandle"); }
PalIndividualCharacterHandle() = default;
explicit PalIndividualCharacterHandle(UObject* Obj) : UnrealWrapper(Obj, ClassOf<PalIndividualCharacterHandle>(), DebugTag()) {}
auto TryGetIndividualParameterObject() -> UObject*
{
struct P { UObject* ReturnValue; } p{ nullptr };
auto Result = CallForReturn(STR("TryGetIndividualParameter"), p, &P::ReturnValue);
return (Result && *Result) ? *Result : nullptr;
}
auto TryGetIndividualActorObject() -> UObject*
{
struct P { UObject* ReturnValue; } p{ nullptr };
auto Result = CallForReturn(STR("TryGetIndividualActor"), p, &P::ReturnValue);
return (Result && *Result) ? *Result : nullptr;
}
auto TryGetIndividualActor() -> PalCharacter
{
UObject* A = TryGetIndividualActorObject();
return A ? PalCharacter{ A } : PalCharacter{};
}
auto TryGetPhantomActor(int32 PhantomId) -> UObject*
{
struct P { int32 PhantomId; UObject* ReturnValue; } p{ PhantomId, nullptr };
auto Result = CallForReturn(STR("TryGetPhantomActor"), p, &P::ReturnValue);
return (Result && *Result) ? *Result : nullptr;
}
auto GetPhantomIDByActor(UObject* Character) -> std::optional<int32>
{
struct P { UObject* Character; int32 ReturnValue; } p{ Character, 0 };
return CallForReturn(STR("GetPhantomIDByActor"), p, &P::ReturnValue);
}
auto GetInstanceIds() -> std::optional<FPalInstanceID>
{
return PalIndividual::GetInstanceIds(Get());
}
};
}

View File

@@ -0,0 +1,480 @@
#pragma once
#include <optional>
#include <Unreal/FText.hpp>
#include <Unreal/NameTypes.hpp>
#include <Api/UnrealWrapper.hpp>
#include "PalIndividual.hpp"
#include "PalTypes.hpp"
namespace PalUIExtension::Api::Pal
{
class PalIndividualCharacterParameter : public UnrealWrapper
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/Pal.PalIndividualCharacterParameter"); }
static auto DebugTag() -> const TCHAR* { return STR("PalIndividualCharacterParameter"); }
PalIndividualCharacterParameter() = default;
explicit PalIndividualCharacterParameter(UObject* Obj) : UnrealWrapper(Obj, ClassOf<PalIndividualCharacterParameter>(), DebugTag()) {}
// ---- identity ---------------------------------------------------
auto GetCharacterID() -> std::optional<FName> { return CallForReturn<FName>(STR("GetCharacterID")); }
auto GetUniqueNPCID() -> std::optional<FName> { return CallForReturn<FName>(STR("GetUniqueNPCID")); }
auto GetSkinName() -> std::optional<FName> { return CallForReturn<FName>(STR("GetSkinName")); }
auto GetGenderType() -> std::optional<EPalGenderType_>
{
return CallForReturn<EPalGenderType_>(STR("GetGenderType"));
}
auto GetTribeID() -> std::optional<EPalTribeID_>
{
return CallForReturn<EPalTribeID_>(STR("GetTribeID"));
}
auto IsRarePal() -> std::optional<bool> { return CallForReturn<bool>(STR("IsRarePal")); }
auto IsFavoritePal() -> std::optional<bool> { return CallForReturn<bool>(STR("IsFavoritePal")); }
auto GetFavoriteIndex() -> std::optional<int32> { return CallForReturn<int32>(STR("GetFavoriteIndex")); }
auto IsSkinApplied() -> std::optional<bool> { return CallForReturn<bool>(STR("IsSkinApplied")); }
auto GetSkinAppliedCharacterId() -> std::optional<FGuid_>
{
return CallForReturn<FGuid_>(STR("GetSkinAppliedCharacterId"));
}
auto GetPalId() -> std::optional<FPalInstanceID>
{
struct P { alignas(8) uint8 Frame[48]; } p{}; // FPalInstanceID
if (!Call(STR("GetPalId"), p)) return std::nullopt;
FPalInstanceID Out{};
std::memcpy(&Out, p.Frame, sizeof(Out));
return Out;
}
// Nickname getters
auto GetNickname() -> std::optional<FString>
{
struct P { FString OutName; } p{};
if (!Call(STR("GetNickname"), p)) return std::nullopt;
return p.OutName;
}
auto GetNickNameWithOnlineID() -> std::optional<FString>
{
struct P { FString OutName; } p{};
if (!Call(STR("GetNickNameWithOnlineID"), p)) return std::nullopt;
return p.OutName;
}
auto GetNickNameByCheckBlockedUser() -> std::optional<FString>
{
struct P { FString OutName; } p{};
if (!Call(STR("GetNickNameByCheckBlockedUser"), p)) return std::nullopt;
return p.OutName;
}
// ---- level / experience ------------------------------------------
auto GetLevel() -> std::optional<int32> { return CallForReturn<int32>(STR("GetLevel")); }
auto IsLevelMax() -> std::optional<bool> { return CallForReturn<bool>(STR("IsLevelMax")); }
auto GetExp() -> std::optional<int64> { return CallForReturn<int64>(STR("GetExp")); }
auto GetRank() -> std::optional<int32> { return CallForReturn<int32>(STR("GetRank")); }
auto GetRankUpExp() -> std::optional<int32> { return CallForReturn<int32>(STR("GetRankUpExp")); }
auto GetPalSoulRank() -> std::optional<int32> { return CallForReturn<int32>(STR("GetPalSoulRank")); }
auto IsOverrideLevel() -> std::optional<bool> { return CallForReturn<bool>(STR("IsOverrideLevel")); }
auto GetOverrideLevel() -> std::optional<int32> { return CallForReturn<int32>(STR("GetOverrideLevel")); }
auto SetOverrideLevel(int32 OverrideLevel) -> bool
{
struct P { int32 OverrideLevel; } p{ OverrideLevel };
return Call(STR("SetOverrideLevel"), p);
}
// ---- HP / shield / death -----------------------------------------
auto GetHP() -> std::optional<FFixedPoint64_> { return CallForReturn<FFixedPoint64_>(STR("GetHP")); }
auto GetMaxHP_withBuff() -> std::optional<FFixedPoint64_> { return CallForReturn<FFixedPoint64_>(STR("GetMaxHP_withBuff")); }
auto GetMaxHP() -> std::optional<int32> { return CallForReturn<int32>(STR("GetMaxHP")); }
auto GetHPRank() -> std::optional<int32> { return CallForReturn<int32>(STR("GetHPRank")); }
auto GetShieldHP() -> std::optional<FFixedPoint64_> { return CallForReturn<FFixedPoint64_>(STR("GetShieldHP")); }
auto GetShieldMaxHP() -> std::optional<FFixedPoint64_> { return CallForReturn<FFixedPoint64_>(STR("GetShieldMaxHP")); }
auto GetCurrentPhaseHPLimit() -> std::optional<FFixedPoint64_>
{
struct P { FFixedPoint64_ OutPhaseHPLimit; bool ReturnValue; } p{ {}, false };
auto Result = CallForReturn(STR("GetCurrentPhaseHPLimit"), p, &P::ReturnValue);
if (!Result || !*Result) return std::nullopt;
return p.OutPhaseHPLimit;
}
auto IsPhaseHPLimitValid() -> std::optional<bool> { return CallForReturn<bool>(STR("IsPhaseHPLimitValid")); }
auto AddHP(FFixedPoint64_ PlusHP) -> bool
{
struct P { FFixedPoint64_ PlusHP; } p{ PlusHP };
return Call(STR("AddHP"), p);
}
auto FullRecoveryHP() -> bool { return CallVoid(STR("FullRecoveryHP")); }
auto IsHPFullRecovered() -> std::optional<bool> { return CallForReturn<bool>(STR("IsHPFullRecovered")); }
auto SetShieldHP(FFixedPoint64_ NextShieldHP) -> bool
{
struct P { FFixedPoint64_ NextSheildHP; } p{ NextShieldHP }; // engine spelling
return Call(STR("SetShieldHP"), p);
}
auto SetShieldMaxHP(FFixedPoint64_ NextShieldMaxHP) -> bool
{
struct P { FFixedPoint64_ NextSheildMaxHP; } p{ NextShieldMaxHP }; // engine spelling
return Call(STR("SetShieldMaxHP"), p);
}
auto DecreaseShieldHPByDamage(int32 Damage) -> bool
{
struct P { int32 Damage; } p{ Damage };
return Call(STR("DecreaseShieldHPByDamage"), p);
}
auto IsDead() -> std::optional<bool> { return CallForReturn<bool>(STR("IsDead")); }
auto GetPhysicalHealth() -> std::optional<EPalStatusPhysicalHealthType_>
{
return CallForReturn<EPalStatusPhysicalHealthType_>(STR("GetPhysicalHealth"));
}
auto SetPhysicalHealth(EPalStatusPhysicalHealthType_ PhysicalHealth) -> bool
{
struct P { EPalStatusPhysicalHealthType_ PhysicalHealth; } p{ PhysicalHealth };
return Call(STR("SetPhysicalHealth"), p);
}
auto IsRespawnReady() -> std::optional<bool> { return CallForReturn<bool>(STR("IsRespawnReady")); }
auto GetRespawnTime() -> std::optional<float> { return CallForReturn<float>(STR("GetRespawnTime")); }
// ---- combat stats -------------------------------------------------
auto GetMeleeAttack() -> std::optional<int32> { return CallForReturn<int32>(STR("GetMeleeAttack")); }
auto GetMeleeAttack_withBuff() -> std::optional<int32> { return CallForReturn<int32>(STR("GetMeleeAttack_withBuff")); }
auto GetShotAttack() -> std::optional<int32> { return CallForReturn<int32>(STR("GetShotAttack")); }
auto GetShotAttack_withBuff() -> std::optional<int32> { return CallForReturn<int32>(STR("GetShotAttack_withBuff")); }
auto GetDefense() -> std::optional<int32> { return CallForReturn<int32>(STR("GetDefense")); }
auto GetDefense_withBuff() -> std::optional<int32> { return CallForReturn<int32>(STR("GetDefense_withBuff")); }
auto GetAttackRank() -> std::optional<int32> { return CallForReturn<int32>(STR("GetAttackRank")); }
auto GetDefenceRank() -> std::optional<int32> { return CallForReturn<int32>(STR("GetDefenceRank")); }
auto HasMasteredWaza(EPalWazaID_ WazaID) -> std::optional<bool>
{
struct P { EPalWazaID_ WazaID; bool ReturnValue; } p{ WazaID, false };
return CallForReturn(STR("HasMasteredWaza"), p, &P::ReturnValue);
}
auto AddEquipWaza(EPalWazaID_ WazaID) -> bool
{
struct P { EPalWazaID_ WazaID; } p{ WazaID };
return Call(STR("AddEquipWaza"), p);
}
auto RemoveEquipWaza(EPalWazaID_ WazaID) -> bool
{
struct P { EPalWazaID_ WazaID; } p{ WazaID };
return Call(STR("RemoveEquipWaza"), p);
}
auto ClearEquipWaza() -> bool { return CallVoid(STR("ClearEquipWaza")); }
// ---- sanity / hunger / stomach ------------------------------------
auto GetSanityValue() -> std::optional<float> { return CallForReturn<float>(STR("GetSanityValue")); }
auto GetMaxSanityValue() -> std::optional<float> { return CallForReturn<float>(STR("GetMaxSanityValue")); }
auto GetSanityRate() -> std::optional<float> { return CallForReturn<float>(STR("GetSanityRate")); }
auto GetAffectSanityValue() -> std::optional<float> { return CallForReturn<float>(STR("GetAffectSanityValue")); }
auto GetHungerType() -> std::optional<EPalStatusHungerType_>
{
return CallForReturn<EPalStatusHungerType_>(STR("GetHungerType"));
}
auto GetFullStomach() -> std::optional<float> { return CallForReturn<float>(STR("GetFullStomach")); }
auto GetMaxFullStomach() -> std::optional<float> { return CallForReturn<float>(STR("GetMaxFullStomach")); }
auto GetFullStomachRate() -> std::optional<float> { return CallForReturn<float>(STR("GetFullStomachRate")); }
auto GetFullStomachDecreasingRate() -> std::optional<float> { return CallForReturn<float>(STR("GetFullStomachDecreasingRate")); }
auto IsFullStomachDecreaseStoppedByFood() -> std::optional<bool>
{
return CallForReturn<bool>(STR("IsFullStomachDecreaseStoppedByFood"));
}
auto SetFullStomach(float NextValue) -> bool
{
struct P { float NextValue; } p{ NextValue };
return Call(STR("SetFullStomach"), p);
}
auto SetDecreaseFullStomachRates(FName Name, float Rate) -> bool
{
struct P { FName Name; float Rate; } p{ Name, Rate };
return Call(STR("SetDecreaseFullStomachRates"), p);
}
auto RemoveDecreaseFullStomachRates(FName Name) -> bool
{
struct P { FName Name; } p{ Name };
return Call(STR("RemoveDecreaseFullStomachRates"), p);
}
auto GetEffectFoodName() -> std::optional<FName> { return CallForReturn<FName>(STR("GetEffectFoodName")); }
auto GetEffectFoodName_FullStomachKeep() -> std::optional<FName> { return CallForReturn<FName>(STR("GetEffectFoodName_FullStomachKeep")); }
auto GetEffectFoodTimeRate() -> std::optional<float> { return CallForReturn<float>(STR("GetEffectFoodTimeRate")); }
auto GetEffectFoodTimeRate_FullStomachKeep() -> std::optional<float> { return CallForReturn<float>(STR("GetEffectFoodTimeRate_FullStomachKeep")); }
auto GetFoodStatusRate(EPalFoodStatusEffectType_ EffectType) -> std::optional<float>
{
struct P { EPalFoodStatusEffectType_ EffectType; float ReturnValue; } p{ EffectType, 0.f };
return CallForReturn(STR("GetFoodStatusRate"), p, &P::ReturnValue);
}
// ---- friendship -----------------------------------------------------
auto GetFriendshipRank() -> std::optional<int32> { return CallForReturn<int32>(STR("GetFriendshipRank")); }
auto GetFriendshipPoint() -> std::optional<int32> { return CallForReturn<int32>(STR("GetFriendshipPoint")); }
auto AddFriendShip(int32 Value, bool bApplyPassiveSkill) -> bool
{
struct P { int32 Value; bool bApplyPassiveSkill; } p{ Value, bApplyPassiveSkill };
return Call(STR("AddFriendShip"), p);
}
// ---- status points --------------------------------------------------
auto GetUnusedStatusPoint() -> std::optional<int32> { return CallForReturn<int32>(STR("GetUnusedStatusPoint")); }
auto DecrementUnusedStatusPoint() -> bool { return CallVoid(STR("DecrementUnusedStatusPoint")); }
auto IsStatusPointAllMax() -> std::optional<bool> { return CallForReturn<bool>(STR("IsStatusPointAllMax")); }
auto GetStatusPoint(FName StatusName) -> std::optional<int32>
{
struct P { FName StatusName; int32 ReturnValue; } p{ StatusName, 0 };
return CallForReturn(STR("GetStatusPoint"), p, &P::ReturnValue);
}
auto GetTotalStatusPoint(FName StatusName) -> std::optional<int32>
{
struct P { FName StatusName; int32 ReturnValue; } p{ StatusName, 0 };
return CallForReturn(STR("GetTotalStatusPoint"), p, &P::ReturnValue);
}
auto GetExStatusPoint(FName StatusName) -> std::optional<int32>
{
struct P { FName StatusName; int32 ReturnValue; } p{ StatusName, 0 };
return CallForReturn(STR("GetExStatusPoint"), p, &P::ReturnValue);
}
auto SetStatusPoint(FName StatusName, int32 Point) -> bool
{
struct P { FName StatusName; int32 Point; } p{ StatusName, Point };
return Call(STR("SetStatusPoint"), p);
}
auto SetExStatusPoint(FName StatusName, int32 Point) -> bool
{
struct P { FName StatusName; int32 Point; } p{ StatusName, Point };
return Call(STR("SetExStatusPoint"), p);
}
// Two outputs (bool return + int32& out), same non-CallForReturn
// shape as CheckValidWorkSuitability in PalDetailWidget.hpp.
auto IsStatusPointAddable(FName StatusName) -> std::optional<int32>
{
struct P { FName StatusName; int32 AddablePoint; bool ReturnValue; } p{ StatusName, 0, false };
if (!Call(STR("IsStatusPointAddable"), p) || !p.ReturnValue) return std::nullopt;
return p.AddablePoint;
}
// ---- work suitability -------------------------------------------------
auto GetCurrentWorkSuitability() -> std::optional<EPalWorkSuitability_>
{
return CallForReturn<EPalWorkSuitability_>(STR("GetCurrentWorkSuitability"));
}
auto GetWorkSpeedRank() -> std::optional<int32> { return CallForReturn<int32>(STR("GetWorkSpeedRank")); }
auto GetCraftSpeed() -> std::optional<int32> { return CallForReturn<int32>(STR("GetCraftSpeed")); }
auto GetCraftSpeed_withBuff() -> std::optional<int32> { return CallForReturn<int32>(STR("GetCraftSpeed_withBuff")); }
auto GetCraftSpeedSickRate() -> std::optional<float> { return CallForReturn<float>(STR("GetCraftSpeedSickRate")); }
auto GetBaseCampCraftSpeedBuffRate() -> std::optional<float> { return CallForReturn<float>(STR("GetBaseCampCraftSpeedBuffRate")); }
auto GetWorkerSick() -> std::optional<EPalBaseCampWorkerSickType_>
{
return CallForReturn<EPalBaseCampWorkerSickType_>(STR("GetWorkerSick"));
}
auto GetBaseCampWorkerEventType() -> std::optional<EPalBaseCampWorkerEventType_>
{
return CallForReturn<EPalBaseCampWorkerEventType_>(STR("GetBaseCampWorkerEventType"));
}
auto HasWorkSuitability(EPalWorkSuitability_ Suitability) -> std::optional<bool>
{
struct P { EPalWorkSuitability_ InWorkSuitability; bool ReturnValue; } p{ Suitability, false };
return CallForReturn(STR("HasWorkSuitability"), p, &P::ReturnValue);
}
auto HasWorkSuitabilityRank(EPalWorkSuitability_ Suitability, int32 Rank) -> std::optional<bool>
{
struct P { EPalWorkSuitability_ InWorkSuitability; int32 SuitabilityRank; bool ReturnValue; }
p{ Suitability, Rank, false };
return CallForReturn(STR("HasWorkSuitabilityRank"), p, &P::ReturnValue);
}
auto GetWorkSuitabilityRank(EPalWorkSuitability_ Suitability) -> std::optional<int32>
{
struct P { EPalWorkSuitability_ InWorkSuitability; int32 ReturnValue; } p{ Suitability, 0 };
return CallForReturn(STR("GetWorkSuitabilityRank"), p, &P::ReturnValue);
}
auto GetWorkSuitabilityRankWithCharacterRank(EPalWorkSuitability_ Suitability) -> std::optional<int32>
{
struct P { EPalWorkSuitability_ WorkSuitability; int32 ReturnValue; } p{ Suitability, 0 };
return CallForReturn(STR("GetWorkSuitabilityRankWithCharacterRank"), p, &P::ReturnValue);
}
auto GetRankBasedWorkSuitabilityBonus(EPalWorkSuitability_ Suitability) -> std::optional<int32>
{
struct P { EPalWorkSuitability_ WorkSuitability; int32 ReturnValue; } p{ Suitability, 0 };
return CallForReturn(STR("GetRankBasedWorkSuitabilityBonus"), p, &P::ReturnValue);
}
auto GetCraftSpeedByWorkSuitability(EPalWorkSuitability_ Suitability) -> std::optional<int32>
{
struct P { EPalWorkSuitability_ WorkSuitability; int32 ReturnValue; } p{ Suitability, 0 };
return CallForReturn(STR("GetCraftSpeedByWorkSuitability"), p, &P::ReturnValue);
}
auto GetCraftSpeedBuffRate(EPalWorkSuitability_ Suitability) -> std::optional<float>
{
struct P { EPalWorkSuitability_ WorkSuitability; float ReturnValue; } p{ Suitability, 0.f };
return CallForReturn(STR("GetCraftSpeedBuffRate"), p, &P::ReturnValue);
}
auto GetCraftSpeed_withBuff_WorkSuitability(EPalWorkSuitability_ Suitability) -> std::optional<int32>
{
struct P { EPalWorkSuitability_ Suitability; int32 ReturnValue; } p{ Suitability, 0 };
return CallForReturn(STR("GetCraftSpeed_withBuff_WorkSuitability"), p, &P::ReturnValue);
}
auto SetWorkSuitabilityAddRank(EPalWorkSuitability_ Suitability, int32 AddRank) -> bool
{
struct P { EPalWorkSuitability_ WorkSuitability; int32 addRank; } p{ Suitability, AddRank };
return Call(STR("SetWorkSuitabilityAddRank"), p);
}
// ---- passive skills ---------------------------------------------------
//
// GetPassiveSkillList (TArray<FName>) is NOT wrapped -- see class
// comment. Add/Remove take scalar FName params, no such problem.
auto AddPassiveSkill(FName AddSkill, FName OverrideSkill) -> bool
{
struct P { FName AddSkill; FName OverrideSkill; } p{ AddSkill, OverrideSkill };
return Call(STR("AddPassiveSkill"), p);
}
auto RemovePassiveSkill(FName SkillId) -> bool
{
struct P { FName SkillId; } p{ SkillId };
return Call(STR("RemovePassiveSkill"), p);
}
auto GetPassiveRateBySkillEffect(EPalPassiveSkillEffectType_ EffectType) -> std::optional<float>
{
struct P { EPalPassiveSkillEffectType_ EffectType; float ReturnValue; } p{ EffectType, 0.f };
return CallForReturn(STR("GetPassiveRateBySkillEffect"), p, &P::ReturnValue);
}
auto GetPassiveRateByEquipment(EPalPassiveSkillEffectType_ EffectType) -> std::optional<float>
{
struct P { EPalPassiveSkillEffectType_ EffectType; float ReturnValue; } p{ EffectType, 0.f };
return CallForReturn(STR("GetPassiveRateByEquipment"), p, &P::ReturnValue);
}
// ---- classification / misc flags ---------------------------------------
auto GetSizeType() -> std::optional<EPalSizeType_> { return CallForReturn<EPalSizeType_>(STR("GetSizeType")); }
auto GetGroupType() -> std::optional<EPalGroupType_> { return CallForReturn<EPalGroupType_>(STR("GetGroupType")); }
auto GetGroupId() -> std::optional<FGuid_> { return CallForReturn<FGuid_>(STR("GetGroupId")); }
auto GetBaseCampId() -> std::optional<FGuid_> { return CallForReturn<FGuid_>(STR("GetBaseCampId")); }
auto GetArenaRank() -> std::optional<EPalArenaRank_> { return CallForReturn<EPalArenaRank_>(STR("GetArenaRank")); }
auto GetArenaRankPoint() -> std::optional<int32> { return CallForReturn<int32>(STR("GetArenaRankPoint")); }
auto IsInArena() -> std::optional<bool> { return CallForReturn<bool>(STR("IsInArena")); }
auto IsInRaidArea() -> std::optional<bool> { return CallForReturn<bool>(STR("IsInRaidArea")); }
auto IsUncapturable() -> std::optional<bool> { return CallForReturn<bool>(STR("IsUncapturable")); }
auto IsForceCapturable() -> std::optional<bool> { return CallForReturn<bool>(STR("IsForceCapturable")); }
auto IsAwakening() -> std::optional<bool> { return CallForReturn<bool>(STR("IsAwakening")); }
auto IsPartBroken() -> std::optional<bool> { return CallForReturn<bool>(STR("IsPartBroken")); }
auto IsParts() -> std::optional<bool> { return CallForReturn<bool>(STR("IsParts")); }
auto GetParentParameterObject() -> UObject*
{
struct P { UObject* ReturnValue; } p{ nullptr };
auto Result = CallForReturn(STR("GetParentParameter"), p, &P::ReturnValue);
return (Result && *Result) ? *Result : nullptr;
}
auto IsAssignedToExpedition() -> std::optional<bool> { return CallForReturn<bool>(STR("IsAssignedToExpedition")); }
auto IsAssignedToExpeditionIn(FGuid_ MapObjectId) -> std::optional<bool>
{
struct P { FGuid_ MapObjectConcreteInstanceId; bool ReturnValue; } p{ MapObjectId, false };
return CallForReturn(STR("IsAssignedToExpeditionIn"), p, &P::ReturnValue);
}
auto IsExcludedFromTeamMission() -> std::optional<bool> { return CallForReturn<bool>(STR("IsExcludedFromTeamMission")); }
auto CanTargetFromAI() -> std::optional<bool> { return CallForReturn<bool>(STR("CanTargetFromAI")); }
auto HasGenusCategory(EPalGenusCategoryType_ Category) -> std::optional<bool>
{
struct P { EPalGenusCategoryType_ Category; bool ReturnValue; } p{ Category, false };
return CallForReturn(STR("HasGenusCategory"), p, &P::ReturnValue);
}
auto CanAddTalentByItem(FName ItemName) -> std::optional<bool>
{
struct P { FName ItemName; bool ReturnValue; } p{ ItemName, false };
return CallForReturn(STR("CanAddTalentByItem"), p, &P::ReturnValue);
}
auto GetNPCWeaponType() -> std::optional<EPalWeaponType_> { return CallForReturn<EPalWeaponType_>(STR("GetNPCWeaponType")); }
auto GetEquipItemContainerId() -> std::optional<FPalContainerId_>
{
return CallForReturn<FPalContainerId_>(STR("GetEquipItemContainerId"));
}
auto SetUncapturable(bool bInUncapturable) -> bool
{
struct P { bool bInUncapturable; } p{ bInUncapturable };
return Call(STR("SetUncapturable"), p);
}
auto SetForceCapturable(bool bInForceCapturable) -> bool
{
struct P { bool bInForceCapturable; } p{ bInForceCapturable };
return Call(STR("SetForceCapturable"), p);
}
auto SetInArena(bool InArena) -> bool
{
struct P { bool InArena; } p{ InArena };
return Call(STR("SetInArena"), p);
}
auto SetInRaidArea(bool InRaidArea) -> bool
{
struct P { bool InRaidArea; } p{ InRaidArea };
return Call(STR("SetInRaidArea"), p);
}
auto SetCanTargetFromAI(bool bInCanTargetFromAI) -> bool
{
struct P { bool bInCanTargetFromAI; } p{ bInCanTargetFromAI };
return Call(STR("SetCanTargetFromAI"), p);
}
auto SetSkinName(FName InSkinName) -> bool
{
struct P { FName InSkinName; } p{ InSkinName };
return Call(STR("SetSkinName"), p);
}
auto SetSkinAppliedCharacterId(FGuid_ InCharacterId) -> bool
{
struct P { FGuid_ InCharacterId; } p{ InCharacterId };
return Call(STR("SetSkinAppliedCharacterId"), p);
}
auto SetSecurityPoliceTargetPlayerId(FGuid_ PlayerId) -> bool
{
struct P { FGuid_ PlayerId; } p{ PlayerId };
return Call(STR("SetSecurityPoliceTargetPlayerId"), p);
}
auto SetInvaderData(EPalInvaderType_ InvaderType, FGuid_ InBaseCampId) -> bool
{
struct P { EPalInvaderType_ InvaderType; FGuid_ InBaseCampId; } p{ InvaderType, InBaseCampId };
return Call(STR("SetInvaderData"), p);
}
auto SetDisableNaturalUpdate(FName Key, bool Disable) -> bool
{
struct P { FName Key; bool Disable; } p{ Key, Disable };
return Call(STR("SetDisableNaturalUpdate"), p);
}
auto SetDisableNaturalHealing(FName Key, bool Disable) -> bool
{
struct P { FName Key; bool Disable; } p{ Key, Disable };
return Call(STR("SetDisableNaturalHealing"), p);
}
auto NaturalUpdateSaveParameter(EPalCharacterNaturalUpdateType_ Type) -> bool
{
struct P { EPalCharacterNaturalUpdateType_ Type; } p{ Type };
return Call(STR("NaturalUpdateSaveParameter"), p);
}
auto SetForcePartBreak() -> bool { return CallVoid(STR("SetForcePartBreak")); }
// ---- medical bed / misc actions -----------------------------------
auto StartRecuperatingInMedicalBed() -> bool { return CallVoid(STR("StartRecuperatingInMedicalBed")); }
auto RecuperateInMedicalBed() -> bool { return CallVoid(STR("RecuperateInMedicalBed")); }
auto EndRecuperatingInMedicalBed() -> bool { return CallVoid(STR("EndRecuperatingInMedicalBed")); }
auto StartRemainderOfLifeTimer() -> bool { return CallVoid(STR("StartRemainderOfLifeTimer")); }
};
}

110
Api/Pal/PalTypes.hpp Normal file
View File

@@ -0,0 +1,110 @@
#pragma once
#include <cstdint>
namespace PalUIExtension::Api::Pal
{
struct FGuid_ { uint32_t A{}, B{}, C{}, D{}; };
struct FFixedPoint64_ { int64_t Value{}; };
struct FPalContainerId_ { FGuid_ ID{}; };
using EPalTribeID_ = uint16_t;
using EPalWazaID_ = uint16_t;
using EPalPassiveSkillEffectType_ = uint8_t;
enum class EPalStatusPhysicalHealthType_ : uint8_t
{
Healthful = 0, MinorInjury = 1, Severe = 2,
Dying = 3, DeadBody = 4, CloudCemetery = 5,
};
enum class EPalGroupType_ : uint8_t
{
Undefined = 0, Neutral = 1, Organization = 2, IndependentGuild = 3,
Guild = 4, ResidentEnemy = 5, RaidBoss = 6,
};
enum class EPalArenaRank_ : uint8_t
{
Bronze = 0, Silver = 1, Gold = 2, Platinum = 3,
Diamond = 4, Master = 5, Legend = 6,
};
enum class EPalGenusCategoryType_ : uint8_t
{
None = 0, Humanoid = 1, FourLegged = 2, Dragon = 3,
Fish = 4, Bird = 5, Monster = 6, Other = 7,
};
enum class EPalExpCalcType_ : uint8_t { None = 0, Build = 1, Craft = 2 };
enum class EPalSizeType_ : uint8_t
{
None = 0, XS = 1, S = 2, M = 3, L = 4, XL = 5,
};
enum class EPalStatusHungerType_ : uint8_t
{
Default = 0, Hunger = 1, Starvation = 2,
};
enum class EPalWeaponType_ : uint8_t
{
None = 0, ThrowObject = 1, Handgun = 2, AssaultRifle = 3, Shotgun = 4,
SniperRifle = 5, RocketLauncher = 6, MeleeWeapon = 7, Bow = 8,
BowGun = 9, FlameThrower = 10, GatlingGun = 11, Liftup = 12,
LaserRifle = 13, MissileLauncher = 14, GrenadeLauncher = 15,
Katana = 16, MetalDetector = 17, GiantClub = 18, FishingRod = 19,
LaserMiningTool = 20, MAX = 21,
};
enum class EPalBaseCampWorkerEventType_ : uint8_t
{
None = 0, Escape = 1, OverworkDeath = 2, Sick = 3, DodgeWork = 4,
DodgeWork_Short = 5, DodgeWork_Sleep = 6, EatTooMuch = 7,
Trantrum = 8, FightWithFriend = 9, TurnFoodBox = 10, DestroyBuilding = 11,
};
enum class EPalBaseCampWorkerSickType_ : uint8_t
{
None = 0, Cold = 1, Sprain = 2, Bulimia = 3, GastricUlcer = 4,
Fracture = 5, Weakness = 6, DepressionSprain = 7, DisturbingElement = 8,
};
enum class EPalInvaderType_ : uint8_t { None = 0, InvaderEnemy = 1, VisitorNPC = 2 };
enum class EPalFoodStatusEffectType_ : uint8_t
{
None = 0, Attack = 1, Defense = 2, HungerResist = 3, SANResist = 4,
WorkSpeed = 5, Regene_Hp = 6, MaxSP = 7, Exp_Increase = 8,
LeanBackInvalid = 9, KnockbackInvalid = 10, ExplosionResist = 11,
LeanBackAndKnockbackInvalid = 12, FullStomachKeep = 13,
};
enum class EPalCharacterNaturalUpdateType_ : uint8_t
{
Player = 0, OtomoPal = 1, BaseCampPal = 2,
};
enum class EPalOrganizationType_ : uint8_t
{
None = 0, Guild = 1, Police = 2, City = 3, Brigade = 4,
TeamBlackHunter = 5, TeamFireCult = 6, TeamWelfareGroup = 7,
Ally = 8, MAX = 9,
};
enum class EPalBattleBGMType_ : uint8_t
{
None = 0, Cute = 1, Cool = 2, Strong = 3, Human_Common = 4,
Human_Villain = 5, FieldBoss_Tier_1 = 6, FieldBoss_Tier_2 = 7,
FieldBoss_Tier_3 = 8, FieldBoss_Tier_4 = 9, FieldBoss_Tier_5 = 10,
Predator = 11, EyeofCthulhu = 12, WildlifeSanctuaryDrone = 13,
Machine = 14, Legend = 15, KingWhale = 16, RaidBoss = 17,
TowerBoss = 18, RaidBoss_KingBahamut = 19, RaidBoss_DarkMechaDragon = 20,
RaidBoss_MoonLord = 21, RaidBoss_LegendDeer = 22, RaidBoss_LegendDeer2 = 23,
WorldTreeDragon = 24,
};
}

View File

@@ -0,0 +1,155 @@
#pragma once
#include <optional>
#include <Unreal/FText.hpp>
#include <Unreal/NameTypes.hpp>
#include <Api/UnrealWrapper.hpp>
#include "PalIndividual.hpp" // EPalElementType_
namespace PalUIExtension::Api::Pal
{
class PalUIStatusModel : public UnrealWrapper
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/Pal.PalUIStatusModel"); }
static auto DebugTag() -> const TCHAR* { return STR("PalUIStatusModel"); }
PalUIStatusModel() = default;
explicit PalUIStatusModel(UObject* Obj) : UnrealWrapper(Obj, ClassOf<PalUIStatusModel>(), DebugTag()) {}
// Count of pals currently loaded into the model -- "pal number".
auto GetDisplayPalNum() -> std::optional<uint8>
{
return CallForReturn<uint8>(STR("GetDisplayPalNum"));
}
auto GetDisplayPalHandle(uint8 Index) -> UObject*
{
struct P { uint8 Index; UObject* ReturnValue; } p{ Index, nullptr };
auto Result = CallForReturn(STR("GetDisplayPalHandle"), p, &P::ReturnValue);
return (Result && *Result) ? *Result : nullptr;
}
auto GetNowSelectedIndex() -> std::optional<uint8>
{
return CallForReturn<uint8>(STR("GetNowSelectedIndex"));
}
auto ChangeIndex(uint8 Index) -> bool
{
struct P { uint8 Index; } p{ Index };
return Call(STR("ChangeIndex"), p);
}
auto OnClickedPalIcon(int32 Index) -> bool
{
struct P { int32 Index; } p{ Index };
return Call(STR("OnClickedPalIcon"), p);
}
// ---- extended: mechanical Index -> stat getters ----------------
auto GetDisplayLevel(int32 Index) -> std::optional<int32>
{
struct P { int32 Index; int32 ReturnValue; } p{ Index, 0 };
return CallForReturn(STR("GetDisplayLevel"), p, &P::ReturnValue);
}
auto GetDisplaySpeed(int32 Index) -> std::optional<int32>
{
struct P { int32 Index; int32 ReturnValue; } p{ Index, 0 };
return CallForReturn(STR("GetDisplaySpeed"), p, &P::ReturnValue);
}
auto GetDisplayMeleeAttack(int32 Index) -> std::optional<int32>
{
struct P { int32 Index; int32 ReturnValue; } p{ Index, 0 };
return CallForReturn(STR("GetDisplayMeleeAttack"), p, &P::ReturnValue);
}
auto GetDisplayShotAttack(int32 Index) -> std::optional<int32>
{
struct P { int32 Index; int32 ReturnValue; } p{ Index, 0 };
return CallForReturn(STR("GetDisplayShotAttack"), p, &P::ReturnValue);
}
auto GetDisplayDefence(int32 Index) -> std::optional<int32>
{
struct P { int32 Index; int32 ReturnValue; } p{ Index, 0 };
return CallForReturn(STR("GetDisplayDefence"), p, &P::ReturnValue);
}
auto GetDisplayEatingHabits(int32 Index) -> std::optional<int32>
{
struct P { int32 Index; int32 ReturnValue; } p{ Index, 0 };
return CallForReturn(STR("GetDisplayEatingHabits"), p, &P::ReturnValue);
}
auto GetDisplayNowExp(int32 Index) -> std::optional<int32>
{
struct P { int32 Index; int32 ReturnValue; } p{ Index, 0 };
return CallForReturn(STR("GetDisplayNowExp"), p, &P::ReturnValue);
}
auto GetDisplayNextExp(int32 Index) -> std::optional<int32>
{
struct P { int32 Index; int32 ReturnValue; } p{ Index, 0 };
return CallForReturn(STR("GetDisplayNextExp"), p, &P::ReturnValue);
}
auto GetDisplayHunger(int32 Index) -> std::optional<float>
{
struct P { int32 Index; float ReturnValue; } p{ Index, 0.f };
return CallForReturn(STR("GetDisplayHunger"), p, &P::ReturnValue);
}
auto GetDisplayMaxHunger(int32 Index) -> std::optional<float>
{
struct P { int32 Index; float ReturnValue; } p{ Index, 0.f };
return CallForReturn(STR("GetDisplayMaxHunger"), p, &P::ReturnValue);
}
auto GetDisplayElementType1(int32 Index) -> std::optional<EPalElementType_>
{
struct P { int32 Index; EPalElementType_ ReturnValue; } p{ Index, EPalElementType_::None };
return CallForReturn(STR("GetDisplayElementType1"), p, &P::ReturnValue);
}
auto GetDisplayElementType2(int32 Index) -> std::optional<EPalElementType_>
{
struct P { int32 Index; EPalElementType_ ReturnValue; } p{ Index, EPalElementType_::None };
return CallForReturn(STR("GetDisplayElementType2"), p, &P::ReturnValue);
}
// Raw byte -- see class comment. Do not assume ordering/meaning.
auto GetDisplayPhysicalHealthTypeRaw(int32 Index) -> std::optional<uint8>
{
struct P { int32 Index; uint8 ReturnValue; } p{ Index, 0 };
return CallForReturn(STR("GetDisplayPhysicalHealthType"), p, &P::ReturnValue);
}
// ---- extended: FText / FString getters -------------------------
auto GetDisplayDefaultName(int32 Index) -> std::optional<FText>
{
struct P { int32 Index; FText ReturnValue; } p{ Index, {} };
return CallForReturn(STR("GetDisplayDefaultName"), p, &P::ReturnValue);
}
auto GetDisplayCoopActionName(int32 Index) -> std::optional<FText>
{
struct P { int32 Index; FText ReturnValue; } p{ Index, {} };
return CallForReturn(STR("GetDisplayCoopActionName"), p, &P::ReturnValue);
}
auto GetDisplayActiveSkillName(int32 Index, int32 SkillIndex) -> std::optional<FText>
{
struct P { int32 Index; int32 SkillIndex; FText ReturnValue; } p{ Index, SkillIndex, {} };
return CallForReturn(STR("GetDisplayActiveSkillName"), p, &P::ReturnValue);
}
auto GetDisplayNickName(int32 Index) -> std::optional<FString>
{
struct P { int32 Index; FString OutName; } p{ Index, {} };
if (!Call(STR("GetDisplayNickName"), p)) return std::nullopt;
return p.OutName;
}
auto ChangeNickname(int32 Index, const TCHAR* NewNickName) -> bool
{
struct P { int32 Index; FString NewNickName; } p{ Index, FString(NewNickName) };
return Call(STR("ChangeNickname"), p);
}
};
}

123
Api/Pal/PalUtility.hpp Normal file
View File

@@ -0,0 +1,123 @@
#pragma once
#include <cstdint>
#include <optional>
#include <unordered_map>
#include <Reflection/UnrealObjectRef.hpp>
#include <Api/UnrealWrapper.hpp>
#include "PalDatabaseCharacterParameter.hpp"
namespace PalUIExtension::Api::Pal
{
class PalUtility : public UnrealWrapper
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/Pal.PalUtility"); }
static auto CDOPath() -> const TCHAR* { return STR("/Script/Pal.Default__PalUtility"); }
static auto DebugTag() -> const TCHAR* { return STR("PalUtility"); }
static auto Instance() -> PalUtility&
{
static PalUtility Singleton{};
return Singleton;
}
// ---- static getters -----------------------------------------
//
// Add more here as you need them -- see the class comment for
// the one-line shape.
auto GetDatabaseCharacterParameter(UObject* WorldContextObject) -> PalDatabaseCharacterParameter
{
return GetSingleton<PalDatabaseCharacterParameter>(
STR("GetDatabaseCharacterParameter"), WorldContextObject);
}
auto ZukanIndexFor(FName CharacterId, UObject* WorldContextObject) -> std::optional<int32>
{
const uint64_t Key = NameKey(CharacterId);
if (auto It = s_zukan_cache.find(Key); It != s_zukan_cache.end())
{
if (It->second == kNoZukanEntry) return std::nullopt;
return It->second;
}
PalDatabaseCharacterParameter DB = GetDatabaseCharacterParameter(WorldContextObject);
if (!DB)
{
return std::nullopt;
}
auto Index = DB.GetZukanIndexResolved(CharacterId);
s_zukan_cache.emplace(Key, Index.value_or(kNoZukanEntry));
return Index;
}
static auto ClearZukanCache() -> void { s_zukan_cache.clear(); }
auto NocturnalFor(FName CharacterId, UObject* WorldContextObject) -> std::optional<bool>
{
const uint64_t Key = NameKey(CharacterId);
if (auto It = s_nocturnal_cache.find(Key); It != s_nocturnal_cache.end())
{
return It->second != 0;
}
PalDatabaseCharacterParameter DB = GetDatabaseCharacterParameter(WorldContextObject);
if (!DB) return std::nullopt;
const FName RowName = PalDatabaseCharacterParameter::ResolveBaseRowName(CharacterId);
auto Nocturnal = DB.GetNocturnal(RowName);
if (!Nocturnal.has_value()) return std::nullopt;
s_nocturnal_cache.emplace(Key, *Nocturnal ? uint8_t{ 1 } : uint8_t{ 0 });
return Nocturnal;
}
static auto ClearNocturnalCache() -> void { s_nocturnal_cache.clear(); }
private:
PalUtility() : UnrealWrapper(ResolveCDO(), ClassOf<PalUtility>(), DebugTag()) {}
static constexpr int32 kNoZukanEntry = -1;
static inline std::unordered_map<uint64_t, int32> s_zukan_cache{};
static inline std::unordered_map<uint64_t, uint8_t> s_nocturnal_cache{};
static auto NameKey(FName N) -> uint64_t
{
return (static_cast<uint64_t>(N.GetComparisonIndex().ToUnstableInt()) << 32)
| static_cast<uint64_t>(static_cast<uint32_t>(N.GetNumber()));
}
PalUtility(const PalUtility&) = delete;
auto operator=(const PalUtility&) -> PalUtility & = delete;
static auto ResolveCDO() -> UObject*
{
static Reflection::UnrealObjectRef Ref{ CDOPath(), DebugTag() };
return Ref.Get();
}
auto CallStaticObjectGetter(const TCHAR* FunctionName, UObject* WorldContextObject) -> UObject*
{
struct P { UObject* WorldContextObject; UObject* ReturnValue; } p{ WorldContextObject, nullptr };
auto Result = CallForReturn(FunctionName, p, &P::ReturnValue);
return (Result && *Result) ? *Result : nullptr;
}
template <typename T>
auto GetSingleton(const TCHAR* FunctionName, UObject* WorldContextObject) -> T
{
UObject* Obj = CallStaticObjectGetter(FunctionName, WorldContextObject);
return Obj ? T{ Obj } : T{};
}
};
}

284
Api/PalBoxPalListWidget.hpp Normal file
View File

@@ -0,0 +1,284 @@
#pragma once
#include <optional>
#include <Unreal/FText.hpp>
#include <Unreal/NameTypes.hpp>
#include <Api/UmgTypes.hpp>
#include <Api/UnrealWrapper.hpp>
#include <Api/Widget.hpp>
#include <Api/UserWidget.hpp>
#include <Api/WidgetTree.hpp>
#include <Api/TextBlock.hpp>
#include <Api/CanvasPanel.hpp>
#include <Api/Pal/PalIndividual.hpp>
#include <Api/Pal/PalTypes.hpp>
namespace PalUIExtension::Api
{
class PalBoxPalListWidget : public UserWidget
{
public:
static auto ClassPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/WBP_IngameMenu_PalBox_PalList.WBP_IngameMenu_PalBox_PalList_C");
}
static auto WidgetTreePath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/WBP_IngameMenu_PalBox_PalList.WBP_IngameMenu_PalBox_PalList_C:WidgetTree");
}
static auto DebugTag() -> const TCHAR* { return STR("PalListEntry"); }
PalBoxPalListWidget() = default;
explicit PalBoxPalListWidget(UObject* Obj) : UserWidget(Obj, ClassOf<PalBoxPalListWidget>(), DebugTag()) {}
static auto BindFromSlotPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/WBP_IngameMenu_PalBox_PalList.WBP_IngameMenu_PalBox_PalList_C:BindFromSlot");
}
static auto SetupPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/WBP_IngameMenu_PalBox_PalList.WBP_IngameMenu_PalBox_PalList_C:Setup");
}
static auto UnbindPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/WBP_IngameMenu_PalBox_PalList.WBP_IngameMenu_PalBox_PalList_C:Unbind");
}
static auto SetEmptyPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/WBP_IngameMenu_PalBox_PalList.WBP_IngameMenu_PalBox_PalList_C:SetEmpty");
}
static auto OnUpdateHandleSlotPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/WBP_IngameMenu_PalBox_PalList.WBP_IngameMenu_PalBox_PalList_C:OnUpdateHandleSlot");
}
static auto OnUpdateSkinPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/WBP_IngameMenu_PalBox_PalList.WBP_IngameMenu_PalBox_PalList_C:OnUpdateSkin");
}
static auto UpdateHPPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/WBP_IngameMenu_PalBox_PalList.WBP_IngameMenu_PalBox_PalList_C:UpdateHP");
}
static auto UpdateLevelPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/WBP_IngameMenu_PalBox_PalList.WBP_IngameMenu_PalBox_PalList_C:Update Level");
}
// ==============================================================
// FRAME LAYOUTS
// ==============================================================
// ParmsSize 0x08. Full frame ends 0x3C by my arithmetic.
struct BindFromSlotFrame
{
UObject* TargetSlot; // 0x00 UPalIndividualSlot-ish
uint8 CompilerLocals[0x34]; // 0x08
};
// ParmsSize 0x08. Full frame ends 0x1C.
struct SetupFrame
{
UObject* TargetSlot; // 0x00
uint8 CompilerLocals[0x14]; // 0x08
};
struct UnbindFrame
{
uint8 CompilerLocals[0x30];
};
struct OnUpdateHandleSlotFrame
{
UObject* Slot; // 0x00
UObject* LastHandle; // 0x08
};
// ParmsSize 0x08 (FName). Full frame ends 0x62.
struct OnUpdateSkinFrame
{
FName NewSkinName; // 0x00
uint8 CompilerLocals[0x5A]; // 0x08
};
// ParmsSize 0x10. Full frame ends 0x158
struct UpdateHPFrame
{
Pal::FFixedPoint64_ nowHP; // 0x00
Pal::FFixedPoint64_ nowMaxHP; // 0x08
uint8 CompilerLocals[0x148]; // 0x10
};
// ParmsSize 0x10. Note DOUBLE, not FFixedPoint64
struct UpdateHungerFrame
{
double nowHunger; // 0x00
double nowMaxHunger; // 0x08
uint8 CompilerLocals[0x64]; // 0x10
};
// ParmsSize 0x04. Full frame ends 0x38.
struct UpdateLevelFrame
{
int32 NewLevel; // 0x00
uint8 CompilerLocals[0x38]; // 0x04
};
// ParmsSize 0x04. Full frame ends 0x39.
struct UpdateFavoriteFrame
{
int32 NewIndex; // 0x00
uint8 CompilerLocals[0x35]; // 0x04
};
// ParmsSize 0x10 (FString). Full frame ends 0x28.
struct UpdateNicknameFrame
{
FString NewNickName; // 0x00
uint8 CompilerLocals[0x18]; // 0x10
};
struct SetEnableDragDropFrame { bool bIsEnable; };
// EnumProperty at 0x08 with one byte before the frame ends.
struct DraggedButtonEventFrame
{
UObject* Widget; // 0x00
uint8 PressType; // 0x08
};
// ==============================================================
// CALLS
// ==============================================================
auto BindFromSlot(UObject* Slot) -> bool
{
BindFromSlotFrame p{ Slot, {} };
return Call(STR("BindFromSlot"), p);
}
auto Setup(UObject* Slot) -> bool
{
SetupFrame p{ Slot, {} };
return Call(STR("Setup"), p);
}
auto Unbind() -> bool
{
UnbindFrame p{};
return Call(STR("Unbind"), p);
}
auto SetEmpty() -> bool { return CallVoid(STR("SetEmpty")); }
auto SetEnableDragDrop(bool bIsEnable) -> bool
{
SetEnableDragDropFrame p{ bIsEnable };
return Call(STR("SetEnableDragDrop"), p);
}
auto SetLevel(int32 NewLevel) -> bool
{
UpdateLevelFrame p{ NewLevel, {} };
return Call(STR("Update Level"), p); // the space is real
}
auto SetFavorite(int32 NewIndex) -> bool
{
UpdateFavoriteFrame p{ NewIndex, {} };
return Call(STR("UpdateFavorite"), p);
}
auto UpdateHP(Pal::FFixedPoint64_ NowHP, Pal::FFixedPoint64_ NowMaxHP) -> bool
{
UpdateHPFrame p{ NowHP, NowMaxHP, {} };
return Call(STR("UpdateHP"), p);
}
auto UpdateHunger(double NowHunger, double NowMaxHunger) -> bool
{
UpdateHungerFrame p{ NowHunger, NowMaxHunger, {} };
return Call(STR("UpdateHunger"), p);
}
auto UpdateSkin(FName NewSkinName) -> bool
{
OnUpdateSkinFrame p{ NewSkinName, {} };
return Call(STR("OnUpdateSkin"), p);
}
static auto SlotHandle(UObject* Slot) -> UObject*
{
if (!Slot) return nullptr;
Pal::LooseObject S{ Slot, STR("PalIndividualSlot") };
return S.CallForReturn<UObject*>(STR("GetHandle")).value_or(nullptr);
}
static auto SlotIsEmpty(UObject* Slot) -> std::optional<bool>
{
if (!Slot) return std::nullopt;
Pal::LooseObject S{ Slot, STR("PalIndividualSlot") };
return S.CallForReturn<bool>(STR("IsEmpty"));
}
static auto SpeciesFromSlot(UObject* Slot) -> std::optional<FName>
{
return Pal::PalIndividual::CharacterIdFromHandle(SlotHandle(Slot));
}
auto BoundSlot() const -> UObject* { return BoundObject(STR("TargetSlot")); }
auto BoundHandle() const -> UObject* { return SlotHandle(BoundSlot()); }
// ==============================================================
// BOUND WIDGETS (promoted variables -- property read, no walk)
// ==============================================================
auto TextHP() const -> TextBlock { return WrapText(STR("Text_HP")); }
auto TextMaxHP() const -> TextBlock { return WrapText(STR("Text_MaxHP")); }
auto TextLevel() const -> TextBlock { return WrapText(STR("Text_LevelValue")); }
auto TextNickName() const -> TextBlock { return WrapText(STR("Text_NickName")); }
auto TextLvLabel() const -> TextBlock { return WrapText(STR("BP_PalTextBlock_LvText")); }
auto CanvasRoot() const -> CanvasPanel { return WrapCanvas(STR("CanvasPanel_119")); }
auto CanvasSelectFrame() const -> CanvasPanel { return WrapCanvas(STR("PalSelect_BaseTri")); }
auto GaugeHP() const -> UObject* { return BoundObject(STR("Gauge_HP")); }
auto GaugeHunger() const -> UObject* { return BoundObject(STR("Gauge_Hunger")); }
auto ImageHPBar() const -> UObject* { return BoundObject(STR("Image_HP_Bar")); }
auto ImageHungerBar() const -> UObject* { return BoundObject(STR("Image_Huger_Bar")); } // engine spelling
auto ImageCheck() const -> UObject* { return BoundObject(STR("Image_Check")); }
auto ImageLocked() const -> UObject* { return BoundObject(STR("Image_Icon_Locked")); }
auto ImageListBar() const -> UObject* { return BoundObject(STR("Image_Pal_List_Bar")); }
auto ImageEmpty() const -> UObject* { return BoundObject(STR("Empty")); }
auto PalIcon() const -> UObject* { return BoundObject(STR("PalIcon")); }
auto RetainerBox() const -> UObject* { return BoundObject(STR("RetainerBox_0")); }
auto InvisibleButton() const -> UObject* { return BoundObject(STR("WBP_PalInvisibleButton")); }
auto DynamicMaterial() const -> UObject* { return BoundObject(STR("dynamicMaterial")); }
v auto AnimFocus() const -> UObject* { return BoundObject(STR("Anm_Focus")); }
auto AnimNormal() const -> UObject* { return BoundObject(STR("Anm_Normal")); }
auto AnimNormalToFocus() const -> UObject* { return BoundObject(STR("Anm_NormalToFocus")); }
auto AnimPalAlive() const -> UObject* { return BoundObject(STR("Anm_Pal_Alive")); }
auto AnimPalDead() const -> UObject* { return BoundObject(STR("Anm_Pal_Dead")); }
// ---- plain properties ----------------------------------------
auto IsDragDropEnabled() const -> std::optional<bool>
{
return PropertyBoolValue(STR("bEnableDragDrop"));
}
private:
auto WrapCanvas(const TCHAR* Name) const -> CanvasPanel
{
UObject* O = BoundObject(Name);
return O ? CanvasPanel{ O } : CanvasPanel{};
}
auto WrapText(const TCHAR* Name) const -> TextBlock
{
UObject* O = BoundObject(Name);
return O ? TextBlock{ O } : TextBlock{};
}
protected:
PalBoxPalListWidget(UObject* Obj, UClass* Expected, const TCHAR* Tag)
: UserWidget(Obj, Expected, Tag) {}
};
}

224
Api/PalBoxWidget.hpp Normal file
View File

@@ -0,0 +1,224 @@
#pragma once
#include <optional>
#include <vector>
#include <Unreal/NameTypes.hpp>
#include "UnrealWrapper.hpp"
#include "UmgTypes.hpp"
#include "Widget.hpp"
#include "UserWidget.hpp"
#include "WidgetTree.hpp"
#include "CanvasPanel.hpp"
#include "SizeBox.hpp"
#include "TextBlock.hpp"
#include "PalDetailWidget.hpp"
#include "PalBoxPalListWidget.hpp"
namespace PalUIExtension::Api
{
class PalBoxWidget : public UserWidget
{
public:
static auto ClassPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/"
"WBP_IngameMenu_PalBox.WBP_IngameMenu_PalBox_C");
}
static auto DebugTag() -> const TCHAR* { return STR("PalBox"); }
PalBoxWidget() = default;
explicit PalBoxWidget(UObject* Obj) : UserWidget(Obj, ClassOf<PalBoxWidget>(), DebugTag()) {}
auto PalDetail() const -> PalDetailWidget
{
UObject* O = BoundObject(STR("WBP_IngameMenu_PalBox_PalDetail"));
return O ? PalDetailWidget{ O } : PalDetailWidget{};
}
static constexpr int32 kPartySlotCount = 5;
auto PartySlot(int32 Index) const -> PalBoxPalListWidget
{
if (Index < 0 || Index >= kPartySlotCount) return PalBoxPalListWidget{};
const TCHAR* Names[kPartySlotCount] = {
STR("WBP_IngameMenu_PalBox_PalList_0"),
STR("WBP_IngameMenu_PalBox_PalList_1"),
STR("WBP_IngameMenu_PalBox_PalList_2"),
STR("WBP_IngameMenu_PalBox_PalList_3"),
STR("WBP_IngameMenu_PalBox_PalList_4"),
};
UObject* O = BoundObject(Names[Index]);
return O ? PalBoxPalListWidget{ O } : PalBoxPalListWidget{};
}
// Convenience for "do something to every party tile."
auto PartySlots() const -> std::vector<PalBoxPalListWidget>
{
std::vector<PalBoxPalListWidget> Out;
Out.reserve(kPartySlotCount);
for (int32 i = 0; i < kPartySlotCount; ++i)
{
PalBoxPalListWidget W = PartySlot(i);
if (W.IsValid()) Out.push_back(W);
}
return Out;
}
auto BoxPalList() const -> UObject* { return BoundObject(STR("WBP_BoxPalList")); }
auto BaseCampPalList() const -> UObject* { return BoundObject(STR("WBP_BaseCampPalList")); }
// ==============================================================
// INJECTION ANCHORS
// ==============================================================
auto CanvasPalBox() const -> CanvasPanel { return WrapCanvas(STR("Canvas_PalBox")); }
auto CanvasPalCamp() const -> CanvasPanel { return WrapCanvas(STR("Canvas_PalCamp")); }
auto FindCanvas(const TCHAR* Name) -> CanvasPanel { return Child<CanvasPanel>(Name); }
auto SizeBoxPresetButton() const -> SizeBox
{
UObject* O = BoundObject(STR("SizeBox_PresetButton"));
return O ? SizeBox{ O } : SizeBox{};
}
// ==============================================================
// BOUND WIDGETS
// ==============================================================
auto TextBaseCampName() const -> TextBlock
{
UObject* O = BoundObject(STR("Text_BaseCampName"));
return O ? TextBlock{ O } : TextBlock{};
}
auto CloseButton() const -> UObject* { return BoundObject(STR("CloseButton")); }
auto PresetButton() const -> UObject* { return BoundObject(STR("WBP_CommonButton_Preset")); }
auto ReturnButton() const -> UObject* { return BoundObject(STR("WBP_CommonButton_Return")); }
auto MenuButton() const -> UObject* { return BoundObject(STR("WBP_Menu_btn")); }
auto ImageFrame() const -> UObject* { return BoundObject(STR("Image_Frame")); }
// ==============================================================
// STATE
// ==============================================================
auto IsDragDropEnabled() const -> std::optional<bool>
{
return PropertyBoolValue(STR("bIsEnableDragDropControl"));
}
auto PartyPalDetails() const -> std::vector<UObject*>
{
std::vector<UObject*> Out;
auto* Arr = TryPropertyPtr<TArrayRead_<UObject*>>(STR("PartyPalDetails"));
if (!Arr) return Out;
if (!Arr->IsPlausible(kMaxPlausibleEntries))
{
Output::send<LogLevel::Warning>(
STR("[{}] PartyPalDetails does not look like a live TArray (Num={}, Max={})\n"),
m_tag, Arr->Num, Arr->Max);
return Out;
}
Out.reserve(static_cast<size_t>(Arr->Num));
for (int32 i = 0; i < Arr->Num; ++i)
{
if (UObject* Entry = Arr->Data[i]) Out.push_back(Entry);
}
return Out;
}
// ==============================================================
// HOOK TARGETS
// ==============================================================
static auto DisplayPalDetailPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/"
"WBP_IngameMenu_PalBox.WBP_IngameMenu_PalBox_C:Display Pal Detail");
}
static auto SetupPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/"
"WBP_IngameMenu_PalBox.WBP_IngameMenu_PalBox_C:Setup");
}
static auto SetupPartyPalPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/"
"WBP_IngameMenu_PalBox.WBP_IngameMenu_PalBox_C:Setup Party Pal");
}
static auto SetBaseCampPalListPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/"
"WBP_IngameMenu_PalBox.WBP_IngameMenu_PalBox_C:SetBaseCampPalList");
}
static auto SetEnableDragDropPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/"
"WBP_IngameMenu_PalBox.WBP_IngameMenu_PalBox_C:SetEnableDragDrop");
}
static auto OnClickedPartySlotInternalPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/"
"WBP_IngameMenu_PalBox.WBP_IngameMenu_PalBox_C:OnClickedPartySlotInternal");
}
static auto OnHoveredPartySlotInternalPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/"
"WBP_IngameMenu_PalBox.WBP_IngameMenu_PalBox_C:OnHoveredPartySlot_Internal");
}
static auto OnInitializedPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/"
"WBP_IngameMenu_PalBox.WBP_IngameMenu_PalBox_C:OnInitialized");
}
static auto PreConstructPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/"
"WBP_IngameMenu_PalBox.WBP_IngameMenu_PalBox_C:PreConstruct");
}
// ---- frames --------------------------------------------------
struct DisplayPalDetailFrame
{
UObject* Slot; // 0x00
uint8 slotType; // 0x08
};
struct SetupFrame { UObject* Parameter; }; // 0x00, ParmsSize 0x08
struct SetEnableDragDropFrame { bool bIsEnableDragDrop; };
struct SlotFrame { UObject* Slot; };
struct SetBaseCampPalListFrame
{
UObject** Data;
int32 Num;
int32 Max;
};
static_assert(sizeof(SetBaseCampPalListFrame) == 16,
"SetBaseCampPalList frame is 16 bytes on the dumped build");
private:
static constexpr int32 kMaxPlausibleEntries = 1024;
auto WrapCanvas(const TCHAR* Name) const -> CanvasPanel
{
UObject* O = BoundObject(Name);
return O ? CanvasPanel{ O } : CanvasPanel{};
}
protected:
PalBoxWidget(UObject* Obj, UClass* Expected, const TCHAR* Tag)
: UserWidget(Obj, Expected, Tag) {}
};
}

View File

@@ -0,0 +1,155 @@
#pragma once
#include <optional>
#include <vector>
#include <Unreal/UClass.hpp>
#include "UnrealWrapper.hpp"
#include "Widget.hpp"
#include "UserWidget.hpp"
#include "WrapBox.hpp"
namespace PalUIExtension::Api
{
// ==================================================================
// WBP_PalCharacterScrollList_C -- the end of the chain. This is the
// widget that creates, owns and destroys the slot tiles.
//
// PalBoxWidget
// -> WBP_BoxPalList -> WBP_BoxPalListBase -> WBP_BoxPalScrollList
// -> WBP_BaseCampPalList ------------------------> (same class)
// == THIS
// -> MyBlock (UWrapBox)
// -> the tiles
// ==================================================================
class PalCharacterScrollList : public UserWidget
{
public:
static auto ClassPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/CommonWidget/CommonScrollList/"
"WBP_PalCharacterScrollList.WBP_PalCharacterScrollList_C");
}
static auto DebugTag() -> const TCHAR* { return STR("PalCharacterScrollList"); }
PalCharacterScrollList() = default;
explicit PalCharacterScrollList(UObject* Obj) : UserWidget(Obj, ClassOf<PalCharacterScrollList>(), DebugTag()) {}
// ==============================================================
// THE TILES
// ==============================================================
auto TileContainer() const -> WrapBox
{
UObject* O = BoundObject(STR("MyBlock"));
return O ? WrapBox{ O } : WrapBox{};
}
auto Tiles() -> std::vector<UObject*>
{
WrapBox Box = TileContainer();
if (!Box.IsValid()) return {};
return Box.GetChildObjects();
}
auto TileClass() const -> UClass*
{
return PropertyValue<UClass*>(STR("slotWidgetClass")).value_or(nullptr);
}
auto TileClassName() const -> RC::StringType
{
UClass* C = TileClass();
return C ? C->GetFullName() : RC::StringType{ STR("<null>") };
}
// ==============================================================
// HOOK TARGETS
// ==============================================================
static auto CreateSlotWidgetPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/CommonWidget/CommonScrollList/"
"WBP_PalCharacterScrollList.WBP_PalCharacterScrollList_C:CreateSlotWidget");
}
static auto AddSlotButtonToScrollListPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/CommonWidget/CommonScrollList/"
"WBP_PalCharacterScrollList.WBP_PalCharacterScrollList_C:AddSlotButtonToScrollList");
}
static auto BindButtonEventsPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/CommonWidget/CommonScrollList/"
"WBP_PalCharacterScrollList.WBP_PalCharacterScrollList_C:BindButtonEvents");
}
static auto OnUpdateSlotBindedPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/CommonWidget/CommonScrollList/"
"WBP_PalCharacterScrollList.WBP_PalCharacterScrollList_C:OnUpdateSlot_Binded");
}
static auto OnSlotSyncedInternalPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/CommonWidget/CommonScrollList/"
"WBP_PalCharacterScrollList.WBP_PalCharacterScrollList_C:OnSlotSynced_Internal");
}
static auto OnEmptySlotInternalPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/CommonWidget/CommonScrollList/"
"WBP_PalCharacterScrollList.WBP_PalCharacterScrollList_C:OnEmptySlot_Internal");
}
static auto AddCharacterSlotPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/CommonWidget/CommonScrollList/"
"WBP_PalCharacterScrollList.WBP_PalCharacterScrollList_C:AddCharacterSlot");
}
static auto ClearAllChildrenPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/CommonWidget/CommonScrollList/"
"WBP_PalCharacterScrollList.WBP_PalCharacterScrollList_C:ClearAllChildren");
}
static auto ClearInnerChildrenPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/CommonWidget/CommonScrollList/"
"WBP_PalCharacterScrollList.WBP_PalCharacterScrollList_C:ClearInnnerChildren");
}
// ---- frames --------------------------------------------------
struct CreateSlotWidgetFrame
{
UObject* TargetSlot;
UObject* createdSlotButton;
};
// Covers AddSlotButtonToScrollList (createdSlotButton),
// BindButtonEvents (createdSlotButton), OnUpdateSlot_Binded
// (SlotButton), OnSlotSynced_Internal (SelfButton),
// OnEmptySlot_Internal (SlotButton), OnHovered_Internal /
// OnUnhovered_Internal / OnFocused_Internal /
// OnUnfocused_Internal (Widget), and "On Right Clicked Internal"
// (ButtonBase). Eleven functions, one shape -- a single tile
// pointer at 0x00. Naming it once is the honest version.
struct TileFrame { UObject* Tile; };
struct AddCharacterSlotFrame { UObject* TargetSlot; };
struct TileClickFrame
{
UObject* Widget; // 0x00
uint8 PressType; // 0x08
};
struct AddCharacterSlotsFrame
{
UObject** Data;
int32 Num;
int32 Max;
};
protected:
PalCharacterScrollList(UObject* Obj, UClass* Expected, const TCHAR* Tag) : UserWidget(Obj, Expected, Tag) {}
};
}

View File

@@ -0,0 +1,179 @@
#pragma once
#include <optional>
#include "UnrealWrapper.hpp"
#include "UmgTypes.hpp"
#include "Widget.hpp"
#include "UserWidget.hpp"
#include "WidgetTree.hpp"
#include "CanvasPanel.hpp"
#include "Overlay.hpp"
#include "TextBlock.hpp"
namespace PalUIExtension::Api
{
class PalCommonCharacterSlot : public UserWidget
{
public:
static auto ClassPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/Thumbnails/Character/WBP_PalCommonCharacterSlot.WBP_PalCommonCharacterSlot_C");
}
static auto DebugTag() -> const TCHAR* { return STR("PalCharacterSlot"); }
PalCommonCharacterSlot() = default;
explicit PalCommonCharacterSlot(UObject* Obj)
: UserWidget(Obj, ClassOf<PalCommonCharacterSlot>(), DebugTag()) {}
// ==============================================================
// ANCHORS
// ==============================================================
auto IconOverlay() -> Overlay
{
return Child<Overlay>(STR("Overlay_60"));
}
auto OverlayAwakening() const -> Overlay { return WrapOverlay(STR("Overlay_Awakening")); }
auto OverlayBan() const -> Overlay { return WrapOverlay(STR("Overlay_Ban")); }
auto OverlayDoping() const -> Overlay { return WrapOverlay(STR("Overlay_Doping")); }
auto OverlayRevive() const -> Overlay { return WrapOverlay(STR("Overlay_Revive")); }
auto IconWidget() const -> UObject* { return BoundObject(STR("WBP_PalCommonCharacterIcon")); }
auto ImageWorkIcon() const -> UObject* { return BoundObject(STR("Image_IconPalWork")); }
auto ImageExpedition() const -> UObject* { return BoundObject(STR("Image_Icon_Expedition")); }
auto ImageLocked() const -> UObject* { return BoundObject(STR("Image_Icon_Locked")); }
auto ImageGlobalImport() const -> UObject* { return BoundObject(STR("Image_Icon_GlobalInport")); } // engine spelling
auto ImageSleep() const -> UObject* { return BoundObject(STR("Image_PalSleep")); }
auto ImageMeal() const -> UObject* { return BoundObject(STR("Image_Meal")); }
auto ImageMutant() const -> UObject* { return BoundObject(STR("Image_Mutant")); }
auto ImageRare() const -> UObject* { return BoundObject(STR("Image_Rare")); }
auto ImageStrong() const -> UObject* { return BoundObject(STR("Image_Strong")); }
auto ImageLowHealth() const -> UObject* { return BoundObject(STR("Image_PalLowHealth")); }
auto ImageDying() const -> UObject* { return BoundObject(STR("Image_PalDying")); }
auto ImageBattle() const -> UObject* { return BoundObject(STR("Image_PalBattleImage")); }
auto TextHPPercent() const -> TextBlock { return WrapText(STR("Text_HPPercent")); }
auto TextReviveTimer() const -> TextBlock { return WrapText(STR("Text_ReviveTimer")); }
// ---- state ----------------------------------------------------
auto IsBattleModeDisplayed() const -> std::optional<bool> { return PropertyBoolValue(STR("isBattleModeDisplay")); }
auto IsLowHealthBlinking() const -> std::optional<bool> { return PropertyBoolValue(STR("isLowHealthBlink")); }
auto IsHealthPercentDisplayed()const -> std::optional<bool> { return PropertyBoolValue(STR("isDisplayHealthPercent")); }
auto IsRarityDisplayed() const -> std::optional<bool> { return PropertyBoolValue(STR("isDisplayRarity")); }
auto LastWorkDetail() const -> std::optional<bool> { return PropertyBoolValue(STR("lastWorkDetail")); }
auto IsDopingEffectDisplayed() const -> std::optional<bool> { return PropertyBoolValue(STR("bIsDisplayDopingEffect")); }
auto IsFavoriteMarkDisplayed() const -> std::optional<bool> { return PropertyBoolValue(STR("bIsDisplayFavoriteMark")); }
auto IsAwakeningMarkDisplayed()const -> std::optional<bool> { return PropertyBoolValue(STR("bIsDisplayAwakeningMark")); }
auto CachedLastHPPercent() const -> std::optional<double> { return PropertyValue<double>(STR("chachedLastHPPercent")); } // engine spelling
// ==============================================================
// HOOK TARGETS
// ==============================================================
static auto OnUpdateSlotBindedPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/Thumbnails/Character/WBP_PalCommonCharacterSlot.WBP_PalCommonCharacterSlot_C:On Update Slot Binded");
}
static auto OnInitializedPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/Thumbnails/Character/"
"WBP_PalCommonCharacterSlot.WBP_PalCommonCharacterSlot_C:OnInitialized");
}
static auto OnSetEmptyBindedPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/Thumbnails/Character/WBP_PalCommonCharacterSlot.WBP_PalCommonCharacterSlot_C:On Set Empty Binded");
}
static auto OnNotifyWorkDetailBindedPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/Thumbnails/Character/"
"WBP_PalCommonCharacterSlot.WBP_PalCommonCharacterSlot_C:OnNotifyWorkDetail_Binded");
}
static auto DisplayBanMarkPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/Thumbnails/Character/"
"WBP_PalCommonCharacterSlot.WBP_PalCommonCharacterSlot_C:DisplayBanMark");
}
// ---- frames ---------------------------------------------------
struct UpdateSlotBindedFrame { UObject* TargetSlot; };
struct BoolFrame { bool bValue; };
struct RegisterIconWidgetFrame { UObject* IconWidget; };
struct UpdateFavoriteFrame { int32 NewIndex; };
struct UpdateHPFrame
{
int64 nowHP;
int64 nowMaxHP;
};
// On Set Rarity Binded: two adjacent bools.
struct SetRarityFrame
{
bool IsBoss; // 0x00
bool IsRare; // 0x01
};
private:
auto WrapOverlay(const TCHAR* Name) const -> Overlay
{
UObject* O = BoundObject(Name);
return O ? Overlay{ O } : Overlay{};
}
auto WrapText(const TCHAR* Name) const -> TextBlock
{
UObject* O = BoundObject(Name);
return O ? TextBlock{ O } : TextBlock{};
}
protected:
PalCommonCharacterSlot(UObject* Obj, UClass* Expected, const TCHAR* Tag)
: UserWidget(Obj, Expected, Tag) {}
};
class PalCommonCharacterIcon : public UserWidget
{
public:
static auto ClassPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/Thumbnails/Character/WBP_PalCommonCharacterIcon.WBP_PalCommonCharacterIcon_C");
}
static auto DebugTag() -> const TCHAR* { return STR("PalCharacterIcon"); }
PalCommonCharacterIcon() = default;
explicit PalCommonCharacterIcon(UObject* Obj) : UserWidget(Obj, ClassOf<PalCommonCharacterIcon>(), DebugTag()) {}
auto IconImage() const -> UObject* { return BoundObject(STR("iconImage")); }
auto Throbber() const -> UObject* { return BoundObject(STR("CircularThrobber_96")); }
auto DynamicMaterial() const -> UObject* { return BoundObject(STR("dynamicMaterial")); }
auto IsEmpty() const -> std::optional<bool> { return PropertyBoolValue(STR("IsEmpty")); }
auto SphereMaskRadius() const -> std::optional<double> { return PropertyValue<double>(STR("SphereMaskRadius")); }
static auto OnLoadedPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/Thumbnails/Character/WBP_PalCommonCharacterIcon.WBP_PalCommonCharacterIcon_C:OnLoaded");
}
static auto OnEmptyPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/Thumbnails/Character/WBP_PalCommonCharacterIcon.WBP_PalCommonCharacterIcon_C:OnEmpty");
}
protected:
PalCommonCharacterIcon(UObject* Obj, UClass* Expected, const TCHAR* Tag)
: UserWidget(Obj, Expected, Tag) {}
};
}

View File

@@ -0,0 +1,139 @@
#pragma once
#include <optional>
#include "UnrealWrapper.hpp"
#include "UmgTypes.hpp"
#include "Widget.hpp"
#include "UserWidget.hpp"
#include "WidgetTree.hpp"
#include "CanvasPanel.hpp"
#include "Overlay.hpp"
namespace PalUIExtension::Api
{
// ==================================================================
// WBP_PalCommonCharacterSlotButton_C
// ==================================================================
class PalCommonCharacterSlotButton : public UserWidget
{
public:
static auto ClassPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/Thumbnails/Character/"
"WBP_PalCommonCharacterSlotButton.WBP_PalCommonCharacterSlotButton_C");
}
static auto DebugTag() -> const TCHAR* { return STR("PalSlotButton"); }
PalCommonCharacterSlotButton() = default;
explicit PalCommonCharacterSlotButton(UObject* Obj)
: UserWidget(Obj, ClassOf<PalCommonCharacterSlotButton>(), DebugTag()) {}
// ==============================================================
// NESTED WIDGETS
// ==============================================================
auto IconWidget() const -> UObject* { return BoundObject(STR("WBP_PalCommonCharacterSlot")); }
auto InvisibleButton() const -> UObject* { return BoundObject(STR("WBP_PalInvisibleButton")); }
// ==============================================================
// ANCHORS
// ==============================================================
auto RootCanvas() const -> CanvasPanel { return WrapCanvas(STR("CanvasPanel_36")); }
auto OverlaySelect() const -> Overlay { return WrapOverlay(STR("Overlay_Select")); }
// TODO: Review below and remove or update.
auto IconAnchor() -> Overlay
{
return Child<Overlay>(STR("Overlay_60"),
WidgetTree::EScope::IncludeNestedUserWidgets);
}
auto Check0() const -> UObject* { return BoundObject(STR("Check_0")); }
auto Check1() const -> UObject* { return BoundObject(STR("Check_1")); }
auto Check2() const -> UObject* { return BoundObject(STR("Check_2")); }
auto FocusedFrame() const -> UObject* { return BoundObject(STR("FocusedFrame")); }
auto PushEff() const -> UObject* { return BoundObject(STR("PushEff")); }
// ---- state ----------------------------------------------------
auto IsDisplayingDopingEffect() const -> std::optional<bool> { return PropertyBoolValue(STR("bIsDisplayDopingEffect")); }
auto HasCheckAnimPlayed() const -> std::optional<bool> { return PropertyBoolValue(STR("bCheckAnimPlayed")); }
// ==============================================================
// HOOK TARGETS
// ==============================================================
static auto DestructPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/Thumbnails/Character/"
"WBP_PalCommonCharacterSlotButton.WBP_PalCommonCharacterSlotButton_C:Destruct");
}
static auto RegisterCharacterSlotWidgetPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/Thumbnails/Character/"
"WBP_PalCommonCharacterSlotButton.WBP_PalCommonCharacterSlotButton_C:RegisterCharacterSlotWidget");
}
static auto OnInitializedPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/Thumbnails/Character/"
"WBP_PalCommonCharacterSlotButton.WBP_PalCommonCharacterSlotButton_C:OnInitialized");
}
static auto SetEnablePath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/Thumbnails/Character/"
"WBP_PalCommonCharacterSlotButton.WBP_PalCommonCharacterSlotButton_C:SetEnable");
}
static auto SetSearchResultActivePath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/Thumbnails/Character/"
"WBP_PalCommonCharacterSlotButton.WBP_PalCommonCharacterSlotButton_C:SetSearchResultActive");
}
// ---- frames ---------------------------------------------------
struct WidgetFrame { UObject* Widget; }; // PlayFocusAnim / PlayUnfocusAnim
struct RegisterCharacterSlotWidgetFrame { UObject* characterSlotWidget; };
struct RegisterButtonFrame { UObject* Button; };
struct BoolFrame { bool bValue; }; // SetEnable, SetSearchResultActive, the AnmEvent_* family
// ==============================================================
// CALLS
// ==============================================================
auto SetEnable(bool bIsEnable) -> bool
{
BoolFrame p{ bIsEnable };
return Call(STR("SetEnable"), p);
}
auto SetEnableGreyoutOnly(bool bIsEnable) -> bool
{
BoolFrame p{ bIsEnable };
return Call(STR("SetEnable_GreyoutOnly"), p);
}
auto SetSearchResultActive(bool bActive) -> bool
{
// The parameter is spelled bAvtive in the engine. Dont fix. Lol.
BoolFrame p{ bActive };
return Call(STR("SetSearchResultActive"), p);
}
private:
auto WrapCanvas(const TCHAR* Name) const -> CanvasPanel
{
UObject* O = BoundObject(Name);
return O ? CanvasPanel{ O } : CanvasPanel{};
}
auto WrapOverlay(const TCHAR* Name) const -> Overlay
{
UObject* O = BoundObject(Name);
return O ? Overlay{ O } : Overlay{};
}
protected:
PalCommonCharacterSlotButton(UObject* Obj, UClass* Expected, const TCHAR* Tag)
: UserWidget(Obj, Expected, Tag) {}
};
}

270
Api/PalDetailWidget.hpp Normal file
View File

@@ -0,0 +1,270 @@
#pragma once
#include <optional>
#include <cstring>
#include <Unreal/FText.hpp>
#include <Unreal/NameTypes.hpp>
#include "UmgTypes.hpp"
#include "UnrealWrapper.hpp"
#include "Widget.hpp"
#include "UserWidget.hpp"
#include "WidgetTree.hpp"
#include "TextBlock.hpp"
#include "CanvasPanel.hpp"
#include <Api/Pal/PalIndividual.hpp>
#include <Api/Pal/PalIndividualCharacterHandle.hpp>
#include <Api/Pal/PalIndividualCharacterParameter.hpp>
using namespace PalUIExtension::Api::Pal;
namespace PalUIExtension::Api
{
class PalDetailWidget : public UserWidget
{
public:
static auto ClassPath() -> const TCHAR* {
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/WBP_IngameMenu_PalBox_PalDetail.WBP_IngameMenu_PalBox_PalDetail_C");
}
static auto WidgetTreePath() -> const TCHAR* {
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/WBP_IngameMenu_PalBox_PalDetail.WBP_IngameMenu_PalBox_PalDetail_C:WidgetTree");
}
static auto DebugTag() -> const TCHAR* { return STR("PalDetail"); }
PalDetailWidget() = default;
explicit PalDetailWidget(UObject* Obj) : UserWidget(Obj, ClassOf<PalDetailWidget>(), DebugTag()) {
}
struct GetBindedHandleFrame
{
UObject* targetHandle; // 0x00 out
bool bValidSoftRef; // 0x08
uint8 Pad09[7]; // 0x09
UObject* SoftRefAsObject; // 0x10
UObject* CastAsHandle; // 0x18
bool bCastSucceeded; // 0x20
uint8 Pad21[7]; // 0x21
};
static_assert(sizeof(GetBindedHandleFrame) == 40,
"GetBindedHandle frame is 40 bytes on the dumped build");
struct GetBindedIndividualParameterFrame
{
UObject* IndividualParameter; // 0x00 out
UObject* Handle; // 0x08
UObject* TryGetResult; // 0x10
bool bValid0; // 0x18
bool bValid1; // 0x19
uint8 Pad1A[6]; // 0x1A
};
static_assert(sizeof(GetBindedIndividualParameterFrame) == 32,
"GetBindedIndividualParameter frame is 32 bytes on the dumped build");
auto GetBindedHandle() -> UObject*
{
GetBindedHandleFrame p{};
if (!Call(STR("GetBindedHandle"), p)) return nullptr;
return p.targetHandle;
}
auto GetBindedIndividualParameter() -> UObject*
{
GetBindedIndividualParameterFrame p{};
if (!Call(STR("GetBindedIndividualParameter"), p)) return nullptr;
return p.IndividualParameter;
}
auto CurrentHandle() -> PalIndividualCharacterHandle
{
UObject* H = GetBindedHandle();
return H ? PalIndividualCharacterHandle{ H } : PalIndividualCharacterHandle{};
}
auto CurrentParameter() -> PalIndividualCharacterParameter
{
UObject* P = GetBindedIndividualParameter();
return P ? PalIndividualCharacterParameter{ P } : PalIndividualCharacterParameter{};
}
auto ResolveIdentity() -> PalIdentity
{
if (UObject* Param = GetBindedIndividualParameter())
{
return PalIndividual::IdentifyFromParameter(Param);
}
return PalIndividual::Identify(GetBindedHandle());
}
auto CurrentCharacterId() -> std::optional<FName>
{
if (UObject* Param = GetBindedIndividualParameter())
{
return PalIndividual::CharacterId(Param);
}
return PalIndividual::CharacterIdFromHandle(GetBindedHandle());
}
auto CanvasBase() const -> CanvasPanel { return WrapCanvas(STR("Canvas_Base")); }
auto CanvasBaseToolTip() const -> CanvasPanel { return WrapCanvas(STR("Canvas_Base_ToolTip")); }
auto CanvasInfo() const -> CanvasPanel { return WrapCanvas(STR("Canvas_Info")); }
auto CanvasFood() const -> CanvasPanel { return WrapCanvas(STR("Canvas_Food")); }
auto CanvasPartnerSkill() const -> CanvasPanel { return WrapCanvas(STR("Canvas_PartnerSkill")); }
auto CanvasPotencial() const -> CanvasPanel { return WrapCanvas(STR("CanvasPanel_Potencial")); }
auto CanvasDebuff() const -> CanvasPanel { return WrapCanvas(STR("CanvasPanelDebuff")); }
auto CanvasStatePalWork() const -> CanvasPanel { return WrapCanvas(STR("CanvasPanelStatePalWork")); }
auto TextPalName() const -> TextBlock { return WrapText(STR("Text_PalName")); }
auto TextLevel() const -> TextBlock { return WrapText(STR("Text_LevelValue")); }
auto TextNowHP() const -> TextBlock { return WrapText(STR("Text_NowHP")); }
auto TextMaxHP() const -> TextBlock { return WrapText(STR("Text_MaxHP")); }
auto TextNowHunger() const -> TextBlock { return WrapText(STR("Text_NowHunger")); }
auto TextMaxHunger() const -> TextBlock { return WrapText(STR("Text_MaxHunger")); }
auto TextNowSanity() const -> TextBlock { return WrapText(STR("Text_NowSanity")); }
auto TextMaxSanity() const -> TextBlock { return WrapText(STR("Text_MaxSanity")); }
auto TextAttack() const -> TextBlock { return WrapText(STR("Text_AttackValue")); }
auto TextDefense() const -> TextBlock { return WrapText(STR("Text_DefenseValue")); }
auto TextWorkValue() const -> TextBlock { return WrapText(STR("Text_WorkValue")); }
auto TextWorkTypeName() const -> TextBlock { return WrapText(STR("Text_WorkTypeName")); }
auto TextNextExp() const -> TextBlock { return WrapText(STR("Text_NextExp")); }
auto TextFriendshipRank() const -> TextBlock { return WrapText(STR("Text_FriendshipRank_Num")); }
auto GaugeHP() const -> UObject* { return BoundObject(STR("Gauge_HP")); }
auto GaugeHunger() const -> UObject* { return BoundObject(STR("Gauge_Hunger")); }
auto GaugeExp() const -> UObject* { return BoundObject(STR("Gauge_Exp")); }
auto GaugeFriendship() const -> UObject* { return BoundObject(STR("Gauge_Friendship")); }
auto ImageRare() const -> UObject* { return BoundObject(STR("Image_Rare")); }
auto ImageStrong() const -> UObject* { return BoundObject(STR("Image_Strong")); }
auto ImageMutant() const -> UObject* { return BoundObject(STR("Image_Mutant")); }
auto CharacterIcon() const -> UObject* { return BoundObject(STR("WBP_PalCommonCharacterIcon")); }
auto ElementIcon0() const -> UObject* { return BoundObject(STR("WBP_PalElementIcon")); }
auto ElementIcon1() const -> UObject* { return BoundObject(STR("WBP_PalElementIcon_1")); }
// ---- plain properties ----------------------------------------
auto SavedMaxHunger() const -> std::optional<float> { return PropertyValue<float>(STR("savedMaxHunger")); }
auto SavedMaxSanity() const -> std::optional<float> { return PropertyValue<float>(STR("savedMaxSanity")); }
auto HidePassive() const -> std::optional<bool> { return PropertyValue<bool>(STR("HidePassive")); }
// ---- calls ---------------------------------------------------
auto SetEmpty() -> bool { return CallVoid(STR("SetEmpty")); }
auto SetupPartnerSkillInfo() -> bool { return CallVoid(STR("SetupPartnerSkillInfo")); }
auto ToggleSkillInfo() -> bool { return CallVoid(STR("ToggleSKillInfo")); } // engine spelling
auto OverrideHideTalent() -> bool { return CallVoid(STR("OverrideHideTalent")); }
auto SetVisiblePartnerSkillInfo(bool bVisible) -> bool
{
struct P { bool bVisible; } p{ bVisible };
return Call(STR("SetVisiblePartnerSkillInfo"), p);
}
auto SetVisibleToggleSkillInfoKeyGuide(bool bVisible) -> bool
{
struct P { bool bVisible; } p{ bVisible };
return Call(STR("SetVisibleToggleSkillInfoKeyGuide"), p);
}
auto SetAwakening(bool bAwaked) -> bool
{
struct P { bool bAwaked; } p{ bAwaked };
return Call(STR("SetAwakening"), p);
}
auto SetImportedFlag(bool bImportedFlag) -> bool
{
struct P { bool bImportedFlag; } p{ bImportedFlag };
return Call(STR("SetImportedFlag"), p);
}
auto SetDetailMode(bool IsTip) -> bool
{
struct P { bool IsTip; } p{ IsTip };
return Call(STR("Set Detail Mode"), p); // the spaces are real
}
auto SetRarity(bool IsBoss, bool IsRare) -> bool
{
struct P { bool IsBoss; bool IsRare; } p{ IsBoss, IsRare };
return Call(STR("SetRarity"), p);
}
auto SetRank(int32 Rank) -> bool
{
struct P { int32 Rank; } p{ Rank };
return Call(STR("SetRank"), p);
}
auto SetSoulRank(int32 Rank) -> bool
{
struct P { int32 Rank; } p{ Rank };
return Call(STR("SetSoulRank"), p);
}
auto SetFoodAmount(int32 FoodAmount) -> bool
{
struct P { int32 FoodAmount; } p{ FoodAmount };
return Call(STR("SetFoodAmount"), p);
}
auto SetFriendShip(int32 FriendshipRank, int32 FriendshipPoint) -> bool
{
struct P { int32 FriendshipRank; int32 FriendshipPoint; } p{ FriendshipRank, FriendshipPoint };
return Call(STR("SetFriendShip"), p);
}
auto SetGender(EPalGenderType_ GenderType) -> bool
{
struct P { EPalGenderType_ GenderType; } p{ GenderType };
return Call(STR("SetGender"), p);
}
auto SetElementType(EPalElementType_ Type1, EPalElementType_ Type2) -> bool
{
struct P { EPalElementType_ type1; EPalElementType_ type2; } p{ Type1, Type2 };
return Call(STR("SetElementType"), p);
}
struct WorkSuitabilityCheck { bool bValid{}; EPalWorkSuitability_ Out{}; };
auto CheckValidWorkSuitability(EPalWorkSuitability_ In) -> std::optional<WorkSuitabilityCheck>
{
struct P
{
EPalWorkSuitability_ InWorkSuitability;
bool IsValid;
EPalWorkSuitability_ OutWorkSuitability;
} p{ In, false, EPalWorkSuitability_::None };
if (!Call(STR("Check Valid Work Suitability"), p)) return std::nullopt;
return WorkSuitabilityCheck{ p.IsValid, p.OutWorkSuitability };
}
// ---- hook targets --------------------------------------------
static auto BindFromHandlePath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/WBP_IngameMenu_PalBox_PalDetail.WBP_IngameMenu_PalBox_PalDetail_C:BindFromHandle");
}
static auto SetEmptyPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/WBP_IngameMenu_PalBox_PalDetail.WBP_IngameMenu_PalBox_PalDetail_C:SetEmpty");
}
static auto UpdateStatusParameterPath() -> const TCHAR*
{
return STR("/Game/Pal/Blueprint/UI/UserInterface/IngameMenu/PalBox/WBP_IngameMenu_PalBox_PalDetail.WBP_IngameMenu_PalBox_PalDetail_C:Update Status Parameter");
}
struct BindFromHandleFrame { UObject* targetHandle; }; // UPalIndividualCharacterHandle*
struct UpdateStatusParameterFrame { UObject* individualParam; }; // UPalIndividualCharacterParameter*
private:
auto WrapCanvas(const TCHAR* Name) const -> CanvasPanel
{
UObject* O = BoundObject(Name);
return O ? CanvasPanel{ O } : CanvasPanel{};
}
auto WrapText(const TCHAR* Name) const -> TextBlock
{
UObject* O = BoundObject(Name);
return O ? TextBlock{ O } : TextBlock{};
}
protected:
PalDetailWidget(UObject* Obj, UClass* Expected, const TCHAR* Tag)
: UserWidget(Obj, Expected, Tag) {
}
};
}

50
Api/PanelSlot.hpp Normal file
View File

@@ -0,0 +1,50 @@
#pragma once
#include "UmgTypes.hpp"
#include "UnrealWrapper.hpp"
namespace PalUIExtension::Api
{
// ------------------------------------------------------------------
// UPanelSlot.
//
// Worth saying plainly: this class exposes ZERO UFUNCTIONs. Its entire
// surface is two Instanced UPROPERTYs, Parent and Content. So this
// wrapper cannot use ProcessEvent at all -- it exists only to give
// CanvasPanelSlot a place to inherit property access from, and to give
// you something typed to hold when AddChild() hands back a UPanelSlot*
// whose concrete type you do not know yet.
//
// If you were expecting a base class full of shared calls to inherit,
// that expectation does not survive contact with the engine here. The
// real shared behaviour lives in UnrealWrapper, not in UPanelSlot.
// ------------------------------------------------------------------
class PanelSlot : public UnrealWrapper
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/UMG.PanelSlot"); }
static auto DebugTag() -> const TCHAR* { return STR("PanelSlot"); }
PanelSlot() = default;
explicit PanelSlot(UObject* Obj) : PanelSlot(Obj, ClassOf<PanelSlot>(), DebugTag()) {}
// Raw UObject* rather than wrappers: Parent is a UPanelWidget and
// Content is a UWidget, and wrapping either here would mean
// including headers that (transitively) include this one.
auto GetParentObject() const -> UObject*
{
auto Ptr = PropertyPtr<UObject*>(STR("Parent"));
return Ptr ? *Ptr : nullptr;
}
auto GetContentObject() const -> UObject*
{
auto Ptr = PropertyPtr<UObject*>(STR("Content"));
return Ptr ? *Ptr : nullptr;
}
protected:
PanelSlot(UObject* Obj, UClass* Expected, const TCHAR* Tag)
: UnrealWrapper(Obj, Expected, Tag) {}
};
}

173
Api/PanelWidget.hpp Normal file
View File

@@ -0,0 +1,173 @@
#pragma once
#include <vector>
#include "Widget.hpp"
#include "PanelSlot.hpp"
namespace PalUIExtension::Api
{
class PanelWidget : public Widget
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/UMG.PanelWidget"); }
static auto DebugTag() -> const TCHAR* { return STR("PanelWidget"); }
PanelWidget() = default;
explicit PanelWidget(UObject* Obj)
: Widget(Obj, ClassOf<PanelWidget>(), DebugTag()) {}
auto GetChildrenCount() -> std::optional<int32>
{
return CallForReturn<int32>(STR("GetChildrenCount"));
}
auto HasAnyChildren() -> std::optional<bool>
{
return CallForReturn<bool>(STR("HasAnyChildren"));
}
auto GetChildAt(int32 Index) -> Widget
{
struct P { int32 Index; UObject* ReturnValue; } p{ Index, nullptr };
auto Result = CallForReturn(STR("GetChildAt"), p, &P::ReturnValue);
return (Result && *Result) ? Widget{ *Result } : Widget{};
}
auto GetChildIndex(UObject* Content) -> std::optional<int32>
{
struct P { UObject* Content; int32 ReturnValue; } p{ Content, -1 };
return CallForReturn(STR("GetChildIndex"), p, &P::ReturnValue);
}
auto HasChild(UObject* Content) -> std::optional<bool>
{
struct P { UObject* Content; bool ReturnValue; } p{ Content, false };
return CallForReturn(STR("HasChild"), p, &P::ReturnValue);
}
auto AddChild(UObject* Content) -> PanelSlot
{
struct P { UObject* Content; UObject* ReturnValue; } p{ Content, nullptr };
auto Result = CallForReturn(STR("AddChild"), p, &P::ReturnValue);
return (Result && *Result) ? PanelSlot{ *Result } : PanelSlot{};
}
auto RemoveChild(UObject* Content) -> std::optional<bool>
{
struct P { UObject* Content; bool ReturnValue; } p{ Content, false };
return CallForReturn(STR("RemoveChild"), p, &P::ReturnValue);
}
auto RemoveChildAt(int32 Index) -> std::optional<bool>
{
struct P { int32 Index; bool ReturnValue; } p{ Index, false };
return CallForReturn(STR("RemoveChildAt"), p, &P::ReturnValue);
}
auto ClearChildren() -> bool { return CallVoid(STR("ClearChildren")); }
// ------------------------------------------------------------------
// Deliberately NOT a wrapper around the engine's GetAllChildren.
//
// That UFUNCTION returns TArray<UWidget*> by value. ProcessEvent
// constructs that array into the frame with the engine's allocator
// and then hands you ownership of the allocation. Getting that
// wrong is either a leak on every call or a free across a module
// boundary, and neither shows up as a crash near the cause.
//
// The count + index loop is two extra ProcessEvent calls per child
// and zero ownership questions. If you are calling this per-frame
// over a large panel, cache the result; if you are calling it once
// in a Construct hook, the cost is irrelevant.
// ------------------------------------------------------------------
auto GetAllChildren() -> std::vector<Widget>
{
std::vector<Widget> Children;
auto Count = GetChildrenCount();
if (!Count || *Count <= 0) return Children;
Children.reserve(static_cast<size_t>(*Count));
for (int32 i = 0; i < *Count; ++i)
{
Widget Child = GetChildAt(i);
if (Child) Children.push_back(Child);
}
return Children;
}
// ------------------------------------------------------------------
// Children WITHOUT ProcessEvent.
//
// GetAllChildren() above costs 1 + N ProcessEvent calls. That is
// fine once in a Construct hook and not fine as the inner loop of a
// whole-tree walk, where it becomes 1 + N per PANEL.
//
// UPanelWidget::Slots is a TArray<UPanelSlot*> UPROPERTY (confirmed
// in the dump: UHTHeaderDump/UMG/Public/PanelWidget.h line 14), and
// UPanelSlot::Content is a UPROPERTY too. So the full child list is
// reachable as pure memory reads. Slot order is child order -- the
// engine's own GetChildAt(i) is Slots[i]->Content.
//
// Nothing here is copied out of engine memory except the pointers
// themselves, which is what makes reading the TArray safe; see the
// TArrayRead_ comment in UmgTypes.hpp for where that line is.
//
// Returns false if Slots could not be read or looked implausible,
// WITHOUT touching Out -- so a caller can fall back to
// GetAllChildren() and know it isn't double-appending.
// ------------------------------------------------------------------
static constexpr int32 kMaxPlausibleChildren = 4096;
auto TryGetChildObjects(std::vector<UObject*>& Out) const -> bool
{
auto* Slots = TryPropertyPtr<TArrayRead_<UObject*>>(STR("Slots"));
if (!Slots) return false;
if (!Slots->IsPlausible(kMaxPlausibleChildren))
{
Output::send<LogLevel::Warning>(
STR("[{}] 'Slots' does not look like a live TArray ")
STR("(Num={}, Max={}, Data={}) -- falling back to ProcessEvent\n"),
m_tag, Slots->Num, Slots->Max,
Slots->Data ? STR("set") : STR("null"));
return false;
}
Out.reserve(Out.size() + static_cast<size_t>(Slots->Num));
for (int32 i = 0; i < Slots->Num; ++i)
{
UObject* Slot = Slots->Data[i];
if (!Slot) continue;
// Read Content off the slot directly rather than through the
// PanelSlot wrapper: the wrapper's IsA check would log on
// anything unexpected, and this runs once per node of a
// whole-tree walk.
if (UObject* Content = RawObject{ Slot, STR("PanelSlot") }.TryObjectProperty(STR("Content")))
{
Out.push_back(Content);
}
}
return true;
}
// Property read first, ProcessEvent second. Use this anywhere you
// just want the children and don't care which route produced them.
auto GetChildObjects() -> std::vector<UObject*>
{
std::vector<UObject*> Out;
if (TryGetChildObjects(Out)) return Out;
for (Widget& Child : GetAllChildren())
{
if (UObject* O = Child.Get()) Out.push_back(O);
}
return Out;
}
protected:
PanelWidget(UObject* Obj, UClass* Expected, const TCHAR* Tag)
: Widget(Obj, Expected, Tag) {}
};
}

131
Api/SizeBox.hpp Normal file
View File

@@ -0,0 +1,131 @@
#pragma once
#include "PanelWidget.hpp"
namespace PalUIExtension::Api
{
// ------------------------------------------------------------------
// USizeBox : public UContentWidget : public UPanelWidget.
//
// UContentWidget is not in this tree's header dump, so there is no
// Api::ContentWidget rung to inherit from. This derives from
// PanelWidget directly, skipping it. That is safe, not a shortcut:
// Invoke() dispatches through Target->GetFunctionByNameInChain() on
// the LIVE object's actual engine class chain (USizeBox ->
// UContentWidget -> UPanelWidget -> UWidget -> ...), never through
// this wrapper's C++ inheritance. Our wrapper hierarchy only
// determines which typed accessors are in scope at the call site; it
// has no effect on what ProcessEvent can reach. UContentWidget itself
// adds no BlueprintCallable surface beyond what UPanelWidget already
// gives you (its one member, GetContentSlot(), is a plain C++ helper
// on the engine side, not a UFUNCTION), so nothing is actually lost
// by skipping the rung -- there would be nothing to put in it.
//
// Every AddChild / GetChildAt / GetAllChildren / GetChildObjects call
// from PanelWidget works unchanged. SizeBox only adds the
// override/clear surface below.
// ------------------------------------------------------------------
class SizeBox : public PanelWidget
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/UMG.SizeBox"); }
static auto DebugTag() -> const TCHAR* { return STR("SizeBox"); }
SizeBox() = default;
explicit SizeBox(UObject* Obj)
: PanelWidget(Obj, ClassOf<SizeBox>(), DebugTag()) {}
// ---- setters (write through the UFUNCTION so SynchronizeProperties runs)
auto SetWidthOverride(float InWidthOverride) -> bool
{
struct P { float InWidthOverride; } p{ InWidthOverride };
return Call(STR("SetWidthOverride"), p);
}
auto SetHeightOverride(float InHeightOverride) -> bool
{
struct P { float InHeightOverride; } p{ InHeightOverride };
return Call(STR("SetHeightOverride"), p);
}
auto SetMinDesiredWidth(float InMinDesiredWidth) -> bool
{
struct P { float InMinDesiredWidth; } p{ InMinDesiredWidth };
return Call(STR("SetMinDesiredWidth"), p);
}
auto SetMinDesiredHeight(float InMinDesiredHeight) -> bool
{
struct P { float InMinDesiredHeight; } p{ InMinDesiredHeight };
return Call(STR("SetMinDesiredHeight"), p);
}
auto SetMaxDesiredWidth(float InMaxDesiredWidth) -> bool
{
struct P { float InMaxDesiredWidth; } p{ InMaxDesiredWidth };
return Call(STR("SetMaxDesiredWidth"), p);
}
auto SetMaxDesiredHeight(float InMaxDesiredHeight) -> bool
{
struct P { float InMaxDesiredHeight; } p{ InMaxDesiredHeight };
return Call(STR("SetMaxDesiredHeight"), p);
}
auto SetMinAspectRatio(float InMinAspectRatio) -> bool
{
struct P { float InMinAspectRatio; } p{ InMinAspectRatio };
return Call(STR("SetMinAspectRatio"), p);
}
auto SetMaxAspectRatio(float InMaxAspectRatio) -> bool
{
struct P { float InMaxAspectRatio; } p{ InMaxAspectRatio };
return Call(STR("SetMaxAspectRatio"), p);
}
// ---- clear (each Set* above also flips its bOverride_ flag on;
// these are the only way to flip it back off -- there is no
// "SetWidthOverride with override=false" overload on the engine
// side, Clear* is a distinct UFUNCTION)
auto ClearWidthOverride() -> bool { return CallVoid(STR("ClearWidthOverride")); }
auto ClearHeightOverride() -> bool { return CallVoid(STR("ClearHeightOverride")); }
auto ClearMinDesiredWidth() -> bool { return CallVoid(STR("ClearMinDesiredWidth")); }
auto ClearMinDesiredHeight() -> bool { return CallVoid(STR("ClearMinDesiredHeight")); }
auto ClearMaxDesiredWidth() -> bool { return CallVoid(STR("ClearMaxDesiredWidth")); }
auto ClearMaxDesiredHeight() -> bool { return CallVoid(STR("ClearMaxDesiredHeight")); }
auto ClearMinAspectRatio() -> bool { return CallVoid(STR("ClearMinAspectRatio")); }
auto ClearMaxAspectRatio() -> bool { return CallVoid(STR("ClearMaxAspectRatio")); }
// ---- property reads: the float values ------------------------
auto GetWidthOverride() const -> std::optional<float> { return PropertyValue<float>(STR("WidthOverride")); }
auto GetHeightOverride() const -> std::optional<float> { return PropertyValue<float>(STR("HeightOverride")); }
auto GetMinDesiredWidth() const -> std::optional<float> { return PropertyValue<float>(STR("MinDesiredWidth")); }
auto GetMinDesiredHeight() const -> std::optional<float> { return PropertyValue<float>(STR("MinDesiredHeight")); }
auto GetMaxDesiredWidth() const -> std::optional<float> { return PropertyValue<float>(STR("MaxDesiredWidth")); }
auto GetMaxDesiredHeight() const -> std::optional<float> { return PropertyValue<float>(STR("MaxDesiredHeight")); }
auto GetMinAspectRatio() const -> std::optional<float> { return PropertyValue<float>(STR("MinAspectRatio")); }
auto GetMaxAspectRatio() const -> std::optional<float> { return PropertyValue<float>(STR("MaxAspectRatio")); }
// ---- property reads: the override flags -----------------------
//
// These are `uint8 bOverride_X : 1;` bitfields, EIGHT of them
// declared back to back on the header. That is exactly the shape
// where PropertyValue<uint8>/PropertyPtr<uint8> goes quietly
// wrong: the compiler is free to pack all eight into one shared
// byte, and a plain offset+size read would hand back that whole
// byte -- every flag OR'd together -- for a call asking about
// just one of them, with no warning, because the property exists
// and 1 byte does equal sizeof(uint8).
//
// PropertyBoolValue (UnrealWrapper.hpp) exists specifically for
// this: it reads FBoolProperty's own ByteOffset/FieldMask and
// tests the correct single bit rather than the storage byte. Use
// it for every bOverride_* flag; do not reach for PropertyValue
// here even though it would compile.
auto IsWidthOverridden() const -> std::optional<bool> { return PropertyBoolValue(STR("bOverride_WidthOverride")); }
auto IsHeightOverridden() const -> std::optional<bool> { return PropertyBoolValue(STR("bOverride_HeightOverride")); }
auto IsMinDesiredWidthOverridden() const -> std::optional<bool> { return PropertyBoolValue(STR("bOverride_MinDesiredWidth")); }
auto IsMinDesiredHeightOverridden() const -> std::optional<bool> { return PropertyBoolValue(STR("bOverride_MinDesiredHeight")); }
auto IsMaxDesiredWidthOverridden() const -> std::optional<bool> { return PropertyBoolValue(STR("bOverride_MaxDesiredWidth")); }
auto IsMaxDesiredHeightOverridden() const -> std::optional<bool> { return PropertyBoolValue(STR("bOverride_MaxDesiredHeight")); }
auto IsMinAspectRatioOverridden() const -> std::optional<bool> { return PropertyBoolValue(STR("bOverride_MinAspectRatio")); }
auto IsMaxAspectRatioOverridden() const -> std::optional<bool> { return PropertyBoolValue(STR("bOverride_MaxAspectRatio")); }
};
}

41
Api/SizeBoxSlot.hpp Normal file
View File

@@ -0,0 +1,41 @@
#pragma once
#include "PanelSlot.hpp"
namespace PalUIExtension::Api
{
class SizeBoxSlot : public PanelSlot
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/UMG.SizeBoxSlot"); }
static auto DebugTag() -> const TCHAR* { return STR("SizeBoxSlot"); }
SizeBoxSlot() = default;
explicit SizeBoxSlot(UObject* Obj)
: PanelSlot(Obj, ClassOf<SizeBoxSlot>(), DebugTag()) {}
// ---- setters (write through the UFUNCTION so SynchronizeProperties runs)
auto SetPadding(FMargin_ InPadding) -> bool
{
struct P { FMargin_ InPadding; } p{ InPadding };
return Call(STR("SetPadding"), p);
}
auto SetHorizontalAlignment(EHorizontalAlignment_ InHorizontalAlignment) -> bool
{
struct P { EHorizontalAlignment_ InHorizontalAlignment; } p{ InHorizontalAlignment };
return Call(STR("SetHorizontalAlignment"), p);
}
auto SetVerticalAlignment(EVerticalAlignment_ InVerticalAlignment) -> bool
{
struct P { EVerticalAlignment_ InVerticalAlignment; } p{ InVerticalAlignment };
return Call(STR("SetVerticalAlignment"), p);
}
// ---- property reads
auto GetPadding() const -> std::optional<FMargin_> { return PropertyValue<FMargin_>(STR("Padding")); }
auto GetHorizontalAlignment()const -> std::optional<EHorizontalAlignment_> { return PropertyValue<EHorizontalAlignment_>(STR("HorizontalAlignment")); }
auto GetVerticalAlignment() const -> std::optional<EVerticalAlignment_> { return PropertyValue<EVerticalAlignment_>(STR("VerticalAlignment")); }
};
}

289
Api/TextBlock.hpp Normal file
View File

@@ -0,0 +1,289 @@
#pragma once
#include <Unreal/FText.hpp>
#include "TextLayoutWidget.hpp"
namespace PalUIExtension::Api
{
class TextBlock : public TextLayoutWidget
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/UMG.TextBlock"); }
static auto DebugTag() -> const TCHAR* { return STR("TextBlock"); }
TextBlock() = default;
explicit TextBlock(UObject* Obj)
: TextLayoutWidget(Obj, ClassOf<TextBlock>(), DebugTag()) {}
// ---- text -----------------------------------------------------------
//
// Note:
// SetText takes FText BY VALUE on the engine side (unlike UWidget::SetToolTipText, which takes const FText&).
auto SetText(const FText& InText) -> bool
{
struct P { FText InText; } p{ InText };
return Call(STR("SetText"), p);
}
auto GetText() -> std::optional<FText>
{
return CallForReturn<FText>(STR("GetText"));
}
// ---- colour ---------------------------------------------------------
auto SetColorAndOpacity(FSlateColor_ InColorAndOpacity) -> bool
{
struct P { FSlateColor_ InColorAndOpacity; } p{ InColorAndOpacity };
return Call(STR("SetColorAndOpacity"), p);
}
// Convenience: the common case is a literal colour, styling mode 0.
auto SetColor(FLinearColor_ Color) -> bool
{
return SetColorAndOpacity(FSlateColor_{ Color, static_cast<uint8>(ESlateColorStylingMode_::UseColor_Specified) });
}
auto SetOpacity(float InOpacity) -> bool
{
struct P { float InOpacity; } p{ InOpacity };
return Call(STR("SetOpacity"), p);
}
auto SetShadowOffset(FVector2D_ InShadowOffset) -> bool
{
struct P { FVector2D_ InShadowOffset; } p{ InShadowOffset };
return Call(STR("SetShadowOffset"), p);
}
auto SetShadowColorAndOpacity(FLinearColor_ InColor) -> bool
{
struct P { FLinearColor_ InColor; } p{ InColor };
return Call(STR("SetShadowColorAndOpacity"), p);
}
// ---- layout / policy -------------------------------------------------
auto SetMinDesiredWidth(float InMinDesiredWidth) -> bool
{
struct P { float InMinDesiredWidth; } p{ InMinDesiredWidth };
return Call(STR("SetMinDesiredWidth"), p);
}
auto SetAutoWrapText(bool InAutoTextWrap) -> bool
{
struct P { bool InAutoTextWrap; } p{ InAutoTextWrap };
return Call(STR("SetAutoWrapText"), p);
}
auto SetTextTransformPolicy(ETextTransformPolicy_ InTransformPolicy) -> bool
{
struct P { ETextTransformPolicy_ InTransformPolicy; } p{ InTransformPolicy };
return Call(STR("SetTextTransformPolicy"), p);
}
auto SetTextOverflowPolicy(ETextOverflowPolicy_ InOverflowPolicy) -> bool
{
struct P { ETextOverflowPolicy_ InOverflowPolicy; } p{ InOverflowPolicy };
return Call(STR("SetTextOverflowPolicy"), p);
}
// ---- materials -------------------------------------------------------
// Plain object pointers. Nothing subtle here.
auto SetFontMaterial(UObject* InMaterial) -> bool
{
struct P { UObject* InMaterial; } p{ InMaterial };
return Call(STR("SetFontMaterial"), p);
}
auto SetFontOutlineMaterial(UObject* InMaterial) -> bool
{
struct P { UObject* InMaterial; } p{ InMaterial };
return Call(STR("SetFontOutlineMaterial"), p);
}
auto GetDynamicFontMaterial() -> std::optional<UObject*> { return CallForReturn<UObject*>(STR("GetDynamicFontMaterial")); }
auto GetDynamicOutlineMaterial() -> std::optional<UObject*> { return CallForReturn<UObject*>(STR("GetDynamicOutlineMaterial")); }
// ---- property reads ---------------------------------------------------
auto GetMinDesiredWidth() const -> std::optional<float> { return PropertyValue<float>(STR("MinDesiredWidth")); }
auto GetShadowOffset() const -> std::optional<FVector2D_> { return PropertyValue<FVector2D_>(STR("ShadowOffset")); }
auto GetShadowColorAndOpacity()const -> std::optional<FLinearColor_> { return PropertyValue<FLinearColor_>(STR("ShadowColorAndOpacity")); }
auto GetColorAndOpacity() const -> std::optional<FSlateColor_> { return PropertyValue<FSlateColor_>(STR("ColorAndOpacity")); }
auto GetTextTransformPolicy() const -> std::optional<ETextTransformPolicy_>{ return PropertyValue<ETextTransformPolicy_>(STR("TextTransformPolicy")); }
auto GetTextOverflowPolicy() const -> std::optional<ETextOverflowPolicy_> { return PropertyValue<ETextOverflowPolicy_>(STR("TextOverflowPolicy")); }
auto GetSimpleTextMode() const -> std::optional<bool> { return PropertyValue<bool>(STR("bSimpleTextMode")); }
auto GetWrapWithInvalidationPanel() const -> std::optional<bool> { return PropertyValue<bool>(STR("bWrapWithInvalidationPanel")); }
// ======================================================================
// FONT
//
// SetFont(FSlateFontInfo) and SetStrikeBrush(FSlateBrush) are NOT
// exposed as value-taking calls, and this is not caution for its
// own sake:
//
// FSlateFontInfo holds TSharedPtr<const FCompositeFont>.
// FSlateBrush holds FSlateResourceHandle, which holds a shared ref.
//
// Any POD mirror you build will copy those pointers without touching
// their refcounts. Then either the engine's copy-assign increments a
// count you never decrement (leak), or a destructor runs on a count
// you never incremented (a free of a live font, surfacing as a crash
// in the renderer somewhere unrelated). There is no struct layout
// that fixes this -- the problem is that these types are not data.
//
// What works instead: mutate the live struct in place through
// reflection. No copy is ever made, so no refcount is ever touched.
// ======================================================================
// Reads the current font size. Whether the engine stores it as float
// or int32 varies by engine version and both are 4 bytes, so the
// size check cannot tell them apart. This logs both readings once --
// exactly one of them will be a sane font size (something like 12 to
// 48); that tells you which typed setter to call. Run it once, then
// delete the call.
auto LogFontSize() const -> void
{
void* SizePtr = FontMemberPtr(STR("Size"), 4);
if (!SizePtr) return;
float AsFloat = *reinterpret_cast<float*>(SizePtr);
int32 AsInt = *reinterpret_cast<int32*>(SizePtr);
Output::send<LogLevel::Warning>(
STR("[TextBlock] Font.Size reads as float={} / int32={} -- ")
STR("whichever looks like a font size is the real type\n"),
AsFloat, AsInt);
}
auto SetFontSizeFloat(float NewSize) -> bool
{
if (void* P = FontMemberPtr(STR("Size"), 4)) { *reinterpret_cast<float*>(P) = NewSize; return PushFontToSlate(); }
return false;
}
auto SetFontSizeInt(int32 NewSize) -> bool
{
if (void* P = FontMemberPtr(STR("Size"), 4)) { *reinterpret_cast<int32*>(P) = NewSize; return PushFontToSlate(); }
return false;
}
auto SetFontLetterSpacing(int32 Value) -> bool
{
if (void* P = FontMemberPtr(STR("LetterSpacing"), 4)) { *reinterpret_cast<int32*>(P) = Value; return PushFontToSlate(); }
return false;
}
auto SetFontSkewAmount(float Value) -> bool
{
if (void* P = FontMemberPtr(STR("SkewAmount"), 4)) { *reinterpret_cast<float*>(P) = Value; return PushFontToSlate(); }
return false;
}
// Sets the named typeface (e.g. "Bold", "Regular", "Italic") on the
// live font. FName is a trivially-copyable index pair, so this one
// is a safe direct write.
auto SetFontTypeface(FName TypefaceName) -> bool
{
if (void* P = FontMemberPtr(STR("TypefaceFontName"), sizeof(FName)))
{
*reinterpret_cast<FName*>(P) = TypefaceName;
return PushFontToSlate();
}
return false;
}
// Raw access, for anything not covered above. Returns null and logs
// if the member is missing or is not the size you expected.
auto FontMemberPtr(const TCHAR* MemberName, size_t ExpectedSize) const -> void*
{
if (!m_obj) return nullptr;
FProperty* FontProp = m_obj->GetPropertyByNameInChain(STR("Font"));
if (!FontProp)
{
Output::send<LogLevel::Warning>(STR("[TextBlock] no 'Font' property\n"));
return nullptr;
}
// FStructProperty::GetStruct() gives the UScriptStruct whose
// property chain describes FSlateFontInfo's members.
auto* AsStruct = static_cast<FStructProperty*>(FontProp);
UScriptStruct* FontStruct = AsStruct->GetStruct();
if (!FontStruct)
{
Output::send<LogLevel::Warning>(STR("[TextBlock] 'Font' is not a struct property\n"));
return nullptr;
}
// The `TFieldRange iter = FontStruct->ForEachProperty();` that
// used to sit here was a leftover from before this was a
// range-for: it built a second, unused range object every call.
FProperty* Member = nullptr;
for (FProperty* Property : FontStruct->ForEachProperty())
{
if (Property && Property->GetName() == MemberName) { Member = Property; break; }
}
if (!Member)
{
Output::send<LogLevel::Warning>(
STR("[TextBlock] FSlateFontInfo has no member '{}' on this build\n"), MemberName);
return nullptr;
}
if (static_cast<size_t>(Member->GetSize()) != ExpectedSize)
{
Output::send<LogLevel::Warning>(
STR("[TextBlock] FSlateFontInfo::{} is {} bytes, expected {}\n"),
MemberName, Member->GetSize(), ExpectedSize);
return nullptr;
}
uint8* FontBase = reinterpret_cast<uint8*>(m_obj) + FontProp->GetOffset_Internal();
return FontBase + Member->GetOffset_Internal();
}
private:
// ------------------------------------------------------------------
// The in-place write above changes UTextBlock::Font but does not
// reach the SSlateTextBlock -- that only happens inside
// SynchronizeProperties, which the setters call and a raw write
// does not. There is no UFUNCTION that forces a resync.
//
// So this calls SetFont with the params block pointing AT THE LIVE
// Font ITSELF. The engine does Font = InFontInfo, a real C++
// self-assignment (safe for TSharedPtr and FName), and then runs
// the resync. Nothing is copied on our side, so nothing can get the
// refcount wrong -- with one open question:
//
// Does UE4SS's ProcessEvent destruct the params buffer after the
// call? If it does, this destructs the widget's live Font.
//
// Evidence it does not: your existing FTextFromString passes an
// FString and reads back an FText through this same path, repeatedly,
// without crashing. That is suggestive, not proof -- a refcount
// underflow frees memory that stays readable until something else
// claims it, so the crash lands far from the cause.
//
// This is the single highest-value thing to nail down in this
// codebase: the answer unlocks FText, FSlateFontInfo, FSlateBrush
// and TArray returns all at once. Test it in isolation: pass an
// FString whose refcount you can watch, call through ProcessEvent,
// and read the count after.
// ------------------------------------------------------------------
auto PushFontToSlate() -> bool
{
if (!m_obj) return false;
FProperty* FontProp = m_obj->GetPropertyByNameInChain(STR("Font"));
if (!FontProp) return false;
void* LiveFont = reinterpret_cast<uint8*>(m_obj) + FontProp->GetOffset_Internal();
UFunction* Fn = m_obj->GetFunctionByNameInChain(STR("SetFont"));
if (!Fn)
{
Output::send<LogLevel::Warning>(STR("[TextBlock] SetFont not found\n"));
return false;
}
m_obj->ProcessEvent(Fn, LiveFont);
return true;
}
};
}

68
Api/TextLayoutWidget.hpp Normal file
View File

@@ -0,0 +1,68 @@
#pragma once
#include "Widget.hpp"
namespace PalUIExtension::Api
{
// ------------------------------------------------------------------
// UTextLayoutWidget. One UFUNCTION (SetJustification) and a handful of
// protected UPROPERTYs.
//
// "protected" is a C++ access specifier. It is invisible to reflection:
// the properties are in the class's FProperty chain either way, and
// PropertyPtr reaches them fine. Do not read protected as unreachable.
//
// AutoWrapText is uint8 : 1 and is therefore NOT exposed as a property
// read -- see the bitfield note in UnrealWrapper.hpp. UTextBlock has a
// SetAutoWrapText UFUNCTION; use that.
// ------------------------------------------------------------------
class TextLayoutWidget : public Widget
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/UMG.TextLayoutWidget"); }
static auto DebugTag() -> const TCHAR* { return STR("TextLayoutWidget"); }
TextLayoutWidget() = default;
explicit TextLayoutWidget(UObject* Obj)
: Widget(Obj, ClassOf<TextLayoutWidget>(), DebugTag()) {}
auto SetJustification(ETextJustify_ InJustification) -> bool
{
struct P { ETextJustify_ InJustification; } p{ InJustification };
return Call(STR("SetJustification"), p);
}
// ---- property reads -------------------------------------------------
auto GetJustification() const -> std::optional<ETextJustify_> { return PropertyValue<ETextJustify_>(STR("Justification")); }
auto GetWrappingPolicy() const -> std::optional<ETextWrappingPolicy_>{ return PropertyValue<ETextWrappingPolicy_>(STR("WrappingPolicy")); }
auto GetWrapTextAt() const -> std::optional<float> { return PropertyValue<float>(STR("WrapTextAt")); }
auto GetLineHeightPercentage()const -> std::optional<float> { return PropertyValue<float>(STR("LineHeightPercentage")); }
auto GetMargin() const -> std::optional<FMargin_> { return PropertyValue<FMargin_>(STR("Margin")); }
// ---- property writes ------------------------------------------------
//
// These have no UFUNCTION setter, so a direct write is the only
// option -- and it will NOT reach the Slate widget until something
// else triggers SynchronizeProperties. Set them before the widget
// is added to a panel, or accept that they take effect late.
auto SetWrapTextAt(float Value) -> bool
{
if (float* P = PropertyPtr<float>(STR("WrapTextAt"))) { *P = Value; return true; }
return false;
}
auto SetLineHeightPercentage(float Value) -> bool
{
if (float* P = PropertyPtr<float>(STR("LineHeightPercentage"))) { *P = Value; return true; }
return false;
}
auto SetMargin(FMargin_ Value) -> bool
{
if (FMargin_* P = PropertyPtr<FMargin_>(STR("Margin"))) { *P = Value; return true; }
return false;
}
protected:
TextLayoutWidget(UObject* Obj, UClass* Expected, const TCHAR* Tag)
: Widget(Obj, Expected, Tag) {}
};
}

156
Api/UmgTypes.hpp Normal file
View File

@@ -0,0 +1,156 @@
#pragma once
#include <Unreal/UObject.hpp>
namespace PalUIExtension::Api
{
using namespace RC;
using namespace RC::Unreal;
template <typename T>
struct TArrayRead_
{
T* Data{};
int32 Num{};
int32 Max{};
auto IsPlausible(int32 SanityLimit) const -> bool
{
if (Num < 0 || Max < 0 || Num > Max) return false;
if (Num > SanityLimit) return false;
return Num == 0 || Data != nullptr;
}
};
struct FVector2D_ { double X{}; double Y{}; };
struct FVector_ { double X{}; double Y{}; double Z{}; };
struct FMargin_ { float Left{}; float Top{}; float Right{}; float Bottom{}; };
struct FAnchors_ { FVector2D_ Minimum{}; FVector2D_ Maximum{}; };
// GUESS (field order).
struct FAnchorData_ { FMargin_ Offsets{}; FAnchors_ Anchors{}; FVector2D_ Alignment{}; };
// GUESS (field order). Engine source order is Translation, Scale, Shear, Angle.
struct FWidgetTransform_
{
FVector2D_ Translation{};
FVector2D_ Scale{ 1.0, 1.0 };
FVector2D_ Shear{};
float Angle{};
};
enum class ESlateVisibility_ : uint8
{
Visible = 0,
Collapsed = 1,
Hidden = 2,
HitTestInvisible = 3,
SelfHitTestInvisible = 4,
};
enum class EWidgetClipping_ : uint8
{
Inherit = 0,
ClipToBounds = 1,
ClipToBoundsWithoutIntersecting = 2,
ClipToBoundsAlways = 3,
OnDemand = 4,
};
enum class EUINavigation_ : uint8
{
Left = 0,
Right = 1,
Up = 2,
Down = 3,
Next = 4,
Previous = 5,
Num = 6,
Invalid = 7,
};
enum class EUINavigationRule_ : uint8
{
Escape = 0,
Explicit = 1,
Wrap = 2,
Stop = 3,
Custom = 4,
CustomBoundary = 5,
Invalid = 6,
};
using EMouseCursorByte_ = uint8;
enum class EHorizontalAlignment_ : uint8
{
Fill = 0,
Left = 1,
Center = 2,
Right = 3,
};
enum class EVerticalAlignment_ : uint8
{
Fill = 0,
Top = 1,
Center = 2,
Bottom = 3,
};
struct FLinearColor_ { float R{}; float G{}; float B{}; float A{ 1.0f }; };
struct FSlateColor_
{
FLinearColor_ SpecifiedColor{};
uint8 ColorUseRule{ 0 }; // UseColor_Specified
};
enum class ESlateColorStylingMode_ : uint8
{
UseColor_Specified = 0,
UseColor_Specified_Link = 1,
UseColor_Foreground = 2,
UseColor_Foreground_Subdued = 3,
};
enum class ETextTransformPolicy_ : uint8
{
None = 0,
ToLower = 1,
ToUpper = 2,
};
enum class ETextOverflowPolicy_ : uint8
{
Clip = 0,
Ellipsis = 1,
};
enum class ETextWrappingPolicy_ : uint8
{
DefaultWrapping = 0,
AllowPerCharacterWrapping = 1,
};
// TEnumAsByte<ETextJustify::Type>
enum class ETextJustify_ : uint8
{
Left = 0,
Center = 1,
Right = 2,
};
enum class ESlateSizeRule_ : uint8
{
Automatic = 0,
Fill = 1,
};
struct FSlateChildSize_
{
float Value{};
ESlateSizeRule_ SizeRule{ ESlateSizeRule_::Automatic };
};
}

476
Api/UnrealWrapper.hpp Normal file
View File

@@ -0,0 +1,476 @@
#pragma once
#include <optional>
#include <type_traits>
#include <DynamicOutput/DynamicOutput.hpp>
#include <Unreal/UObjectGlobals.hpp>
#include <Unreal/UObject.hpp>
#include <Unreal/UClass.hpp>
#include <Unreal/CoreUObject/UObject/Class.hpp>
#include <Unreal/CoreUObject/UObject/UnrealType.hpp>
#include <Reflection/UnrealApiCall.hpp>
#include <Reflection/UnrealObjectRef.hpp>
namespace PalUIExtension::Api
{
using namespace RC;
using namespace RC::Unreal;
namespace Detail
{
struct Tag_Umg {};
// ONE dispatcher per return type, shared by every wrapper in this
// tree -- not one per wrapper class.
//
// The reason this is correct: UnrealApiCall::Invoke resolves the
// function with Target->GetFunctionByNameInChain(), which walks the
// *live object's* class chain. It never touches the ClassPath the
// UnrealApiCall was constructed with. So the class path is only
// used by ResolveClass/FindInstance/ConstructInstance, none of
// which we call here. Giving each wrapper class its own set of
// per-return-type statics would buy nothing but N redundant
// StaticFindObject calls.
//
// The corollary matters for the inheritance below: a base-class
// wrapper calling a function on a derived object works, because
// dispatch is by the object's chain, not by the wrapper's type.
//
// The path below is a placeholder that is never resolved. If you
// ever call ResolveClass on one of these, it will resolve UObject
// and the resulting IsA check will be meaningless.
template <typename R>
inline auto Api() -> Reflection::UnrealApiCall<Tag_Umg, R>&
{
static Reflection::UnrealApiCall<Tag_Umg, R> Instance{
STR("/Script/CoreUObject.Object"), STR("UMG") };
return Instance;
}
}
// Resolves and caches the UClass for a wrapper type T.
// T must expose static ClassPath() and static DebugTag().
template <typename T>
inline auto ClassOf() -> UClass*
{
static Reflection::UnrealObjectRef Ref{ T::ClassPath(), T::DebugTag() };
return static_cast<UClass*>(Ref.Get());
}
// ------------------------------------------------------------------
// Base for every UObject wrapper here.
//
// Lifetime: holds a raw UObject* and does nothing to keep it alive.
// These wrappers are meant to live inside one hook callback. Storing
// one across frames means storing a pointer the GC can invalidate,
// and a freed UObject slot will eventually be reused by a different
// object -- so a stale pointer does not read as null, it reads as
// some other widget.
// ------------------------------------------------------------------
class UnrealWrapper
{
public:
auto IsValid() const -> bool { return m_obj != nullptr; }
explicit operator bool() const { return IsValid(); }
auto Get() const -> UObject* { return m_obj; }
// ------------------------------------------------------------------
// Diagnostics.
//
// These were copy-pasted identically into PalDetailWidget and
// MainMenuPalWidget, differing only in the log prefix -- which
// m_tag already carries. Hoisted here so every wrapper has them and
// there is one copy to fix when the reflection API shifts.
//
// LogFunctionSignature is the tool for settling a frame layout
// without guessing: it prints the engine's own offsets and sizes
// for a UFunction's parameter block, which is exactly what the
// hand-written ParamsStructs in this codebase have to match. Run it
// before trusting a new struct, not after it misbehaves.
// ------------------------------------------------------------------
auto LogFunctionSignature(const TCHAR* FunctionName) const -> void
{
if (!m_obj) return;
UFunction* Fn = m_obj->GetFunctionByNameInChain(FunctionName);
if (!Fn)
{
Output::send<LogLevel::Warning>(
STR("[{}] '{}' is not in the chain of {}\n"),
m_tag, FunctionName, m_obj->GetFullName());
return;
}
Output::send<LogLevel::Warning>(
STR("[{}] {} -- frame is {} bytes:\n"),
m_tag, FunctionName, Fn->GetStructureSize());
for (FProperty* Prop : Fn->ForEachProperty())
{
if (!Prop) continue;
Output::send<LogLevel::Warning>(
STR(" [0x{:X}] {} ({} bytes)\n"),
Prop->GetOffset_Internal(), Prop->GetName(), Prop->GetSize());
}
}
// Every UPROPERTY on the object's class chain, with offset and
// size. The companion to the above for the property side: this is
// how you find out what a widget is actually called internally, or
// whether the field you want is a promoted variable at all.
auto LogProperties() const -> void
{
if (!m_obj) return;
UClass* Cls = m_obj->GetClassPrivate();
if (!Cls)
{
Output::send<LogLevel::Warning>(
STR("[{}] no class on {}\n"), m_tag, m_obj->GetFullName());
return;
}
Output::send<LogLevel::Warning>(
STR("[{}] properties of {}:\n"), m_tag, m_obj->GetFullName());
for (FProperty* Prop : Cls->ForEachPropertyInChain())
{
if (!Prop) continue;
Output::send<LogLevel::Warning>(
STR(" [0x{:X}] {} ({} bytes)\n"),
Prop->GetOffset_Internal(), Prop->GetName(), Prop->GetSize());
}
}
protected:
UnrealWrapper() = default;
UnrealWrapper(UObject* Obj, UClass* Expected, const TCHAR* DebugTag)
: m_tag(DebugTag ? DebugTag : STR("Wrapper"))
{
if (!Obj)
{
Output::send<LogLevel::Warning>(
STR("[{}] constructed from null object\n"), m_tag);
return;
}
if (!Expected)
{
// Class path did not resolve. Accept the object rather than
// reject it -- Invoke dispatches by the object's own chain,
// so calls will still work; you just lost the type check.
Output::send<LogLevel::Warning>(
STR("[{}] class unresolved -- accepting {} unchecked\n"),
m_tag, Obj->GetFullName());
m_obj = Obj;
return;
}
if (!Obj->IsA(Expected))
{
Output::send<LogLevel::Warning>(
STR("[{}] {} is not of the expected class\n"),
m_tag, Obj->GetFullName());
return;
}
m_obj = Obj;
}
struct NoParams {};
// ------------------------------------------------------------------
// Deliberate opt-out of the class check.
//
// The (Obj, Expected, Tag) constructor below treats a null Expected
// as "the ClassPath failed to resolve" and logs a Warning about it,
// which is right for that case and wrong for a caller that never
// had a ClassPath to begin with (LooseObject). The difference
// matters more than it looks: that Warning calls
// Obj->GetFullName(), which walks the whole outer chain calling
// GetName() -- an FName::ToString, i.e. a ProcessEvent -- at every
// level, then formats the line and writes it to every open output
// device, because Output::send has no level filter (see
// Static/LogGate.hpp).
//
// Paid once at startup for a genuinely unresolved class, that is
// fine. Paid on every LooseObject construction on a per-bind path,
// it is a dozen-plus ProcessEvent calls and a disk write per pal
// selection. This tag ctor says "unchecked on purpose" and stays
// silent.
//
// It does NOT weaken anything: LooseObject was already accepting
// the object unchecked. The only thing removed is a diagnostic
// that was describing a condition that wasn't happening.
// ------------------------------------------------------------------
struct UncheckedTag { explicit UncheckedTag() = default; };
UnrealWrapper(UObject* Obj, UncheckedTag, const TCHAR* DebugTag)
: m_obj(Obj), m_tag(DebugTag ? DebugTag : STR("Wrapper"))
{
}
// Void-returning UFUNCTIONs only. Detail::Api<void> will log if the
// engine function actually has a ReturnValue property, which is the
// signal that you meant to use CallForReturn.
template <typename ParamsStruct>
auto Call(const TCHAR* FunctionName, ParamsStruct& Params) const -> bool
{
if (!m_obj)
{
Output::send<LogLevel::Warning>(
STR("[{}] '{}' on an invalid wrapper\n"), m_tag, FunctionName);
return false;
}
return Detail::Api<void>().Invoke(m_obj, FunctionName, Params);
}
auto CallVoid(const TCHAR* FunctionName) const -> bool
{
NoParams p{};
return Call(FunctionName, p);
}
// Zero-parameter getter.
template <typename R>
auto CallForReturn(const TCHAR* FunctionName) const -> std::optional<R>
{
struct Params { R ReturnValue; };
Params p{};
return CallForReturn<Params, R>(FunctionName, p, &Params::ReturnValue);
}
// Getter with parameters. ReturnValue must be the LAST member of
// ParamsStruct, matching the engine's frame layout.
template <typename ParamsStruct, typename R>
auto CallForReturn(const TCHAR* FunctionName, ParamsStruct& Params,
R ParamsStruct::* ReturnMember) const -> std::optional<R>
{
if (!m_obj)
{
Output::send<LogLevel::Warning>(
STR("[{}] '{}' on an invalid wrapper\n"), m_tag, FunctionName);
return std::nullopt;
}
return Detail::Api<R>().InvokeForReturn(m_obj, FunctionName, Params, ReturnMember);
}
// ------------------------------------------------------------------
// Direct UPROPERTY access.
//
// Cheaper than ProcessEvent and the only option for properties with
// no UFUNCTION accessor (UPanelSlot::Parent and ::Content, for
// instance -- that class exposes zero UFUNCTIONs).
//
// Two things this does NOT handle:
// 1. Bitfield bools (uint8 bFoo : 1). FProperty::GetSize() returns
// 1 for those too, so the size check passes and you read the
// whole byte -- every flag packed into it, not just yours.
// Use the UFUNCTION getter for those. That is why UWidget's
// bIsEnabled/bIsVariable are not exposed here.
// 2. Writes. Poking a property directly skips the setter's
// SynchronizeProperties call, so the Slate widget will not
// update. Read by property, write by UFUNCTION.
//
// UE4SS API name check: GetPropertyByNameInChain and
// GetOffset_Internal are what I expect this version to expose;
// verify against your UObject.hpp / UnrealType.hpp before assuming
// this compiles as written.
// ------------------------------------------------------------------
template <typename T>
auto PropertyPtr(const TCHAR* PropertyName) const -> T*
{
if (!m_obj) return nullptr;
FProperty* Prop = m_obj->GetPropertyByNameInChain(PropertyName);
if (!Prop)
{
Output::send<LogLevel::Warning>(
STR("[{}] property '{}' not found on {}\n"),
m_tag, PropertyName, m_obj->GetFullName());
return nullptr;
}
if (static_cast<size_t>(Prop->GetSize()) != sizeof(T))
{
Output::send<LogLevel::Warning>(
STR("[{}] property '{}' is {} bytes engine-side, read as {} bytes\n"),
m_tag, PropertyName, Prop->GetSize(), sizeof(T));
return nullptr;
}
return reinterpret_cast<T*>(
reinterpret_cast<uint8*>(m_obj) + Prop->GetOffset_Internal());
}
// ------------------------------------------------------------------
// PropertyPtr for the case where ABSENCE IS AN EXPECTED ANSWER, not
// a mistake.
//
// PropertyPtr logs a Warning when the property is missing, and that
// log line calls Obj->GetFullName() -- an outer-chain walk with an
// FName::ToString at every level (see the UncheckedTag note above).
// That is the right behaviour when you are naming a property you
// believe exists and want to hear about it if you are wrong.
//
// It is the wrong behaviour when you are PROBING: "does this widget
// happen to be a UUserWidget, i.e. does it have a WidgetTree?" is a
// question asked once per node during a tree walk, and the answer is
// "no" for almost every node. Logging that is not a diagnostic, it's
// a per-node GetFullName() plus a disk write.
//
// A size mismatch still logs. That one is never an expected answer:
// the property exists but is not the shape you think it is, which is
// the silent-corruption case, not the absent case.
// ------------------------------------------------------------------
template <typename T>
auto TryPropertyPtr(const TCHAR* PropertyName) const -> T*
{
if (!m_obj) return nullptr;
FProperty* Prop = m_obj->GetPropertyByNameInChain(PropertyName);
if (!Prop) return nullptr;
if (static_cast<size_t>(Prop->GetSize()) != sizeof(T))
{
Output::send<LogLevel::Warning>(
STR("[{}] property '{}' is {} bytes engine-side, read as {} bytes\n"),
m_tag, PropertyName, Prop->GetSize(), sizeof(T));
return nullptr;
}
return reinterpret_cast<T*>(
reinterpret_cast<uint8*>(m_obj) + Prop->GetOffset_Internal());
}
template <typename T>
auto PropertyValue(const TCHAR* PropertyName) const -> std::optional<T>
{
if (T* Ptr = PropertyPtr<T>(PropertyName)) return *Ptr;
return std::nullopt;
}
// Convenience for the most common property shape in this tree: an
// ObjectProperty read back as a bare pointer. Silent on absence.
auto TryObjectProperty(const TCHAR* PropertyName) const -> UObject*
{
auto Ptr = TryPropertyPtr<UObject*>(PropertyName);
return Ptr ? *Ptr : nullptr;
}
// ------------------------------------------------------------------
// For `uint8 bFoo : 1;` UPROPERTY bitfields specifically. Do NOT
// reach for PropertyValue<uint8>/PropertyPtr<uint8> on one of
// these -- it is the wrong tool and it does not fail loudly.
//
// UHT reflects a bitfield as an FBoolProperty whose GetSize()
// reports the storage unit (1 byte), not "1 bit" -- there is no
// size check that can tell a lone byte-backed bool apart from one
// of several single-bit flags packed into a shared byte. When a
// header declares several `uint8 bX : 1;` fields back to back
// (exactly the bOverride_* run on USizeBox), the compiler is free
// to pack all of them into one physical byte. PropertyPtr<uint8>
// would then hand back that WHOLE byte -- every packed flag
// OR'd together -- for a call that named only one of them, and
// both the found-a-property and the size-matches checks would
// pass. That is a silent wrong answer, not a logged failure.
//
// FBoolProperty carries the two pieces of information that make
// this correct: GetByteOffset(), the offset of the specific byte
// within the struct (added ON TOP of the base FProperty offset
// ContainerPtrToValuePtr already applies -- see
// FBoolProperty::GetPropertyValue in UnrealType.hpp, which is
// exactly the two-offset addition this function replicates), and
// GetFieldMask(), the single bit within that byte belonging to
// this specific flag. Test the bit, don't read the byte.
auto PropertyBoolValue(const TCHAR* PropertyName) const -> std::optional<bool>
{
if (!m_obj) return std::nullopt;
FProperty* Prop = m_obj->GetPropertyByNameInChain(PropertyName);
if (!Prop)
{
Output::send<LogLevel::Warning>(
STR("[{}] property '{}' not found on {}\n"),
m_tag, PropertyName, m_obj->GetFullName());
return std::nullopt;
}
FBoolProperty* BoolProp = CastField<FBoolProperty>(Prop);
if (!BoolProp)
{
Output::send<LogLevel::Warning>(
STR("[{}] property '{}' is not a bool/bitfield property on {} -- ")
STR("use PropertyValue<T> for a plain field instead\n"),
m_tag, PropertyName, m_obj->GetFullName());
return std::nullopt;
}
const uint8* Base = reinterpret_cast<const uint8*>(m_obj)
+ Prop->GetOffset_Internal() + BoolProp->GetByteOffset();
return (*Base & BoolProp->GetFieldMask()) != 0;
}
UObject* m_obj{};
const TCHAR* m_tag{ STR("Wrapper") };
};
// ------------------------------------------------------------------
// RawObject -- the wrapper for objects this tree has no class for.
//
// Every helper that wants to read a property off an object it cannot
// name a ClassPath for was previously spelling this inline as a local
// struct deriving UnrealWrapper with Expected=nullptr (see the old
// WidgetTree::FromUserWidget). That spelling takes the "class path did
// not resolve" branch of the checking constructor, which emits a
// Warning and calls GetFullName() EVERY TIME -- on a per-bind path,
// that is the exact cost the UncheckedTag comment above exists to
// avoid.
//
// This uses UncheckedTag, so it is silent by construction. It is the
// same idea as Pal/PalIndividual.hpp's LooseObject, hoisted to where
// the UMG side can reach it without including the Pal headers.
// ------------------------------------------------------------------
class RawObject : public UnrealWrapper
{
public:
RawObject() = default;
explicit RawObject(UObject* Obj, const TCHAR* Tag = STR("Raw"))
: UnrealWrapper(Obj, UncheckedTag{}, Tag) {}
using UnrealWrapper::Call;
using UnrealWrapper::CallVoid;
using UnrealWrapper::CallForReturn;
using UnrealWrapper::PropertyPtr;
using UnrealWrapper::PropertyValue;
using UnrealWrapper::TryPropertyPtr;
using UnrealWrapper::TryObjectProperty;
};
// ------------------------------------------------------------------
// Cheap "is this object one of ours" probe.
//
// Wrapper constructors log when the IsA check fails, which is right for
// "I expected a CanvasPanel and got something else" and wrong for "walk
// this tree and tell me which nodes are panels." Use this to ask first,
// then construct.
//
// A false here can also mean the ClassPath never resolved. That is
// deliberately NOT treated as "yes" -- unlike the wrapper constructors,
// which accept unchecked on an unresolved class because refusing would
// break a call that would otherwise have worked. Here the answer feeds
// a branch, so guessing "yes" would send a non-panel down the panel
// path. ClassOf<T>() logs once on the failed resolve either way.
// ------------------------------------------------------------------
template <typename T>
inline auto ObjectIsA(UObject* Obj) -> bool
{
if (!Obj) return false;
UClass* Cls = ClassOf<T>();
return Cls && Obj->IsA(Cls);
}
}

175
Api/UserWidget.hpp Normal file
View File

@@ -0,0 +1,175 @@
#pragma once
#include <Unreal/NameTypes.hpp>
#include "Widget.hpp"
#include "WidgetTree.hpp"
namespace PalUIExtension::Api
{
// ------------------------------------------------------------------
// UUserWidget. Base for every WBP_..._C generated class.
//
// NAMESPACE: this file previously declared itself in
// PalStatusOverlay::Api while every other header in the tree is in
// PalUIExtension::Api. Nothing included it, so nothing caught it -- the
// first #include from dllmain.cpp would have failed to find any of
// these types. Fixed; if you have local code referring to
// PalStatusOverlay::Api::UserWidget, that is why it no longer resolves.
// Static/WidgetRegistry.hpp had the same split and is fixed to match.
//
// GetWidgetFromName is NOT available on this build. It does not appear
// in the UserWidget.h you pulled from UMG.zip, which is UHT's own
// generated listing of every UFUNCTION this class has. Stock UE ships
// it BlueprintPure; this build either stripped it or the dump is
// incomplete.
//
// That is no longer an obstacle rather than a caveat: FindWidget below
// walks the widget tree instead of calling it. See WidgetTree.hpp for
// why walking beats both the missing UFUNCTION and the global-object
// scan that UnrealApiCall::FindInstance does.
// ------------------------------------------------------------------
class UserWidget : public Widget
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/UMG.UserWidget"); }
static auto DebugTag() -> const TCHAR* { return STR("UserWidget"); }
UserWidget() = default;
explicit UserWidget(UObject* Obj)
: Widget(Obj, ClassOf<UserWidget>(), DebugTag()) {}
// ---- the widget tree ------------------------------------------
//
// WidgetTree is a plain UPROPERTY (Transient/DuplicateTransient,
// not a bitfield) -- direct property read, no ProcessEvent frame.
auto GetWidgetTree() const -> WidgetTree
{
return WidgetTree::FromUserWidget(m_obj);
}
// Find a widget by its designer name anywhere in THIS widget's
// tree. Replaces the GetWidgetFromName call this class used to make
// opportunistically.
//
// Scope defaults to ThisTreeOnly -- names are unique per tree, not
// globally, and a nested WBP has its own "Text_PalName" just like
// its parent does. See WidgetTree::EScope.
auto FindWidget(const TCHAR* Name,
WidgetTree::EScope Scope = WidgetTree::EScope::ThisTreeOnly) -> Widget
{
return GetWidgetTree().Find(Name, Scope);
}
auto FindWidgetObject(const TCHAR* Name,
WidgetTree::EScope Scope = WidgetTree::EScope::ThisTreeOnly) -> UObject*
{
return GetWidgetTree().FindObjectByName(Name, Scope);
}
// Typed variant: UserWidget{W}.FindWidgetAs<CanvasPanel>(STR("Canvas_Base"))
template <typename T>
auto FindWidgetAs(const TCHAR* Name,
WidgetTree::EScope Scope = WidgetTree::EScope::ThisTreeOnly) -> T
{
return GetWidgetTree().FindAs<T>(Name, Scope);
}
// ---- bound widget properties ------------------------------------
//
// The OTHER way to reach a child, and the cheaper one when it
// applies. UMG promotes any widget marked "Is Variable" in the
// designer to a UPROPERTY on the generated class, so the pointer
// sits at a fixed offset -- no walk at all. PalDetailWidget's
// CanvasBase()/TextPalName()/etc. are exactly this.
//
// The catch, and the reason FindWidget still earns its place: this
// only works for widgets that were marked as variables. Anything
// left unpromoted (most auto-named CanvasPanel_N containers) has no
// property and can only be found by walking.
auto BoundObject(const TCHAR* Name) const -> UObject*
{
return TryObjectProperty(Name);
}
// Property first, tree walk second. Use this when you do not know
// whether a given child was promoted to a variable, and do not want
// to care.
auto ChildObject(const TCHAR* Name,
WidgetTree::EScope Scope = WidgetTree::EScope::ThisTreeOnly) -> UObject*
{
if (UObject* Bound = BoundObject(Name)) return Bound;
return FindWidgetObject(Name, Scope);
}
template <typename T>
auto Child(const TCHAR* Name,
WidgetTree::EScope Scope = WidgetTree::EScope::ThisTreeOnly) -> T
{
UObject* O = ChildObject(Name, Scope);
if (!O)
{
Output::send<LogLevel::Warning>(
STR("[{}] no child named '{}' (neither a bound variable nor in the tree)\n"),
m_tag, Name);
return T{};
}
if (!ObjectIsA<T>(O))
{
Output::send<LogLevel::Warning>(
STR("[{}] child '{}' resolved to {}, which is not a {}\n"),
m_tag, Name, O->GetFullName(), T::DebugTag());
return T{};
}
return T{ O };
}
// ---- viewport lifecycle -------------------------------------------
auto AddToViewport(int32 ZOrder) -> bool
{
struct P { int32 ZOrder; } p{ ZOrder };
return Call(STR("AddToViewport"), p);
}
auto RemoveFromViewport() -> bool { return CallVoid(STR("RemoveFromViewport")); }
auto GetIsVisible() -> std::optional<bool> { return CallForReturn<bool>(STR("GetIsVisible")); }
// BlueprintImplementableEvent. Dispatch is by the object's own
// class chain regardless of native-vs-event, so this call works
// the same either way -- but if no Blueprint graph overrides it,
// the engine's default thunk hands back bool's zero value (false),
// not an error. A false here does not prove non-interactable.
auto IsInteractable() -> std::optional<bool> { return CallForReturn<bool>(STR("IsInteractable")); }
// ---- cosmetic setters ----------------------------------------------
auto SetPadding(FMargin_ InPadding) -> bool
{
struct P { FMargin_ InPadding; } p{ InPadding };
return Call(STR("SetPadding"), p);
}
auto SetColorAndOpacity(FLinearColor_ InColorAndOpacity) -> bool
{
struct P { FLinearColor_ InColorAndOpacity; } p{ InColorAndOpacity };
return Call(STR("SetColorAndOpacity"), p);
}
auto SetForegroundColor(FSlateColor_ InForegroundColor) -> bool
{
struct P { FSlateColor_ InForegroundColor; } p{ InForegroundColor };
return Call(STR("SetForegroundColor"), p);
}
// ---- plain properties ------------------------------------------
auto GetPriority() const -> std::optional<int32> { return PropertyValue<int32>(STR("Priority")); }
auto GetColorAndOpacity()const -> std::optional<FLinearColor_>{ return PropertyValue<FLinearColor_>(STR("ColorAndOpacity")); }
auto GetPaddingValue() const -> std::optional<FMargin_> { return PropertyValue<FMargin_>(STR("Padding")); }
// bIsFocusable / bStopAction / bHasScriptImplementedTick /
// bHasScriptImplementedPaint are packed uint8:1 bitfields -- not
// exposed, same reasoning as UWidget::bIsEnabled in Widget.hpp.
// No UFUNCTION getter exists for any of them either; there is no
// clean substitute, only a byte read and a manual mask.
protected:
UserWidget(UObject* Obj, UClass* Expected, const TCHAR* Tag)
: Widget(Obj, Expected, Tag) {}
};
}

27
Api/VerticalBox.hpp Normal file
View File

@@ -0,0 +1,27 @@
#pragma once
#include "PanelWidget.hpp"
#include "VerticalBoxSlot.hpp"
namespace PalUIExtension::Api
{
// UVerticalBox : public UPanelWidget directly -- same shape as
// Overlay / HorizontalBox.
class VerticalBox : public PanelWidget
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/UMG.VerticalBox"); }
static auto DebugTag() -> const TCHAR* { return STR("VerticalBox"); }
VerticalBox() = default;
explicit VerticalBox(UObject* Obj)
: PanelWidget(Obj, ClassOf<VerticalBox>(), DebugTag()) {}
auto AddChildToVerticalBox(UObject* Content) -> VerticalBoxSlot
{
struct P { UObject* Content; UObject* ReturnValue; } p{ Content, nullptr };
auto Result = CallForReturn(STR("AddChildToVerticalBox"), p, &P::ReturnValue);
return (Result && *Result) ? VerticalBoxSlot{ *Result } : VerticalBoxSlot{};
}
};
}

49
Api/VerticalBoxSlot.hpp Normal file
View File

@@ -0,0 +1,49 @@
#pragma once
#include "PanelSlot.hpp"
namespace PalUIExtension::Api
{
// Same four-member surface as HorizontalBoxSlot. Kept separate for
// the same reason -- see HorizontalBoxSlot.hpp.
class VerticalBoxSlot : public PanelSlot
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/UMG.VerticalBoxSlot"); }
static auto DebugTag() -> const TCHAR* { return STR("VerticalBoxSlot"); }
VerticalBoxSlot() = default;
explicit VerticalBoxSlot(UObject* Obj)
: PanelSlot(Obj, ClassOf<VerticalBoxSlot>(), DebugTag()) {}
// ---- setters (write through the UFUNCTION so SynchronizeProperties runs)
auto SetSize(FSlateChildSize_ InSize) -> bool
{
struct P { FSlateChildSize_ InSize; } p{ InSize };
return Call(STR("SetSize"), p);
}
auto SetPadding(FMargin_ InPadding) -> bool
{
struct P { FMargin_ InPadding; } p{ InPadding };
return Call(STR("SetPadding"), p);
}
auto SetHorizontalAlignment(EHorizontalAlignment_ InHorizontalAlignment) -> bool
{
struct P { EHorizontalAlignment_ InHorizontalAlignment; } p{ InHorizontalAlignment };
return Call(STR("SetHorizontalAlignment"), p);
}
auto SetVerticalAlignment(EVerticalAlignment_ InVerticalAlignment) -> bool
{
struct P { EVerticalAlignment_ InVerticalAlignment; } p{ InVerticalAlignment };
return Call(STR("SetVerticalAlignment"), p);
}
// ---- property reads
auto GetSize() const -> std::optional<FSlateChildSize_> { return PropertyValue<FSlateChildSize_>(STR("Size")); }
auto GetPadding() const -> std::optional<FMargin_> { return PropertyValue<FMargin_>(STR("Padding")); }
auto GetHorizontalAlignment() const -> std::optional<EHorizontalAlignment_> { return PropertyValue<EHorizontalAlignment_>(STR("HorizontalAlignment")); }
auto GetVerticalAlignment() const -> std::optional<EVerticalAlignment_> { return PropertyValue<EVerticalAlignment_>(STR("VerticalAlignment")); }
};
}

227
Api/Widget.hpp Normal file
View File

@@ -0,0 +1,227 @@
#pragma once
#include <Unreal/FText.hpp>
#include <Unreal/NameTypes.hpp>
#include "UmgTypes.hpp"
#include "UnrealWrapper.hpp"
namespace PalUIExtension::Api
{
class Widget : public UnrealWrapper
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/UMG.Widget"); }
static auto DebugTag() -> const TCHAR* { return STR("Widget"); }
Widget() = default;
explicit Widget(UObject* Obj) : Widget(Obj, ClassOf<Widget>(), DebugTag()) {}
// ---- visibility / enable ------------------------------------------
auto SetVisibility(ESlateVisibility_ InVisibility) -> bool
{
struct P { ESlateVisibility_ InVisibility; } p{ InVisibility };
return Call(STR("SetVisibility"), p);
}
auto GetVisibility() -> std::optional<ESlateVisibility_>
{
return CallForReturn<ESlateVisibility_>(STR("GetVisibility"));
}
auto SetIsEnabled(bool bInIsEnabled) -> bool
{
struct P { bool bInIsEnabled; } p{ bInIsEnabled };
return Call(STR("SetIsEnabled"), p);
}
auto GetIsEnabled() -> std::optional<bool>
{
return CallForReturn<bool>(STR("GetIsEnabled"));
}
auto IsVisible() -> std::optional<bool> { return CallForReturn<bool>(STR("IsVisible")); }
auto IsRendered() -> std::optional<bool> { return CallForReturn<bool>(STR("IsRendered")); }
auto IsInViewport() -> std::optional<bool> { return CallForReturn<bool>(STR("IsInViewport")); }
auto IsHovered() -> std::optional<bool> { return CallForReturn<bool>(STR("IsHovered")); }
// ---- render transform ---------------------------------------------
auto SetRenderTransform(FWidgetTransform_ InTransform) -> bool
{
struct P { FWidgetTransform_ InTransform; } p{ InTransform };
return Call(STR("SetRenderTransform"), p);
}
auto SetRenderTranslation(FVector2D_ Translation) -> bool
{
struct P { FVector2D_ Translation; } p{ Translation };
return Call(STR("SetRenderTranslation"), p);
}
auto SetRenderScale(FVector2D_ Scale) -> bool
{
struct P { FVector2D_ Scale; } p{ Scale };
return Call(STR("SetRenderScale"), p);
}
auto SetRenderShear(FVector2D_ Shear) -> bool
{
struct P { FVector2D_ Shear; } p{ Shear };
return Call(STR("SetRenderShear"), p);
}
auto SetRenderTransformPivot(FVector2D_ Pivot) -> bool
{
struct P { FVector2D_ Pivot; } p{ Pivot };
return Call(STR("SetRenderTransformPivot"), p);
}
auto SetRenderTransformAngle(float Angle) -> bool
{
struct P { float Angle; } p{ Angle };
return Call(STR("SetRenderTransformAngle"), p);
}
auto GetRenderTransformAngle() -> std::optional<float>
{
return CallForReturn<float>(STR("GetRenderTransformAngle"));
}
auto SetRenderOpacity(float InOpacity) -> bool
{
struct P { float InOpacity; } p{ InOpacity };
return Call(STR("SetRenderOpacity"), p);
}
auto GetRenderOpacity() -> std::optional<float>
{
return CallForReturn<float>(STR("GetRenderOpacity"));
}
// ---- clipping / cursor ---------------------------------------------
auto SetClipping(EWidgetClipping_ InClipping) -> bool
{
struct P { EWidgetClipping_ InClipping; } p{ InClipping };
return Call(STR("SetClipping"), p);
}
auto GetClipping() -> std::optional<EWidgetClipping_>
{
return CallForReturn<EWidgetClipping_>(STR("GetClipping"));
}
auto SetCursor(EMouseCursorByte_ InCursor) -> bool
{
struct P { EMouseCursorByte_ InCursor; } p{ InCursor };
return Call(STR("SetCursor"), p);
}
auto ResetCursor() -> bool { return CallVoid(STR("ResetCursor")); }
// ---- tooltips -------------------------------------------------------
//
// FText here is the same ownership question flagged in
// FTextFromString: you are handing a refcounted object into a frame
// the engine will destruct. Confirm who owns the allocation before
// shipping this on a hot path.
auto SetToolTipText(const FText& InToolTipText) -> bool
{
struct P { FText InToolTipText; } p{ InToolTipText };
return Call(STR("SetToolTipText"), p);
}
auto SetToolTip(UObject* InWidget) -> bool
{
struct P { UObject* Widget; } p{ InWidget };
return Call(STR("SetToolTip"), p);
}
// ---- focus ----------------------------------------------------------
auto SetKeyboardFocus() -> bool { return CallVoid(STR("SetKeyboardFocus")); }
auto SetFocus() -> bool { return CallVoid(STR("SetFocus")); }
auto SetUserFocus(UObject* PlayerController) -> bool
{
struct P { UObject* PlayerController; } p{ PlayerController };
return Call(STR("SetUserFocus"), p);
}
auto HasKeyboardFocus() -> std::optional<bool> { return CallForReturn<bool>(STR("HasKeyboardFocus")); }
auto HasMouseCapture() -> std::optional<bool> { return CallForReturn<bool>(STR("HasMouseCapture")); }
auto HasAnyUserFocus() -> std::optional<bool> { return CallForReturn<bool>(STR("HasAnyUserFocus")); }
auto HasFocusedDescendants()-> std::optional<bool> { return CallForReturn<bool>(STR("HasFocusedDescendants")); }
auto HasUserFocus(UObject* PlayerController) -> std::optional<bool>
{
struct P { UObject* PlayerController; bool ReturnValue; } p{ PlayerController, false };
return CallForReturn(STR("HasUserFocus"), p, &P::ReturnValue);
}
auto HasUserFocusedDescendants(UObject* PlayerController) -> std::optional<bool>
{
struct P { UObject* PlayerController; bool ReturnValue; } p{ PlayerController, false };
return CallForReturn(STR("HasUserFocusedDescendants"), p, &P::ReturnValue);
}
auto HasMouseCaptureByUser(int32 UserIndex, int32 PointerIndex) -> std::optional<bool>
{
struct P { int32 UserIndex; int32 PointerIndex; bool ReturnValue; }
p{ UserIndex, PointerIndex, false };
return CallForReturn(STR("HasMouseCaptureByUser"), p, &P::ReturnValue);
}
// ---- navigation -----------------------------------------------------
auto SetNavigationRuleBase(EUINavigation_ Direction, EUINavigationRule_ Rule) -> bool
{
struct P { EUINavigation_ Direction; EUINavigationRule_ Rule; } p{ Direction, Rule };
return Call(STR("SetNavigationRuleBase"), p);
}
auto SetNavigationRuleExplicit(EUINavigation_ Direction, UObject* InWidget) -> bool
{
struct P { EUINavigation_ Direction; UObject* InWidget; } p{ Direction, InWidget };
return Call(STR("SetNavigationRuleExplicit"), p);
}
auto SetNavigationRule(EUINavigation_ Direction, EUINavigationRule_ Rule, FName WidgetToFocus) -> bool
{
struct P { EUINavigation_ Direction; EUINavigationRule_ Rule; FName WidgetToFocus; }
p{ Direction, Rule, WidgetToFocus };
return Call(STR("SetNavigationRule"), p);
}
auto SetAllNavigationRules(EUINavigationRule_ Rule, FName WidgetToFocus) -> bool
{
struct P { EUINavigationRule_ Rule; FName WidgetToFocus; } p{ Rule, WidgetToFocus };
return Call(STR("SetAllNavigationRules"), p);
}
// ---- layout / lifetime ----------------------------------------------
auto GetDesiredSize() -> std::optional<FVector2D_>
{
return CallForReturn<FVector2D_>(STR("GetDesiredSize"));
}
auto ForceLayoutPrepass() -> bool { return CallVoid(STR("ForceLayoutPrepass")); }
auto InvalidateLayoutAndVolatility() -> bool { return CallVoid(STR("InvalidateLayoutAndVolatility")); }
auto ForceVolatile(bool bForce) -> bool
{
struct P { bool bForce; } p{ bForce };
return Call(STR("ForceVolatile"), p);
}
auto RemoveFromParent() -> bool { return CallVoid(STR("RemoveFromParent")); }
// ---- relationships ---------------------------------------------------
//
// Returns the raw UPanelWidget*. Not a PanelWidget wrapper, because
// PanelWidget derives from this class and cannot be named here.
// Wrap it at the call site: PanelWidget{ *w.GetParentObject() }.
auto GetParentObject() -> std::optional<UObject*>
{
return CallForReturn<UObject*>(STR("GetParent"));
}
// UWidget::Slot is a UPROPERTY with no getter.
auto GetSlotObject() const -> UObject*
{
auto Ptr = PropertyPtr<UObject*>(STR("Slot"));
return Ptr ? *Ptr : nullptr;
}
auto GetOwningPlayer() -> std::optional<UObject*> { return CallForReturn<UObject*>(STR("GetOwningPlayer")); }
auto GetOwningLocalPlayer() -> std::optional<UObject*> { return CallForReturn<UObject*>(STR("GetOwningLocalPlayer")); }
auto GetGameInstance() -> std::optional<UObject*> { return CallForReturn<UObject*>(STR("GetGameInstance")); }
protected:
Widget(UObject* Obj, UClass* Expected, const TCHAR* Tag)
: UnrealWrapper(Obj, Expected, Tag) {}
};
}

View File

@@ -0,0 +1,45 @@
#pragma once
#include <Api/UnrealWrapper.hpp>
#include <Api/WidgetTree.hpp>
namespace PalUIExtension::Api
{
// ==================================================================
// UWidgetBlueprintGeneratedClass -- the CLASS object behind every WBP_...
// ==================================================================
class WidgetBlueprintGeneratedClass : public UnrealWrapper
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/UMG.WidgetBlueprintGeneratedClass"); }
static auto DebugTag() -> const TCHAR* { return STR("WBPGeneratedClass"); }
WidgetBlueprintGeneratedClass() = default;
explicit WidgetBlueprintGeneratedClass(UObject* Obj) : UnrealWrapper(Obj, ClassOf<WidgetBlueprintGeneratedClass>(), DebugTag()) {}
static auto Of(UObject* Instance) -> WidgetBlueprintGeneratedClass
{
if (!Instance) return WidgetBlueprintGeneratedClass{};
UClass* Cls = Instance->GetClassPrivate();
if (!Cls) return WidgetBlueprintGeneratedClass{};
UClass* Expected = ClassOf<WidgetBlueprintGeneratedClass>();
if (!Expected || !Cls->IsA(Expected)) return WidgetBlueprintGeneratedClass{};
return WidgetBlueprintGeneratedClass{ static_cast<UObject*>(Cls) };
}
auto TemplateTree() const -> WidgetTree
{
UObject* Tree = TryObjectProperty(STR("WidgetTree"));
return Tree ? WidgetTree{ Tree } : WidgetTree{};
}
auto TemplateTreeObject() const -> UObject*
{
return TryObjectProperty(STR("WidgetTree"));
}
};
}

125
Api/WidgetLayoutLibrary.hpp Normal file
View File

@@ -0,0 +1,125 @@
#pragma once
#include <optional>
#include <Reflection/UnrealObjectRef.hpp>
#include "UmgTypes.hpp"
#include "UnrealWrapper.hpp"
#include "PanelSlot.hpp"
#include "CanvasPanelSlot.hpp"
namespace PalUIExtension::Api
{
class WidgetLayoutLibrary : public UnrealWrapper
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/UMG.WidgetLayoutLibrary"); }
static auto CDOPath() -> const TCHAR* { return STR("/Script/UMG.Default__WidgetLayoutLibrary"); }
static auto DebugTag() -> const TCHAR* { return STR("WidgetLayoutLibrary"); }
static auto Instance() -> WidgetLayoutLibrary&
{
static WidgetLayoutLibrary Singleton{};
return Singleton;
}
// ---- slot casts ---------------------------------------------------
auto SlotAsCanvasSlot(UObject* Widget) -> CanvasPanelSlot
{
return GetSingleton<CanvasPanelSlot>(STR("SlotAsCanvasSlot"), Widget);
}
auto SlotAsWrapBoxSlot(UObject* Widget) -> PanelSlot { return GetSingleton<PanelSlot>(STR("SlotAsWrapBoxSlot"), Widget); }
auto SlotAsWidgetSwitcherSlot(UObject* Widget) -> PanelSlot { return GetSingleton<PanelSlot>(STR("SlotAsWidgetSwitcherSlot"), Widget); }
auto SlotAsVerticalBoxSlot(UObject* Widget) -> PanelSlot { return GetSingleton<PanelSlot>(STR("SlotAsVerticalBoxSlot"), Widget); }
auto SlotAsUniformGridSlot(UObject* Widget) -> PanelSlot { return GetSingleton<PanelSlot>(STR("SlotAsUniformGridSlot"), Widget); }
auto SlotAsSizeBoxSlot(UObject* Widget) -> PanelSlot { return GetSingleton<PanelSlot>(STR("SlotAsSizeBoxSlot"), Widget); }
auto SlotAsScrollBoxSlot(UObject* Widget) -> PanelSlot { return GetSingleton<PanelSlot>(STR("SlotAsScrollBoxSlot"), Widget); }
auto SlotAsScaleBoxSlot(UObject* Widget) -> PanelSlot { return GetSingleton<PanelSlot>(STR("SlotAsScaleBoxSlot"), Widget); }
auto SlotAsSafeBoxSlot(UObject* Widget) -> PanelSlot { return GetSingleton<PanelSlot>(STR("SlotAsSafeBoxSlot"), Widget); }
auto SlotAsOverlaySlot(UObject* Widget) -> PanelSlot { return GetSingleton<PanelSlot>(STR("SlotAsOverlaySlot"), Widget); }
auto SlotAsHorizontalBoxSlot(UObject* Widget) -> PanelSlot { return GetSingleton<PanelSlot>(STR("SlotAsHorizontalBoxSlot"), Widget); }
auto SlotAsGridSlot(UObject* Widget) -> PanelSlot { return GetSingleton<PanelSlot>(STR("SlotAsGridSlot"), Widget); }
auto SlotAsBorderSlot(UObject* Widget) -> PanelSlot { return GetSingleton<PanelSlot>(STR("SlotAsBorderSlot"), Widget); }
// ---- viewport / mouse ----------------------------------------------
auto RemoveAllWidgets(UObject* WorldContextObject) -> bool
{
struct P { UObject* WorldContextObject; } p{ WorldContextObject };
return Call(STR("RemoveAllWidgets"), p);
}
auto GetViewportSize(UObject* WorldContextObject) -> std::optional<FVector2D_>
{
struct P { UObject* WorldContextObject; FVector2D_ ReturnValue; } p{ WorldContextObject, {} };
return CallForReturn(STR("GetViewportSize"), p, &P::ReturnValue);
}
auto GetViewportScale(UObject* WorldContextObject) -> std::optional<float>
{
struct P { UObject* WorldContextObject; float ReturnValue; } p{ WorldContextObject, 0.f };
return CallForReturn(STR("GetViewportScale"), p, &P::ReturnValue);
}
auto GetMousePositionOnViewport(UObject* WorldContextObject) -> std::optional<FVector2D_>
{
struct P { UObject* WorldContextObject; FVector2D_ ReturnValue; } p{ WorldContextObject, {} };
return CallForReturn(STR("GetMousePositionOnViewport"), p, &P::ReturnValue);
}
auto GetMousePositionOnPlatform() -> std::optional<FVector2D_>
{
return CallForReturn<FVector2D_>(STR("GetMousePositionOnPlatform"));
}
struct MousePositionDPI { float X{}; float Y{}; };
auto GetMousePositionScaledByDPI(UObject* Player) -> std::optional<MousePositionDPI>
{
struct P { UObject* Player; float LocationX; float LocationY; bool ReturnValue; }
p{ Player, 0.f, 0.f, false };
if (!Call(STR("GetMousePositionScaledByDPI"), p) || !p.ReturnValue) return std::nullopt;
return MousePositionDPI{ p.LocationX, p.LocationY };
}
auto ProjectWorldLocationToWidgetPosition(UObject* PlayerController, FVector_ WorldLocation, bool bPlayerViewportRelative) -> std::optional<FVector2D_>
{
struct P
{
UObject* PlayerController;
FVector_ WorldLocation;
FVector2D_ ScreenPosition;
bool bPlayerViewportRelative;
bool ReturnValue;
} p{ PlayerController, WorldLocation, {}, bPlayerViewportRelative, false };
if (!Call(STR("ProjectWorldLocationToWidgetPosition"), p) || !p.ReturnValue) return std::nullopt;
return p.ScreenPosition;
}
private:
WidgetLayoutLibrary() : UnrealWrapper(ResolveCDO(), ClassOf<WidgetLayoutLibrary>(), DebugTag()) {}
WidgetLayoutLibrary(const WidgetLayoutLibrary&) = delete;
auto operator=(const WidgetLayoutLibrary&) -> WidgetLayoutLibrary& = delete;
static auto ResolveCDO() -> UObject*
{
static Reflection::UnrealObjectRef Ref{ CDOPath(), DebugTag() };
return Ref.Get();
}
auto CallStaticObjectGetter(const TCHAR* FunctionName, UObject* InObject) -> UObject*
{
struct P { UObject* InObject; UObject* ReturnValue; } p{ InObject, nullptr };
auto Result = CallForReturn(FunctionName, p, &P::ReturnValue);
return (Result && *Result) ? *Result : nullptr;
}
template <typename T>
auto GetSingleton(const TCHAR* FunctionName, UObject* InObject) -> T
{
UObject* Obj = CallStaticObjectGetter(FunctionName, InObject);
return Obj ? T{ Obj } : T{};
}
};
}

349
Api/WidgetTree.hpp Normal file
View File

@@ -0,0 +1,349 @@
#pragma once
#include <string>
#include <vector>
#include <Unreal/NameTypes.hpp>
#include "UnrealWrapper.hpp"
#include "Widget.hpp"
#include "PanelWidget.hpp"
namespace PalUIExtension::Api
{
class WidgetTree : public UnrealWrapper
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/UMG.WidgetTree"); }
static auto DebugTag() -> const TCHAR* { return STR("WidgetTree"); }
WidgetTree() = default;
explicit WidgetTree(UObject* Obj) : UnrealWrapper(Obj, ClassOf<WidgetTree>(), DebugTag()) {}
static auto FromUserWidget(UObject* UserWidget) -> WidgetTree
{
if (!UserWidget) return WidgetTree{};
UObject* Tree = RawObject{ UserWidget, STR("UserWidget") }.TryObjectProperty(STR("WidgetTree"));
return Tree ? WidgetTree{ Tree } : WidgetTree{};
}
// The tree a widget LIVES IN.
static auto ContainingTree(UObject* AnyWidget) -> WidgetTree
{
if (!AnyWidget) return WidgetTree{};
UClass* TreeClass = ClassOf<WidgetTree>();
if (!TreeClass) return WidgetTree{}; // ClassOf already logged
if (UObject* Tree = AnyWidget->GetTypedOuter(TreeClass))
{
return WidgetTree{ Tree };
}
UObject* Outer = AnyWidget->GetOuterPrivate();
for (int32 Hops = 0; Outer && Hops < kMaxOuterHops; ++Hops)
{
if (Outer->IsA(TreeClass)) return WidgetTree{ Outer };
Outer = Outer->GetOuterPrivate();
}
return WidgetTree{};
}
static auto FromWidget(UObject* AnyWidget) -> WidgetTree
{
if (WidgetTree Owned = FromUserWidget(AnyWidget)) return Owned;
return ContainingTree(AnyWidget);
}
// ---- root ---------------------------------------------------------
auto GetRootWidgetObject() const -> UObject*
{
auto Ptr = PropertyPtr<UObject*>(STR("RootWidget"));
return Ptr ? *Ptr : nullptr;
}
auto Root() const -> Widget
{
UObject* R = GetRootWidgetObject();
return R ? Widget{ R } : Widget{};
}
// ==================================================================
// TRAVERSAL
// ==================================================================
enum class EScope : uint8
{
ThisTreeOnly,
IncludeNestedUserWidgets,
};
// What a visitor wants to happen next.
enum class EVisit : uint8
{
Continue, // descend into this node's children
SkipChildren, // this node is fine, its subtree is not interesting
Stop, // end the walk entirely
};
static constexpr int32 kDefaultNodeBudget = 8192;
static constexpr int32 kMaxDepth = 64;
static constexpr int32 kMaxOuterHops = 32;
template <typename Fn>
auto ForEach(Fn&& Visit, EScope Scope = EScope::ThisTreeOnly, int32 NodeBudget = kDefaultNodeBudget) -> bool
{
WalkState State{ Scope, NodeBudget, false, false };
WalkNode(GetRootWidgetObject(), 0, State, Visit);
if (State.bExhausted)
{
Output::send<LogLevel::Warning>(
STR("[WidgetTree] walk aborted -- exceeded {} nodes or depth {}. ")
STR("Results are incomplete.\n"), NodeBudget, kMaxDepth);
}
return !State.bExhausted;
}
// ==================================================================
// SEARCH
// ==================================================================
auto FindObjectByName(FName Name, EScope Scope = EScope::ThisTreeOnly) -> UObject*
{
if (Name.IsNone()) return nullptr;
UObject* Found = nullptr;
ForEach([&](UObject* Node, int32) -> EVisit {
if (Node->GetNamePrivate() == Name)
{
Found = Node;
return EVisit::Stop;
}
return EVisit::Continue;
}, Scope);
return Found;
}
auto FindObjectByName(const TCHAR* Name, EScope Scope = EScope::ThisTreeOnly) -> UObject*
{
if (!Name) return nullptr;
return FindObjectByName(FName(Name, FNAME_Find), Scope);
}
// Same search, wrapped. Invalid Widget on a miss.
auto Find(const TCHAR* Name, EScope Scope = EScope::ThisTreeOnly) -> Widget
{
UObject* O = FindObjectByName(Name, Scope);
return O ? Widget{ O } : Widget{};
}
template <typename T>
auto FindAs(const TCHAR* Name, EScope Scope = EScope::ThisTreeOnly) -> T
{
UObject* O = FindObjectByName(Name, Scope);
if (!O)
{
Output::send<LogLevel::Warning>(
STR("[WidgetTree] no widget named '{}' in this tree\n"), Name);
return T{};
}
if (!ObjectIsA<T>(O))
{
Output::send<LogLevel::Warning>(
STR("[WidgetTree] '{}' resolved to {}, which is not a {}\n"),
Name, O->GetFullName(), T::DebugTag());
return T{};
}
return T{ O };
}
template <typename T>
auto FindFirstOfType(EScope Scope = EScope::ThisTreeOnly) -> T
{
UClass* Cls = ClassOf<T>();
if (!Cls) return T{};
UObject* Found = nullptr;
ForEach([&](UObject* Node, int32) -> EVisit
{
if (Node->IsA(Cls)) { Found = Node; return EVisit::Stop; }
return EVisit::Continue;
}, Scope);
return Found ? T{ Found } : T{};
}
// Every widget of a given wrapper type, in traversal order.
template <typename T>
auto FindAllOfType(EScope Scope = EScope::ThisTreeOnly) -> std::vector<T>
{
std::vector<T> Out;
UClass* Cls = ClassOf<T>();
if (!Cls) return Out;
ForEach([&](UObject* Node, int32) -> EVisit
{
if (Node->IsA(Cls)) Out.emplace_back(Node);
return EVisit::Continue;
}, Scope);
return Out;
}
// Flat list of every widget object the walk reaches.
auto AllWidgetObjects(EScope Scope = EScope::ThisTreeOnly) -> std::vector<UObject*>
{
std::vector<UObject*> Out;
ForEach([&](UObject* Node, int32) -> EVisit
{
Out.push_back(Node);
return EVisit::Continue;
}, Scope);
return Out;
}
// ==================================================================
// DIAGNOSTICS
// ==================================================================
auto Dump(EScope Scope = EScope::IncludeNestedUserWidgets) -> void
{
if (!m_obj)
{
Output::send<LogLevel::Warning>(STR("[WidgetTree] Dump on an invalid tree\n"));
return;
}
Output::send<LogLevel::Warning>(
STR("[WidgetTree] dump of {}\n"), m_obj->GetFullName());
int32 Count = 0;
ForEach([&](UObject* Node, int32 Depth) -> EVisit
{
++Count;
std::basic_string<TCHAR> Indent;
Indent.reserve(static_cast<size_t>(Depth) * 2);
for (int32 i = 0; i < Depth; ++i) Indent += STR(" ");
UClass* Cls = Node->GetClassPrivate();
std::basic_string<TCHAR> ClassName =
Cls ? Cls->GetName() : std::basic_string<TCHAR>(STR("<no class>"));
if (ObjectIsA<PanelWidget>(Node))
{
Output::send<LogLevel::Warning>(
STR(" {}{} [{}] ({} children)\n"),
Indent.c_str(), Node->GetName().c_str(), ClassName.c_str(),
static_cast<int32>(PanelWidget{ Node }.GetChildObjects().size()));
}
else
{
Output::send<LogLevel::Warning>(
STR(" {}{} [{}]\n"),
Indent.c_str(), Node->GetName().c_str(), ClassName.c_str());
}
return EVisit::Continue;
}, Scope);
Output::send<LogLevel::Warning>(STR("[WidgetTree] {} widgets\n"), Count);
}
// ==================================================================
// CONSTRUCTION
// ==================================================================
auto ConstructWidget(UClass* WidgetClass, const TCHAR* InstanceName) -> UObject*
{
if (!m_obj)
{
Output::send<LogLevel::Warning>(
STR("[WidgetTree] ConstructWidget on an invalid tree\n"));
return nullptr;
}
if (!WidgetClass)
{
Output::send<LogLevel::Warning>(
STR("[WidgetTree] ConstructWidget: null class\n"));
return nullptr;
}
FStaticConstructObjectParameters Params{ WidgetClass };
Params.Outer = m_obj;
if (InstanceName) Params.Name = FName(InstanceName);
UObject* Obj = UObjectGlobals::StaticConstructObject(Params);
if (!Obj)
{
Output::send<LogLevel::Warning>(
STR("[WidgetTree] ConstructWidget: StaticConstructObject returned null for '{}'\n"),
InstanceName ? InstanceName : STR("<unnamed>"));
}
return Obj;
}
template <typename T>
auto ConstructWidget(const TCHAR* InstanceName) -> T
{
UObject* O = ConstructWidget(ClassOf<T>(), InstanceName);
return O ? T{ O } : T{};
}
private:
struct WalkState
{
EScope Scope;
int32 Budget;
bool bStopped;
bool bExhausted;
};
static auto OwnedTreeObject(UObject* Node) -> UObject*
{
return RawObject{ Node, STR("Widget") }.TryObjectProperty(STR("WidgetTree"));
}
template <typename Fn>
static auto WalkNode(UObject* Node, int32 Depth, WalkState& State, Fn& Visit) -> void
{
if (!Node || State.bStopped) return;
if (State.Budget-- <= 0 || Depth >= kMaxDepth)
{
State.bExhausted = true;
State.bStopped = true;
return;
}
const EVisit Result = Visit(Node, Depth);
if (Result == EVisit::Stop) { State.bStopped = true; return; }
if (Result == EVisit::SkipChildren) return;
if (UObject* NestedTree = OwnedTreeObject(Node))
{
if (State.Scope == EScope::IncludeNestedUserWidgets)
{
UObject* NestedRoot =
RawObject{ NestedTree, STR("WidgetTree") }.TryObjectProperty(STR("RootWidget"));
WalkNode(NestedRoot, Depth + 1, State, Visit);
}
return;
}
if (!ObjectIsA<PanelWidget>(Node)) return;
for (UObject* Child : PanelWidget{ Node }.GetChildObjects())
{
if (State.bStopped) return;
WalkNode(Child, Depth + 1, State, Visit);
}
}
};
}

25
Api/WrapBox.hpp Normal file
View File

@@ -0,0 +1,25 @@
#pragma once
#include "PanelWidget.hpp"
#include "PanelSlot.hpp"
namespace PalUIExtension::Api
{
class WrapBox : public PanelWidget
{
public:
static auto ClassPath() -> const TCHAR* { return STR("/Script/UMG.WrapBox"); }
static auto DebugTag() -> const TCHAR* { return STR("WrapBox"); }
WrapBox() = default;
explicit WrapBox(UObject* Obj)
: PanelWidget(Obj, ClassOf<WrapBox>(), DebugTag()) {}
auto AddChildToWrapBox(UObject* Content) -> PanelSlot
{
struct P { UObject* Content; UObject* ReturnValue; } p{ Content, nullptr };
auto Result = CallForReturn(STR("AddChildToWrapBox"), p, &P::ReturnValue);
return (Result && *Result) ? PanelSlot{ *Result } : PanelSlot{};
}
};
}