diff --git a/include/RE/B/BSScriptUtil.h b/include/RE/B/BSScriptUtil.h new file mode 100644 index 00000000..c4e863e6 --- /dev/null +++ b/include/RE/B/BSScriptUtil.h @@ -0,0 +1,1089 @@ +#pragma once + +#include "RE/B/BSFixedString.h" +#include "RE/B/BSTSmartPointer.h" +#include "RE/I/IVirtualMachine.h" +#include "RE/V/VirtualMachine.h" +#include "RE/B/BSTScatterTable.h" +#include "RE/T/TESForm.h" +#include "RE/G/GameVM.h" +#include "SFSE/Logger.h" +#include "SFSE/SFSE.h" +#include "RE/O/ObjectTypeInfo.h" +#include "RE/O/Object.h" +#include "RE/S/StackFrame.h" +#include "RE/S/Struct.h" +#include "RE/N/NativeFunctionBase.h" + +namespace RE::BSScript +{ + namespace detail + { + struct wrapper_accessor; + + template + using decay_t = + std::conditional_t< + std::is_pointer_v, + std::remove_cv_t>, + std::remove_cvref_t>; + + template + [[nodiscard]] std::optional GetTypeInfo(); + + template + void PackVariable(Variable& a_var, T&& a_val); + + template + [[nodiscard]] T UnpackVariable(const Variable& a_var); + + template + [[nodiscard]] consteval auto make_structure_tag( + stl::nttp::string a_lhs, + stl::nttp::string a_rhs) noexcept + -> stl::nttp::string + { + char buf[a_lhs.length() + 1 + a_rhs.length() + 1]{ '\0' }; + std::copy_n(a_lhs.data(), a_lhs.length(), buf); + buf[a_lhs.length()] = '#'; + std::copy_n(a_rhs.data(), a_rhs.length(), buf + a_lhs.length() + 1); + return { static_cast(buf) }; + } + } + + template < + stl::nttp::string Object, + stl::nttp::string Structure> + class structure_wrapper + { + private: + static constexpr stl::nttp::string _full = detail::make_structure_tag(Object, Structure); + + public: + static constexpr std::string_view name{ _full.data(), _full.length() }; + + structure_wrapper() + { + if (!_proxy) { + const auto game = GameVM::GetSingleton(); + const auto vm = game ? game->GetVM() : nullptr; + if (!vm || + !vm->CreateStruct(name, _proxy) || + !_proxy) { + SFSE::log::error( + FMT_STRING("failed to create structure of type \"{}\""), + name); + assert(false); + } + } + } + + template + std::optional find(std::string_view a_name, bool a_quiet = false) const + { + if (_proxy && _proxy->type) { + const auto& mappings = _proxy->type->varNameIndexMap; + const auto it = mappings.find(a_name); + if (it != mappings.end()) { + const auto& var = _proxy->variables[it->Value]; + return detail::UnpackVariable(var); + } + } + + if (!a_quiet) { + SFSE::log::warn( + FMT_STRING("failed to find var \"{}\" on structure \"{}\""), + a_name, + name); + } + + return std::nullopt; + } + + template + bool insert(std::string_view a_name, T&& a_val) + { + if (_proxy && _proxy->type) { + auto& mappings = _proxy->type->varNameIndexMap; + const auto it = mappings.find(a_name); + if (it != mappings.end()) { + auto& var = _proxy->variables[it->Value]; + detail::PackVariable(var, std::forward(a_val)); + return true; + } + } + + SFSE::log::warn( + FMT_STRING("failed to pack var \"{}\" on structure \"{}\""), + a_name, + name); + return false; + } + + protected: + friend struct detail::wrapper_accessor; + + explicit structure_wrapper(BSTSmartPointer a_proxy) noexcept : + _proxy(std::move(a_proxy)) + { + assert(_proxy != nullptr); + } + + [[nodiscard]] BSTSmartPointer get_proxy() const& { return _proxy; } + [[nodiscard]] BSTSmartPointer get_proxy() && { return std::move(_proxy); } + + private: + BSTSmartPointer _proxy; + }; + + template + struct script_traits final + { + using is_array = std::false_type; + using is_string = std::false_type; + using is_nullable = std::false_type; + }; + + template < + class T, + class Allocator> + struct script_traits< + std::vector> + final + { + using is_array = std::true_type; + }; + + template + struct script_traits< + std::basic_string_view> + final + { + using is_string = std::true_type; + }; + + template < + class Traits, + class Allocator> + struct script_traits< + std::basic_string> + final + { + using is_string = std::true_type; + }; + + template + struct script_traits< + std::optional> + final + { + using is_nullable = std::true_type; + }; + + namespace detail + { + template + struct _is_bstsmartptr : + std::false_type + {}; + + template class R> + struct _is_bstsmartptr> : + std::true_type + {}; + + template + using is_bstsmartptr = _is_bstsmartptr>; + + template + inline constexpr bool is_bstsmartptr_v = is_bstsmartptr::value; + + template + struct _is_structure_wrapper : + std::false_type + {}; + + template + struct _is_structure_wrapper> : + std::true_type + {}; + + template + using is_structure_wrapper = _is_structure_wrapper>; + + template + inline constexpr bool is_structure_wrapper_v = is_structure_wrapper::value; + + // clang-format off + template + concept invocable_r = requires(F&& a_func, Args&&... a_args) + { + { std::invoke(std::forward(a_func), std::forward(a_args)...) } -> std::same_as; + }; + // clang-format on + + template + concept decays_to = std::same_as, To>; + + template + concept static_tag = std::same_as; + + // clang-format off + template + concept object = + std::derived_from, TESForm> && + requires { std::remove_cv_t::FORMTYPE; }; + // clang-format on + + template + concept vmobject = std::same_as, Object>; + + template + concept vmobject_ptr = + is_bstsmartptr_v && + vmobject::element_type>; + + template + concept vmvariable = std::same_as, Variable>; + + template + concept string = + std::same_as< + typename script_traits>::is_string, + std::true_type> && + std::convertible_to && + std::constructible_from; + + template + concept integral = + (std::integral && !std::same_as, bool>) || + std::is_enum_v; + + template + concept signed_integral = + (std::signed_integral && !std::same_as, bool>) || + (std::is_enum_v && std::signed_integral>); + + template + concept unsigned_integral = + (std::unsigned_integral && !std::same_as, bool>) || + (std::is_enum_v && std::unsigned_integral>); + + template + concept floating_point = std::floating_point; + + // clang-format off + template + concept boolean = std::same_as, bool>; + // clang-format on + + // clang-format off + template + concept array = + std::same_as< + typename script_traits>::is_array, + std::true_type> && + std::is_default_constructible_v && + requires(T a_array, typename T::value_type a_value) + { + { a_array.begin() } -> std::same_as; + { a_array.end() } -> std::same_as; + { a_array.size() } -> std::same_as; + a_array.push_back(static_cast(a_value)); + }; + // clang-format on + + template + concept wrapper = is_structure_wrapper_v; + + template + concept nullable = + std::same_as< + typename script_traits>::is_nullable, + std::true_type> && + std::is_default_constructible_v && + ((array || wrapper)) && // + requires(T a_nullable) + { + // clang-format off + static_cast(a_nullable); + { *static_cast(a_nullable) } -> decays_to; + // clang-format on + }; + + template + concept valid_self = + static_tag || + ((std::is_lvalue_reference_v && + (object> || vmobject>))); + + template + concept valid_parameter = + (std::is_pointer_v && + (object> || + (std::is_const_v> && vmvariable>))) || + (!std::is_reference_v && + (vmobject_ptr || + string || + integral || + floating_point || + boolean || + array || + wrapper || + nullable)); + + template + concept valid_return = + valid_parameter || + std::same_as; + + struct wrapper_accessor + { + template + [[nodiscard]] static T construct(const Variable& a_var) // + requires(is_structure_wrapper_v) + { + return T(get(a_var)); + } + + template + [[nodiscard]] static auto get_proxy(T&& a_wrapper) // + requires(wrapper>) + { + return std::forward(a_wrapper).get_proxy(); + } + }; + } + + template + [[nodiscard]] constexpr std::uint32_t GetVMTypeID() noexcept + { + return static_cast(T::FORMTYPE); + } + + template + [[nodiscard]] std::optional GetTypeInfo() // + requires(std::same_as) + { + return TypeInfo::RawType::kNone; + } + + template + [[nodiscard]] std::optional GetTypeInfo() + { + return TypeInfo::RawType::kNone; + } + + template + [[nodiscard]] std::optional GetTypeInfo() + { + const auto game = GameVM::GetSingleton(); + const auto vm = game ? game->GetVM() : nullptr; + BSTSmartPointer typeInfo; + if (!vm || + !vm->GetScriptObjectType(GetVMTypeID(), typeInfo) || + !typeInfo) { + assert(false); + SFSE::log::error("failed to get type info for object"sv); + return std::nullopt; + } else { + return typeInfo.get(); + } + } + + template + [[nodiscard]] std::optional GetTypeInfo() + { + const auto game = GameVM::GetSingleton(); + const auto vm = game ? game->GetVM() : nullptr; + REL::Relocation baseObjectName{ REL::ID(648543) }; + BSTSmartPointer typeInfo; + if (!vm || + !vm->GetScriptObjectType(*baseObjectName, typeInfo) || + !typeInfo) { + assert(false); + SFSE::log::error("failed to get type info for vm object"sv); + return std::nullopt; + } else { + return typeInfo.get(); + } + } + + template + [[nodiscard]] std::optional GetTypeInfo() + { + return TypeInfo::RawType::kVar; + } + + template + [[nodiscard]] std::optional GetTypeInfo() + { + return TypeInfo::RawType::kString; + } + + template + [[nodiscard]] std::optional GetTypeInfo() + { + return TypeInfo::RawType::kInt; + } + + template + [[nodiscard]] std::optional GetTypeInfo() + { + return TypeInfo::RawType::kFloat; + } + + template + [[nodiscard]] std::optional GetTypeInfo() + { + return TypeInfo::RawType::kBool; + } + + template + [[nodiscard]] std::optional GetTypeInfo() + { + using value_type = detail::decay_t::value_type>; + + auto typeInfo = detail::GetTypeInfo(); + if (typeInfo) { + typeInfo->SetArray(true); + } + + return typeInfo; + } + + template + [[nodiscard]] std::optional GetTypeInfo() + { + const auto game = GameVM::GetSingleton(); + const auto vm = game ? game->GetVM() : nullptr; + if constexpr (detail::is_structure_wrapper_v) { + BSTSmartPointer typeInfo; + if (!vm || + !vm->GetScriptStructType(T::name, typeInfo) || + !typeInfo) { + assert(false); + SFSE::log::error("failed to get type info for structure"sv); + return std::nullopt; + } else { + return typeInfo.get(); + } + } else { + static_assert(false && sizeof(T)); + } + } + + template + [[nodiscard]] std::optional GetTypeInfo() + { + using value_type = typename std::remove_cv_t::value_type; + return detail::GetTypeInfo(); + } + + namespace detail + { + template + __forceinline std::optional GetTypeInfo() + { + return BSScript::GetTypeInfo(); + } + } + + template + void PackVariable(Variable& a_var, const volatile T* a_val) + { + if (!a_val) { + a_var = nullptr; + return; + } + + const auto success = [&]() { + const auto game = GameVM::GetSingleton(); + const auto vm = game ? game->GetVM() : nullptr; + BSTSmartPointer typeInfo; + if (!vm || + !vm->GetScriptObjectType(GetVMTypeID(), typeInfo) || + !typeInfo) { + return false; + } + + const auto& handles = vm->GetObjectHandlePolicy(); + const auto handle = handles.GetHandleForObject( + GetVMTypeID(), + const_cast( + static_cast(a_val))); + if (handle == handles.EmptyHandle()) { + return false; + } + + BSTSmartPointer object; + if (!vm->FindBoundObject(handle, typeInfo->name.c_str(), false, object, false) && + vm->CreateObject(typeInfo->name, object) && + object) { + auto& binding = vm->GetObjectBindPolicy(); + binding.BindObject(object, handle); + } + + if (!object) { + return false; + } + + a_var = std::move(object); + return true; + }(); + + if (!success) { + assert(false); + SFSE::log::error("failed to pack variable"sv); + a_var = nullptr; + } + } + + template + void PackVariable(Variable& a_var, T a_val) + { + a_var = a_val ? std::move(a_val) : nullptr; + } + + template + void PackVariable(Variable& a_var, const volatile T* a_val) + { + a_var = a_val ? const_cast(a_val) : nullptr; + } + + template + void PackVariable(Variable& a_var, T&& a_val) // + requires(detail::string>) + { + a_var = BSFixedString(std::forward(a_val)); + } + + template + void PackVariable(Variable& a_var, T a_val) + { + a_var = static_cast(a_val); + } + + template + void PackVariable(Variable& a_var, T a_val) + { + a_var = static_cast(a_val); + } + + template + void PackVariable(Variable& a_var, T a_val) + { + a_var = static_cast(a_val); + } + + template + void PackVariable(Variable& a_var, T a_val) + { + a_var = static_cast(a_val); + } + + template + void PackVariable(Variable& a_var, T&& a_val) // + requires(detail::array>) + { + using value_type = detail::decay_t::value_type>; + using reference_type = + std::conditional_t< + std::is_lvalue_reference_v, + typename std::remove_cvref_t::value_type&, + typename std::remove_cvref_t::value_type&&>; + + const auto success = [&]() { + const auto game = GameVM::GetSingleton(); + const auto vm = game ? game->GetVM() : nullptr; + const auto typeInfo = GetTypeInfo>(); + const auto size = a_val.size(); + BSTSmartPointer out; + if (!typeInfo || + !vm || + !vm->CreateArray(*typeInfo, static_cast(size), out) || + !out) { + return false; + } + + std::uint32_t i = 0; + for (auto&& elem : std::forward(a_val)) { + detail::PackVariable(out->elements[i++], static_cast(elem)); + } + + a_var = std::move(out); + return true; + }(); + + if (!success) { + assert(false); + SFSE::log::error("failed to pack array"sv); + a_var = nullptr; + } + } + + template + void PackVariable(Variable& a_var, T&& a_val) // + requires(detail::wrapper>) + { + a_var = detail::wrapper_accessor::get_proxy(std::forward(a_val)); + } + + template + void PackVariable(Variable& a_var, T&& a_val) // + requires(detail::nullable>) + { + if (a_val) { + detail::PackVariable(a_var, *std::forward(a_val)); + } else { + a_var = nullptr; + } + } + + namespace detail + { + template + __forceinline void PackVariable(Variable & a_var, T && a_val) + { + BSScript::PackVariable(a_var, std::forward(a_val)); + } + } + + template + [[nodiscard]] std::monostate UnpackVariable([[maybe_unused]] const Variable& a_var) + { + assert(a_var.is()); + return {}; + } + + template + [[nodiscard]] T* UnpackVariable(const Variable& a_var) + { + if (a_var.is() && get(a_var) == nullptr) { + return nullptr; + } + + const auto result = [&]() -> void* { + if (!a_var.is()) { + assert(false); + return nullptr; + } + + const auto game = GameVM::GetSingleton(); + const auto vm = game ? game->GetVM() : nullptr; + const auto object = get(a_var); + if (!vm || !object) { + return nullptr; + } + + const auto& handles = vm->GetObjectHandlePolicy(); + const auto handle = object->GetHandle(); + if (!handles.IsHandleLoaded(handle)) { + return nullptr; + } + + return handles.GetObjectForHandle(GetVMTypeID(), handle); + }(); + + if (!result) { + assert(false); + SFSE::log::error("failed to get object from variable"sv); + } + + return static_cast(result); + } + + template + [[nodiscard]] T UnpackVariable(const Variable& a_var) + { + return get(a_var); + } + + template + [[nodiscard]] const T* UnpackVariable(const Variable& a_var) + { + return get(a_var); + } + + template + [[nodiscard]] T UnpackVariable(const Variable& a_var) + { + if (!a_var.is()) { + assert(false); + return T(); + } + + if constexpr (std::same_as || + std::same_as) { + return get(a_var); + } else { + const auto str = get(a_var); + return T(static_cast(str)); + } + } + + template + [[nodiscard]] T UnpackVariable(const Variable& a_var) + { + if (!a_var.is()) { + assert(false); + return T(); + } + + return static_cast(get(a_var)); + } + + template + [[nodiscard]] T UnpackVariable(const Variable& a_var) + { + if (!a_var.is()) { + assert(false); + return T(); + } + + return static_cast(get(a_var)); + } + + template + [[nodiscard]] T UnpackVariable(const Variable& a_var) + { + if (!a_var.is()) { + assert(false); + return T(); + } + + return static_cast(get(a_var)); + } + + template + [[nodiscard]] T UnpackVariable(const Variable& a_var) + { + if (!a_var.is()) { + assert(false); + return T(); + } + + return static_cast(get(a_var)); + } + + template + [[nodiscard]] T UnpackVariable(const Variable& a_var) + { + if (!a_var.is()) { + assert(false); + return T(); + } + + using value_type = typename T::value_type; + + T out; + const auto in = get(a_var); + for (const auto& var : in->elements) { + out.push_back(detail::UnpackVariable(var)); + } + + return out; + } + + template + [[nodiscard]] T UnpackVariable(const Variable& a_var) + { + return detail::wrapper_accessor::construct(a_var); + } + + template + [[nodiscard]] T UnpackVariable(const Variable& a_var) + { + if (a_var.is()) { + return T(); + } else { + using value_type = typename T::value_type; + return T(detail::UnpackVariable(a_var)); + } + } + + namespace detail + { + template + [[nodiscard]] __forceinline T UnpackVariable(const Variable& a_var) + { + return BSScript::UnpackVariable>(a_var); + } + + template + consteval bool ValidateParameter() noexcept + { + // Must be one of (optionally cv-qualified): + // * A pointer to one of: + // * (optionally cv-qualified) `RE::TESForm` or any subclass thereof + // * (optionally volatile) `const RE::BSScript::Variable` + // * `RE::BSTSmartPointer` + // * A string type which is one of: + // * std::string + // * std::string_view + // * RE::BSFixedString + // * RE::BSFixedStringCS + // * A custom type which implements: + // * A specialization of `RE::BSScript::script_traits` which provides a typedef `is_string` as `std::true_type` + // * A conversion to `std::string_view` + // * An integral type which is one of (optionally signed/unsigned): + // * `char` + // * `short` + // * `int` + // * `long` + // * `long long` + // * An enumeration type + // * A floating point type which is one of: + // * `float` + // * `double` + // * `long double` + // * `bool` + // * An array type which is one of: + // * std::vector + // * RE::BSTArray + // * A custom type which implements: + // * A specialization of `RE::BSScript::script_traits` which provides a typedef `is_array` as `std::true_type` + // * A default constructor + // * The following typedefs: + // * `iterator` + // * `size_type` + // * `value_type` + // * The following methods: + // * `begin` + // * Callable with no arguments + // * Returns a type of `iterator` + // * `end` + // * Callable with no arguments + // * Returns a type of `iterator` + // * `size` + // * Callable with no arguments + // * Returns a type of `size_type` + // * `push_back` + // * Callable with an rvalue reference of type `value_type` + // * Additionally, the array's `value_type` must be a valid parameter type + // * A wrapper type which is one of: + // * `RE::BSScript::structure_wrapper` + // * A nullable type which is one of: + // * std::optional + // * A custom type which implements: + // * A specialization of `RE::BSScript::script_traits` which provides a typedef `is_nullable` as `std::true_type` + // * A default constructor + // * The following typedefs: + // * `value_type` + // * The following methods: + // * operator bool + // * operator * + // * Returns type of `value_type` + // * Additionally, the nullable's `value_type` must be one of: + // * An array type + // * A structure type + static_assert(detail::valid_parameter, "invalid parameter type"); + if constexpr (detail::array || detail::nullable) { + return detail::ValidateParameter::value_type>(); + } else { + return true; + } + } + + template + consteval bool ValidateReturn() noexcept + { + // Must be one of: + // * `void` + // * A valid parameter type + if constexpr (std::same_as) { + return true; + } else { + return ValidateParameter(); + } + } + + template < + bool LONG, + class S, + class... Args, + class F, + std::size_t... I> + decltype(auto) DispatchHelper( + Variable& a_self, + Internal::VirtualMachine& a_vm, + std::uint32_t a_stackID, + const StackFrame& a_stackFrame, + Stack& a_stack, + const std::function& a_callback, + std::index_sequence) + { + const auto self = [&]() -> S { + // super::Call should guarantee the self parameter is not none + if constexpr (detail::static_tag) { + return std::monostate{}; + } else if constexpr (detail::object>) { + auto* const ptr = BSScript::UnpackVariable>(a_self); + assert(ptr != nullptr); + return *ptr; + } else if constexpr (detail::vmobject>) { + const auto obj = get(a_self); + assert(obj != nullptr); + return *obj; + } else { + static_assert(false && sizeof(S), "unhandled case"); + } + }; + + const auto pframe = std::addressof(a_stackFrame); + const auto page = a_stack.GetPageForFrame(pframe); + const auto args = [&](std::in_place_type_t, std::size_t a_index) { + if (a_stackFrame.size > a_index) { + return UnpackVariable>( + a_stack.GetStackFrameVariable( + pframe, + static_cast(a_index), + page)); + } else { + assert(false); + return T(); + } + }; + + if constexpr (LONG) { + return a_callback( + reinterpret_cast(a_vm), // TODO: static_cast + a_stackID, + self(), + args(std::in_place_type_t{}, I)...); + } else { + return a_callback( + self(), + args(std::in_place_type_t{}, I)...); + } + } + } + + template < + class F, + bool LONG, + class R, + class S, + class... Args> + class NativeFunction : + public NF_util::NativeFunctionBase + { + private: + using super = NF_util::NativeFunctionBase; + + public: + // A function which takes a self parameter must be one of (optionally cv-qualified): + // * An lvalue reference to one of: + // * `RE::TESForm` or any subclass thereof + // * `RE::BSScript::Object` + // * `RE::GameScript::RefrOrInventoryObj` + // A function which does not take a self parameter (a global function) must tag the self slot with `std::monostate` + static_assert(detail::valid_self, "invalid self type"); + + static_assert(detail::ValidateReturn()); + static_assert(((detail::ValidateParameter(), ...), true)); + + template + NativeFunction(std::string_view a_object, std::string_view a_function, Fn a_func, bool a_isLatent) // + requires(detail::invocable_r || + detail::invocable_r) : + super(a_object, a_function, sizeof...(Args), detail::static_tag, a_isLatent), + _stub(std::move(a_func)) + { + assert(super::_params.paramCount == sizeof...(Args)); + std::size_t i = 0; + ((super::_params.entries[i++].second = GetTypeInfo>().value_or(nullptr)), ...); + super::_retType = GetTypeInfo>().value_or(nullptr); + } + + // override (NF_util::NativeFunctionBase) + bool HasStub() const override { return static_cast(_stub); } // 15 + + bool MarshallAndDispatch(Variable& a_self, Internal::VirtualMachine& a_vm, std::uint32_t a_stackID, Variable& a_retVal, const StackFrame& a_stackFrame) const override // 16 + { + a_retVal = nullptr; + + const auto stack = a_stackFrame.parent; + if (!stack) { + assert(false); + SFSE::log::error("native function called without relevant stack"sv); + return false; + } + + const auto invoke = [&]() { + return detail::DispatchHelper( + a_self, + a_vm, + a_stackID, + a_stackFrame, + *stack, + _stub, + std::index_sequence_for{}); + }; + + if constexpr (!std::same_as) { + PackVariable(a_retVal, invoke()); + } else { + invoke(); + } + + return true; + } + + private: + std::function _stub; + }; + + template < + class R, + class S, + class... Args> + NativeFunction(std::string_view, std::string_view, R (*)(S, Args...), bool) + -> NativeFunction< + R(S, Args...), + false, + R, + S, + Args...>; + + template < + class R, + class S, + class... Args> + NativeFunction(std::string_view, std::string_view, R (*)(IVirtualMachine&, std::uint32_t, S, Args...), bool) + -> NativeFunction< + R(IVirtualMachine&, std::uint32_t, S, Args...), + true, + R, + S, + Args...>; + + template + void IVirtualMachine::BindNativeMethod( + stl::zstring a_object, + stl::zstring a_function, + F a_func, + std::optional a_taskletCallable, + bool a_isLatent) + { + NF_util::NativeFunctionBase* func = new NativeFunction( + a_object, + a_function, + std::move(a_func), + a_isLatent); + + const auto success = + BindNativeMethod(func); + + if (!success) { + SFSE::log::warn( + FMT_STRING("failed to register method \"{}\" on object \"{}\""), + a_function, + a_object); + } + + if (success && a_taskletCallable) { + SetCallableFromTasklets(a_object.data(), a_function.data(), *a_taskletCallable); + } + } +} diff --git a/include/RE/B/BSTArray.h b/include/RE/B/BSTArray.h index 0349cfe5..1dbf16d3 100644 --- a/include/RE/B/BSTArray.h +++ b/include/RE/B/BSTArray.h @@ -58,6 +58,8 @@ namespace RE class BSTArrayAllocatorFunctor : public BSTArrayBase::IAllocatorFunctor { public: + using propagate_on_container_move_assignment = std::true_type; + // members Allocator* _allocator; // 00 }; @@ -66,6 +68,8 @@ namespace RE class BSTArrayHeapAllocator { public: + using propagate_on_container_move_assignment = std::true_type; + [[nodiscard]] void* allocate(std::size_t a_size) { const auto mem = malloc(a_size); @@ -83,16 +87,43 @@ namespace RE class BSScrapArrayAllocator { public: + using propagate_on_container_move_assignment = std::false_type; + + BSScrapArrayAllocator() noexcept = default; + BSScrapArrayAllocator(const BSScrapArrayAllocator&) = delete; + BSScrapArrayAllocator(BSScrapArrayAllocator&&) = delete; + + ~BSScrapArrayAllocator() noexcept = default; + + BSScrapArrayAllocator& operator=(const BSScrapArrayAllocator&) = delete; + BSScrapArrayAllocator& operator=(BSScrapArrayAllocator&&) = delete; + void* allocate(std::size_t a_size) { - const auto mem = _allocator->Allocate(a_size, 0); + if (!_allocator) { + _allocator = MemoryManager::GetSingleton()->GetThreadScrapHeap(); + } + + if (!_allocator) { + stl::report_and_fail("failed to get thread scrap heap"sv); + } + + const auto mem = _allocator->Allocate(a_size, alignof(void*)); if (!mem) { - stl::report_and_fail("out of memory"sv); + stl::report_and_fail("failed to handle allocation request"sv); + } else { + return mem; + } + } + + void deallocate(void* a_ptr) + { + if (_allocator) { + _allocator->Deallocate(a_ptr, 0); + } else { + stl::report_and_fail("failed to deallocate block"sv); } - std::memset(mem, 0, a_size); - return mem; } - void deallocate(void* a_ptr) { _allocator->Deallocate(a_ptr, 0); } protected: // members @@ -125,11 +156,49 @@ namespace RE // 4) explicit BSTArray(size_type a_count) { resize(a_count); } + BSTArray(const BSTArray& a_rhs) + { + clear(); + reserve_exact(a_rhs.size()); + for (const auto& i : a_rhs) { + emplace_back(i); + } + } + + BSTArray(BSTArray&& a_rhs) + { + clear(); + reserve_exact(a_rhs.size()); + for (const auto& i : a_rhs) { + emplace_back(i); + } + a_rhs.clear(); + } + + BSTArray& operator=(const BSTArray& a_rhs) + { + clear(); + reserve_exact(a_rhs.size()); + for (const auto& i : a_rhs) { + emplace_back(i); + } + } + + BSTArray& operator=(BSTArray&& a_rhs) + { + clear(); + reserve_exact(a_rhs.size()); + for (const auto& i : a_rhs) { + emplace_back(i); + } + a_rhs.clear(); + } + ~BSTArray() { if (capacity() > 0) { clear(); - allocator_type().deallocate(data()); + allocator_type::deallocate(data()); set_data(nullptr); set_capacity(0, 0); } @@ -270,12 +339,12 @@ namespace RE const auto ndata = static_cast( - allocator_type().allocate(a_capacity * sizeof(value_type))); + allocator_type::allocate(a_capacity * sizeof(value_type))); const auto odata = data(); if (ndata != odata) { std::uninitialized_move_n(odata, size(), ndata); std::destroy_n(odata, size()); - allocator_type().deallocate(odata); + allocator_type::deallocate(odata); set_data(ndata); set_capacity(a_capacity, a_capacity * sizeof(value_type)); } diff --git a/include/RE/G/GameVM.h b/include/RE/G/GameVM.h index 5d3e4d1e..fc3503ed 100644 --- a/include/RE/G/GameVM.h +++ b/include/RE/G/GameVM.h @@ -166,6 +166,11 @@ namespace RE return *singleton; } + inline BSScript::IVirtualMachine* GetVM() const + { + return impl.get(); + } + // members std::uint64_t unkB0; // 00B0 std::uint64_t unkB8; // 00B8 diff --git a/include/RE/I/IFunction.h b/include/RE/I/IFunction.h index 3dc7d364..9ddfd303 100644 --- a/include/RE/I/IFunction.h +++ b/include/RE/I/IFunction.h @@ -3,6 +3,8 @@ #include "RE/B/BSFixedString.h" #include "RE/B/BSIntrusiveRefCounted.h" #include "RE/T/TypeInfo.h" +#include "RE/S/StackFrame.h" +#include "RE/E/ErrorLogger.h" namespace RE::BSScript { @@ -11,48 +13,63 @@ namespace RE::BSScript class VirtualMachine; } - class StackFrame; - class Variable; - class IVirtualMachine; + class VMClassInfo; + class VMClassRegistry; + class VMState; + class VMValue; - class IFunction + class IFunction : + public BSIntrusiveRefCounted { public: IFunction() {} virtual ~IFunction() {} + enum class CallResult : std::uint32_t + { + kCompleted, + kSetupForVM, + kInProgress, + kFailedRetry, + kFailedAbort + }; + + enum class FunctionType : std::uint32_t + { + kNormal, + kPropertyGetter, + kPropertySetter + }; + struct Unk13 { std::uint64_t unk00; std::uint32_t unk08; }; - virtual BSFixedString* GetName(void) = 0; - virtual BSFixedString* GetClassName(void) = 0; - virtual BSFixedString* GetStateName(void) = 0; - virtual TypeInfo* GetReturnType(TypeInfo* a_typeInfo) = 0; - virtual std::uint64_t GetNumParams(void) = 0; - virtual std::uint64_t* GetParam(std::uint32_t a_idx, BSFixedString* a_nameOut, std::uint64_t* a_typeOut) = 0; - virtual std::uint64_t GetNumParams2(void) = 0; - virtual bool IsNative(void) = 0; - virtual bool IsStatic(void) = 0; - virtual bool Unk_0A(void) = 0; - virtual std::uint32_t Unk_0B(void) = 0; - virtual std::uint32_t GetUserFlags(void) = 0; - virtual BSFixedString* GetDocString(void) = 0; - virtual void Unk_0E(std::uint32_t a_unk) = 0; - virtual std::uint32_t Invoke(std::uint64_t a_unk0, std::uint64_t a_unk1, IVirtualMachine* a_vm, StackFrame* a_frame) = 0; - virtual BSFixedString* Unk_10(void) = 0; // file/line number? + virtual BSFixedString& GetName() = 0; + virtual BSFixedString& GetObjectTypeName() = 0; + virtual BSFixedString& GetStateName() = 0; + virtual TypeInfo* GetReturnType(TypeInfo* a_dst) = 0; + virtual std::uint64_t GetParamCount() = 0; + virtual TypeInfo* GetParam(std::uint32_t a_idx, BSFixedString* a_nameOut, TypeInfo* a_typeOut) = 0; + virtual std::uint64_t GetStackFrameSize() = 0; + virtual bool GetIsNative() = 0; + virtual bool GetIsStatic() = 0; + virtual bool GetIsEmpty() = 0; + virtual FunctionType GetFunctionType() = 0; + virtual std::uint32_t GetUserFlags() = 0; + virtual BSFixedString& GetDocString() = 0; + virtual void InsertLocals(std::uint32_t a_frame) = 0; + virtual CallResult Call(const BSTSmartPointer& a_stack, ErrorLogger& a_errorLogger, Internal::VirtualMachine& a_vm, StackFrame* a_frame) = 0; + virtual BSFixedString& GetSourceFilename() = 0; virtual bool TranslateIPToLineNumber(std::uint32_t a_instructionPointer, std::uint32_t* r_lineNumber) = 0; - virtual std::uint64_t* Unk_12(std::uint64_t* a_out) = 0; // new, might be type reflection - virtual Unk13* Unk_13(Unk13* a_out) = 0; // new, might be type reflection - virtual bool GetParamInfo(std::uint32_t a_idx, void* a_out) = 0; // param list stuff + virtual std::uint64_t* Unk_12(std::uint64_t* a_out) = 0; // new, might be type reflection + virtual Unk13* Unk_13(Unk13* a_out) = 0; // new, might be type reflection + virtual bool GetVarNameForStackIndex(std::uint32_t a_idx, BSFixedString& a_variableName) = 0; virtual void* Unk_15(std::uint64_t a_arg0, std::uint64_t a_arg1) = 0; // param list stuff, loop - virtual bool GetUnk41(void) = 0; - virtual void SetUnk41(bool a_arg) = 0; - - // members - BSIntrusiveRefCounted refCount; // 08 + virtual bool CanBeCalledFromTasklets() = 0; + virtual void SetCallableFromTasklets(bool a_taskletCallable) = 0; }; static_assert(sizeof(IFunction) == 0x10); } diff --git a/include/RE/I/IStackCallbackSaveInterface.h b/include/RE/I/IStackCallbackSaveInterface.h index c47cdb73..30da82d0 100644 --- a/include/RE/I/IStackCallbackSaveInterface.h +++ b/include/RE/I/IStackCallbackSaveInterface.h @@ -1,6 +1,7 @@ #pragma once #include "RE/B/BSTSmartPointer.h" +#include "RE/B/BSIntrusiveRefCounted.h" namespace RE { @@ -8,7 +9,23 @@ namespace RE namespace BSScript { - class IStackCallbackFunctor; + class Variable; + + class __declspec(novtable) alignas(0x08) IStackCallbackFunctor : + public BSIntrusiveRefCounted + { + public: + virtual ~IStackCallbackFunctor(){}; // 00 + + // add + virtual void CallQueued() = 0; // 01 + virtual void CallCanceled() = 0; // 02 + virtual void StartMultiDispatch() = 0; // 03 + virtual void EndMultiDispatch() = 0; // 04 + virtual void operator()(Variable) = 0; // 05 + virtual bool CanSave() { return false; }; // 06 + }; + static_assert(sizeof(IStackCallbackFunctor) == 0x10); class __declspec(novtable) IStackCallbackSaveInterface { diff --git a/include/RE/I/IVirtualMachine.h b/include/RE/I/IVirtualMachine.h index 1f07a5c2..48c480c1 100644 --- a/include/RE/I/IVirtualMachine.h +++ b/include/RE/I/IVirtualMachine.h @@ -13,7 +13,7 @@ namespace RE { template - using BSTThreadScrapFunction = msvc::function; + using BSTThreadScrapFunction = std::function; namespace BSScript { @@ -133,6 +133,14 @@ namespace RE virtual void PostCachedErrorToLogger(const ICachedErrorMessage& a_errorFunctor, ErrorLogger::Severity a_severity) const = 0; // 41 virtual void PostCachedErrorToLogger(const ICachedErrorMessage& a_errorFunctor, std::uint32_t a_stackID, ErrorLogger::Severity a_severity) const = 0; // 42 + template + void BindNativeMethod( + stl::zstring a_object, + stl::zstring a_function, + F a_func, + std::optional a_taskletCallable, + bool a_isLatent); + void PostError(std::string_view a_msg, std::uint32_t a_stackID, ErrorLogger::Severity a_severity) { class ErrorImpl : diff --git a/include/RE/N/NativeFunction.h b/include/RE/N/NativeFunction.h deleted file mode 100644 index 0c042e37..00000000 --- a/include/RE/N/NativeFunction.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -#include "RE/N/NativeFunctionBase.h" - -namespace RE::BSScript -{ - class NativeFunction : public NF_util::NativeFunctionBase - { - public: - NativeFunction() = delete; - - NativeFunction(const char* a_name, const char* a_className, bool a_isStatic, std::uint32_t a_numParams) - { - using func_t = std::add_pointer_t; - REL::Relocation func{ REL::ID(196396) }; - func(this, a_name, a_className, a_isStatic, a_numParams); - } - - virtual ~NativeFunction() - { - using func_t = std::add_pointer_t; - REL::Relocation func{ REL::ID(196398) }; - func(this); - } - - virtual bool HasCallback(void) override { return _callback != 0; } - virtual bool Run(Variable* a_selfValue, IVirtualMachine* a_vm, std::uint32_t a_arg2, Variable* a_resultValue, StackFrame* a_frame) = 0; - - protected: - void* _callback; // 50 - }; - static_assert(sizeof(NativeFunction) == 0x60); -} diff --git a/include/RE/N/NativeFunctionBase.h b/include/RE/N/NativeFunctionBase.h index 9369a3d4..7937e19c 100644 --- a/include/RE/N/NativeFunctionBase.h +++ b/include/RE/N/NativeFunctionBase.h @@ -2,17 +2,42 @@ #include "RE/I/IFunction.h" #include "RE/T/TypeInfo.h" +#include "RE/B/BSTTuple.h" namespace RE::BSScript { namespace Internal { class VirtualMachine; + + class VDescTable + { + public: + using value_type = BSTTuple; + using size_type = std::uint16_t; + + VDescTable(size_type a_params, size_type a_locals) : + paramCount(a_params), + totalEntries(a_params + a_locals) + { + const auto total = paramCount + totalEntries; + if (total > 0) { + entries = new value_type[total]; + } + } + + ~VDescTable() { delete[] entries; } + + // members + value_type* entries{ nullptr }; // 00 + size_type paramCount{ 0 }; // 08 + size_type totalEntries{ 0 }; // 0A + }; + static_assert(sizeof(VDescTable) == 0x10); } class StackFrame; class Variable; - class IVirtualMachine; namespace NF_util { @@ -21,111 +46,120 @@ namespace RE::BSScript public: SF_RTTI_VTABLE(BSScript__NF_util__NativeFunctionBase); - NativeFunctionBase() {} + NativeFunctionBase( + std::string_view a_object, + std::string_view a_function, + std::uint16_t a_paramCount, + bool a_isStatic, + bool a_isLatent) : + _name(a_function), + _className(a_object), + _params(a_paramCount, 0), + _isStatic(a_isStatic), + _isLatent(a_isLatent) + { + for (std::size_t i = 0; i < _params.paramCount; ++i) { + _params.entries[i].first = std::format("param{}", i + 1); + } + } virtual ~NativeFunctionBase() {} - struct alignas(0x10) ParameterInfo - { - public: - struct Entry - { - public: - // members - BSFixedString name; // 00 - TypeInfo type; // 08 - }; - - // members - Entry* data; // 00 length = numParams + unk0A - std::uint16_t numParams; // 08 - std::uint16_t unk0A; // 0A - }; - static_assert(sizeof(ParameterInfo) == 0x10); - - virtual BSFixedString* GetName(void) override { return &_name; } - virtual BSFixedString* GetClassName(void) override { return &_className; } - virtual BSFixedString* GetStateName(void) override { return &_stateName; } - virtual TypeInfo* GetReturnType(TypeInfo* a_dst) + virtual BSFixedString& GetName() override { return _name; } + virtual BSFixedString& GetObjectTypeName() override { return _className; } + virtual BSFixedString& GetStateName() override { return _stateName; } + + virtual TypeInfo* GetReturnType(TypeInfo* a_dst) override { *a_dst = _retType; return a_dst; } - virtual std::uint64_t GetNumParams(void) override { return _params.numParams; } - virtual std::uint64_t* GetParam(std::uint32_t a_idx, BSFixedString* a_nameOut, std::uint64_t* a_typeOut) + + virtual std::uint64_t GetParamCount() override { return _params.paramCount; } + + virtual TypeInfo* GetParam(std::uint32_t a_idx, BSFixedString* a_nameOut, TypeInfo* a_typeOut) override { - using func_t = std::add_pointer_t; + using func_t = std::add_pointer_t; REL::Relocation func{ ID::BSScript::Internal::NF_util::NativeFunctionBase::GetParam }; return func(&_params, a_idx, a_nameOut, a_typeOut); } - virtual std::uint64_t GetNumParams2(void) override { return _params.unk0A; } - virtual bool IsNative(void) override { return true; } - virtual bool IsStatic(void) override { return _isStatic; } - virtual bool Unk_0A(void) override { return false; } - virtual std::uint32_t Unk_0B(void) override { return 0; } - virtual std::uint32_t GetUserFlags(void) override { return _userFlags; } - virtual BSFixedString* GetDocString(void) override { return &_docString; } - virtual void Unk_0E(std::uint32_t a_unk) override { (void)a_unk; } // always nop? - virtual std::uint32_t Invoke(std::uint64_t a_unk0, std::uint64_t a_unk1, IVirtualMachine* a_vm, StackFrame* a_frame) override + + virtual std::uint64_t GetStackFrameSize() override { return _params.totalEntries; } + virtual bool GetIsNative() override { return true; } + virtual bool GetIsStatic() override { return _isStatic; } + virtual bool GetIsEmpty() override { return false; } + virtual FunctionType GetFunctionType() override { return FunctionType::kNormal; } + virtual std::uint32_t GetUserFlags() override { return _userFlags; } + virtual BSFixedString& GetDocString() override { return _docString; } + virtual void InsertLocals(std::uint32_t a_frame) override { (void)a_frame; } + + virtual CallResult Call(const BSTSmartPointer& a_stack, ErrorLogger& a_errorLogger, Internal::VirtualMachine& a_vm, StackFrame* a_frame) override { - using func_t = decltype(&NativeFunctionBase::Invoke); + using func_t = decltype(&NativeFunctionBase::Call); REL::Relocation func{ ID::BSScript::Internal::NF_util::NativeFunctionBase::Invoke }; - return func(this, a_unk0, a_unk1, a_vm, a_frame); + return func(this, a_stack, a_errorLogger, a_vm, a_frame); } - virtual BSFixedString* Unk_10(void) override + + virtual BSFixedString& GetSourceFilename() override { - using func_t = decltype(&NativeFunctionBase::Unk_10); + using func_t = decltype(&NativeFunctionBase::GetSourceFilename); REL::Relocation func{ ID::BSScript::Internal::NF_util::NativeFunctionBase::Unk_10 }; return func(this); } + virtual bool TranslateIPToLineNumber(std::uint32_t a_IP, std::uint32_t* r_lineNumber) override { (void)a_IP; *r_lineNumber = 0; return false; } + virtual std::uint64_t* Unk_12(std::uint64_t* a_out) override { *a_out = 0; // a_out[4] = 0; // as std::uint8_t? - return a_out; } + virtual Unk13* Unk_13(Unk13* a_out) override { a_out->unk00 = 0; a_out->unk08 = 0; - return a_out; // a_out[8] = 0; // as std::uint8_t? + return a_out; } - virtual bool GetParamInfo(std::uint32_t a_idx, void* a_out) override + + virtual bool GetVarNameForStackIndex(std::uint32_t a_idx, BSFixedString& a_variableName) override { - using func_t = decltype(&NativeFunctionBase::GetParamInfo); + using func_t = decltype(&NativeFunctionBase::GetVarNameForStackIndex); REL::Relocation func{ ID::BSScript::Internal::NF_util::NativeFunctionBase::GetParamInfo }; - return func(this, a_idx, a_out); + return func(this, a_idx, a_variableName); } - virtual void* Unk_15(std::uint64_t a_arg0, std::uint64_t a_arg1) + + virtual void* Unk_15(std::uint64_t a_arg0, std::uint64_t a_arg1) override { using func_t = decltype(&NativeFunctionBase::Unk_15); REL::Relocation func{ ID::BSScript::Internal::NF_util::NativeFunctionBase::Unk_15 }; return func(this, a_arg0, a_arg1); } - virtual bool GetUnk41(void) override { return _isCallableFromTasklet; } - virtual void SetUnk41(bool a_arg) override { _isCallableFromTasklet = a_arg; } - virtual bool HasCallback() = 0; - virtual bool Run(Variable* a_selfValue, IVirtualMachine* a_vm, std::uint32_t a_arg2, Variable* a_resultValue, StackFrame* a_frame) = 0; + + virtual bool CanBeCalledFromTasklets() override { return _isCallableFromTasklet; } + virtual void SetCallableFromTasklets(bool a_taskletCallable) override { _isCallableFromTasklet = a_taskletCallable; } + + // add + virtual bool HasStub() const = 0; + virtual bool MarshallAndDispatch(Variable& a_self, Internal::VirtualMachine& a_vm, std::uint32_t a_stackID, Variable& a_resultValue, const StackFrame& a_stackFrame) const = 0; protected: - BSFixedString _name; // 10 - BSFixedString _className; // 18 - BSFixedString _stateName{ "" }; // 20 - TypeInfo _retType; // 28 - ParameterInfo _params; // 30 - bool _isStatic; // 40 - bool _isCallableFromTasklet{}; // 41 - bool _isLatent{}; // 42 - std::uint8_t _pad43; // 43 - std::uint32_t _userFlags{}; // 44 - BSFixedString _docString; // 48 + BSFixedString _name; // 10 + BSFixedString _className; // 18 + BSFixedString _stateName{ "" }; // 20 + TypeInfo _retType; // 28 + Internal::VDescTable _params; // 30 + bool _isStatic; // 40 + bool _isCallableFromTasklet{}; // 41 + bool _isLatent{}; // 42 + std::uint32_t _userFlags{}; // 44 + BSFixedString _docString; // 48 }; static_assert(sizeof(NativeFunctionBase) == 0x50); } diff --git a/include/RE/N/NiAVObject.h b/include/RE/N/NiAVObject.h index aa95cbb8..0b20da03 100644 --- a/include/RE/N/NiAVObject.h +++ b/include/RE/N/NiAVObject.h @@ -1,7 +1,7 @@ #pragma once #include "RE/B/BSFixedString.h" -#include "RE/N/NiBound.h" #include "RE/N/NiTransform.h" +#include "RE/N/NiBound.h" namespace RE { @@ -126,12 +126,24 @@ namespace RE virtual void* Unk82(); virtual void* Unk83(); - BSFixedString name; - uint32_t refcount; + void IncRefCount() + { + _InterlockedExchangeAdd(&refcount, 1); + } + + void DecRefCount() + { + if (_InterlockedExchangeAdd(&refcount, -1) == 1) + DeleteThis(); + } + + volatile long refcount; uint32_t pad0C; + BSFixedString name; void* controller; void* unk28; void* unk30; + void* unk38; NiNode* parent; //38 NiTransform local; //40 NiTransform world; //80 @@ -143,4 +155,8 @@ namespace RE void* unk128; }; static_assert(sizeof(NiAVObject) == 0x130); + // FIXME: clang-cl chokes on these assertions + // static_assert(offsetof(NiAVObject, NiAVObject::parent) == 0x38); + // static_assert(offsetof(NiAVObject, NiAVObject::local) == 0x40); + // static_assert(offsetof(NiAVObject, NiAVObject::world) == 0x80); } diff --git a/include/RE/S/Struct.h b/include/RE/S/Struct.h index 3038444e..ccd1832a 100644 --- a/include/RE/S/Struct.h +++ b/include/RE/S/Struct.h @@ -18,10 +18,13 @@ namespace RE::BSScript // members BSSpinLock structLock; // 04 + bool constructed{ true }; // 0C + bool valid{ false }; // 0D BSTSmartPointer type; // 10 - bool constructed{ true }; // 18 - bool valid{ false }; // 19 - Variable variables[0]; // 20 + Variable variables[0]; // 18 }; - static_assert(sizeof(Struct) == 0x20); + static_assert(sizeof(Struct) == 0x18); + // FIXME: clang-cl chokes on these assertions + // static_assert(offsetof(Struct, Struct::type) == 0x10); + // static_assert(offsetof(Struct, Struct::variables) == 0x18); } diff --git a/include/RE/S/StructTypeInfo.h b/include/RE/S/StructTypeInfo.h index cb60ce58..90a2d406 100644 --- a/include/RE/S/StructTypeInfo.h +++ b/include/RE/S/StructTypeInfo.h @@ -7,6 +7,7 @@ #include "RE/O/ObjectTypeInfo.h" #include "RE/T/TypeInfo.h" #include "RE/V/Variable.h" +#include "RE/B/BSTScatterTable.h" namespace RE::BSScript { @@ -54,7 +55,7 @@ namespace RE::BSScript BSFixedString name; // 10 BSTSmartPointer containingObjTypeInfo; // 18 BSTArray variables; // 20 - /*BSTHashMap*/ char varNameIndexMap[0x38]; // 30 + BSTHashMap varNameIndexMap; // 30 stl::enumeration linkedValid; // 68 }; static_assert(sizeof(StructTypeInfo) == 0x70); diff --git a/include/RE/Starfield.h b/include/RE/Starfield.h index 7de9039f..d52c4d27 100644 --- a/include/RE/Starfield.h +++ b/include/RE/Starfield.h @@ -192,6 +192,7 @@ #include "RE/B/BSLog.h" #include "RE/B/BSReflection.h" #include "RE/B/BSResourceEnums.h" +#include "RE/B/BSScriptUtil.h" #include "RE/B/BSStorage.h" #include "RE/B/BSStringPool.h" #include "RE/B/BSStringT.h" @@ -295,7 +296,6 @@ #include "RE/M/Misc.h" #include "RE/M/MissileProjectile.h" #include "RE/M/msvc.h" -#include "RE/N/NativeFunction.h" #include "RE/N/NativeFunctionBase.h" #include "RE/N/NiAVObject.h" #include "RE/N/NiBound.h" diff --git a/include/SFSE/API.h b/include/SFSE/API.h index f80f93d5..f4a49d22 100644 --- a/include/SFSE/API.h +++ b/include/SFSE/API.h @@ -6,6 +6,11 @@ #define SFSEAPI __cdecl +namespace RE::BSScript +{ + class IVirtualMachine; +} + namespace SFSE { void Init(const LoadInterface* a_intfc, bool a_log = true) noexcept; @@ -25,4 +30,6 @@ namespace SFSE Trampoline& GetTrampoline(); void AllocTrampoline(std::size_t a_size, bool a_trySFSEReserve = true); + + void SetPapyrusCallback(const std::function a_callback, bool a_trySFSEReserve = true); } diff --git a/src/SFSE/API.cpp b/src/SFSE/API.cpp index dea7377a..68c14946 100644 --- a/src/SFSE/API.cpp +++ b/src/SFSE/API.cpp @@ -140,4 +140,53 @@ namespace SFSE trampoline.create(a_size); } + + struct PapyrusRegistrationHook + { + typedef void (*call_t)(RE::BSScript::IVirtualMachine**); + + PapyrusRegistrationHook(bool a_trySFSEReserve) + { + static constexpr size_t hookSize{ 14 }; + + if (allocated) { + return; + } + + if (const auto intfc = GetTrampolineInterface(); intfc && a_trySFSEReserve) { + const auto memory = intfc->AllocateFromBranchPool(hookSize); + if (memory) { + trampoline.set_trampoline(memory, hookSize); + allocated = true; + } + } + + if (!allocated) { + trampoline.create(hookSize); + } + + // Call to GameVM::BindEverythingToScript(IVirtualMachine**) from GameVM::GameVM() + REL::Relocation hookLoc{ REL::ID(169912), 0x514 }; + func = reinterpret_cast(trampoline.write_call<5>(hookLoc.address(), &thunk)); + } + + static void thunk(RE::BSScript::IVirtualMachine** a_vm) + { + func(a_vm); + if (callback != nullptr) { + callback(a_vm); + } + } + + inline static bool allocated{ false }; + inline static call_t func; + inline static std::function callback; + Trampoline trampoline; + }; + + void SetPapyrusCallback(const std::function a_callback, bool a_trySFSEReserve) + { + static PapyrusRegistrationHook hook{ a_trySFSEReserve }; + PapyrusRegistrationHook::callback = a_callback; + } }