diff --git a/include/RE/B/BSInputDevice.h b/include/RE/B/BSInputDevice.h new file mode 100644 index 00000000..efb092b7 --- /dev/null +++ b/include/RE/B/BSInputDevice.h @@ -0,0 +1,28 @@ +#pragma once + +#include "RE/B/BSFixedString.h" + +namespace RE +{ + class BSInputDevice + { + public: + SF_RTTI_VTABLE(BSInputDevice); + + virtual ~BSInputDevice(); // 00 + + // add + virtual void Initialize() = 0; // 01 + virtual void Poll(float a_pollDelta) = 0; // 02 + virtual void Shutdown() = 0; // 03 + virtual bool GetKeyNameFromCode(std::uint32_t a_keyCode, BSFixedStringCS& a_name) const; // 04 + virtual std::uint32_t GetKeyCodeFromName(const char* a_name) const; // 05 + virtual bool GetMappedKeyCode(std::uint32_t a_keyCode, std::uint32_t& a_mappedKeyCode) const; // 06 + virtual bool IsEnabled() const { return true; } // 07 + virtual void ClearInputState() = 0; // 08 + + // members + std::uint8_t unk08[0x78]; // 08 + }; + static_assert(sizeof(BSInputDevice) == 0x80); +} diff --git a/include/RE/B/BSInputDeviceManager.h b/include/RE/B/BSInputDeviceManager.h new file mode 100644 index 00000000..084db02a --- /dev/null +++ b/include/RE/B/BSInputDeviceManager.h @@ -0,0 +1,32 @@ +#pragma once + +#include "RE/B/BSInputEventSingleUser.h" +#include "RE/B/BSKeyboardDevice.h" +#include "RE/B/BSTSingleton.h" + +namespace RE +{ + class BSInputDeviceManager : + public BSTSingletonSDM, // 00 + public BSInputEventSingleUser // 10 + { + public: + SF_RTTI_VTABLE(BSInputDeviceManager); + + ~BSInputDeviceManager() override = default; + + [[nodiscard]] static BSInputDeviceManager* GetSingleton(); + + [[nodiscard]] BSKeyboardDevice* GetKeyboard() const + { + return static_cast(devices[0]); + } + + // members + std::uint64_t unk60; // 60 + BSInputDevice* devices[5]; // 68 + std::uint8_t unk90[0x48]; // 90 + }; + static_assert(offsetof(BSInputDeviceManager, devices) == 0x68); + static_assert(sizeof(BSInputDeviceManager) == 0xD8); +} diff --git a/include/RE/B/BSInputEventSingleUser.h b/include/RE/B/BSInputEventSingleUser.h new file mode 100644 index 00000000..560f41d8 --- /dev/null +++ b/include/RE/B/BSInputEventSingleUser.h @@ -0,0 +1,21 @@ +#pragma once + +#include "RE/B/BSInputEventReceiver.h" +#include "RE/B/BSInputEventUser.h" + +namespace RE +{ + class BSInputEventSingleUser : + public BSInputEventReceiver, // 00 + public BSInputEventUser // 10 + { + public: + SF_RTTI_VTABLE(BSInputEventSingleUser); + + virtual ~BSInputEventSingleUser() = default; // 00 + + // override (BSInputEventReceiver) + virtual void PerformInputProcessing(const InputEvent* a_queueHead) override; // 01 + }; + static_assert(sizeof(BSInputEventSingleUser) == 0x50); +} diff --git a/include/RE/B/BSInputEventUser.h b/include/RE/B/BSInputEventUser.h index a5a0df83..f5b75b5e 100644 --- a/include/RE/B/BSInputEventUser.h +++ b/include/RE/B/BSInputEventUser.h @@ -51,8 +51,12 @@ namespace RE virtual ~InputEvent() = default; // 00 // add - virtual bool HasIDCode() const { return false; } - virtual const BSFixedString QUserEvent() const { return ""; } + virtual bool HasIDCode() const { return false; } + virtual const BSFixedString& QUserEvent() const + { + static const BSFixedString empty; + return empty; + } // members DeviceType deviceType{ DeviceType::kNone }; // 08 @@ -76,9 +80,11 @@ namespace RE // override (InputEvent) virtual bool HasIDCode() const override { return true; } - virtual const BSFixedString QUserEvent() const override + virtual const BSFixedString& QUserEvent() const override { - return disabled ? "DISABLED" : strUserEvent; + using func_t = const BSFixedString& (*)(const IDEvent*); + static REL::Relocation func{ ID::IDEvent::QUserEvent }; + return func(this); } // members @@ -150,6 +156,13 @@ namespace RE SF_HEAP_REDEFINE_NEW(BSInputEventUser); + void DispatchEvent(const InputEvent* a_event) + { + using func_t = decltype(&BSInputEventUser::DispatchEvent); + static REL::Relocation func{ ID::BSInputEventUser::DispatchEvent }; + func(this, a_event); + } + // members std::uint8_t pad08[0x30]; // 08 bool inputEventHandlingEnabled{ true }; // 38 diff --git a/include/RE/B/BSKeyboardDevice.h b/include/RE/B/BSKeyboardDevice.h new file mode 100644 index 00000000..138648cc --- /dev/null +++ b/include/RE/B/BSKeyboardDevice.h @@ -0,0 +1,16 @@ +#pragma once + +#include "RE/B/BSInputDevice.h" + +namespace RE +{ + class BSKeyboardDevice : + public BSInputDevice // 00 + { + public: + SF_RTTI_VTABLE(BSKeyboardDevice); + + virtual ~BSKeyboardDevice() = default; // 00 + }; + static_assert(sizeof(BSKeyboardDevice) == 0x80); +} diff --git a/include/RE/C/ControlMap.h b/include/RE/C/ControlMap.h new file mode 100644 index 00000000..b87dacd1 --- /dev/null +++ b/include/RE/C/ControlMap.h @@ -0,0 +1,152 @@ +#pragma once + +#include "RE/B/BSFixedString.h" +#include "RE/B/BSInputEventUser.h" +#include "RE/B/BSTArray.h" +#include "RE/B/BSTSingleton.h" +#include "RE/U/UserEvents.h" + +#include +#include +#include +#include + +namespace RE +{ + class ControlMap : + public BSTSingletonSDM // 000 + { + public: + SF_RTTI_VTABLE(ControlMap); + + using InputContextID = UserEvents::INPUT_CONTEXT_ID; + + enum class BindingSlot : std::uint8_t + { + kMain = 0, + kAlternate = 1, + kUnbound = 2 + }; + + struct UserEventMapping + { + BSFixedString eventID; // 00 + std::uint32_t keyCode; // 08 + std::uint32_t modifierKeyCode; // 0C + BindingSlot bindingSlot; // 10 + std::uint8_t unk11; // 11 + std::uint16_t unk12; // 12 + std::uint8_t sortIndex; // 14 + std::byte pad15[3]; // 15 + std::uint32_t contextMask; // 18 + std::uint8_t metadata; // 1C + bool visibleInControls; // 1D + bool usesLinkedBinding; // 1E + std::uint8_t unk1F; // 1F + bool required; // 20 + std::byte pad21[7]; // 21 + }; + static_assert(offsetof(UserEventMapping, keyCode) == 0x08); + static_assert(offsetof(UserEventMapping, bindingSlot) == 0x10); + static_assert(offsetof(UserEventMapping, sortIndex) == 0x14); + static_assert(offsetof(UserEventMapping, contextMask) == 0x18); + static_assert(offsetof(UserEventMapping, visibleInControls) == 0x1D); + static_assert(offsetof(UserEventMapping, required) == 0x20); + static_assert(sizeof(UserEventMapping) == 0x28); + + // Partial layout. + struct InputContext + { + std::array, 3> deviceMappings; // 00 + }; + static_assert(sizeof(InputContext) == 0x30); + + struct ActiveInputContext + { + InputContextID inputContextID; // 00 + std::byte pad01[0x0F]; // 01 + }; + static_assert(sizeof(ActiveInputContext) == 0x10); + + static constexpr std::size_t MAPPABLE_INPUT_CONTEXT_COUNT = 0x4E; + static constexpr std::size_t INPUT_CONTEXT_NAME_COUNT = 0x51; + + // Formats a defaults row; keyboard codes are virtual-key codes. + [[nodiscard]] static std::string FormatMappingRow( + const char* a_event, const char* a_keyboard, const char* a_mouse, const char* a_gamepad, + bool a_keyboardVisible, bool a_mouseVisible, bool a_gamepadVisible, + std::uint32_t a_controlMask, std::uint32_t a_groupMask, bool a_required); + + [[nodiscard]] static std::string FormatMappingRow( + const char* a_event, std::uint32_t a_keyboard, std::uint32_t a_mouse, std::uint32_t a_gamepad, + bool a_keyboardVisible, bool a_mouseVisible, bool a_gamepadVisible, + std::uint32_t a_controlMask, std::uint32_t a_groupMask, bool a_required); + + // Loading or parsing replaces mappings and invalidates borrowed views. + void LoadMappings(); + + // Parses complete defaults; saved overrides and link resolution are separate. + void ParseMappings(const char* a_text); + + void ResolveLinkedMappings(); + + [[nodiscard]] static ControlMap* GetSingleton() + { + static REL::Relocation singleton{ ID::ControlMap::Singleton }; + return *singleton; + } + + [[nodiscard]] InputContext* GetInputContext(InputContextID a_id) noexcept + { + const auto index = static_cast(a_id); + return index < inputContexts.size() ? inputContexts[index] : nullptr; + } + + [[nodiscard]] const InputContext* GetInputContext(InputContextID a_id) const noexcept + { + const auto index = static_cast(a_id); + return index < inputContexts.size() ? inputContexts[index] : nullptr; + } + + // Borrowed on the game thread; invalidated by remapping. + [[nodiscard]] std::span GetMappings( + InputContextID a_id, InputEvent::DeviceType a_device) const noexcept + { + const auto device = static_cast(a_device); + const auto* inputContext = GetInputContext(a_id); + if (!inputContext || device >= inputContext->deviceMappings.size()) { + return {}; + } + + const auto& mappings = inputContext->deviceMappings[device]; + return { mappings.data(), mappings.size() }; + } + + // Invalidated by context-stack changes. + [[nodiscard]] std::span GetActiveInputContexts() const noexcept + { + return { activeInputContexts.data(), activeInputContexts.size() }; + } + + [[nodiscard]] static std::string_view GetInputContextName(InputContextID a_id) noexcept + { + const auto index = static_cast(a_id); + if (index >= INPUT_CONTEXT_NAME_COUNT) { + return {}; + } + + static REL::Relocation names{ ID::ControlMap::InputContextNameTable }; + const auto* name = names.get()[index]; + return name ? std::string_view{ name } : std::string_view{}; + } + + // members + std::array inputContexts; // 010 + std::byte pad280[0x28]; // 280 + BSTArray activeInputContexts; // 2A8 + std::byte pad2B8[0xE8]; // 2B8 + }; + static_assert(offsetof(ControlMap, inputContexts) == 0x10); + static_assert(offsetof(ControlMap, activeInputContexts) == 0x2A8); + static_assert(sizeof(ControlMap) == 0x3A0); +} diff --git a/include/RE/IDs.h b/include/RE/IDs.h index ec67782b..63041c57 100644 --- a/include/RE/IDs.h +++ b/include/RE/IDs.h @@ -272,6 +272,19 @@ namespace RE::ID inline constexpr REL::ID IterateAdvance{ 39372 }; } + namespace BSInputDevice + { + inline constexpr REL::ID dtor{ 124249 }; + inline constexpr REL::ID GetKeyNameFromCode{ 124283 }; + inline constexpr REL::ID GetKeyCodeFromName{ 124284 }; + inline constexpr REL::ID GetMappedKeyCode{ 124285 }; + } + + namespace BSInputDeviceManager + { + inline constexpr REL::ID Singleton{ 937644 }; + } + namespace BSInputEnableLayer { } @@ -284,9 +297,17 @@ namespace RE::ID inline constexpr REL::ID Singleton{ 938036 }; } + namespace BSInputEventSingleUser + { + inline constexpr REL::ID PerformInputProcessing{ 88658 }; + } + namespace BSInputEventUser { - inline constexpr REL::ID Unk09{ 0 }; // 178899 + inline constexpr REL::ID ctor{ 74686 }; + inline constexpr REL::ID ScalarDeletingDestructor{ 74688 }; + inline constexpr REL::ID DispatchEvent{ 124087 }; + inline constexpr REL::ID Unk09{ 124088 }; } namespace BSLog @@ -310,6 +331,11 @@ namespace RE::ID inline constexpr REL::ID UnlockSubroutine{ 37393 }; } + namespace BSPCKeyboardDevice + { + inline constexpr REL::ID KeyNameTable{ 361050 }; + } + namespace BSPointerHandleManagerInterface { inline constexpr REL::ID GetSmartPointer{ 35638 }; @@ -448,6 +474,8 @@ namespace RE::ID namespace BSTArray { inline constexpr REL::ID U32PushGrow{ 35755 }; + inline constexpr REL::ID AppendIndex{ 123859 }; + inline constexpr REL::ID HeapAllocatorFunctorVtable{ 392794 }; } namespace BSTEventSource @@ -780,9 +808,24 @@ namespace RE::ID inline constexpr REL::ID GetEventSource{ 0 }; // 131197 } + namespace ControlMap + { + inline constexpr REL::ID FormatMappingRow{ 124100 }; + inline constexpr REL::ID FormatMappingRowWithKeyCodes{ 124101 }; + inline constexpr REL::ID MappingRowStringDtor{ 35706 }; + inline constexpr REL::ID Singleton{ 938003 }; + inline constexpr REL::ID InputContextNameTable{ 360965 }; + inline constexpr REL::ID LoadMappings{ 124116 }; + inline constexpr REL::ID ParseMappings{ 124117 }; + inline constexpr REL::ID ResolveLinkedMappings{ 124124 }; + inline constexpr REL::ID ValidateMapping{ 124132 }; + } + namespace ControlsRemappedEvent { inline constexpr REL::ID GetEventSource{ 0 }; // 136133 + inline constexpr REL::ID Dispatch{ 88944 }; + inline constexpr REL::ID EventSourceStorage{ 947462 }; } namespace CraftingMenu_CloseMenu @@ -1069,6 +1112,11 @@ namespace RE::ID inline constexpr REL::ID GetEventSource{ 0 }; // 133042 } + namespace IDEvent + { + inline constexpr REL::ID QUserEvent{ 124035 }; + } + namespace idLogging { inline constexpr REL::ID Singleton{ 0 }; // 895197 @@ -1234,6 +1282,18 @@ namespace RE::ID { inline constexpr REL::ID Singleton{ 883591 }; inline constexpr REL::ID WorldRoot{ 0 }; // 887308 + inline constexpr REL::ID InitializeInputSingletons{ 99490 }; + inline constexpr REL::ID ShutdownInputSingletons{ 99491 }; + } + + namespace MenuControls + { + inline constexpr REL::ID Singleton{ 938076 }; + inline constexpr REL::ID InitializeHandlers{ 114215 }; + inline constexpr REL::ID CleanupHandlers{ 114216 }; + inline constexpr REL::ID UnregisterHandler{ 114217 }; + inline constexpr REL::ID PerformInputProcessing{ 114220 }; + inline constexpr REL::ID ScalarDeletingDestructor{ 99538 }; } namespace msvc @@ -1844,6 +1904,16 @@ namespace RE::ID inline constexpr REL::ID GetEventSource{ 0 }; // 140250 } + namespace SettingsDataModel + { + inline constexpr REL::ID PublishBindings{ 88747 }; + inline constexpr REL::ID PublishBinding{ 88748 }; + inline constexpr REL::ID BindingDefinitions{ 918356 }; + inline constexpr REL::ID EvaluateRemapCandidate{ 88672 }; + inline constexpr REL::ID Singleton{ 939451 }; + inline constexpr REL::ID CancelRemap{ 88684 }; + } + namespace ShipCameraStateToggled { inline constexpr REL::ID GetEventSource{ 0 }; // 120266 diff --git a/include/RE/M/MenuControls.h b/include/RE/M/MenuControls.h new file mode 100644 index 00000000..b72c8b80 --- /dev/null +++ b/include/RE/M/MenuControls.h @@ -0,0 +1,66 @@ +#pragma once + +#include "RE/B/BSInputEventReceiver.h" +#include "RE/B/BSInputEventUser.h" +#include "RE/B/BSTSingleton.h" + +#include +#include + +namespace RE +{ + class MenuControls : + public BSInputEventReceiver, // 00 + public BSTSingletonSDM // 10 + { + public: + SF_RTTI_VTABLE(MenuControls); + + // Engine-owned; mutate through RegisterHandler/UnregisterHandler. + struct HandlerStorage + { + std::uint32_t size{}; // 00 + std::uint32_t capacity{}; // 04 + BSInputEventUser** data{}; // 08 + }; + static_assert(sizeof(HandlerStorage) == 0x10); + static_assert(offsetof(HandlerStorage, data) == 0x08); + + // override (BSInputEventReceiver) + virtual void PerformInputProcessing(const InputEvent* a_queueHead) override; // 01 + + [[nodiscard]] static MenuControls* GetSingleton(); + + // Borrowed handlers must outlive registration. Do not mutate during dispatch. + [[nodiscard]] bool RegisterHandler(BSInputEventUser* a_handler); + void UnregisterHandler(BSInputEventUser* a_handler); + + [[nodiscard]] std::uint32_t GetHandlerCount() const noexcept { return handlers.size; } + + // Invalidated by registration, removal or teardown. + [[nodiscard]] std::span GetHandlers() const noexcept + { + return { handlers.data, handlers.size }; + } + + // members + HandlerStorage handlers; // 20 + BSInputEventUser* disconnectHandler{}; // 30 + BSInputEventUser* deviceChangeHandler{}; // 38 + BSInputEventUser* quickSaveLoadHandler{}; // 40 + BSInputEventUser* menuOpenHandler{}; // 48 + BSInputEventUser* unk50{}; // 50 + BSInputEventUser* gpuCaptureHandler{}; // 58 + BSInputEventUser* cpuCaptureHandler{}; // 60 + BSInputEventUser* cameraZoomHandler{}; // 68 + std::byte unk70[0x08]{}; // 70 + + protected: + MenuControls() = default; + virtual ~MenuControls() = default; // 00 + }; + static_assert(offsetof(MenuControls, handlers) == 0x20); + static_assert(offsetof(MenuControls, disconnectHandler) == 0x30); + static_assert(offsetof(MenuControls, cameraZoomHandler) == 0x68); + static_assert(sizeof(MenuControls) == 0x78); +} diff --git a/include/RE/S/SettingsDataModel.h b/include/RE/S/SettingsDataModel.h new file mode 100644 index 00000000..0c4a4f8e --- /dev/null +++ b/include/RE/S/SettingsDataModel.h @@ -0,0 +1,193 @@ +#pragma once + +#include "RE/B/BSFixedString.h" +#include "RE/B/BSInputEventSingleUser.h" +#include "RE/B/BSTArray.h" +#include "RE/B/BSTEvent.h" +#include "RE/B/BSTSingleton.h" +#include "RE/I/IDataModel.h" +#include "RE/U/UserEvents.h" + +#include +#include +#include + +namespace RE +{ + class SettingsPanel_RemapMode; + class SettingsPanel_SaveControls; + class SettingsPanel_SaveSettings; + class SettingsPanel_SliderChanged; + class SettingsPanel_StepperChanged; + class SettingsPanel_CheckBoxChanged; + class SettingsPanel_ResetToDefaults; + class SettingsPanel_OpenSettings; + class SettingsPanel_OpenCategory; + class SettingsPanel_ClearBinding; + class SettingsPanel_RemapConfirmed; + class SettingsPanel_ValidateControls; + class SettingsPanel_LinkClicked; + + class SettingsDataModel : + public IDataModel, // 000 + public BSInputEventSingleUser, // 010 + public BSTSingletonExplicit, // 0C8 + public BSTEventSink, // 060 + public BSTEventSink, // 068 + public BSTEventSink, // 070 + public BSTEventSink, // 078 + public BSTEventSink, // 080 + public BSTEventSink, // 088 + public BSTEventSink, // 090 + public BSTEventSink, // 098 + public BSTEventSink, // 0A0 + public BSTEventSink, // 0A8 + public BSTEventSink, // 0B0 + public BSTEventSink, // 0B8 + public BSTEventSink // 0C0 + { + public: + SF_RTTI_VTABLE(SettingsDataModel); + + using InputContextID = UserEvents::INPUT_CONTEXT_ID; + + struct BindingDefinition + { + InputContextID context; // 00 + std::byte pad01[3]; // 01 + std::uint32_t contextOrder; // 04 + std::uint32_t sortIndex; // 08 + bool required; // 0C + std::byte pad0D[3]; // 0D + BSFixedString event; // 10 + std::uint32_t variantCount; // 18 + std::uint32_t variantCapacity; // 1C + const void* variants; // 20 + }; + static_assert(offsetof(BindingDefinition, event) == 0x10); + static_assert(offsetof(BindingDefinition, variants) == 0x20); + static_assert(sizeof(BindingDefinition) == 0x28); + + virtual ~SettingsDataModel(); // 00 + + // Borrowed on the settings/UI thread; invalidated when bindings are rebuilt. + [[nodiscard]] static std::span GetBindingDefinitions() + { + static REL::Relocation*> definitions{ ID::SettingsDataModel::BindingDefinitions }; + return { definitions->data(), definitions->size() }; + } + + [[nodiscard]] static SettingsDataModel* GetSingleton() + { + static REL::Relocation singleton{ ID::SettingsDataModel::Singleton }; + return *singleton; + } + + static void CancelRemap() + { + using func_t = decltype(&SettingsDataModel::CancelRemap); + static REL::Relocation func{ ID::SettingsDataModel::CancelRemap }; + func(); + } + + [[nodiscard]] static constexpr std::uint32_t GetControlsMenuOrder(InputContextID a_id) noexcept + { + for (std::uint32_t i = 0; i < CONTROLS_MENU_INPUT_CONTEXT_ORDER.size(); ++i) { + if (CONTROLS_MENU_INPUT_CONTEXT_ORDER[i] == a_id) { + return i; + } + } + + return static_cast(CONTROLS_MENU_INPUT_CONTEXT_ORDER.size()) + + static_cast(a_id); + } + + [[nodiscard]] static constexpr InputContextID GetControlsMenuCategory(InputContextID a_id) noexcept + { + const auto id = static_cast(a_id); + if (id >= 0x06 && id <= 0x0A) { + return InputContextID::kQuickContainer; + } + if (id >= 0x02 && id <= 0x05) { + return InputContextID::kBasicMenuNav; + } + if (a_id == InputContextID::kShipHUD || a_id == InputContextID::kShipHUD_Cancel) { + return InputContextID::kShipHUD; + } + if (a_id == InputContextID::kSpaceship_Interaction || a_id == InputContextID::kShipFlightCam_FreeRot) { + return InputContextID::kSpaceship_Interaction; + } + return a_id; + } + + inline static constexpr std::array CONTROLS_MENU_INPUT_CONTEXT_ORDER{ + InputContextID::kMainGameplay, + InputContextID::kShipHUD, + InputContextID::kShipHUD_Cancel, + InputContextID::kShip_QuickContainer, + InputContextID::kShipHUD_Targeting, + InputContextID::kShipHUD_Alt, + InputContextID::kSpaceship_Interaction, + InputContextID::kShipFlightCam_FreeRot, + InputContextID::kShipHUD_CruiseMode, + InputContextID::kBasicMenuNav, + InputContextID::kVirtualController, + InputContextID::kMenu3DModelRotation, + InputContextID::kMenu3DModelZoom, + InputContextID::kQuickContainer, + InputContextID::kMultiActivate_A, + InputContextID::kMultiActivate_X, + InputContextID::kMultiActivate_Y, + InputContextID::kMultiActivate_B, + InputContextID::kLeftThumbstick, + InputContextID::kRightThumbstick, + InputContextID::kCursor, + InputContextID::kCursor_LeftStick, + InputContextID::kBook, + InputContextID::kDataSlateButtons, + InputContextID::kWorkshop, + InputContextID::kWorkshop_QuickMenu, + InputContextID::kTerminal, + InputContextID::kPhotoMode, + InputContextID::kSecurity, + InputContextID::kScope, + InputContextID::kSitWait, + InputContextID::kDataMenu, + InputContextID::kDataMenu_Shortcuts, + InputContextID::kShieldFocus, + InputContextID::kZeroG, + InputContextID::kStarMap, + InputContextID::kStarMap_JumpWindow, + InputContextID::kStarMap_SystemInspect, + InputContextID::kSurfaceMap, + InputContextID::kSpaceshipEditor, + InputContextID::kShipBuilder_Empty, + InputContextID::kShipBuilder_Add, + InputContextID::kShipBuilder_Edit, + InputContextID::kShipBuilder_Color, + InputContextID::kShipBuilder_FlightCheck, + InputContextID::kShipBuilder_ExitConfirm, + InputContextID::kShipBuilder_Selection, + InputContextID::kShipBuilder_Upgrade, + InputContextID::kSpaceshipEditorSimplified, + InputContextID::kShipBuilder_Add_Simple, + InputContextID::kShipBuilder_Empty_Simple, + InputContextID::kShipBuilder_Edit_Simple, + InputContextID::kShipBuilder_Selection_Simple, + InputContextID::kShipInfo, + InputContextID::kShipInfo_BuySell, + InputContextID::kShipInfo_Modify, + InputContextID::kMonocle, + InputContextID::kVehicle + }; + static_assert(CONTROLS_MENU_INPUT_CONTEXT_ORDER.size() == 58); + + // members + std::byte unk0C8[0x878]; // 0C8 + }; + static_assert(offsetof(SettingsDataModel, unk08) == 0x8); + static_assert(offsetof(SettingsDataModel, currInputTimeCount) == 0x18); + static_assert(offsetof(SettingsDataModel, inputEventHandlingEnabled) == 0x58); + static_assert(offsetof(SettingsDataModel, unk0C8) == 0xC8); + static_assert(sizeof(SettingsDataModel) == 0x940); +} diff --git a/include/RE/Starfield.h b/include/RE/Starfield.h index a297f5b4..5360a12f 100644 --- a/include/RE/Starfield.h +++ b/include/RE/Starfield.h @@ -198,11 +198,15 @@ #include "RE/B/BSFixedString.h" #include "RE/B/BSFloat3DCurve.h" #include "RE/B/BSFloatCurve.h" +#include "RE/B/BSInputDevice.h" +#include "RE/B/BSInputDeviceManager.h" #include "RE/B/BSInputEnableLayer.h" #include "RE/B/BSInputEnableManager.h" #include "RE/B/BSInputEventReceiver.h" +#include "RE/B/BSInputEventSingleUser.h" #include "RE/B/BSInputEventUser.h" #include "RE/B/BSIntrusiveRefCounted.h" +#include "RE/B/BSKeyboardDevice.h" #include "RE/B/BSLock.h" #include "RE/B/BSLog.h" #include "RE/B/BSModelNode.h" @@ -244,6 +248,7 @@ #include "RE/C/ConeProjectile.h" #include "RE/C/Console.h" #include "RE/C/ConsoleLog.h" +#include "RE/C/ControlMap.h" #include "RE/C/CreationRenderer.h" #include "RE/D/DebuggerMessages.h" #include "RE/D/DecalData.h" @@ -326,6 +331,7 @@ #include "RE/M/MagicTarget.h" #include "RE/M/Main.h" #include "RE/M/MemoryManager.h" +#include "RE/M/MenuControls.h" #include "RE/M/MenuPaperDoll.h" #include "RE/M/MenuTopicManager.h" #include "RE/M/Misc.h" @@ -389,6 +395,7 @@ #include "RE/S/SettingCollection.h" #include "RE/S/SettingCollectionList.h" #include "RE/S/SettingCollectionMap.h" +#include "RE/S/SettingsDataModel.h" #include "RE/S/Sexes.h" #include "RE/S/SimpleAllocMemoryPagePolicy.h" #include "RE/S/Sky.h" diff --git a/include/RE/U/UserEvents.h b/include/RE/U/UserEvents.h index 30999fbf..8a84d6c8 100644 --- a/include/RE/U/UserEvents.h +++ b/include/RE/U/UserEvents.h @@ -2,6 +2,94 @@ namespace RE { + namespace UserEvents + { + enum class INPUT_CONTEXT_ID : std::uint8_t + { + kMainGameplay = 0x00, + kConsoleOpening = 0x01, + kBasicMenuNav = 0x02, + kVirtualController = 0x03, + kMenu3DModelRotation = 0x04, + kMenu3DModelZoom = 0x05, + kQuickContainer = 0x06, + kMultiActivate_A = 0x07, + kMultiActivate_X = 0x08, + kMultiActivate_Y = 0x09, + kMultiActivate_B = 0x0A, + kLeftThumbstick = 0x0B, + kRightThumbstick = 0x0C, + kCursor = 0x0D, + kCursor_LeftStick = 0x0E, + kBook = 0x0F, + kDataSlateButtons = 0x10, + kConsole = 0x11, + kDebugOverlay = 0x12, + kTFC = 0x13, + kDebug = 0x14, + kDebugCamera = 0x15, + kWorkshop = 0x16, + kWorkshop_QuickMenu = 0x17, + kTerminal = 0x18, + kPhotoMode = 0x19, + kSecurity = 0x1A, + kScope = 0x1B, + kSitWait = 0x1C, + kDataMenu = 0x1D, + kDataMenu_Shortcuts = 0x1E, + kShieldFocus = 0x1F, + kZeroG = 0x20, + kShipHUD = 0x21, + kShipHUD_Cancel = 0x22, + kShip_QuickContainer = 0x23, + kShipHUD_Targeting = 0x24, + kShipHUD_Alt = 0x25, + kSpaceship_Interaction = 0x26, + kShipFlightCam_FreeRot = 0x27, + kStarMap = 0x28, + kStarMap_JumpWindow = 0x29, + kStarMap_SystemInspect = 0x2A, + kSurfaceMap = 0x2B, + kSpaceshipEditor = 0x2C, + kShipBuilder_Empty = 0x2D, + kShipBuilder_Add = 0x2E, + kShipBuilder_Edit = 0x2F, + kShipBuilder_Color = 0x30, + kShipBuilder_FlightCheck = 0x31, + kShipBuilder_ExitConfirm = 0x32, + kShipBuilder_Selection = 0x33, + kShipBuilder_Upgrade = 0x34, + kShipInfo = 0x35, + kShipInfo_BuySell = 0x36, + kShipInfo_Modify = 0x37, + kMonocle = 0x38, + kMonocleKBMZoom = 0x39, + kLevelUp = 0x3A, + kNewMission = 0x3B, + kMovementOverride = 0x3C, + kTextInput = 0x3D, + kMissionFromStarMap = 0x3E, + kSpaceshipEditorSimplified = 0x3F, + kShipBuilder_Add_Simple = 0x40, + kShipBuilder_Empty_Simple = 0x41, + kShipBuilder_Edit_Simple = 0x42, + kShipBuilder_Selection_Simple = 0x43, + kShipHUD_Monocle = 0x44, + kBasicMenuNav_JustCancel = 0x45, + kStarMap_QuickSelect = 0x46, + kCreations = 0x47, + kLogin = 0x48, + kVehicle = 0x49, + kVehicle_Hangar = 0x4A, + kAlmanac = 0x4B, + kOpenAlmanac = 0x4C, + kShipHUD_CruiseMode = 0x4D, + kStopContextProcessing = 0x4E, + kCount = 0x4F, + kNone = 0x50 + }; + } + enum class USER_EVENT_SENDER_ID : std::uint32_t { None = 0, diff --git a/src/RE/B/BSInputDevice.cpp b/src/RE/B/BSInputDevice.cpp new file mode 100644 index 00000000..97273326 --- /dev/null +++ b/src/RE/B/BSInputDevice.cpp @@ -0,0 +1,32 @@ +#include "RE/B/BSInputDevice.h" + +namespace RE +{ + BSInputDevice::~BSInputDevice() + { + using func_t = void (*)(BSInputDevice*, std::uint32_t); + static REL::Relocation func{ ID::BSInputDevice::dtor }; + func(this, 0); // Run the scalar deleting destructor without freeing storage. + } + + bool BSInputDevice::GetKeyNameFromCode(std::uint32_t a_keyCode, BSFixedStringCS& a_name) const + { + using func_t = decltype(&BSInputDevice::GetKeyNameFromCode); + static REL::Relocation func{ ID::BSInputDevice::GetKeyNameFromCode }; + return func(this, a_keyCode, a_name); + } + + std::uint32_t BSInputDevice::GetKeyCodeFromName(const char* a_name) const + { + using func_t = decltype(&BSInputDevice::GetKeyCodeFromName); + static REL::Relocation func{ ID::BSInputDevice::GetKeyCodeFromName }; + return func(this, a_name); + } + + bool BSInputDevice::GetMappedKeyCode(std::uint32_t a_keyCode, std::uint32_t& a_mappedKeyCode) const + { + using func_t = decltype(&BSInputDevice::GetMappedKeyCode); + static REL::Relocation func{ ID::BSInputDevice::GetMappedKeyCode }; + return func(this, a_keyCode, a_mappedKeyCode); + } +} diff --git a/src/RE/B/BSInputDeviceManager.cpp b/src/RE/B/BSInputDeviceManager.cpp new file mode 100644 index 00000000..1e01d6fc --- /dev/null +++ b/src/RE/B/BSInputDeviceManager.cpp @@ -0,0 +1,10 @@ +#include "RE/B/BSInputDeviceManager.h" + +namespace RE +{ + BSInputDeviceManager* BSInputDeviceManager::GetSingleton() + { + static REL::Relocation singleton{ ID::BSInputDeviceManager::Singleton }; + return *singleton; + } +} diff --git a/src/RE/B/BSInputEventSingleUser.cpp b/src/RE/B/BSInputEventSingleUser.cpp new file mode 100644 index 00000000..ef41b7dc --- /dev/null +++ b/src/RE/B/BSInputEventSingleUser.cpp @@ -0,0 +1,11 @@ +#include "RE/B/BSInputEventSingleUser.h" + +namespace RE +{ + void BSInputEventSingleUser::PerformInputProcessing(const InputEvent* a_queueHead) + { + using func_t = decltype(&BSInputEventSingleUser::PerformInputProcessing); + static REL::Relocation func{ ID::BSInputEventSingleUser::PerformInputProcessing }; + func(this, a_queueHead); + } +} diff --git a/src/RE/C/ControlMap.cpp b/src/RE/C/ControlMap.cpp new file mode 100644 index 00000000..62edd2b2 --- /dev/null +++ b/src/RE/C/ControlMap.cpp @@ -0,0 +1,83 @@ +#include "RE/C/ControlMap.h" + +#include + +namespace RE +{ + namespace + { + struct alignas(8) MappingRowString + { + ~MappingRowString() + { + static REL::Relocation destroy{ ID::ControlMap::MappingRowStringDtor }; + destroy(this); + } + + [[nodiscard]] const char* c_str() const + { + if (capacity <= sizeof(storage)) { + return storage; + } + const char* data; + std::memcpy(&data, storage, sizeof(data)); + return data; + } + + char storage[0xC]{}; + std::uint16_t capacity{ 1 }; + std::uint16_t unk0E{}; + }; + static_assert(sizeof(MappingRowString) == 0x10); + static_assert(offsetof(MappingRowString, capacity) == 0xC); + } + + std::string ControlMap::FormatMappingRow( + const char* a_event, const char* a_keyboard, const char* a_mouse, const char* a_gamepad, + bool a_keyboardVisible, bool a_mouseVisible, bool a_gamepadVisible, + std::uint32_t a_controlMask, std::uint32_t a_groupMask, bool a_required) + { + using func_t = MappingRowString* (*)(MappingRowString*, const char*, const char*, const char*, const char*, + bool, bool, bool, std::uint32_t, std::uint32_t, bool); + static REL::Relocation func{ ID::ControlMap::FormatMappingRow }; + MappingRowString text; + func(&text, a_event, a_keyboard, a_mouse, a_gamepad, + a_keyboardVisible, a_mouseVisible, a_gamepadVisible, a_controlMask, a_groupMask, a_required); + return text.c_str(); + } + + std::string ControlMap::FormatMappingRow( + const char* a_event, std::uint32_t a_keyboard, std::uint32_t a_mouse, std::uint32_t a_gamepad, + bool a_keyboardVisible, bool a_mouseVisible, bool a_gamepadVisible, + std::uint32_t a_controlMask, std::uint32_t a_groupMask, bool a_required) + { + using func_t = MappingRowString* (*)(MappingRowString*, const char*, std::uint32_t, std::uint32_t, std::uint32_t, + bool, bool, bool, std::uint32_t, std::uint32_t, bool); + static REL::Relocation func{ ID::ControlMap::FormatMappingRowWithKeyCodes }; + MappingRowString text; + func(&text, a_event, a_keyboard, a_mouse, a_gamepad, + a_keyboardVisible, a_mouseVisible, a_gamepadVisible, a_controlMask, a_groupMask, a_required); + return text.c_str(); + } + + void ControlMap::LoadMappings() + { + using func_t = decltype(&ControlMap::LoadMappings); + static REL::Relocation func{ ID::ControlMap::LoadMappings }; + func(this); + } + + void ControlMap::ParseMappings(const char* a_text) + { + using func_t = decltype(&ControlMap::ParseMappings); + static REL::Relocation func{ ID::ControlMap::ParseMappings }; + func(this, a_text); + } + + void ControlMap::ResolveLinkedMappings() + { + using func_t = decltype(&ControlMap::ResolveLinkedMappings); + static REL::Relocation func{ ID::ControlMap::ResolveLinkedMappings }; + func(this); + } +} diff --git a/src/RE/M/MenuControls.cpp b/src/RE/M/MenuControls.cpp new file mode 100644 index 00000000..76ab9a43 --- /dev/null +++ b/src/RE/M/MenuControls.cpp @@ -0,0 +1,57 @@ +#include "RE/M/MenuControls.h" + +namespace RE +{ + MenuControls* MenuControls::GetSingleton() + { + static REL::Relocation singleton{ ID::MenuControls::Singleton }; + return *singleton; + } + + void MenuControls::PerformInputProcessing(const InputEvent* a_queueHead) + { + using func_t = void (*)(MenuControls*, const InputEvent*); + static REL::Relocation func{ ID::MenuControls::PerformInputProcessing }; + func(this, a_queueHead); + } + + bool MenuControls::RegisterHandler(BSInputEventUser* a_handler) + { + if (!a_handler || handlers.size > handlers.capacity || (handlers.capacity && !handlers.data)) { + return false; + } + for (auto* handler : GetHandlers()) { + if (handler == a_handler) { + return true; + } + } + + struct Allocator + { + std::uintptr_t vtable; + HandlerStorage* array; + }; + static_assert(sizeof(Allocator) == 0x10); + static_assert(offsetof(Allocator, array) == 0x08); + using func_t = std::uint32_t (*)(HandlerStorage*, Allocator*, std::uint32_t, std::uint32_t); + static REL::Relocation append{ ID::BSTArray::AppendIndex }; + Allocator allocator{ ID::BSTArray::HeapAllocatorFunctorVtable.address(), &handlers }; + a_handler->inputEventHandlingEnabled = true; + const auto index = append(&handlers, &allocator, handlers.capacity, sizeof(BSInputEventUser*)); + if (index == 0xFFFFFFFFu) { + return false; + } + handlers.data[index] = a_handler; + return true; + } + + void MenuControls::UnregisterHandler(BSInputEventUser* a_handler) + { + if (!a_handler) { + return; + } + using func_t = void (*)(MenuControls*, BSInputEventUser*); + static REL::Relocation remove{ ID::MenuControls::UnregisterHandler }; + remove(this, a_handler); + } +}