From 225893e0856a2e980e93b59764de2573286404a4 Mon Sep 17 00:00:00 2001 From: Brendan Borthwick Date: Thu, 21 Sep 2023 15:14:57 -0700 Subject: [PATCH] Menus, TESNPC, Scaleform + Hook --- sfse/GameEvents.h | 30 +++++- sfse/GameFormComponents.h | 43 ++++++-- sfse/GameForms.h | 60 ++++++++++- sfse/GameMenu.h | 193 +++++++++++++++++++++++++++++++++ sfse/GameObjects.h | 40 ++++--- sfse/GameSettings.cpp | 34 +++--- sfse/GameSettings.h | 17 +-- sfse/GameUI.h | 117 +++++++++++++++++++- sfse/Hooks_Scaleform.cpp | 44 ++++++++ sfse/Hooks_Scaleform.h | 4 + sfse/Hooks_Script.cpp | 29 ++++- sfse/PluginAPI.h | 25 +++-- sfse/PluginManager.cpp | 8 ++ sfse/ScaleformFunctions.h | 37 +++++++ sfse/ScaleformMemory.cpp | 3 + sfse/ScaleformMemory.h | 72 +++++++++++++ sfse/ScaleformMovie.h | 145 +++++++++++++++++++++++++ sfse/ScaleformState.h | 71 +++++++++++++ sfse/ScaleformTypes.h | 47 ++++++++ sfse/ScaleformValue.h | 218 ++++++++++++++++++++++++++++++++++++++ 20 files changed, 1170 insertions(+), 67 deletions(-) create mode 100644 sfse/GameMenu.h create mode 100644 sfse/ScaleformFunctions.h create mode 100644 sfse/ScaleformMemory.cpp create mode 100644 sfse/ScaleformMemory.h create mode 100644 sfse/ScaleformMovie.h create mode 100644 sfse/ScaleformState.h create mode 100644 sfse/ScaleformTypes.h create mode 100644 sfse/ScaleformValue.h diff --git a/sfse/GameEvents.h b/sfse/GameEvents.h index 1ed4571..25210e7 100644 --- a/sfse/GameEvents.h +++ b/sfse/GameEvents.h @@ -1,5 +1,7 @@ #pragma once +#include "sfse/GameTypes.h" + namespace BSTEventDetail { class SinkBase @@ -7,6 +9,12 @@ namespace BSTEventDetail public: virtual ~SinkBase() = 0; }; + + class SourceBase + { + public: + virtual ~SourceBase() = 0; + }; } enum EventResult @@ -25,14 +33,30 @@ class BSTEventSink : public BSTEventDetail::SinkBase }; template -class BSTEventSource +class BSTEventSource : public BSTEventDetail::SourceBase { public: - // Sinks go here + virtual ~BSTEventSource(); + + BSTArray> pSinksA; + u32 unk18; + u32 unk1C; + u32 unk20; + u32 unk24; }; struct MenuOpenCloseEvent { - void*/*BSFixedString*/ MenuName; + BSFixedString MenuName; bool bOpening; }; + +struct UpdateSceneRectEvent{}; +struct MenuModeChangeEvent{}; +struct MenuPauseChangeEvent{}; +struct MenuPauseCounterChangeEvent{}; +struct TutorialEvent{}; +struct BSCursorTypeChange{}; +struct BSCursorRotationChange{}; +struct BIUIMenuVisiblePausedBeginEvent{}; +struct BIUIMenuVisiblePausedEndEvent{}; \ No newline at end of file diff --git a/sfse/GameFormComponents.h b/sfse/GameFormComponents.h index 23c37c2..58fcd3f 100644 --- a/sfse/GameFormComponents.h +++ b/sfse/GameFormComponents.h @@ -11,6 +11,7 @@ class BGSPerk; class TESRace; class BGSLocationRefType; class BGSTerminal; +class BGSModelMaterialSwap; class BaseFormComponent { @@ -300,14 +301,7 @@ static_assert(sizeof(TESRaceForm) == 0x10); class BGSOverridePackCollection : public BaseFormComponent { public: - u64 unk08; - u64 unk10; - u64 unk18; - u64 unk20; - u64 unk28; - u64 unk30; - u64 unk38; - u64 unk40; + u64 unk08[8]; }; static_assert(sizeof(BGSOverridePackCollection) == 0x48); @@ -333,3 +327,36 @@ class BGSAttachParentArray : public BaseFormComponent u32 uiSize; // 10 }; static_assert(sizeof(BGSAttachParentArray) == 0x18); + +class TESModel : public BaseFormComponent +{ +public: + virtual void Unk_08(); // 08 - Record related + virtual void Unk_09(); // 09 + virtual void Unk_0A(); // 0A + virtual void Unk_0B(); // 0B + virtual void Unk_0C(); // 0C + virtual void Unk_0D(); // 0D + virtual BGSModelMaterialSwap* GetAsModelMaterialSwap(); // 0E + + BSFixedString cModel; // 08 + u64 unk10; // 10 + u32 unk18; // 18 + u32 unk1C; // 1C +}; +static_assert(sizeof(TESModel) == 0x20); + +class TESModelTri : public TESModel +{ +public: + +}; +static_assert(sizeof(TESModelTri) == 0x20); + +class BGSModelMaterialSwap : public TESModel +{ +public: + float fColorRemappingIndex; // 20 + u32 pad24; // 24 +}; +static_assert(sizeof(BGSModelMaterialSwap) == 0x28); \ No newline at end of file diff --git a/sfse/GameForms.h b/sfse/GameForms.h index bea8034..2fd979b 100644 --- a/sfse/GameForms.h +++ b/sfse/GameForms.h @@ -6,6 +6,7 @@ class TESFile; class TESObjectREFR; +class BGSMorphableObject; class TESForm : public TESFormRefCount, public BSReflection::IObject { @@ -161,4 +162,61 @@ class BGSListForm : u64 unk80; // 80 u64 unk88; // 88 }; -static_assert(sizeof(BGSListForm) == 0x90); \ No newline at end of file +static_assert(sizeof(BGSListForm) == 0x90); + +class BGSMorphableObject : public TESForm +{ +public: + BSFixedString unk38; // 38 + BGSMorphableObject* unk40; // 40 - Seems to point to itself? + BSFixedString performanceMaterial; // 48 + BSFixedString chargenMaterial; // 50 + BSFixedString unk58; // 58 + u32 unk60; // 60 + u32 unk64; // 64 +}; +static_assert(sizeof(BGSMorphableObject) == 0x68); + +class BGSHeadPart : + public TESForm, + public TESFullName, // 38 + public BGSModelMaterialSwap // 48 +{ +public: + enum HeadPartType : s32 + { + HeadPartMisc = 0, + HeadPartFace, + HeadPartRightEye, + HeadPartHair, + HeadPartFacialHair, + HeadPart5, + HeadPartEyebrows, + HeadPartJewelry, + HeadPart8, + HeadPartTeeth, + HeadPart10, + HeadPart11, + HeadPartLeftEye, + HeadPartEyelashes + }; + + BSFixedString unk70; // 70 + BGSHeadPart* unk78; // 78 + TESModel ChargenModel; // 80 + TESModelTri MorphsA[3]; // A0 + u8 unk100; // 100 + u8 pad101[7]; // 101 + u64 unk108; // 108 + BSTArray extraParts; // 110 + BGSMorphableObject* morph; // 120 + u64 unk128; // 128 + BGSListForm* unk130; // 130 + BSFixedString unk138; // 138 + BSFixedString unk140; // 140 + HeadPartType eType; // 148 + u32 unk14C; // 14C +}; +static_assert(sizeof(BGSHeadPart) == 0x150); +static_assert(offsetof(BGSHeadPart, strFullName) == 0x40); +static_assert(offsetof(BGSHeadPart, cModel) == 0x50); \ No newline at end of file diff --git a/sfse/GameMenu.h b/sfse/GameMenu.h new file mode 100644 index 0000000..2c0ea19 --- /dev/null +++ b/sfse/GameMenu.h @@ -0,0 +1,193 @@ +#pragma once + +#include "sfse/ScaleformFunctions.h" +#include "sfse/GameUI.h" +#include "sfse/GameEvents.h" +#include "sfse/ScaleformValue.h" + +class TESNPC; + +namespace Scaleform +{ +namespace GFx +{ +class MovieImpl; +} +} + +class IMenu : + public SWFToCodeFunctionHandler, // 00 + public BSInputEventUser, // 10 + public BSTEventSink // 50 +{ +public: + virtual const char* GetName() const ; // 03 + virtual const char* GetRootPath() const; // 04 + virtual void Unk_05(); // 05 + virtual void Unk_06(); // 06 + virtual bool LoadMovie(bool addEventDispatcher, bool unk2); // 07 + virtual void Unk_08(); // 08 + virtual void Unk_09(); // 09 + virtual void Unk_0A(); // 0A + virtual void Unk_0B(); // 0B + virtual void Unk_0C(); // 0C + virtual void Unk_0D(); // 0D + virtual void Unk_0E(); // 0E + virtual void Unk_0F(); // 0F + virtual void Unk_10(); // 10 + virtual void Unk_11(); // 11 + virtual void Unk_12(); // 12 + virtual void Unk_13(); // 13 + virtual void Unk_14(); // 14 + virtual void Unk_15(); // 15 + virtual void Unk_16(); // 16 + virtual void Unk_17(); // 17 + virtual void Unk_18(); // 18 + virtual void Unk_19(); // 19 + virtual void Unk_1A(); // 1A + + s64 unk58; // 58 + s64 unk60; // 60 + Scaleform::GFx::Value MenuObj; // 80 + Scaleform::GFx::MovieImpl* pUIMovie; // 88 + u64 unk90; // 90 - EventDispatcherBackend? + u64 unk98; // 98 + u64 unkA0; // A0 + u32 unkA8; // A8 + u32 unkAC; // AC + BSFixedString MenuName; // B0 + BSFixedString unkB8; // B8 + u64 unkC0; // C0 - Flags? + u32 unkC8; // C8 + u32 unkCC; // CC + u32 unkD0; // D0 + u32 unkD4; // D4 + u64 unkD8; // D8 + u64 unkE0; // E0 + u64 unkE8; // E8 + u64 unkF0; // F0 + u64 unkF8; // F8 + u64 unk100; // 100 + u32 unk108; // 108 + u32 unk10C; // 10C + u64 unk110; // 110 + u64 unk118; // 118 + u64 unk120; // 120 + u64 unk128; // 128 +}; +static_assert(offsetof(IMenu, pUIMovie) == 0x88); +static_assert(offsetof(IMenu, MenuName) == 0xB0); + +class GameMenuBase : public IMenu +{ +public: + +}; + +struct CharGen_CloseMenu {}; +struct CharGen_PresetChange {}; +struct CharGen_HeadpartPresetChange {}; +struct CharGen_SetSex {}; +struct CharGen_SetSlider {}; +struct CharGen_StartTextEntry {}; +struct CharGen_EndTextEntry {}; +struct CharGen_CancelTextEntry {}; +struct CharGen_ShowPlayerRenameMessage {}; +struct CharGen_ShowChooseBackgroundMessage {}; +struct CharGen_SetBodyValues {}; +struct CharGen_SetCameraPosition {}; +struct CharGen_HeadpartPlusSelectorChange {}; +struct CharGen_SkintoneChange {}; +struct CharGen_DirtScarsEtcChange {}; +struct CharGen_ToggleMarking {}; +struct CharGen_SetTrait {}; +struct CharGen_SetBackground {}; +struct CharGen_EyeColorChange {}; +struct CharGen_BrowChange {}; +struct CharGen_HairChange {}; +struct CharGen_HairColorChange {}; +struct CharGen_FacialHairChange {}; +struct CharGen_FacialHairColorChange {}; +struct CharGen_BrowColorChange {}; +struct CharGen_TeethChange {}; +struct CharGen_CyclePronoun {}; +struct CharGen_SetPronoun {}; +struct CharGen_TogglePreviewHabSuit {}; +struct CharGen_SwitchLocomotion {}; +struct CharGen_SwitchBodyType {}; +struct CharGen_RotatePaperdoll {}; +struct CharGen_RollOnLocomotion {}; +struct CharGen_RollOffLocomotion {}; +struct CharGen_TeethRollOn {}; +struct CharGen_TeethRollOff {}; +struct CharGen_JewelryChange {}; +struct CharGen_JewelryColorChange {}; +struct CharGen_StartBodyChange {}; +struct CharGen_EndBodyChange {}; +struct CharGen_SetAdditionalSlider {}; +struct CharGen_SetBlockInputUnderPopup {}; +struct CharGen_PostBlendFaceChange {}; +struct CharGen_PostBlendColorOptionChange {}; +struct CharGen_PostBlendIntensityChange {}; +struct CharGen_MakeupChange {}; +struct CharGen_MarkingsChange {}; +struct UIMenuChargenMenuDisablePaperdoll {}; + +// 658 +class ChargenMenu : + public IMenu, // 00 + public IDataModel, // 130 + public BSTEventSink, // 140 + public BSTEventSink, // 148 + public BSTEventSink, // 150 + public BSTEventSink, // 158 + public BSTEventSink, // 160 + public BSTEventSink, // 168 + public BSTEventSink, // 170 + public BSTEventSink, // 178 + public BSTEventSink, // 180 + public BSTEventSink, // 188 + public BSTEventSink, // 190 + public BSTEventSink, // 198 + public BSTEventSink, // 1A0 + public BSTEventSink, // 1A8 + public BSTEventSink, // 1B0 + public BSTEventSink, // 1B8 + public BSTEventSink, // 1C0 + public BSTEventSink, // 1C8 + public BSTEventSink, // 1D0 + public BSTEventSink, // 1D8 + public BSTEventSink, // 1E0 + public BSTEventSink, // 1E8 + public BSTEventSink, // 1F0 + public BSTEventSink, // 1F8 + public BSTEventSink, // 200 + public BSTEventSink, // 208 + public BSTEventSink, // 210 + public BSTEventSink, // 218 + public BSTEventSink, // 220 + public BSTEventSink, // 228 + public BSTEventSink, // 230 + public BSTEventSink, // 238 + public BSTEventSink, // 240 + public BSTEventSink, // 248 + public BSTEventSink, // 250 + public BSTEventSink, // 258 + public BSTEventSink, // 260 + public BSTEventSink, // 268 + public BSTEventSink, // 270 + public BSTEventSink, // 278 + public BSTEventSink, // 280 + public BSTEventSink, // 288 + public BSTEventSink, // 290 + public BSTEventSink, // 298 + public BSTEventSink, // 2A0 + public BSTEventSink, // 2A8 + public BSTEventSink, // 2B0 + public BSTEventSink // 2B8 +{ +public: + MenuPaperDoll* pPaperDoll; // 2C0 + TESNPC* npc; // 2C8 + // More ... +}; diff --git a/sfse/GameObjects.h b/sfse/GameObjects.h index 35c9132..ee0aaa9 100644 --- a/sfse/GameObjects.h +++ b/sfse/GameObjects.h @@ -141,7 +141,11 @@ class TESNPC : BSTArray HeadPartsA; // 3F0 u64 unk400; // 400 void* unk408; // 408 - void* unk410; // 410 -- 5 floats entries? + struct Unk410Data + { + float unk00[5]; + }; + Unk410Data* unk410; // 410 -- 5 floats entries? struct Unk418 { struct Data @@ -159,26 +163,30 @@ class TESNPC : u64 start; // 20 count = end - start u64 end; // 28 }; - Unk418* unk418; // 418 -- Unk418? + Unk418* unk418; // 418 -- Unk418? ShapeBlendData probably? struct HeadPartData { - u32 unk00; - u32 unk04; - BSFixedString unk08; - BSFixedString unk10; - BSFixedString texture; - u32 unk20; - u32 unk24; + u32 type; // 00 1 - Mask? + u32 unk04; // 04 + BSFixedString unk08; // 08 + BSFixedString material; // 10 + BSFixedString postBlendDetails; // 18 + struct Color + { + u8 r, g, b, a; + }; + Color color; // 20 + u32 intensity; // 24 up to 128 for some reason replaces alpha, color.a does nothing? }; BSTArray HeadPartDataA; // 420 - u32 unk430; // 430 + u32 skinTone; // 430 u32 unk434; // 434 - BSFixedString unk438; // 438 - BSFixedString unk440; // 440 - BSFixedString unk448; // 448 - BSFixedString unk450; // 450 - BSFixedString unk458; // 458 - BSFixedString unk460; // 460 + BSFixedString teeth; // 438 + BSFixedString jewelryColor; // 440 + BSFixedString eyeColor; // 448 + BSFixedString hairColor; // 450 + BSFixedString facialHairColor; // 458 + BSFixedString eyebrowColor; // 460 u64 unk468; // 468 BSFixedString unk470; // 470 u64 unk478; // 478 diff --git a/sfse/GameSettings.cpp b/sfse/GameSettings.cpp index a9f6d26..6b06831 100644 --- a/sfse/GameSettings.cpp +++ b/sfse/GameSettings.cpp @@ -7,37 +7,35 @@ decltype(SettingT::pCollection) SettingT> 8; break; - case kType_ID: *out = data.u32; break; + case kType_Binary: *out = data.u8 ? 1 : 0; break; default: return false; - case kType_Unknown: return false; + case kType_None: return false; } return true; @@ -47,14 +45,12 @@ bool Setting::SetDouble(double value) { switch (GetType()) { - case kType_Integer: data.s32 = static_cast(value); break; + case kType_Int: data.s32 = static_cast(value); break; case kType_Float: data.f32 = static_cast(value); break; case kType_String: return false; - case kType_Bool: data.u8 = value ? 1 : 0; break; - case kType_ID6: data.u32 = ((u32)value) << 8; break; - case kType_ID: data.u32 = static_cast(value); break; + case kType_Binary: data.u8 = value ? 1 : 0; break; default: return false; - case kType_Unknown: return false; + case kType_None: return false; } return true; diff --git a/sfse/GameSettings.h b/sfse/GameSettings.h index 4ab596d..1110382 100644 --- a/sfse/GameSettings.h +++ b/sfse/GameSettings.h @@ -12,13 +12,16 @@ class Setting enum { - kType_Unknown = 0, - kType_Integer, - kType_Float, - kType_String, - kType_Bool, - kType_ID6, // need to find an example of this - kType_ID, + kType_Binary, // b + kType_Char, // c + kType_UChar, // h + kType_Int, // i + kType_UInt, // u + kType_Float, // f + kType_String, // s/S + kType_RGB, // r + kType_RGBA, // a + kType_None }; union Data diff --git a/sfse/GameUI.h b/sfse/GameUI.h index 9fdd15c..d5e35b8 100644 --- a/sfse/GameUI.h +++ b/sfse/GameUI.h @@ -1,5 +1,55 @@ #pragma once +#include "sfse_common/Types.h" +#include "sfse_common/Utilities.h" +#include "sfse/GameSingleton.h" +#include "sfse/GameEvents.h" +#include + +class IMenu; +class TESObjectCELL; +class MenuActor; +class BGSKeyword; +class TESObjectREFR; + +class UICellRenderer : public BSIntrusiveRefCounted +{ +public: + virtual ~UICellRenderer(); + + TESObjectCELL* unk10; // 10 + void* unk18; // 18 - const BSResource2::TEntryType<0,LoadedCellDB::DBTraits,BSResource2::DBDefaultStreamPolicy> + float unk20; // 20 + float unk24; // 24 + u64 unk28; // 28 + void* unk30[4]; // 30 - const BSResource2::TEntryType<0,TextureDB::DBTraits,BSResource2::DBDefaultStreamPolicy> + u64 unk50; // 50 + u8 unk58[8]; // 58 + u64 unk60; // 60 + u64 unk68; // 68 - BSMenu3D::Scene +}; + +class MenuPaperDoll : public UICellRenderer +{ +public: + virtual ~MenuPaperDoll(); + + u64 unk70; // 70 + u64 unk78; // 78 + u64 unk80; // 80 + u64 unk88; // 88 + u64 unk90; // 90 + u64 unk98; // 98 + u64 unkA0; // A0 + u64 unkA8; // A8 + MenuActor* menuActor; // B0 + u64 unkB8[(0x1A0 - 0xB8) >> 3]; + BGSKeyword* unk1A0; // 1A0 + TESObjectREFR* unk1A8; // 1A8 +}; +static_assert(offsetof(MenuPaperDoll, menuActor) == 0xB0); +static_assert(offsetof(MenuPaperDoll, unk1A0) == 0x1A0); + class IDataModel { public: @@ -8,4 +58,69 @@ class IDataModel virtual void Unk_01(); virtual void Unk_02(); virtual void Unk_03(); -}; \ No newline at end of file + + void* unk08; // 08 - Model object? +}; + +class BSInputEventUser +{ +public: + virtual ~BSInputEventUser(); + + virtual void Unk_01(); + virtual void Unk_02(); + virtual void Unk_03(); + virtual void Unk_04(); + virtual void Unk_05(); + virtual void Unk_06(); + virtual void Unk_07(); + virtual void Unk_08(); + virtual void Unk_09(); + + u64 unk08[(0x38 - 0x08) >> 3]; // 08 + bool InputEventHandlingEnabled; // 38 +}; +static_assert(sizeof(BSInputEventUser) == 0x40); + +class BSInputEventReceiver +{ +public: + virtual ~BSInputEventReceiver(); + virtual void Unk_01() = 0; + + void* unk08; // 08 +}; +static_assert(sizeof(BSInputEventReceiver) == 0x10); + +class UI : + //public BSTSingletonSDM, + public BSInputEventReceiver, // 00 + public BSTEventSource, // 10 + public BSTEventSource, // 38 + public BSTEventSource, // 60 + public BSTEventSource, // 88 + public BSTEventSource, // B0 + public BSTEventSource, // D8 + public BSTEventSource, // 100 + public BSTEventSource, // 128 + public BSTEventSource // 150 +{ +public: + virtual ~UI(); + + virtual void Unk_01(); + + u64 unk178[(0x3F0 - 0x178) >> 3]; // 178 + BSTArray openMenus; // 3F0 + u64 unk400[(0x4F8 - 0x400) >> 3]; // 400 + + // This properly locks the data structure, use this + DEFINE_MEMBER_FN_2(IsMenuOpen, bool, 0x02E70990, const BSFixedString&); + + static UI* GetSingleton() + { + RelocPtr singleton(0x56EE0D0); + return *singleton; + } +}; +static_assert(offsetof(UI, openMenus) == 0x3F0); \ No newline at end of file diff --git a/sfse/Hooks_Scaleform.cpp b/sfse/Hooks_Scaleform.cpp index ebd5590..7c49417 100644 --- a/sfse/Hooks_Scaleform.cpp +++ b/sfse/Hooks_Scaleform.cpp @@ -3,6 +3,10 @@ #include "sfse_common/Types.h" #include "sfse_common/Log.h" #include "sfse_common/SafeWrite.h" +#include "sfse_common/BranchTrampoline.h" +#include "xbyak/xbyak.h" + +#include "sfse/GameMenu.h" #include #ifdef _DEBUG @@ -12,6 +16,28 @@ // Scaleform::Log vtable RelocAddr kHook_Scaleform_Log_Offset(0x04077C48 + 0x08); +typedef bool(*_IMenu_LoadMovie)(IMenu* menu, bool addEventDispatcher, bool unk2); +RelocAddr <_IMenu_LoadMovie> IMenu_LoadMovie(0x02E79914); // IMenu vtable +7 +_IMenu_LoadMovie IMenu_LoadMovie_Original = nullptr; + +static std::list s_plugins; +void RegisterMenuPlugin(SFSEMenuInterface::RegisterCallback callback) +{ + s_plugins.push_back(callback); +} + +bool IMenu_LoadMovie_Hook(IMenu* menu, bool addEventDispatcher, bool unk2) +{ + bool ret = IMenu_LoadMovie_Original(menu, addEventDispatcher, unk2); + + for (auto cb : s_plugins) + { + cb(menu); + } + + return ret; +} + void Scaleform_LogMessageVarg(void*/*Scaleform::GFx::Log::State* */ logger, u32 messageType, const char* fmt, va_list args) { DebugLog::log(DebugLog::kLevel_Message, fmt, args); @@ -25,5 +51,23 @@ void Scaleform_LogMessageVarg(void*/*Scaleform::GFx::Log::State* */ logger, u32 void Hooks_Scaleform_Apply() { + { + struct IMenu_LoadMovie_Code : Xbyak::CodeGenerator { + IMenu_LoadMovie_Code(void* buf) : Xbyak::CodeGenerator(4096, buf) + { + Xbyak::Label retnLabel; + mov(ptr[rsp + 0x10], rbx); + jmp(ptr[rip + retnLabel]); + L(retnLabel); + dq(IMenu_LoadMovie.getUIntPtr() + 5); + } + }; + void* codeBuf = g_localTrampoline.startAlloc(); + IMenu_LoadMovie_Code code(codeBuf); + g_localTrampoline.endAlloc(code.getCurr()); + IMenu_LoadMovie_Original = (_IMenu_LoadMovie)codeBuf; + g_branchTrampoline.write5Branch(IMenu_LoadMovie.getUIntPtr(), (uintptr_t)IMenu_LoadMovie_Hook); + } + safeWrite64(kHook_Scaleform_Log_Offset, (uintptr_t)Scaleform_LogMessageVarg); } \ No newline at end of file diff --git a/sfse/Hooks_Scaleform.h b/sfse/Hooks_Scaleform.h index 95d95ec..f592554 100644 --- a/sfse/Hooks_Scaleform.h +++ b/sfse/Hooks_Scaleform.h @@ -1,3 +1,7 @@ #pragma once +#include "sfse/PluginAPI.h" + +void RegisterMenuPlugin(SFSEMenuInterface::RegisterCallback callback); + void Hooks_Scaleform_Apply(); \ No newline at end of file diff --git a/sfse/Hooks_Script.cpp b/sfse/Hooks_Script.cpp index fbd1637..3bb98ee 100644 --- a/sfse/Hooks_Script.cpp +++ b/sfse/Hooks_Script.cpp @@ -13,15 +13,18 @@ #include "sfse/GameObjects.h" #include "sfse/GameChargen.h" #include "sfse/GameSettings.h" +#include "sfse/GameUI.h" +#include "sfse/GameMenu.h" bool Test_Execute(const SCRIPT_PARAMETER* paramInfo, const char*, TESObjectREFR* thisObj, TESObjectREFR* containingObj, Script* script, ScriptLocals* locals, float* result, u32* opcodeOffsetPtr) { if (thisObj) { - dumpClass(thisObj, 0x1100 >> 3/*0x110 >> 3*/); TESNPC* npc = (TESNPC*)thisObj->data.objectReference; + + /*dumpClass(thisObj, 0x1100 >> 3); _MESSAGE("Name: %s", npc->strFullName.c_str()); dumpClass(npc, 0x488 >> 3); - dumpClass(TESNPCData::ChargenDataModel::GetSingleton(), 0x700 >> 3); + dumpClass(TESNPCData::ChargenDataModel::GetSingleton(), 0x700 >> 3);*/ npc->MorphWeight.x = 1.0f; npc->MorphWeight.y = 1.0f; @@ -33,6 +36,20 @@ bool Test_Execute(const SCRIPT_PARAMETER* paramInfo, const char*, TESObjectREFR* } else { + /*auto ui = UI::GetSingleton(); + //dumpClass(ui, 0x500 >> 3); + for (u32 i = 0; i < ui->openMenus.size; ++i) + { + //dumpClass(ui->openMenus.pData[i], 0x658); + if (ui->openMenus.pData[i]->MenuName == BSFixedString("ChargenMenu")) + { + auto chargenMenu = static_cast(ui->openMenus.pData[i]); + if (chargenMenu->pPaperDoll) + { + dumpClass(chargenMenu->npc, sizeof(TESNPC) >> 3); + } + } + }*/ auto& gameSettings = (*SettingT::pCollection); auto setting = gameSettings->GetSetting("sSkinToneDisplayName"); Console_Print("Game Settings: %s", setting->name); @@ -63,6 +80,9 @@ void ConsoleCommandInit_Hook(void* unk1) for (Script::SCRIPT_FUNCTION* iter = g_firstConsoleCommand; iter->eOutput < (Script::kScript_NumConsoleCommands + Script::kScript_ConsoleOpBase); ++iter) { + if (!iter->pExecuteFunction) + continue; + if (!strcmp(iter->pFunctionName, "BetaComment")) { Script::SCRIPT_FUNCTION& cmd = *iter; @@ -74,7 +94,6 @@ void ConsoleCommandInit_Hook(void* unk1) cmd.pExecuteFunction = GetSFSEVersion_Execute; cmd.bEditorFilter = 0; cmd.bInvalidatesCellList = 0; - break; } #ifdef _DEBUG else if (!strcmp(iter->pFunctionName, "GameComment")) @@ -88,8 +107,10 @@ void ConsoleCommandInit_Hook(void* unk1) cmd.pExecuteFunction = Test_Execute; cmd.bEditorFilter = 0; cmd.bInvalidatesCellList = 0; - break; } + + RelocAddr EmptyFunc(0x1256000); + _MESSAGE("%s (%s)", iter->pFunctionName, iter->pExecuteFunction == EmptyFunc ? "Empty" : "Implemented"); #endif } } diff --git a/sfse/PluginAPI.h b/sfse/PluginAPI.h index bc9ece6..8c805ec 100644 --- a/sfse/PluginAPI.h +++ b/sfse/PluginAPI.h @@ -3,15 +3,9 @@ #include typedef std::uint32_t PluginHandle; // treat this as an opaque type -class GFxMovieView; -class GFxValue; -class TaskDelegate; -class UIDelegate_v1; -class InventoryEntryData; -class SFSEDelayFunctorManager; -class SFSEObjectRegistry; -class SFSEPersistentObjectStorage; + class BranchTrampoline; +class IMenu; struct PluginInfo { @@ -35,6 +29,7 @@ enum kInterface_Invalid = 0, kInterface_Messaging, kInterface_Trampoline, + kInterface_Menu, kInterface_Max, }; @@ -112,6 +107,20 @@ struct SFSEMessagingInterface bool (* Dispatch)(PluginHandle sender, std::uint32_t messageType, void * data, std::uint32_t dataLen, const char* receiver); }; +struct SFSEMenuInterface +{ + enum + { + kInterfaceVersion = 1 + }; + + std::uint32_t interfaceVersion; + + // This callback will be called once for every new menu where its MovieImpl is loaded. + typedef bool (*RegisterCallback)(IMenu* menu); + void (*Register)(RegisterCallback callback); +}; + struct SFSETrampolineInterface { enum diff --git a/sfse/PluginManager.cpp b/sfse/PluginManager.cpp index 0815268..9185521 100644 --- a/sfse/PluginManager.cpp +++ b/sfse/PluginManager.cpp @@ -7,6 +7,8 @@ #include "sfse_common/Log.h" #include "sfse_common/Errors.h" +#include "sfse/Hooks_Scaleform.h" + PluginManager g_pluginManager; PluginManager::LoadedPlugin * PluginManager::s_currentLoadingPlugin = nullptr; @@ -41,6 +43,12 @@ static SFSEMessagingInterface g_SFSEMessagingInterface = PluginManager::dispatchMessage, }; +static const SFSEMenuInterface g_SFSEMenuInterface = +{ + SFSEMenuInterface::kInterfaceVersion, + RegisterMenuPlugin, +}; + PluginManager::PluginManager() { // diff --git a/sfse/ScaleformFunctions.h b/sfse/ScaleformFunctions.h new file mode 100644 index 0000000..c4060d7 --- /dev/null +++ b/sfse/ScaleformFunctions.h @@ -0,0 +1,37 @@ +#pragma once + +#include "sfse_common/Types.h" +#include "sfse/ScaleformTypes.h" + +namespace Scaleform +{ +namespace GFx +{ +class Value; +class Movie; +class FunctionHandler : public RefCountBase +{ +public: + virtual ~FunctionHandler(); + + struct Params + { + Value* pRetVal; + Movie* pMovie; + Value* pThis; + Value* pArgsWithThisRef; + Value* pArgs; + u32 ArgCount; + void* pUserData; + }; + + virtual void Call(const Params*); +}; +} +} + +class SWFToCodeFunctionHandler : public Scaleform::GFx::FunctionHandler +{ +public: + virtual void MapCodeObjectFunctions(); +}; \ No newline at end of file diff --git a/sfse/ScaleformMemory.cpp b/sfse/ScaleformMemory.cpp new file mode 100644 index 0000000..bbeff36 --- /dev/null +++ b/sfse/ScaleformMemory.cpp @@ -0,0 +1,3 @@ +#include "sfse/ScaleformMemory.h" + +RelocPtr Scaleform::Memory::pGlobalHeap(0x05429A98); diff --git a/sfse/ScaleformMemory.h b/sfse/ScaleformMemory.h new file mode 100644 index 0000000..19552a1 --- /dev/null +++ b/sfse/ScaleformMemory.h @@ -0,0 +1,72 @@ +#pragma once + +#include "sfse/ScaleformTypes.h" +#include "sfse_common/Relocation.h" + +namespace Scaleform +{ +class StatBag; +class SysAllocPaged; +class AllocInfo; + +class Heap +{ +public: + class SegVisitor {}; + class MemVisitor {}; +}; + +class MemoryHeap +{ +public: + virtual ~MemoryHeap(); + + class HeapDesc {}; + class LimitHandler {}; + class HeapTracer {}; + class RootStats {}; + class SegVisitor {}; + + virtual void CreateArena(UPInt arena, SysAllocPaged*) = 0; + virtual void DestroyArena(UPInt arena) = 0; + virtual bool ArenaIsEmpty(UPInt arena) = 0; + virtual MemoryHeap* CreateHeap(const char* name, const HeapDesc& desc) = 0; + virtual void SetLimitHandler(LimitHandler* handler) = 0; + virtual void SetLimit(UPInt newLimit) = 0; + virtual void AddRef() = 0; + virtual void Release() = 0; + virtual void* Alloc(UPInt size, UPInt align, const AllocInfo* info = nullptr) = 0; + virtual void* Alloc(UPInt size, const AllocInfo* info = nullptr) = 0; + virtual void* Realloc(void* oldPtr, UPInt newSize) = 0; + virtual void Free(void* ptr) = 0; + virtual void* AllocAutoHeap(const void* thisPtr, UPInt size, UPInt align, const AllocInfo* info) = 0; + virtual void* AllocAutoHeap(const void* thisPtr, UPInt size, const AllocInfo* info) = 0; + virtual MemoryHeap* GetAllocHeap(const void* thisPtr) = 0; + virtual UPInt GetUsableSize(const void* ptr) = 0; + virtual void* AllocSysDirect(UPInt size) = 0; + virtual void FreeSysDirect(void* ptr, UPInt size) = 0; + virtual bool GetStats(StatBag* bag) = 0; + virtual UPInt GetFootprint() const = 0; + virtual UPInt GetTotalFootprint() const = 0; + virtual UPInt GetUsedSpace() const = 0; + virtual UPInt GetTotalUsedSpace() const = 0; + virtual void GetRootStats(RootStats* stats) = 0; + virtual void VisitMem(Heap::MemVisitor* visitor, unsigned int flags) = 0; + virtual void VisitRootSegments(SegVisitor* visitor) = 0; + virtual void VisitHeapSegments(SegVisitor* visitor) const = 0; + virtual void SetTracer(HeapTracer* tracer) = 0; + virtual void destroyItself() = 0; + virtual void ultimateCheck() = 0; + virtual void releaseCachedMem() = 0; + virtual bool dumpMemoryLeaks() = 0; + virtual void checkIntegrity() const = 0; + virtual void getUserDebugStats(RootStats* stats) const = 0; + + // Members... +}; +class Memory +{ +public: + static RelocPtr pGlobalHeap; +}; +} \ No newline at end of file diff --git a/sfse/ScaleformMovie.h b/sfse/ScaleformMovie.h new file mode 100644 index 0000000..fed79cd --- /dev/null +++ b/sfse/ScaleformMovie.h @@ -0,0 +1,145 @@ +#pragma once + +#include "sfse/ScaleformState.h" + +namespace Scaleform +{ +class MemoryHeap; +class Log; +class AmpMovieObjectDesc; +class String; + +namespace Render +{ +template +class Point +{ + T x; + T y; +}; +namespace Text +{ +class Allocator; +} +} + +namespace GFx +{ +class Movie : + public RefCountBase, + public StateBag +{ +public: + virtual ~Movie(); + + enum SetVarType + { + SV_Normal, + SV_Sticky1, + SV_Permanent, + }; + + enum SetArrayType + { + SA_Int, + SA_Double, + SA_Float, + SA_String, + SA_StringW, + SA_Value, + }; + + Ptr pASMovieRoot; +}; + +class MovieImpl : public Movie +{ +public: + // More... +}; + +class MovieDefImpl; +class ASString; +class DisplayObject; +class InputEventsQueueEntry; +class InteractiveObject; +class ProcessFocusKeyInfo; +class EventId; +class OrientationEvent; +class AppLifecycleEvent; +class AccelerometerEvent; +class GeolocationEvent; +class Value; +class MemoryContext; +class ASStringManager; +class FunctionHandler; + +class ASMovieRootBase : public RefCountBase +{ +public: + virtual ~ASMovieRootBase(); + + virtual void SetMovie(MovieImpl*); + virtual void AdvanceFrame(bool); + virtual void ChangeMouseCursorType(unsigned int, unsigned int); + virtual bool CheckAvm(); + virtual void ClearDisplayList(); + virtual MovieDefRootNode* CreateMovieDefRootNode(MemoryHeap*, MovieDefImpl*, bool); + virtual void DoActions(); + virtual InteractiveObject* FindTarget(const ASString*); + virtual void ForceCollect(unsigned int); + virtual void ForceEmergencyCollect(); + virtual void SuspendGC(bool); + virtual void ScheduleGC(unsigned int); + virtual void GenerateMouseEvents(unsigned int); + virtual void GenerateTouchEvents(unsigned int); + virtual void GenerateGestureEvents(InteractiveObject*, unsigned int, const Render::Point*, const Render::Point*, const Render::Point*, float, unsigned int); + virtual DisplayObjContainer* GetRootMovie(DisplayObject*); + virtual bool Init(MovieDefImpl*); + virtual void NotifyMouseEvent(const InputEventsQueueEntry*, const MouseState*, int); + virtual void NotifyOnResize(); + virtual void NotifyQueueSetFocus(InteractiveObject*, unsigned int, FocusMovedType); + virtual void NotifyTransferFocus(InteractiveObject*, InteractiveObject*, unsigned int); + virtual bool NotifyOnFocusChange(InteractiveObject*, InteractiveObject*, unsigned int, FocusMovedType, ProcessFocusKeyInfo*); + virtual void NotifyGamePadAnalogEvent(const EventId*); + virtual void NotifyStatusEvent(const EventId*); + virtual void NotifyAccelerometerEvent(const EventId*, int); + virtual void NotifyGeolocationEvent(const EventId*, int); + virtual void OnMovieFocus(bool); + virtual void OnNextFrame(); + virtual void OnDeviceOrientationChanged(const OrientationEvent*); + virtual void OnAppLifecycleEvent(const AppLifecycleEvent*); + virtual void OnAccelerometerEvent(const AccelerometerEvent*); + virtual void OnGeolocationEvent(const GeolocationEvent*); + virtual void PrintObjectsReport(unsigned int, Log*, const char*); + virtual void GetObjectsTree(struct /*Array, 2, ArrayDefaultPolicy>**/void*, MemoryHeap*); + virtual AmpMovieObjectDesc* GetDisplayObjectsTree(MemoryHeap*); + virtual void ProcessLoadQueueEntry(LoadQueueEntry*, LoadStates*); + virtual void ProcessLoadVarsMT(LoadQueueEntry*, LoadStates*, const String*, unsigned __int64, bool); + virtual void ProcessLoadBinaryMT(LoadQueueEntry*, LoadStates*, const /*ArrayPOD**/void*, unsigned __int64, bool); + virtual void RegisterAuxASClasses(); + virtual void ResolveStickyVariables(InteractiveObject*); + virtual void SetExternalInterfaceRetVal(const Value*); + virtual void SetMemoryParams(unsigned int, unsigned int); + virtual void Shutdown(); + virtual void CreateString(Value*, const char*); + virtual void CreateStringW(Value*, const wchar_t*); + virtual void CreateObject(Value*, const char*, const Value*, unsigned int); + virtual void CreateArray(Value*); + virtual void CreateFunction(Value*, FunctionHandler*, void*); + virtual bool SetVariable(const char*, const Value*, Movie::SetVarType); + virtual bool GetVariable(Value*, const char*); + virtual bool SetVariableArray(Movie::SetArrayType, const char*, unsigned int, const void*, unsigned int, Movie::SetVarType); + virtual bool SetVariableArraySize(const char*, unsigned int, Movie::SetVarType); + virtual unsigned int GetVariableArraySize(const char*); + virtual bool GetVariableArray(Movie::SetArrayType, const char*, unsigned int, void*, unsigned int); + virtual bool IsAvailable(const char*); + virtual bool Invoke(const char*, Value*, const char*, ...); + virtual bool Invoke(const char*, Value*, const Value*, unsigned int); + virtual bool InvokeArgs(const char*, Value*, const char*, char*); + virtual MemoryContext* GetMemoryContext(); + virtual ASStringManager* GetStringManager(); + virtual Render::Text::Allocator* GetTextAllocator(); +}; +} +} \ No newline at end of file diff --git a/sfse/ScaleformState.h b/sfse/ScaleformState.h new file mode 100644 index 0000000..f23fe8b --- /dev/null +++ b/sfse/ScaleformState.h @@ -0,0 +1,71 @@ +#pragma once + +#include "sfse/ScaleformTypes.h" + +namespace Scaleform +{ +namespace GFx +{ +struct FileTypeConstants{}; + +class State : public RefCountBase +{ +public: + enum StateType + { + State_None, + State_Translator, + State_Log, + State_ActionControl, + State_UserEventHandler, + State_FSCommandHandler, + State_ExternalInterface, + State_FileOpener, + State_URLBuilder, + State_ImageCreator, + State_ImageFileHandlerRegistry, + State_ParseControl, + State_ProgressHandler, + State_ImportVisitor, + State_FontPackParams, + State_FontLib, + State_FontProvider, + State_FontMap, + State_TaskManager, + State_TextClipboard, + State_TextKeyMap, + State_PreprocessParams, + State_IMEManager, + State_XMLSupport, + State_ZlibSupport, + State_FontCompactorParams, + State_ImagePackerParams, + State_Audio, + State_Video, + State_TestStream, + State_SharedObject, + State_LocSupport, + State_AS2Support, + State_AS3Support + }; +public: + State(StateType st = State_None) : SType(st) { } + virtual ~State() { } + + StateType SType; +}; + +class StateBag : public FileTypeConstants +{ +protected: + virtual StateBag* GetStateBagImpl() const { return 0; } + +public: + virtual ~StateBag() { } + + virtual void SetState(State::StateType state, State* pstate); + virtual State* GetStateAddRef(State::StateType state) const; + virtual void GetStatesAddRef(State** pstateList, const State::StateType* pstates, unsigned count) const; +}; +} +} \ No newline at end of file diff --git a/sfse/ScaleformTypes.h b/sfse/ScaleformTypes.h new file mode 100644 index 0000000..d1ea18a --- /dev/null +++ b/sfse/ScaleformTypes.h @@ -0,0 +1,47 @@ +#pragma once + +#include "sfse_common/Types.h" + +namespace Scaleform +{ +using UPInt = unsigned __int64; + +class RefCountImplCore +{ +public: + RefCountImplCore() : RefCount(1) { } + virtual ~RefCountImplCore() { } + + volatile int RefCount; // 08 +}; + +class RefCountImpl : public RefCountImplCore +{ +public: + RefCountImpl() { } + virtual ~RefCountImpl() { } +}; + +template +class RefCountBaseStatImpl : public T +{ +public: + RefCountBaseStatImpl() { } + virtual ~RefCountBaseStatImpl() { } +}; + +template +class RefCountBase : public RefCountBaseStatImpl +{ +public: + RefCountBase() { } + virtual ~RefCountBase() { } +}; + +template +class Ptr +{ +public: + T* pObject; // 00 +}; +} \ No newline at end of file diff --git a/sfse/ScaleformValue.h b/sfse/ScaleformValue.h new file mode 100644 index 0000000..e3ed56c --- /dev/null +++ b/sfse/ScaleformValue.h @@ -0,0 +1,218 @@ +#pragma once + +#include + +namespace Scaleform +{ +namespace GFx +{ +class MovieImpl; +class Value +{ +public: + enum ValueType + { + VT_Undefined = 0, + VT_Null, + VT_Boolean, + VT_Int, + VT_UInt, + VT_Number, + VT_String, + VT_StringW, + VT_Object, + VT_Array, + VT_DisplayObject, + VT_Function, + }; + enum ValueTypeControl + { + VTC_ConvertBit = 0x80, + VTC_ManagedBit = 0x40, + VTC_TypeMask = VTC_ConvertBit | 0x0F, + }; + union ValueUnion + { + int IValue; + unsigned int UIValue; + long double NValue; + bool BValue; + const char* pString; + const char** pStringManaged; + const wchar_t* pStringW; + void* pData; + }; + + class ObjectInterface + { + public: + class ArrVisitor + { + public: + virtual ~ArrVisitor() {} + virtual void Visit(unsigned int idx, Value* val) = 0; + }; + class ObjVisitor + { + public: + virtual ~ObjVisitor() { } + virtual bool ShowDisplayMembers() { return false; } + virtual void Visit(const char* member, Value* value) = 0; + }; + + virtual ~ObjectInterface(); + + DEFINE_MEMBER_FN_3(HasMember, bool, 0x00ECF900, void* pData, const char* name, bool isDisplayObj); + DEFINE_MEMBER_FN_4(GetMember, bool, 0x00ECFC40, void* pData, const char* name, Value* pValue, bool isDisplayObj); + DEFINE_MEMBER_FN_4(SetMember, bool, 0x00ED0040, void* pData, const char* name, const Value& pValue, bool isDisplayObj); + DEFINE_MEMBER_FN_6(Invoke, bool, 0x00ED0460, void* pData, Value* result, const char* name, const Value* args, unsigned long long numArgs, bool isDisplayObj); + DEFINE_MEMBER_FN_4(CreateEmptyMovieClip, bool, 0x00ED3B80, void* pData, Value* pValue, const char* instanceName, int depth); + DEFINE_MEMBER_FN_6(AttachMovie, bool, 0x00ED3E40, void* pData, Value* pValue, const char* symbolName, const char* instanceName, int depth, const void* initArgs); + DEFINE_MEMBER_FN_2(GetArraySize, unsigned int, 0x00ED1740, void* pData); + DEFINE_MEMBER_FN_2(SetArraySize, bool, 0x00ED1750, void* pData, unsigned int size); + DEFINE_MEMBER_FN_3(GetElement, bool, 0x00ED1770, void* pData, unsigned int index, Value* value); + DEFINE_MEMBER_FN_3(SetElement, bool, 0x00ED1800, void* pData, unsigned int index, const Value& value); + DEFINE_MEMBER_FN_2(PushBack, bool, 0x00ED1A40, void* pData, const Value& value); + DEFINE_MEMBER_FN_2(PopBack, bool, 0x00ED1AA0, void* pData, Value* value); + DEFINE_MEMBER_FN_3(RemoveElements, bool, 0x00ED1B50, void* pData, unsigned idx, int count); + DEFINE_MEMBER_FN_4(VisitElements, void, 0x00ED1860, void* pData, ArrVisitor* visitor, unsigned long idx, int count); + DEFINE_MEMBER_FN_3(VisitMembers, void, 0x00ED1000, void* pData, ObjVisitor* visitor, bool isDisplayObj); + // DeleteMember TBD + DEFINE_MEMBER_FN_3(GetText, bool, 0x00ED3810, void* pData, Value* value, bool html); + DEFINE_MEMBER_FN_3(SetText, bool, 0x00ED3970, void* pData, const char* text, bool html); + DEFINE_MEMBER_FN_3(SetTextW, bool, 0x00ED3A90, void* pData, const wchar_t* text, bool html); + DEFINE_MEMBER_FN_3(GotoAndPlayL, bool, 0x0ED42F0, void* pData, const char* frame, bool stop); + DEFINE_MEMBER_FN_3(GotoAndPlay, bool, 0x0ED43F0, void* pData, unsigned frame, bool stop); + DEFINE_MEMBER_FN_2(GetParent, bool, 0x0ED2070, void* pData, Value* value); + // GetDisplayInfo TBD + // SetDisplayInfo TBD + // GetDisplayMatrix TBD + // SetDisplayMatrix TBD + // GetColorTransform TBD + // SetColorTransform TBD + + DEFINE_MEMBER_FN_2(ObjectAddRef, void, 0x00ECF7A0, Value* value, void* pData); + DEFINE_MEMBER_FN_2(ObjectRelease, void, 0x00ECF840, Value* value, void* pData); + + MovieImpl* pMovieRoot; + }; + + using ObjectVisitor = ObjectInterface::ObjVisitor; + using ArrayVisitor = ObjectInterface::ArrVisitor; + + Value() : pObjectInterface(nullptr), Type(VT_Undefined) { } + Value(ValueType type) : pObjectInterface(nullptr), Type(type) { mValue.pString = 0; } + Value(double v) : pObjectInterface(nullptr), Type(VT_Number) { mValue.NValue = v; } + Value(bool v) : pObjectInterface(nullptr), Type(VT_Boolean) { mValue.BValue = v; } + Value(const char* ps) : pObjectInterface(nullptr), Type(VT_String) { mValue.pString = ps; } + Value(const wchar_t* ps) : pObjectInterface(nullptr), Type(VT_StringW) { mValue.pStringW = ps; } + Value(const Value& src) : pObjectInterface(nullptr), Type(src.Type) + { + mValue = src.mValue; + if (src.IsManagedValue()) AcquireManagedValue(src); + } + ~Value() { if (IsManagedValue()) ReleaseManagedValue(); } + + const Value& operator=(const Value& src) + { + if (this != &src) + { + if (IsManagedValue()) ReleaseManagedValue(); + Type = src.Type; + mValue = src.mValue; + if (src.IsManagedValue()) AcquireManagedValue(src); + } + return *this; + } + + bool operator==(const Value& other) const + { + if (Type != other.Type) return false; + switch (Type & 0x0F) + { + case VT_Boolean: return mValue.BValue == other.mValue.BValue; + case VT_Number: return mValue.NValue == other.mValue.NValue; + case VT_String: return !strcmp(GetString(), other.GetString()); + case VT_StringW: return !wcscmp(GetStringW(), other.GetStringW()); + default: return mValue.pData == other.mValue.pData; + } + } + + bool IsManagedValue() const { return (Type & VTC_ManagedBit) != 0; } + ValueType GetType() const { return ValueType(Type & VTC_TypeMask); } + bool IsUndefined() const { return GetType() == VT_Undefined; } + bool IsNull() const { return GetType() == VT_Null; } + bool IsBool() const { return GetType() == VT_Boolean; } + bool IsNumber() const { return GetType() == VT_Number; } + bool IsString() const { return GetType() == VT_String; } + bool IsStringW() const { return GetType() == VT_StringW; } + bool IsObject() const { return (GetType() == VT_Object) || GetType() == VT_Array || GetType() == VT_DisplayObject; } + bool IsArray() const { return GetType() == VT_Array; } + bool IsDisplayObject() const { return GetType() == VT_DisplayObject; } + bool GetBool() const { return mValue.BValue; } + long double GetNumber() const { return mValue.NValue; } + const char* GetString() const { return IsManagedValue() ? *mValue.pStringManaged : mValue.pString; } + const wchar_t* GetStringW() const { return mValue.pStringW; } + + void SetUndefined() { ChangeType(VT_Undefined); } + void SetNull() { ChangeType(VT_Null); } + void SetBoolean(bool v) { ChangeType(VT_Boolean); mValue.BValue = v; } + void SetNumber(double v) { ChangeType(VT_Number); mValue.NValue = v; } + + // You probably want to a Managed String via CreateString from the owning Movie + void SetString(const char* p) { ChangeType(VT_String); mValue.pString = p; } + void SetStringW(const wchar_t* p) { ChangeType(VT_StringW); mValue.pStringW = p; } + + void ChangeType(ValueType type) + { + if (IsManagedValue()) ReleaseManagedValue(); + Type = type; + } + void AcquireManagedValue(const Value& src) + { + pObjectInterface = src.pObjectInterface; + pObjectInterface->ObjectAddRef(this, mValue.pData); + } + void ReleaseManagedValue() + { + pObjectInterface->ObjectRelease(this, mValue.pData); + pObjectInterface = nullptr; + } + + bool HasMember(const char* name) const { return pObjectInterface->HasMember(mValue.pData, name, IsDisplayObject()); } + bool GetMember(const char* name, Value* pval) const { return pObjectInterface->GetMember(mValue.pData, name, pval, IsDisplayObject()); } + bool SetMember(const char* name, const Value& val) { return pObjectInterface->SetMember(mValue.pData, name, val, IsDisplayObject()); } + bool Invoke(const char* name, Value* presult, const Value* pargs, unsigned long long nargs) { return pObjectInterface->Invoke(mValue.pData, presult, name, pargs, nargs, IsDisplayObject()); } + bool Invoke(const char* name, Value* presult = nullptr) { return Invoke(name, presult, nullptr, 0); } + void VisitMembers(ObjectVisitor* visitor) const { return pObjectInterface->VisitMembers(mValue.pData, visitor, IsDisplayObject()); } + bool SetArraySize(unsigned sz) { return pObjectInterface->SetArraySize(mValue.pData, sz); } + bool GetElement(unsigned idx, Value* pval) const { return pObjectInterface->GetElement(mValue.pData, idx, pval); } + bool SetElement(unsigned idx, const Value& val) { return pObjectInterface->SetElement(mValue.pData, idx, val); } + void VisitElements(ArrayVisitor* visitor, unsigned idx, int count = -1) const { return pObjectInterface->VisitElements(mValue.pData, visitor, idx, count); } + void VisitElements(ArrayVisitor* visitor) const { VisitElements(visitor, 0); } + bool PushBack(const Value& val) { return pObjectInterface->PushBack(mValue.pData, val); } + bool PopBack(Value* pval = nullptr) { return pObjectInterface->PopBack(mValue.pData, pval); } + bool RemoveElements(unsigned idx, int count = -1) { return pObjectInterface->RemoveElements(mValue.pData, idx, count); } + bool RemoveElement(unsigned idx) { return RemoveElements(idx, 1); } + bool ClearElements() { return RemoveElements(0); } + bool SetText(const char* ptext) { return pObjectInterface->SetText(mValue.pData, ptext, false); } + bool SetText(const wchar_t* ptext) { return pObjectInterface->SetTextW(mValue.pData, ptext, false); } + bool SetTextHTML(const char* phtml) { return pObjectInterface->SetText(mValue.pData, phtml, true); } + bool SetTextHTML(const wchar_t* phtml) { return pObjectInterface->SetTextW(mValue.pData, phtml, true); } + bool GetText(Value* pval) const { return pObjectInterface->GetText(mValue.pData, pval, false); } + bool GetTextHTML(Value* pval) const { return pObjectInterface->GetText(mValue.pData, pval, true); } + bool CreateEmptyMovieClip(Value* pmc, const char* instanceName, int depth = -1) { return pObjectInterface->CreateEmptyMovieClip(mValue.pData, pmc, instanceName, depth); } + bool AttachMovie(Value* pmc, const char* symbolName, const char* instanceName, int depth = -1, const void* initArgs = nullptr){ return pObjectInterface->AttachMovie(mValue.pData, pmc, symbolName, instanceName, depth, initArgs); } + bool GotoAndPlay(const char* frame) { return pObjectInterface->GotoAndPlayL(mValue.pData, frame, false); } + bool GotoAndStop(const char* frame) { return pObjectInterface->GotoAndPlayL(mValue.pData, frame, true); } + bool GotoAndPlay(unsigned frame) { return pObjectInterface->GotoAndPlay(mValue.pData, frame, false); } + bool GotoAndStop(unsigned frame) { return pObjectInterface->GotoAndPlay(mValue.pData, frame, true); } + bool GetParent(Value* val) { return pObjectInterface->GetParent(mValue.pData, val); } + + ObjectInterface* pObjectInterface; // 00 + ValueType Type; // 08 + ValueUnion mValue; // 10 + u64 DataAux; // 18 +}; +} +} \ No newline at end of file